diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..57b0a58 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +# This repository had no build.zig and no workflow. Three source files, fifteen +# test blocks, and a manifest that declared a dependency by URL with no hash — +# so nothing could fetch it, nothing could build it, and nobody could depend on +# it. The four imports in knowledge_graph.zig pointed at sibling files that live +# in gHashTag/zig-golden-float, whose own packed_vsa.zig imported +# "knowledge_graph.zig" right back. Neither half compiled. +name: build + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + # The version this package targets. Local 0.16 reports failures that + # do not exist here (std.io.getStdOut, ArrayList.init, std.fs) and + # would hide at least one that does. + version: 0.15.2 + + - name: zig build + run: zig build + + - name: zig build test + run: zig build test --summary all diff --git a/.gitignore b/.gitignore index 330fecf..a8a9eab 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ zig-out/ .env* !.env.example +.zig-cache/ +zig-out/ +zig-pkg/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..c7143a9 --- /dev/null +++ b/build.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +// There was no build.zig in this repository at all. Three source files, fifteen +// test blocks, a manifest declaring a dependency without a hash — and nothing +// that could compile any of it. +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const golden = b.dependency("zig_golden_float", .{ + .target = target, + .optimize = optimize, + }).module("golden-float"); + + const kg_mod = b.addModule("zig-knowledge-graph", .{ + .root_source_file = b.path("src/knowledge_graph.zig"), + .target = target, + .optimize = optimize, + }); + kg_mod.addImport("zig_golden_float", golden); + + // kg_cli and kg_server are NOT built. Both were written against Zig 0.14 + // and never migrated: std.io.getStdOut is gone, std.ArrayList is unmanaged + // so .init(allocator) and one-argument .append no longer exist, and + // http.Server.init takes a reader rather than a connection. That is a + // migration across roughly 1300 lines with many call sites, and it is + // tracked separately. + // + // The library is the part other packages depend on, and it is what this + // change makes usable. Building the two binaries would keep the whole + // package unbuildable for the sake of two tools that have never run. + + // Each root gets its own test target. A single root would reach only what it + // references, and under Zig's lazy analysis an unreferenced import is not a + // weakly-checked file — it is an absent one, along with its test blocks. + const test_step = b.step("test", "Run tests"); + inline for (.{ + .{ "knowledge_graph", "src/knowledge_graph.zig" }, + }) |t| { + const tm = b.createModule(.{ + .root_source_file = b.path(t[1]), + .target = target, + .optimize = optimize, + }); + tm.addImport("zig_golden_float", golden); + test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .name = t[0], .root_module = tm })).step); + } +} diff --git a/build.zig.zon b/build.zig.zon index 9e14e19..b9a5dba 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,9 +1,16 @@ -{ - .name = zig_knowledge_graph, - .version = "0.1.0", - .dependencies = .{ - .zig_golden_float = .{ - .url = "https://github.com/gHashTag/zig-golden-float/archive/main.tar.gz", +.{ + // Was: a dependency declared by URL with no .hash, and no .fingerprint at + // all — so nothing could fetch it and nothing could depend on this package. + // There was also no build.zig, so there was nothing to run either. + .name = .zig_knowledge_graph, + .version = "0.1.0", + .fingerprint = 0x2a92e04f54905954, + .minimum_zig_version = "0.15.0", + .dependencies = .{ + .zig_golden_float = .{ + .url = "https://github.com/gHashTag/zig-golden-float/archive/e7ce32885de2a8c50b7b6a3030d0592202145dd1.tar.gz", + .hash = "golden_float-2.1.0-h7LKhUUNCwAtKVHQ56wjridCZVwXY7_oxT2hXD9xCDdF", + }, }, - }, + .paths = .{ "build.zig", "build.zig.zon", "src", "README.md", "LICENSE" }, } diff --git a/src/knowledge_graph.zig b/src/knowledge_graph.zig index 9984c9c..3ffe4b9 100644 --- a/src/knowledge_graph.zig +++ b/src/knowledge_graph.zig @@ -11,10 +11,17 @@ // φ² + 1/φ² = 3 const std = @import("std"); -const vsa = @import("vsa.zig"); -const hybrid = @import("hybrid.zig"); -const packed_vsa = @import("packed_vsa.zig"); -const packed_trit = @import("packed_trit.zig"); +// These four were flat relative imports of files that are not in this +// repository. They are in gHashTag/zig-golden-float, whose own +// src/vsa/packed_vsa.zig imported "knowledge_graph.zig" — a file that is not +// in THAT repository, but is right here. One directory was split into two and +// every relative import was left pointing at the sibling that stayed behind, +// so neither half compiled. Pointed at the dependency instead. +const golden = @import("zig_golden_float"); +const vsa = golden.vsa; +const hybrid = golden.bigint; +const packed_vsa = golden.packed_vsa; +const packed_trit = golden.packed_trit; const HybridBigInt = hybrid.HybridBigInt; const PackedBigInt = packed_trit.PackedBigInt; @@ -364,7 +371,11 @@ pub const KnowledgeGraph = struct { const file = try std.fs.cwd().createFile(path, .{}); defer file.close(); - var writer = file.writer(); + // Since 0.15 File.writer takes a buffer and returns a File.Writer; + // the thing with writeAll/writeInt on it is its .interface. + var write_buf: [4096]u8 = undefined; + var file_writer = file.writer(&write_buf); + const writer = &file_writer.interface; // Header try writer.writeAll(&FILE_MAGIC); @@ -422,6 +433,10 @@ pub const KnowledgeGraph = struct { try writer.writeInt(u32, graph_trit_len, .little); const graph_packed_len = (self.graph_vector.trit_len + 4) / 5; try writer.writeAll(self.graph_vector.data[0..graph_packed_len]); + + // The writer is buffered now. Without this the tail of the graph never + // reaches disk and load() fails on a file that save() reported writing. + try file_writer.interface.flush(); } /// and and file @@ -429,43 +444,45 @@ pub const KnowledgeGraph = struct { const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); - var reader = file.reader(); + var read_buf: [4096]u8 = undefined; + var file_reader = file.reader(&read_buf); + const reader = &file_reader.interface; var result = Self.init(); // Header var magic: [4]u8 = undefined; - _ = try reader.readAll(&magic); + try reader.readSliceAll(&magic); if (!std.mem.eql(u8, &magic, &FILE_MAGIC)) { return error.InvalidFileFormat; } - const version = try reader.readInt(u32, .little); + const version = try reader.takeInt(u32, .little); if (version != FILE_VERSION) { return error.UnsupportedVersion; } - const entity_count = try reader.readInt(u32, .little); - const relation_count = try reader.readInt(u32, .little); + const entity_count = try reader.takeInt(u32, .little); + const relation_count = try reader.takeInt(u32, .little); // withby buffer for and var name_offset: usize = 0; // Entities for (0..entity_count) |i| { - const name_len = try reader.readInt(u16, .little); + const name_len = try reader.takeInt(u16, .little); // and and in buffer const name_start = name_offset; - _ = try reader.readAll(name_buffer[name_offset .. name_offset + name_len]); + try reader.readSliceAll(name_buffer[name_offset .. name_offset + name_len]); name_offset += name_len; - const id = try reader.readInt(u32, .little); - const trit_len = try reader.readInt(u32, .little); + const id = try reader.takeInt(u32, .little); + const trit_len = try reader.takeInt(u32, .little); const packed_len = (trit_len + 4) / 5; var vec = PackedBigInt.zero(); vec.trit_len = trit_len; - _ = try reader.readAll(vec.data[0..packed_len]); + try reader.readSliceAll(vec.data[0..packed_len]); result.entities[i] = Entity{ .name = name_buffer[name_start .. name_start + name_len], @@ -477,19 +494,19 @@ pub const KnowledgeGraph = struct { // Relations for (0..relation_count) |i| { - const name_len = try reader.readInt(u16, .little); + const name_len = try reader.takeInt(u16, .little); const name_start = name_offset; - _ = try reader.readAll(name_buffer[name_offset .. name_offset + name_len]); + try reader.readSliceAll(name_buffer[name_offset .. name_offset + name_len]); name_offset += name_len; - const id = try reader.readInt(u32, .little); - const trit_len = try reader.readInt(u32, .little); + const id = try reader.takeInt(u32, .little); + const trit_len = try reader.takeInt(u32, .little); const packed_len = (trit_len + 4) / 5; var vec = PackedBigInt.zero(); vec.trit_len = trit_len; - _ = try reader.readAll(vec.data[0..packed_len]); + try reader.readSliceAll(vec.data[0..packed_len]); result.relations[i] = Relation{ .name = name_buffer[name_start .. name_start + name_len], @@ -500,17 +517,17 @@ pub const KnowledgeGraph = struct { } // Triples - const triple_count = try reader.readInt(u32, .little); + const triple_count = try reader.takeInt(u32, .little); for (0..triple_count) |i| { - const subject_id = try reader.readInt(u32, .little); - const predicate_id = try reader.readInt(u32, .little); - const object_id = try reader.readInt(u32, .little); - const trit_len = try reader.readInt(u32, .little); + const subject_id = try reader.takeInt(u32, .little); + const predicate_id = try reader.takeInt(u32, .little); + const object_id = try reader.takeInt(u32, .little); + const trit_len = try reader.takeInt(u32, .little); const packed_len = (trit_len + 4) / 5; var vec = PackedBigInt.zero(); vec.trit_len = trit_len; - _ = try reader.readAll(vec.data[0..packed_len]); + try reader.readSliceAll(vec.data[0..packed_len]); result.triples[i] = Triple{ .subject_id = subject_id, @@ -522,10 +539,10 @@ pub const KnowledgeGraph = struct { } // Graph vector - const graph_trit_len = try reader.readInt(u32, .little); + const graph_trit_len = try reader.takeInt(u32, .little); const graph_packed_len = (graph_trit_len + 4) / 5; result.graph_vector.trit_len = graph_trit_len; - _ = try reader.readAll(result.graph_vector.data[0..graph_packed_len]); + try reader.readSliceAll(result.graph_vector.data[0..graph_packed_len]); return result; }