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:
+140
-159
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user