Initial commit

This commit is contained in:
raf 2024-05-21 09:54:42 +00:00 committed by NotAShelf
commit 3bc4b2b4a1
No known key found for this signature in database
GPG key ID: 02D1DD3FA08B6B29
6 changed files with 285 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
zig-cache/

28
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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});
}
}