# 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 implementation - `PersistentVector[T]` — root pointer + count + shift + tail - `pvecConj` — O(log₃₂ N) append with structural sharing - `pvecNth` — O(log₃₂ N) random access - `pvecAssoc` — O(log₃₂ N) update with path copying - `pvecPop` — O(log₃₂ N) remove last - `newPersistentVector` — builder from `seq[T]` - `toSeq` — convert back to `seq[T]` - `len`, `[]` overloads for seq-like interface **Integration into Runtime** - `lib/cljnim_runtime.nim`: `ckVector` now stores `PersistentVector[CljVal]` - All 51 `vecData` references migrated to use `pvecNth`/`toSeq`/`count` - `cljVector` constructor builds from `seq[CljVal]` via `newPersistentVector` ### Architecture Decisions 1. **Tail optimization**: Like Clojure, maintains a 32-element tail buffer for fast append of recent items 2. **Lazy tree growth**: Root promotion only happens when tree is full (`needsNewRoot` check) 3. **Path copying**: Every `conj`/`assoc` copies only the nodes along the path to the changed leaf 4. **Seq compatibility**: Added `len` and `[]` overloads so `mapIt`, `join`, `items` iterator work transparently ### Bug fixes during implementation - **Recursive `pushLeaf` bug**: When `child.isNil` and `shift != BRANCHING_BITS`, was creating empty internal nodes without recursing to place the leaf. Fixed by calling `pushLeaf(newInternalNode(), index, shift-5, val)` instead of just `newInternalNode()`. ### Tests - `tests/test_pvec.nim` — 14 tests covering: - Empty vector, single element, few elements - `conj` one-by-one (100 elements) - `conj` across tail boundary (32) - `conj` across second level (1024) - `conj` large (10000) - `assoc` in tail and tree - `assoc` preserves old vector (structural sharing) - `pop` from 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`: ```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.