mirror of
https://github.com/NotAShelf/zid.git
synced 2024-11-01 11:01:16 +00:00
Initial commit
This commit is contained in:
commit
3bc4b2b4a1
6 changed files with 285 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
zig-cache/
|
28
LICENSE
Normal file
28
LICENSE
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, raf
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
67
README.md
Normal file
67
README.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# Zid
|
||||||
|
|
||||||
|
Zid is a Content Identifier (CID) Trie implementation in Zig.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Trie structure for fast access to identifiers based on their byte values.
|
||||||
|
- Proper manages memory allocation and deallocation.
|
||||||
|
- Custom error types for better error reporting and handling.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
1. Get the Zig compiler appropriate to your distribution (Generally, if you are
|
||||||
|
sane, `nix-shell -p zig`) will do the trick.)
|
||||||
|
2. Clone the project
|
||||||
|
3. `zig build` or `zig build-exe src/main.zig` (former is recommended, but
|
||||||
|
doesn't matter)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
<!-- deno-fmt-ignore-start -->
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Zid, for the time being, should be considered highly unstable and must be
|
||||||
|
> avoided in actual projects, unless you know what you are doing.
|
||||||
|
|
||||||
|
<!-- deno-fmt-ignore-end -->
|
||||||
|
|
||||||
|
### Adding Identifiers
|
||||||
|
|
||||||
|
To add an identifier to the trie, use the `add` method. Pass the identifier as a
|
||||||
|
byte slice (i.e. `[]u8`):
|
||||||
|
|
||||||
|
```zig
|
||||||
|
try trie.add("your_identifier_here");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Searching for Identifiers
|
||||||
|
|
||||||
|
To search for an identifier in the trie, use the lookup method. Again, pass the
|
||||||
|
identifier as a byte slice:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const result = try trie.lookup("identifier_to_search");
|
||||||
|
```
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
[IPFS docs]: https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid
|
||||||
|
|
||||||
|
### What the hell is a CID?
|
||||||
|
|
||||||
|
See [IPFS docs]. My interpretation of a CID Trie is a tree-like data structure
|
||||||
|
used for _efficiently_ storing and retrieving identifiers, with efficiency being
|
||||||
|
the primary goal.
|
||||||
|
|
||||||
|
### Why doesn't this build?
|
||||||
|
|
||||||
|
It's my first time building a Zig project. Call it a minor friction.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- Support for Multiple Tries
|
||||||
|
- Batch Operations
|
||||||
|
- Dynamic MaxCIDLen (it's currently a runtime constant)
|
||||||
|
- Persistence Layer
|
||||||
|
- Concurrency
|
91
build.zig
Normal file
91
build.zig
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// Although this function looks imperative, note that its job is to
|
||||||
|
// declaratively construct a build graph that will be executed by an external
|
||||||
|
// runner.
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
// Standard target options allows the person running `zig build` to choose
|
||||||
|
// what target to build for. Here we do not override the defaults, which
|
||||||
|
// means any target is allowed, and the default is native. Other options
|
||||||
|
// for restricting supported target set are available.
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
|
||||||
|
// Standard optimization options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||||
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const lib = b.addStaticLibrary(.{
|
||||||
|
.name = "zid",
|
||||||
|
// In this case the main source file is merely a path, however, in more
|
||||||
|
// complicated build scripts, this could be a generated file.
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
// This declares intent for the library to be installed into the standard
|
||||||
|
// location when the user invokes the "install" step (the default step when
|
||||||
|
// running `zig build`).
|
||||||
|
b.installArtifact(lib);
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "zid",
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
// This declares intent for the executable to be installed into the
|
||||||
|
// standard location when the user invokes the "install" step (the default
|
||||||
|
// step when running `zig build`).
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
// This *creates* a Run step in the build graph, to be executed when another
|
||||||
|
// step is evaluated that depends on it. The next line below will establish
|
||||||
|
// such a dependency.
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
|
||||||
|
// By making the run step depend on the install step, it will be run from the
|
||||||
|
// installation directory rather than directly from within the cache directory.
|
||||||
|
// This is not necessary, however, if the application depends on other installed
|
||||||
|
// files, this ensures they will be present and in the expected location.
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
// This allows the user to pass arguments to the application in the build
|
||||||
|
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||||
|
// and can be selected like this: `zig build run`
|
||||||
|
// This will evaluate the `run` step rather than the default, which is "install".
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
// Creates a step for unit testing. This only builds the test executable
|
||||||
|
// but does not run it.
|
||||||
|
const lib_unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||||
|
|
||||||
|
const exe_unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||||
|
|
||||||
|
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||||
|
// the `zig build --help` menu, providing a way for the user to request
|
||||||
|
// running the unit tests.
|
||||||
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
test_step.dependOn(&run_lib_unit_tests.step);
|
||||||
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
|
}
|
13
build.zig.zon
Normal file
13
build.zig.zon
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
.{
|
||||||
|
.name = "zid",
|
||||||
|
.version = "0.1.0",
|
||||||
|
.minimum_zig_version = "0.12.0", // not a hard-requirement, but I've built this package with 0.12 only.
|
||||||
|
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md",
|
||||||
|
},
|
||||||
|
}
|
85
src/main.zig
Normal file
85
src/main.zig
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub var errCollision = std.errors.New("collisions");
|
||||||
|
pub var errNotFound = std.errors.New("not found");
|
||||||
|
|
||||||
|
pub const maxCIDLen = 20;
|
||||||
|
pub const CIDTrie = struct {
|
||||||
|
leaves: [256]?*CIDTrie,
|
||||||
|
end: bool,
|
||||||
|
|
||||||
|
pub fn add(self: *CIDTrie, cid: []u8) !void {
|
||||||
|
if (cid.len > maxCIDLen) return error.CIDTooLong;
|
||||||
|
var trie = self;
|
||||||
|
for (cid) |b| {
|
||||||
|
if (trie.leaves[b] == null) {
|
||||||
|
trie.leaves[b] = try std.heap.page_allocator.alloc(CIDTrie);
|
||||||
|
trie.leaves[b].?.end = false;
|
||||||
|
} else if (trie.leaves[b].?.end) {
|
||||||
|
return errCollision;
|
||||||
|
}
|
||||||
|
if (cid.len == 1) {
|
||||||
|
for (trie.leaves[b].?.leaves) |leaf| {
|
||||||
|
if (leaf != null) return errCollision;
|
||||||
|
}
|
||||||
|
trie.leaves[b].?.end = true;
|
||||||
|
}
|
||||||
|
trie = trie.leaves[b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(self: *CIDTrie, data: []u8) ![]u8 {
|
||||||
|
if (data.len > maxCIDLen) return error.CIDTooLong;
|
||||||
|
if (self.leaves[data[0]] == null) return errNotFound;
|
||||||
|
var nextLevel = self;
|
||||||
|
var currentIndex: usize = 0;
|
||||||
|
|
||||||
|
for (data) |b| {
|
||||||
|
nextLevel = nextLevel.leaves[b];
|
||||||
|
if (nextLevel.? != null and nextLevel.?.end) {
|
||||||
|
return data[0..currentIndex];
|
||||||
|
}
|
||||||
|
currentIndex += 1;
|
||||||
|
}
|
||||||
|
return errNotFound;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
var allocator = std.heap.page_allocator;
|
||||||
|
|
||||||
|
const trie = allocator.create(CIDTrie) catch |err| {
|
||||||
|
std.debug.print("Failed to allocate memory: {}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer allocator.destroy(trie);
|
||||||
|
|
||||||
|
// add entries
|
||||||
|
const addResult = trie.add("hello"[0..]) catch |err| {
|
||||||
|
std.debug.print("Failed to add 'hello': {}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.print("Added 'hello': {}\n", .{addResult}); // this assumes add returns a meaningful value, alternative is to remove
|
||||||
|
|
||||||
|
// lookup entries
|
||||||
|
const lookupHelloResult = trie.lookup("hello"[0..]) catch |err| {
|
||||||
|
std.debug.print("Failed to lookup 'hello': {}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
std.debug.print("Found 'hello': {}\n", .{lookupHelloResult});
|
||||||
|
|
||||||
|
const lookupWorldResult = trie.lookup("world"[0..]) catch |err| {
|
||||||
|
std.debug.print("Failed to lookup 'world': {}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
std.debug.print("Found 'world': {}\n", .{lookupWorldResult});
|
||||||
|
|
||||||
|
// attempt to lookup a nonexistent entry
|
||||||
|
const lookupNonexistentResult = trie.lookup("nonexistent"[0..]);
|
||||||
|
if (lookupNonexistentResult) |result| {
|
||||||
|
std.debug.print("Unexpectedly found 'nonexistent': {}\n", .{result});
|
||||||
|
} else |err| {
|
||||||
|
std.debug.print("Expected error for 'nonexistent': {}\n", .{err});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue