From ca43a95592f036dd6f8a85b61399a5101af58b69 Mon Sep 17 00:00:00 2001 From: Trinity Bee Date: Sat, 10 Oct 2026 06:57:16 +0000 Subject: [PATCH 1/4] Port trinity src/tri/gen_splay_tree.zig to .t27 Re-author the hand-written Zig splay tree (init/insert/find/deinit/ freeRecursive) as a .t27 spec: heap pointers become a fixed node pool linked by indices, the allocator's failure path becomes a full-pool false from insert, and the recursive destroy becomes a post-order count. 8 tests carry the original's own insert/find test plus the empty-tree, root-slot, left-right descent, duplicate-key, full-pool error, deinit-guard and post-order cases. Closes #8463 --- specs/port/trinity/src/tri/gen_splay_tree.t27 | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 specs/port/trinity/src/tri/gen_splay_tree.t27 diff --git a/specs/port/trinity/src/tri/gen_splay_tree.t27 b/specs/port/trinity/src/tri/gen_splay_tree.t27 new file mode 100644 index 0000000000..81942f3bdc --- /dev/null +++ b/specs/port/trinity/src/tri/gen_splay_tree.t27 @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/port/trinity/src/tri/gen_splay_tree.t27 +module port::gen_splay_tree; + +// Port of gHashTag/trinity src/tri/gen_splay_tree.zig at d07bc6251fa3 +// ("tri/splay_tree - Self-adjusting binary search tree"). +// Despite the name, the original never splays: insert is a plain +// BST descent and find is a plain BST walk, and this port keeps exactly that +// behaviour. +// +// The original keeps its nodes on the heap through std.mem.Allocator and +// links them with optional pointers. The pointers and the allocator calls +// are the plumbing; the decisions are pointer-free. So the port holds a +// fixed node pool inside the tree and links nodes by pool index, with +// NO_NODE standing for Zig's null. insert reports the allocator's failure +// path (a full pool) as false instead of an error, and freeRecursive reports +// how many nodes the original would destroy, in its post-order (left, +// right, node). The allocator handle itself is carried, as the original's +// init stores it, as an opaque i64 the caller passes in. + +const NO_NODE: i64 = -1; +const POOL_CAP: usize = 16; + +struct SplayNode { + key: i64, + value: i64, + left: i64, + right: i64, + parent: i64, +} + +struct SplayTree { + nodes: [POOL_CAP]SplayNode, + count: usize, + root: i64, + allocator: i64, +} + +// SplayTree.init(allocator): a fresh tree has no root and no nodes, and it +// remembers the allocator it was given. +pub fn SplayTree_init(allocator: i64) -> SplayTree { + var tree = SplayTree { + nodes: [_]SplayNode{ SplayNode{ key: 0, value: 0, left: NO_NODE, right: NO_NODE, parent: NO_NODE } } ** POOL_CAP, + count: 0, + root: NO_NODE, + allocator: allocator, + }; + return tree; +} + +// SplayTree.insert(tree, key, value) !void: take the root slot when the +// tree is empty; otherwise descend, key < curr.key to the left and anything +// else to the right, and attach the new node at the first missing child, +// recording its parent. The create() error is the pool being full: false. +pub fn SplayTree_insert(tree: *SplayTree, key: i64, value: i64) -> bool { + if (tree.count >= POOL_CAP) { + return false; + } + var idx: i64 = tree.count as i64; + tree.nodes[tree.count] = SplayNode { key: key, value: value, left: NO_NODE, right: NO_NODE, parent: NO_NODE }; + tree.count += 1; + + if (tree.root == NO_NODE) { + tree.root = idx; + return true; + } + + var curr: i64 = tree.root; + while (curr != NO_NODE) { + if (key < tree.nodes[curr as usize].key) { + if (tree.nodes[curr as usize].left == NO_NODE) { + tree.nodes[curr as usize].left = idx; + tree.nodes[idx as usize].parent = curr; + return true; + } + curr = tree.nodes[curr as usize].left; + } else { + if (tree.nodes[curr as usize].right == NO_NODE) { + tree.nodes[curr as usize].right = idx; + tree.nodes[idx as usize].parent = curr; + return true; + } + curr = tree.nodes[curr as usize].right; + } + } + return false; +} + +// SplayTree.find(tree, key) ?i64: an equal key returns that node's value; +// key < node.key walks left, anything else walks right; the walk falling off +// the tree is Zig's null. +pub fn SplayTree_find(tree: SplayTree, key: i64) -> ?i64 { + var curr: i64 = tree.root; + while (curr != NO_NODE) { + if (key == tree.nodes[curr as usize].key) { + return tree.nodes[curr as usize].value; + } + if (key < tree.nodes[curr as usize].key) { + curr = tree.nodes[curr as usize].left; + } else { + curr = tree.nodes[curr as usize].right; + } + } + return null; +} + +// SplayTree.deinit(tree) void: hand the nodes back only when a root exists. +// allocator.destroy is the plumbing; the decision - how many nodes a tree +// with this root gives back - is the returned count. +pub fn SplayTree_deinit(tree: SplayTree) -> i64 { + if (tree.root == NO_NODE) { + return 0; + } + return SplayTree_freeRecursive(tree, tree.root); +} + +// SplayTree.freeRecursive(tree, node) void: left, then right, then the node +// itself - post-order, so a child is always handed back before its parent. +pub fn SplayTree_freeRecursive(tree: SplayTree, node: i64) -> i64 { + if (node == NO_NODE) { + return 0; + } + var left_freed: i64 = SplayTree_freeRecursive(tree, tree.nodes[node as usize].left); + var right_freed: i64 = SplayTree_freeRecursive(tree, tree.nodes[node as usize].right); + return left_freed + right_freed + 1; +} + +// The original's own test, "splay tree insert find": three inserts, one hit, +// one miss. +test splay_tree_insert_find { + var tree = SplayTree_init(1); + assert(SplayTree_insert(&tree, 5, 50)); + assert(SplayTree_insert(&tree, 3, 30)); + assert(SplayTree_insert(&tree, 7, 70)); + + assert(SplayTree_find(tree, 3) == 30); + assert(SplayTree_find(tree, 7) == 70); + assert(SplayTree_find(tree, 99) == null); +} + +test init_starts_empty_and_stores_allocator { + const tree = SplayTree_init(7); + assert(tree.root == NO_NODE); + assert(tree.count == 0); + assert(tree.allocator == 7); + assert(SplayTree_find(tree, 1) == null); +} + +test insert_takes_root_slot { + var tree = SplayTree_init(1); + assert(SplayTree_insert(&tree, 5, 50)); + assert(tree.root == 0); + assert(tree.count == 1); + assert(tree.nodes[0].parent == NO_NODE); + assert(tree.nodes[0].left == NO_NODE); + assert(tree.nodes[0].right == NO_NODE); + assert(SplayTree_find(tree, 5) == 50); +} + +test insert_descends_and_links { + var tree = SplayTree_init(1); + assert(SplayTree_insert(&tree, 5, 50)); + assert(SplayTree_insert(&tree, 3, 30)); + assert(SplayTree_insert(&tree, 7, 70)); + + assert(tree.nodes[0].left == 1); + assert(tree.nodes[0].right == 2); + assert(tree.nodes[1].parent == 0); + assert(tree.nodes[2].parent == 0); + assert(SplayTree_find(tree, 4) == null); +} + +test insert_duplicate_key_goes_right { + var tree = SplayTree_init(1); + assert(SplayTree_insert(&tree, 5, 50)); + assert(SplayTree_insert(&tree, 5, 55)); + + assert(tree.nodes[0].right == 1); + assert(tree.nodes[0].left == NO_NODE); + assert(SplayTree_find(tree, 5) == 50); +} + +test insert_full_pool_is_the_error_path { + var tree = SplayTree_init(1); + var i: usize = 0; + while (i < POOL_CAP) { + assert(SplayTree_insert(&tree, i as i64, (i * 10) as i64)); + i += 1; + } + assert(tree.count == POOL_CAP); + assert(!SplayTree_insert(&tree, 99, 99)); + assert(SplayTree_find(tree, 15) == 150); + assert(SplayTree_find(tree, 99) == null); +} + +test deinit_frees_only_when_root_exists { + const empty = SplayTree_init(1); + assert(SplayTree_deinit(empty) == 0); + + var tree = SplayTree_init(1); + assert(SplayTree_insert(&tree, 5, 50)); + assert(SplayTree_insert(&tree, 3, 30)); + assert(SplayTree_insert(&tree, 7, 70)); + assert(SplayTree_deinit(tree) == 3); +} + +test free_recursive_counts_postorder { + var tree = SplayTree_init(1); + assert(SplayTree_insert(&tree, 5, 50)); + assert(SplayTree_insert(&tree, 3, 30)); + assert(SplayTree_insert(&tree, 7, 70)); + + assert(SplayTree_freeRecursive(tree, tree.nodes[0].left) == 1); + assert(SplayTree_freeRecursive(tree, tree.nodes[0].right) == 1); + assert(SplayTree_freeRecursive(tree, tree.root) == 3); + assert(SplayTree_freeRecursive(tree, NO_NODE) == 0); +} From e845cce342e4ff841aa2efa18a3e98a554caa759 Mon Sep 17 00:00:00 2001 From: "queen-publisher[bot]" Date: Sat, 10 Oct 2026 07:43:12 +0000 Subject: [PATCH 2/4] docs: the coordination entry this branch needs to land A pull request must add exactly one docs/now entry and a bee has no way to know that: its brief names a boundary file and acceptance criteria, and docs/now/ is neither. The publisher adds it rather than failing the gate. Closes #8463 Co-Authored-By: Claude Opus 5 --- ...-trinity-src-tri-gen-splay-tree-zig-zig-5-funct.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docs/now/2026-10-10-published-port-ghashtag-trinity-src-tri-gen-splay-tree-zig-zig-5-funct.md diff --git a/docs/now/2026-10-10-published-port-ghashtag-trinity-src-tri-gen-splay-tree-zig-zig-5-funct.md b/docs/now/2026-10-10-published-port-ghashtag-trinity-src-tri-gen-splay-tree-zig-zig-5-funct.md new file mode 100644 index 0000000000..88ca0b02f6 --- /dev/null +++ b/docs/now/2026-10-10-published-port-ghashtag-trinity-src-tri-gen-splay-tree-zig-zig-5-funct.md @@ -0,0 +1,11 @@ +# NOW -- Port gHashTag/trinity:src/tri/gen_splay_tree.zig (Zig, 5 functions) to specs/port/trinity/src/tri/gen_splay_tree.t27 (published 2026-10-10) + +## A bee's work on #8463, published from `queen-8463` (Closes #8463) + +- The branch changes 1 file(s): `specs/port/trinity/src/tri/gen_splay_tree.t27`. +- `git diff --stat origin/master...queen-8463` reads: 1 file changed, 217 insertions(+) +- This entry is written by the publisher, not by the bee. A pull request must + add exactly one `docs/now/` entry and a bee has no way to know that: its brief + names a boundary file and acceptance criteria, and `docs/now/` is neither. +- What this entry does NOT establish: that the work is correct. The gates on the + pull request judge that, and they are the same gates every other change meets. From e3d24cf1d3d34e76047e324c2bbf0abef64d10fc Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 12 Oct 2026 14:17:06 +0700 Subject: [PATCH 3/4] fix(port): rename a colliding type -- one name, one definition (Refs #8470) The type-conflict ratchet refuses a second definition of a name across specs/: this port collided with specs/tri/trees/splay_tree.t27. The rename is module-scoped; nothing outside this file used the bare names. Co-Authored-By: Claude Opus 5.5 --- specs/port/trinity/src/tri/gen_splay_tree.t27 | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/specs/port/trinity/src/tri/gen_splay_tree.t27 b/specs/port/trinity/src/tri/gen_splay_tree.t27 index 81942f3bdc..c0e84a1603 100644 --- a/specs/port/trinity/src/tri/gen_splay_tree.t27 +++ b/specs/port/trinity/src/tri/gen_splay_tree.t27 @@ -21,7 +21,7 @@ module port::gen_splay_tree; const NO_NODE: i64 = -1; const POOL_CAP: usize = 16; -struct SplayNode { +struct GenSplayNode { key: i64, value: i64, left: i64, @@ -29,18 +29,19 @@ struct SplayNode { parent: i64, } -struct SplayTree { - nodes: [POOL_CAP]SplayNode, +Renamed from SplayTree/SplayNode: specs/tri/trees/splay_tree.t27 already defines both; one name with two definitions has no computable domain size (#2774). +struct GenSplayTree { + nodes: [POOL_CAP]GenSplayNode, count: usize, root: i64, allocator: i64, } -// SplayTree.init(allocator): a fresh tree has no root and no nodes, and it +// GenSplayTree.init(allocator): a fresh tree has no root and no nodes, and it // remembers the allocator it was given. -pub fn SplayTree_init(allocator: i64) -> SplayTree { - var tree = SplayTree { - nodes: [_]SplayNode{ SplayNode{ key: 0, value: 0, left: NO_NODE, right: NO_NODE, parent: NO_NODE } } ** POOL_CAP, +pub fn SplayTree_init(allocator: i64) -> GenSplayTree { + var tree = GenSplayTree { + nodes: [_]GenSplayNode{ GenSplayNode{ key: 0, value: 0, left: NO_NODE, right: NO_NODE, parent: NO_NODE } } ** POOL_CAP, count: 0, root: NO_NODE, allocator: allocator, @@ -48,16 +49,16 @@ pub fn SplayTree_init(allocator: i64) -> SplayTree { return tree; } -// SplayTree.insert(tree, key, value) !void: take the root slot when the +// GenSplayTree.insert(tree, key, value) !void: take the root slot when the // tree is empty; otherwise descend, key < curr.key to the left and anything // else to the right, and attach the new node at the first missing child, // recording its parent. The create() error is the pool being full: false. -pub fn SplayTree_insert(tree: *SplayTree, key: i64, value: i64) -> bool { +pub fn SplayTree_insert(tree: *GenSplayTree, key: i64, value: i64) -> bool { if (tree.count >= POOL_CAP) { return false; } var idx: i64 = tree.count as i64; - tree.nodes[tree.count] = SplayNode { key: key, value: value, left: NO_NODE, right: NO_NODE, parent: NO_NODE }; + tree.nodes[tree.count] = GenSplayNode { key: key, value: value, left: NO_NODE, right: NO_NODE, parent: NO_NODE }; tree.count += 1; if (tree.root == NO_NODE) { @@ -86,10 +87,10 @@ pub fn SplayTree_insert(tree: *SplayTree, key: i64, value: i64) -> bool { return false; } -// SplayTree.find(tree, key) ?i64: an equal key returns that node's value; +// GenSplayTree.find(tree, key) ?i64: an equal key returns that node's value; // key < node.key walks left, anything else walks right; the walk falling off // the tree is Zig's null. -pub fn SplayTree_find(tree: SplayTree, key: i64) -> ?i64 { +pub fn SplayTree_find(tree: GenSplayTree, key: i64) -> ?i64 { var curr: i64 = tree.root; while (curr != NO_NODE) { if (key == tree.nodes[curr as usize].key) { @@ -104,19 +105,19 @@ pub fn SplayTree_find(tree: SplayTree, key: i64) -> ?i64 { return null; } -// SplayTree.deinit(tree) void: hand the nodes back only when a root exists. +// GenSplayTree.deinit(tree) void: hand the nodes back only when a root exists. // allocator.destroy is the plumbing; the decision - how many nodes a tree // with this root gives back - is the returned count. -pub fn SplayTree_deinit(tree: SplayTree) -> i64 { +pub fn SplayTree_deinit(tree: GenSplayTree) -> i64 { if (tree.root == NO_NODE) { return 0; } return SplayTree_freeRecursive(tree, tree.root); } -// SplayTree.freeRecursive(tree, node) void: left, then right, then the node +// GenSplayTree.freeRecursive(tree, node) void: left, then right, then the node // itself - post-order, so a child is always handed back before its parent. -pub fn SplayTree_freeRecursive(tree: SplayTree, node: i64) -> i64 { +pub fn SplayTree_freeRecursive(tree: GenSplayTree, node: i64) -> i64 { if (node == NO_NODE) { return 0; } From 5c919465c298535eac1c6ade5c4811e73e88f4e2 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 12 Oct 2026 15:20:28 +0700 Subject: [PATCH 4/4] fix(port): make the rename note a comment -- the bare line broke the parse (Refs #8470) Co-Authored-By: Claude Opus 5.5 --- specs/port/trinity/src/tri/gen_splay_tree.t27 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/port/trinity/src/tri/gen_splay_tree.t27 b/specs/port/trinity/src/tri/gen_splay_tree.t27 index c0e84a1603..6f9ebfa1cb 100644 --- a/specs/port/trinity/src/tri/gen_splay_tree.t27 +++ b/specs/port/trinity/src/tri/gen_splay_tree.t27 @@ -29,7 +29,7 @@ struct GenSplayNode { parent: i64, } -Renamed from SplayTree/SplayNode: specs/tri/trees/splay_tree.t27 already defines both; one name with two definitions has no computable domain size (#2774). +// Renamed from SplayTree/SplayNode: specs/tri/trees/splay_tree.t27 already defines both; one name with two definitions has no computable domain size (#2774). struct GenSplayTree { nodes: [POOL_CAP]GenSplayNode, count: usize,