Phase 5: HAMT Persistent Map + Persistent Set, 86 tests pass

- T5.3: HAMT Persistent Map (lib/cljnim_pmap.nim)
  O(log32 n) assoc/dissoc/get with structural sharing
  16 unit tests, ckMap migrated from seq to HAMT

- T5.4: Persistent Set backed by HAMT map
  ckSet with conj/disj/contains?/get, set literal #{}

- Fix: cljContains/cljCount return CljVal (not bool/int)
- Fix: equality in generics via explicit cljEq parameter
- Updated roadmap, task board, CI/Makefile
This commit is contained in:
2026-05-08 19:13:09 +03:00
parent d763e25638
commit 6dfc2a9308
9 changed files with 864 additions and 165 deletions
+269
View File
@@ -0,0 +1,269 @@
# Persistent Map — Hash Array Mapped Trie (HAMT)
# Clojure-style 32-way branching with structural sharing
# Takes pre-computed hashes and equality functions from callers
import hashes
const
PMAP_SHIFT* = 5
PMAP_BRANCH* = 32
PMAP_MASK* = 31
PMAP_MAX_SHIFT* = 25 # 6 levels (25,20,15,10,5,0) = 30 bits of hash
type
PMapNode*[K, V] = ref object
children*: seq[PMapNode[K, V]]
keys*: seq[K]
vals*: seq[V]
hashes*: seq[Hash] # stored hashes for leaf nodes, parallel to keys
PersistentMap*[K, V] = object
root*: PMapNode[K, V]
count*: int
proc newPMapLeaf*[K, V](keys: seq[K] = @[], vals: seq[V] = @[], hashes: seq[Hash] = @[]): PMapNode[K, V] =
PMapNode[K, V](keys: keys, vals: vals, hashes: hashes)
proc newPMapInternal*[K, V](): PMapNode[K, V] =
PMapNode[K, V](children: @[])
proc pmapCopyNode*[K, V](n: PMapNode[K, V]): PMapNode[K, V] =
if n.isNil: return nil
if n.children.len > 0:
result = newPMapInternal[K, V]()
result.children = n.children
else:
result = newPMapLeaf[K, V](n.keys, n.vals, n.hashes)
proc newPersistentMap*[K, V](): PersistentMap[K, V] =
PersistentMap[K, V](root: nil, count: 0)
proc pmapGet*[K, V](m: PersistentMap[K, V], key: K, default: V, kh: Hash, eq: proc(a, b: K): bool): V =
if m.root.isNil: return default
var node: PMapNode[K, V] = m.root
var shift = PMAP_MAX_SHIFT
var h = kh
while shift >= 0:
if node.children.len > 0:
let idx = (h shr shift) and PMAP_MASK
if idx >= node.children.len or node.children[idx].isNil:
return default
node = node.children[idx]
shift -= PMAP_SHIFT
continue
for i in 0..<node.keys.len:
if eq(node.keys[i], key):
return node.vals[i]
return default
return default
proc pmapContains*[K, V](m: PersistentMap[K, V], key: K, kh: Hash, eq: proc(a, b: K): bool): bool =
if m.root.isNil: return false
var node: PMapNode[K, V] = m.root
var shift = PMAP_MAX_SHIFT
var h = kh
while shift >= 0:
if node.children.len > 0:
let idx = (h shr shift) and PMAP_MASK
if idx >= node.children.len or node.children[idx].isNil:
return false
node = node.children[idx]
shift -= PMAP_SHIFT
continue
for i in 0..<node.keys.len:
if eq(node.keys[i], key):
return true
return false
return false
proc pmapAssoc*[K, V](m: PersistentMap[K, V], key: K, val: V, kh: Hash, eq: proc(a, b: K): bool): PersistentMap[K, V] =
let h = kh
var added = false
proc doAssoc(node: PMapNode[K, V], shift: int): PMapNode[K, V] =
let idx = (h shr shift) and PMAP_MASK
if node.isNil:
added = true
return newPMapLeaf[K, V](@[key], @[val], @[kh])
if node.children.len > 0:
result = pmapCopyNode(node)
if idx >= result.children.len:
let oldLen = result.children.len
result.children.setLen(idx + 1)
for i in oldLen..<idx:
result.children[i] = nil
let childNode = if idx < node.children.len: node.children[idx] else: nil
result.children[idx] = doAssoc(childNode, shift - PMAP_SHIFT)
return
# Leaf node
if shift == 0:
result = newPMapLeaf[K, V](node.keys, node.vals, node.hashes)
for i in 0..<result.keys.len:
if eq(result.keys[i], key):
result.vals[i] = val
return
result.keys.add(key)
result.vals.add(val)
result.hashes.add(kh)
added = true
return
# Leaf at non-zero shift — promote to internal
let existingKeys = node.keys
let existingHashes = node.hashes
result = newPMapInternal[K, V]()
# Place all existing entries into the new internal node
for ki in 0..<existingKeys.len:
let ek = existingKeys[ki]
let ev = node.vals[ki]
let eh = existingHashes[ki]
let eidx = (eh shr shift) and PMAP_MASK
if eidx >= result.children.len:
let oldLen = result.children.len
result.children.setLen(eidx + 1)
for i in oldLen..<eidx:
result.children[i] = nil
if result.children[eidx].isNil:
result.children[eidx] = newPMapLeaf[K, V](@[ek], @[ev], @[eh])
else:
result.children[eidx].keys.add(ek)
result.children[eidx].vals.add(ev)
result.children[eidx].hashes.add(eh)
# Now insert the new key
let ns = max(idx + 1, result.children.len)
if ns > result.children.len:
let oldLen = result.children.len
result.children.setLen(ns)
for i in oldLen..<ns:
result.children[i] = nil
if result.children[idx].isNil:
result.children[idx] = newPMapLeaf[K, V](@[key], @[val], @[kh])
added = true
else:
result.children[idx] = doAssoc(result.children[idx], shift - PMAP_SHIFT)
if m.root.isNil:
result.root = newPMapLeaf[K, V](@[key], @[val], @[kh])
result.count = 1
else:
result.root = doAssoc(m.root, PMAP_MAX_SHIFT)
result.count = m.count
if added:
result.count += 1
proc pmapDissoc*[K, V](m: PersistentMap[K, V], key: K, kh: Hash, eq: proc(a, b: K): bool): PersistentMap[K, V] =
if m.root.isNil or m.count == 0: return m
var removed = false
let h = kh
proc doDissoc(node: PMapNode[K, V], shift: int): PMapNode[K, V] =
let idx = (h shr shift) and PMAP_MASK
if node.isNil:
return nil
if node.children.len > 0:
result = pmapCopyNode(node)
if idx < result.children.len and not result.children[idx].isNil:
result.children[idx] = doDissoc(result.children[idx], shift - PMAP_SHIFT)
return
# Leaf node
result = newPMapLeaf[K, V]()
for i in 0..<node.keys.len:
if eq(node.keys[i], key):
removed = true
else:
result.keys.add(node.keys[i])
result.vals.add(node.vals[i])
result.hashes.add(node.hashes[i])
if result.keys.len == 0:
return nil
result = m
result.root = doDissoc(m.root, PMAP_MAX_SHIFT)
if removed:
result.count -= 1
if result.count == 0:
result.root = nil
proc pmapKeys*[K, V](m: PersistentMap[K, V]): seq[K] =
result = @[]
if m.root.isNil: return
var stack: seq[PMapNode[K, V]] = @[m.root]
while stack.len > 0:
let node = stack.pop()
if node.isNil: continue
if node.children.len > 0:
for i in countdown(node.children.len - 1, 0):
if not node.children[i].isNil:
stack.add(node.children[i])
else:
result.add(node.keys)
proc pmapVals*[K, V](m: PersistentMap[K, V]): seq[V] =
result = @[]
if m.root.isNil: return
var stack: seq[PMapNode[K, V]] = @[m.root]
while stack.len > 0:
let node = stack.pop()
if node.isNil: continue
if node.children.len > 0:
for i in countdown(node.children.len - 1, 0):
if not node.children[i].isNil:
stack.add(node.children[i])
else:
result.add(node.vals)
proc pmapEntries*[K, V](m: PersistentMap[K, V]): seq[(K, V)] =
result = @[]
if m.root.isNil: return
var stack: seq[PMapNode[K, V]] = @[m.root]
while stack.len > 0:
let node = stack.pop()
if node.isNil: continue
if node.children.len > 0:
for i in countdown(node.children.len - 1, 0):
if not node.children[i].isNil:
stack.add(node.children[i])
else:
for i in 0..<node.keys.len:
result.add((node.keys[i], node.vals[i]))
proc pmapBuild*[K, V](pairs: seq[(K, V)], eq: proc(a, b: K): bool): PersistentMap[K, V] =
for (k, v) in pairs:
result = pmapAssoc(result, k, v, hash(k), eq)
iterator pmapItems*[K, V](m: PersistentMap[K, V]): (K, V) =
if not m.root.isNil:
var stack: seq[PMapNode[K, V]] = @[m.root]
while stack.len > 0:
let node = stack.pop()
if node.isNil: continue
if node.children.len > 0:
for i in countdown(node.children.len - 1, 0):
if not node.children[i].isNil:
stack.add(node.children[i])
else:
for i in 0..<node.keys.len:
yield (node.keys[i], node.vals[i])
proc `$`*[K, V](m: PersistentMap[K, V]): string =
var parts: seq[string] = @[]
for (k, v) in pmapItems(m):
parts.add($k & " " & $v)
"{" & parts.join(", ") & "}"
+140 -159
View File
@@ -1,10 +1,11 @@
import cljnim_pvec
import cljnim_pmap
import strutils, sequtils, hashes, algorithm, os, osproc
type
CljKind* = enum
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
ckList, ckVector, ckMap, ckFn, ckAtom
ckList, ckVector, ckMap, ckSet, ckFn, ckAtom
CljVal* = ref CljValObj
CljValObj = object
@@ -18,36 +19,25 @@ type
of ckSymbol: symName*: string
of ckList: listItems*: seq[CljVal]
of ckVector: vecData*: PersistentVector[CljVal]
of ckMap:
mapKeys*: seq[CljVal]
mapVals*: seq[CljVal]
of ckMap: mapData*: PersistentMap[CljVal, CljVal]
of ckSet: setData*: PersistentMap[CljVal, bool]
of ckFn: fnProc*: proc(args: seq[CljVal]): CljVal
of ckAtom: atomVal*: CljVal
# ---- Constructors ----
# ---- Hashing ----
proc cljNil*(): CljVal = CljVal(kind: ckNil)
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
proc cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64)
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
proc cljList*(items: seq[CljVal]): CljVal = CljVal(kind: ckList, listItems: items)
proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, vecData: newPersistentVector(items))
proc cljFn*(p: proc(args: seq[CljVal]): CljVal): CljVal = CljVal(kind: ckFn, fnProc: p)
proc cljMap*(keys, vals: seq[CljVal]): CljVal =
CljVal(kind: ckMap, mapKeys: keys, mapVals: vals)
proc cljMapFromPairs*(pairs: seq[(CljVal, CljVal)]): CljVal =
var ks: seq[CljVal] = @[]
var vs: seq[CljVal] = @[]
for (k, v) in pairs:
ks.add(k)
vs.add(v)
cljMap(ks, vs)
proc hash*(v: CljVal): Hash =
case v.kind
of ckNil: result = hash(0)
of ckBool: result = hash(v.boolVal)
of ckInt: result = hash(v.intVal)
of ckFloat: result = hash(v.floatVal)
of ckString: result = hash(v.strVal)
of ckKeyword: result = hash(v.kwName)
of ckSymbol: result = hash(v.symName)
of ckFn: result = hash(cast[uint](unsafeAddr v.fnProc))
of ckAtom: result = hash(cast[uint](unsafeAddr v.atomVal))
of ckList, ckVector, ckMap, ckSet: result = hash(0)
# ---- Equality ----
@@ -65,6 +55,40 @@ proc `==`*(a, b: CljVal): bool =
of ckSymbol: a.symName == b.symName
else: false
proc cljEq*(a, b: CljVal): bool = a == b
# ---- Constructors ----
proc cljNil*(): CljVal = CljVal(kind: ckNil)
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
proc cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64)
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
proc cljList*(items: seq[CljVal]): CljVal = CljVal(kind: ckList, listItems: items)
proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, vecData: newPersistentVector(items))
proc cljFn*(p: proc(args: seq[CljVal]): CljVal): CljVal = CljVal(kind: ckFn, fnProc: p)
proc cljMap*(keys, vals: seq[CljVal]): CljVal =
var m = newPersistentMap[CljVal, CljVal]()
for i in 0..<keys.len:
m = pmapAssoc(m, keys[i], vals[i], hash(keys[i]), cljEq)
CljVal(kind: ckMap, mapData: m)
proc cljMapFromPairs*(pairs: seq[(CljVal, CljVal)]): CljVal =
var m = newPersistentMap[CljVal, CljVal]()
for (k, v) in pairs:
m = pmapAssoc(m, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: m)
proc cljSet*(items: seq[CljVal]): CljVal =
var s = newPersistentMap[CljVal, bool]()
for item in items:
s = pmapAssoc(s, item, true, hash(item), cljEq)
CljVal(kind: ckSet, setData: s)
# ---- Display ----
proc cljRepr*(v: CljVal): string =
@@ -81,9 +105,14 @@ proc cljRepr*(v: CljVal): string =
of ckVector: "[" & toSeq(v.vecData).mapIt(cljRepr(it)).join(" ") & "]"
of ckMap:
var parts: seq[string] = @[]
for i in 0..<v.mapKeys.len:
parts.add(cljRepr(v.mapKeys[i]) & " " & cljRepr(v.mapVals[i]))
for (k, v) in pmapItems(v.mapData):
parts.add(cljRepr(k) & " " & cljRepr(v))
"{" & parts.join(", ") & "}"
of ckSet:
var parts: seq[string] = @[]
for (k, v) in pmapItems(v.setData):
parts.add(cljRepr(k))
"#{" & parts.join(" ") & "}"
of ckFn: "#<fn>"
of ckAtom: "(atom " & cljRepr(v.atomVal) & ")"
@@ -331,14 +360,15 @@ proc cljOdd*(v: CljVal): CljVal =
# ---- Collection operations ----
proc cljCount*(v: CljVal): int =
if v.isNil: return 0
proc cljCount*(v: CljVal): CljVal =
if v.isNil: return cljInt(0)
case v.kind
of ckList: v.listItems.len
of ckVector: v.vecData.count
of ckMap: v.mapKeys.len
of ckString: v.strVal.len
else: 0
of ckList: cljInt(v.listItems.len)
of ckVector: cljInt(v.vecData.count)
of ckMap: cljInt(v.mapData.count)
of ckSet: cljInt(v.setData.count)
of ckString: cljInt(v.strVal.len)
else: cljInt(0)
proc cljFirst*(v: CljVal): CljVal =
if v.isNil: return cljNil()
@@ -405,6 +435,8 @@ proc cljConj*(coll: CljVal, item: CljVal): CljVal =
var newItems = toSeq(coll.vecData)
newItems.add(item)
cljVector(newItems)
of ckSet:
CljVal(kind: ckSet, setData: pmapAssoc(coll.setData, item, true, hash(item), cljEq))
else:
cljList(@[item])
@@ -423,6 +455,11 @@ proc cljCons*(item: CljVal, coll: CljVal): CljVal =
else:
cljList(@[item])
proc cljDisj*(s: CljVal, item: CljVal): CljVal =
if s.isNil or s.kind != ckSet:
return cljSet(@[])
CljVal(kind: ckSet, setData: pmapDissoc(s.setData, item, hash(item), cljEq))
proc cljSeq*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
@@ -439,6 +476,9 @@ proc cljSeq*(v: CljVal): CljVal =
for c in v.strVal:
chars.add(cljString($c))
cljList(chars)
of ckSet:
if v.setData.count == 0: cljNil()
else: cljList(pmapKeys(v.setData))
else: cljNil()
proc cljVec*(v: CljVal): CljVal =
@@ -449,7 +489,7 @@ proc cljVec*(v: CljVal): CljVal =
else: cljVector(@[v])
proc cljEmpty*(v: CljVal): CljVal =
cljBool(cljCount(v) == 0)
cljBool(cljCount(v).intVal == 0)
proc cljConcat*(args: seq[CljVal]): CljVal =
var items: seq[CljVal] = @[]
@@ -556,29 +596,23 @@ proc cljPartition*(n: int, coll: CljVal): CljVal =
proc cljFrequencies*(coll: CljVal): CljVal =
if coll.isNil: return cljMap(@[], @[])
var keys: seq[CljVal] = @[]
var vals: seq[CljVal] = @[]
var m = newPersistentMap[CljVal, CljVal]()
var items: seq[CljVal]
case coll.kind
of ckList: items = coll.listItems
of ckVector: items = toSeq(coll.vecData)
else: return cljMap(@[], @[])
for item in items:
var found = false
for j in 0..<keys.len:
if keys[j] == item:
vals[j] = cljInt(vals[j].intVal + 1)
found = true
break
if not found:
keys.add(item)
vals.add(cljInt(1))
cljMap(keys, vals)
let current = pmapGet(m, item, cljNil(), hash(item), cljEq)
if cljIsNil(current):
m = pmapAssoc(m, item, cljInt(1), hash(item), cljEq)
else:
m = pmapAssoc(m, item, cljInt(current.intVal + 1), hash(item), cljEq)
CljVal(kind: ckMap, mapData: m)
proc cljGroupBy*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal =
if coll.isNil: return cljMap(@[], @[])
var keys: seq[CljVal] = @[]
var vals: seq[CljVal] = @[]
var m = newPersistentMap[CljVal, CljVal]()
var items: seq[CljVal]
case coll.kind
of ckList: items = coll.listItems
@@ -586,21 +620,14 @@ proc cljGroupBy*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal =
else: return cljMap(@[], @[])
for item in items:
let key = f(@[item])
var found = false
for j in 0..<keys.len:
if keys[j] == key:
case vals[j].kind
of ckList:
var newItems = vals[j].listItems
newItems.add(item)
vals[j] = cljList(newItems)
else: discard
found = true
break
if not found:
keys.add(key)
vals.add(cljList(@[item]))
cljMap(keys, vals)
let existing = pmapGet(m, key, cljNil(), hash(key), cljEq)
if cljIsNil(existing):
m = pmapAssoc(m, key, cljList(@[item]), hash(key), cljEq)
else:
var newItems = existing.listItems
newItems.add(item)
m = pmapAssoc(m, key, cljList(newItems), hash(key), cljEq)
CljVal(kind: ckMap, mapData: m)
proc cljGroupBy*(f: CljVal, coll: CljVal): CljVal =
if f.kind == ckFn: cljGroupBy(f.fnProc, coll)
@@ -609,83 +636,63 @@ proc cljGroupBy*(f: CljVal, coll: CljVal): CljVal =
# ---- Map operations ----
proc cljGet*(m: CljVal, key: CljVal): CljVal =
if m.isNil or m.kind != ckMap: return cljNil()
for i in 0..<m.mapKeys.len:
if m.mapKeys[i] == key:
return m.mapVals[i]
cljNil()
if m.isNil: return cljNil()
case m.kind
of ckMap: pmapGet(m.mapData, key, cljNil(), hash(key), cljEq)
of ckSet:
if pmapContains(m.setData, key, hash(key), cljEq): key
else: cljNil()
else: cljNil()
proc cljGetDefault*(m: CljVal, key: CljVal, default: CljVal): CljVal =
let v = cljGet(m, key)
if cljIsNil(v): default
else: v
if m.isNil: return default
case m.kind
of ckMap: pmapGet(m.mapData, key, default, hash(key), cljEq)
of ckSet:
if pmapContains(m.setData, key, hash(key), cljEq): key
else: default
else: default
proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal =
if m.isNil or m.kind != ckMap:
return cljMap(@[key], @[val])
var newKeys = m.mapKeys
var newVals = m.mapVals
for i in 0..<newKeys.len:
if newKeys[i] == key:
newVals[i] = val
return cljMap(newKeys, newVals)
newKeys.add(key)
newVals.add(val)
cljMap(newKeys, newVals)
return CljVal(kind: ckMap, mapData: pmapAssoc(newPersistentMap[CljVal, CljVal](), key, val, hash(key), cljEq))
CljVal(kind: ckMap, mapData: pmapAssoc(m.mapData, key, val, hash(key), cljEq))
proc cljDissoc*(m: CljVal, key: CljVal): CljVal =
if m.isNil or m.kind != ckMap: return cljMap(@[], @[])
var newKeys: seq[CljVal] = @[]
var newVals: seq[CljVal] = @[]
for i in 0..<m.mapKeys.len:
if m.mapKeys[i] != key:
newKeys.add(m.mapKeys[i])
newVals.add(m.mapVals[i])
cljMap(newKeys, newVals)
CljVal(kind: ckMap, mapData: pmapDissoc(m.mapData, key, hash(key), cljEq))
proc cljContains*(m: CljVal, key: CljVal): bool =
if m.isNil or m.kind != ckMap: return false
for i in 0..<m.mapKeys.len:
if m.mapKeys[i] == key:
return true
false
proc cljContains*(m: CljVal, key: CljVal): CljVal =
if m.isNil: return cljBool(false)
case m.kind
of ckMap: cljBool(pmapContains(m.mapData, key, hash(key), cljEq))
of ckSet: cljBool(pmapContains(m.setData, key, hash(key), cljEq))
else: cljBool(false)
proc cljKeys*(m: CljVal): CljVal =
if m.isNil or m.kind != ckMap: return cljList(@[])
cljList(m.mapKeys)
cljList(pmapKeys(m.mapData))
proc cljVals*(m: CljVal): CljVal =
if m.isNil or m.kind != ckMap: return cljList(@[])
cljList(m.mapVals)
cljList(pmapVals(m.mapData))
proc cljSelectKeys*(m: CljVal, keys: seq[CljVal]): CljVal =
if m.isNil or m.kind != ckMap: return cljMap(@[], @[])
var newKeys: seq[CljVal] = @[]
var newVals: seq[CljVal] = @[]
var result = newPersistentMap[CljVal, CljVal]()
for key in keys:
for i in 0..<m.mapKeys.len:
if m.mapKeys[i] == key:
newKeys.add(key)
newVals.add(m.mapVals[i])
break
cljMap(newKeys, newVals)
let v = pmapGet(m.mapData, key, cljNil(), hash(key), cljEq)
if not cljIsNil(v):
result = pmapAssoc(result, key, v, hash(key), cljEq)
CljVal(kind: ckMap, mapData: result)
proc cljMerge*(args: seq[CljVal]): CljVal =
var keys: seq[CljVal] = @[]
var vals: seq[CljVal] = @[]
var result = newPersistentMap[CljVal, CljVal]()
for m in args:
if not m.isNil and m.kind == ckMap:
for i in 0..<m.mapKeys.len:
var found = false
for j in 0..<keys.len:
if keys[j] == m.mapKeys[i]:
vals[j] = m.mapVals[i]
found = true
break
if not found:
keys.add(m.mapKeys[i])
vals.add(m.mapVals[i])
cljMap(keys, vals)
for (k, v) in pmapItems(m.mapData):
result = pmapAssoc(result, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: result)
# ---- Higher-order functions ----
@@ -912,6 +919,7 @@ proc cljType*(v: CljVal): CljVal =
of ckList: cljKeyword("list")
of ckVector: cljKeyword("vector")
of ckMap: cljKeyword("map")
of ckSet: cljKeyword("set")
of ckFn: cljKeyword("function")
of ckAtom: cljKeyword("atom")
@@ -989,49 +997,22 @@ proc cljInto*(to: CljVal, src: CljVal): CljVal =
else: to
of ckMap:
if src.kind == ckMap:
var keys = to.mapKeys
var vals = to.mapVals
for i in 0..<src.mapKeys.len:
var found = false
for j in 0..<keys.len:
if keys[j] == src.mapKeys[i]:
vals[j] = src.mapVals[i]
found = true
break
if not found:
keys.add(src.mapKeys[i])
vals.add(src.mapVals[i])
cljMap(keys, vals)
var result = to.mapData
for (k, v) in pmapItems(src.mapData):
result = pmapAssoc(result, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: result)
elif src.kind == ckVector:
var keys = to.mapKeys
var vals = to.mapVals
var result = to.mapData
for pair in src.vecData.items:
if pair.kind == ckVector and pair.vecData.count == 2:
var found = false
for j in 0..<keys.len:
if keys[j] == pvecNth(pair.vecData, 0):
vals[j] = pvecNth(pair.vecData, 1)
found = true
break
if not found:
keys.add(pvecNth(pair.vecData, 0))
vals.add(pvecNth(pair.vecData, 1))
cljMap(keys, vals)
elif src.kind == ckVector:
var keys = to.mapKeys
var vals = to.mapVals
for pair in src.vecData.items:
result = pmapAssoc(result, pvecNth(pair.vecData, 0), pvecNth(pair.vecData, 1), hash(pvecNth(pair.vecData, 0)), cljEq)
CljVal(kind: ckMap, mapData: result)
elif src.kind == ckList:
var result = to.mapData
for pair in src.listItems:
if pair.kind == ckVector and pair.vecData.count == 2:
var found = false
for j in 0..<keys.len:
if keys[j] == pvecNth(pair.vecData, 0):
vals[j] = pvecNth(pair.vecData, 1)
found = true
break
if not found:
keys.add(pvecNth(pair.vecData, 0))
vals.add(pvecNth(pair.vecData, 1))
cljMap(keys, vals)
result = pmapAssoc(result, pvecNth(pair.vecData, 0), pvecNth(pair.vecData, 1), hash(pvecNth(pair.vecData, 0)), cljEq)
CljVal(kind: ckMap, mapData: result)
else: to
else: to