d763e25638
- Add lib/cljnim_pvec.nim: 32-way HAMT Persistent Vector with structural sharing - Migrate ckVector from seq[CljVal] to PersistentVector[CljVal] in runtime - Fix recursive pushLeaf bug (nil nodes at depth > 2) - Fix defn/if return value bug: replace discard with result = in proc bodies - Fix cljNth to accept CljVal index - Add len and [] overloads for PersistentVector seq compatibility - Add tests/test_pvec.nim (14 tests) - Update .gitlab-ci.yml to nim:2.2.10 and add test_pvec - Update docs/ROADMAP.md and add PHASE5_HAMT.md
2.9 KiB
2.9 KiB
Phase 5: HAMT Persistent Vector
Status: COMPLETE
What was implemented
Hash Array Mapped Trie (HAMT) Persistent Vector — 32-way branching trie with path-copying structural sharing, matching Clojure's PersistentVector semantics.
lib/cljnim_pvec.nim— Core implementationPersistentVector[T]— root pointer + count + shift + tailpvecConj— O(log₃₂ N) append with structural sharingpvecNth— O(log₃₂ N) random accesspvecAssoc— O(log₃₂ N) update with path copyingpvecPop— O(log₃₂ N) remove lastnewPersistentVector— builder fromseq[T]toSeq— convert back toseq[T]len,[]overloads for seq-like interface
Integration into Runtime
lib/cljnim_runtime.nim:ckVectornow storesPersistentVector[CljVal]- All 51
vecDatareferences migrated to usepvecNth/toSeq/count cljVectorconstructor builds fromseq[CljVal]vianewPersistentVector
Architecture Decisions
- Tail optimization: Like Clojure, maintains a 32-element tail buffer for fast append of recent items
- Lazy tree growth: Root promotion only happens when tree is full (
needsNewRootcheck) - Path copying: Every
conj/assoccopies only the nodes along the path to the changed leaf - Seq compatibility: Added
lenand[]overloads somapIt,join,itemsiterator work transparently
Bug fixes during implementation
- Recursive
pushLeafbug: Whenchild.isNilandshift != BRANCHING_BITS, was creating empty internal nodes without recursing to place the leaf. Fixed by callingpushLeaf(newInternalNode(), index, shift-5, val)instead of justnewInternalNode().
Tests
tests/test_pvec.nim— 14 tests covering:- Empty vector, single element, few elements
conjone-by-one (100 elements)conjacross tail boundary (32)conjacross second level (1024)conjlarge (10000)associn tail and treeassocpreserves old vector (structural sharing)popfrom tail, across boundary, to empty
Performance Characteristics
| Operation | Complexity | Notes |
|---|---|---|
conj |
O(log₃₂ N) | ~1-3 node copies |
nth |
O(log₃₂ N) | ~3-5 pointer hops |
assoc |
O(log₃₂ N) | ~1-3 node copies |
pop |
O(log₃₂ N) | May pull leaf into tail |
toSeq |
O(N) | Full traversal |
For N ≤ 1M, depth ≤ 3 (shift ≤ 15), making operations effectively constant-time.
Migration Guide for Other AIs
If you need to work with vectors in cljnim_runtime.nim:
# Instead of:
v.vecData.len # Use: v.vecData.count
v.vecData[0] # Use: pvecNth(v.vecData, 0)
v.vecData[1..^1] # Use: toSeq(v.vecData)[1..^1]
for item in v.vecData: # Use: for item in v.vecData.items:
mapIt from sequtils works transparently thanks to len + items iterator overloads.