Phase 5/6 complete: Transients, meta/with-meta, instance?

T5.5 Transients (transient/persistent!/conj!/assoc!)
  - Mutable builder for batch vector/map operations
  - Use with let bindings for proper ordering

T6.10 meta/with-meta — metadata on any CljVal via ref object field
T6.11 instance? — type predicate (instance? :integer 42)

Also marked T9.1 Atoms as done (already implemented earlier)

86 tests pass, all examples work.
Phases 0-6 now complete. Phase 7: 2/4 tasks done.
This commit is contained in:
2026-05-08 19:48:35 +03:00
parent 66d91cd086
commit b67962dbba
4 changed files with 112 additions and 13 deletions
+93 -2
View File
@@ -5,10 +5,11 @@ import strutils, sequtils, hashes, algorithm, os, osproc
type
CljKind* = enum
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
ckList, ckVector, ckMap, ckSet, ckFn, ckAtom
ckList, ckVector, ckMap, ckSet, ckFn, ckAtom, ckTransient
CljVal* = ref CljValObj
CljValObj = object
meta*: CljVal # metadata attached to this value
case kind*: CljKind
of ckNil: discard
of ckBool: boolVal*: bool
@@ -23,6 +24,10 @@ type
of ckSet: setData*: PersistentMap[CljVal, bool]
of ckFn: fnProc*: proc(args: seq[CljVal]): CljVal
of ckAtom: atomVal*: CljVal
of ckTransient:
transKind*: CljKind # ckVector or ckMap — what this transient was created from
transVec*: seq[CljVal] # mutable vector builder
transPairs*: seq[(CljVal, CljVal)] # mutable map builder
# ---- Hashing ----
@@ -37,7 +42,7 @@ proc hash*(v: CljVal): Hash =
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)
of ckList, ckVector, ckMap, ckSet, ckTransient: result = hash(0)
# ---- Equality ----
@@ -115,6 +120,7 @@ proc cljRepr*(v: CljVal): string =
"#{" & parts.join(" ") & "}"
of ckFn: "#<fn>"
of ckAtom: "(atom " & cljRepr(v.atomVal) & ")"
of ckTransient: "#<transient>"
proc cljStr*(v: CljVal): string =
if v.isNil: return ""
@@ -368,6 +374,9 @@ proc cljCount*(v: CljVal): CljVal =
of ckMap: cljInt(v.mapData.count)
of ckSet: cljInt(v.setData.count)
of ckString: cljInt(v.strVal.len)
of ckTransient:
if v.transKind == ckVector: cljInt(v.transVec.len)
else: cljInt(v.transPairs.len)
else: cljInt(0)
proc cljFirst*(v: CljVal): CljVal =
@@ -922,6 +931,38 @@ proc cljType*(v: CljVal): CljVal =
of ckSet: cljKeyword("set")
of ckFn: cljKeyword("function")
of ckAtom: cljKeyword("atom")
of ckTransient: cljKeyword("transient")
proc cljInstanceP*(t: CljVal, v: CljVal): CljVal =
if t.kind != ckKeyword: return cljBool(false)
let vt = cljType(v)
cljBool(t.kwName == vt.kwName)
proc cljMeta*(v: CljVal): CljVal =
if v.isNil: return cljNil()
if v.meta.isNil: return cljNil()
v.meta
proc cljWithMeta*(v: CljVal, m: CljVal): CljVal =
if v.isNil: return v
var result = v # share ref, but we need a copy
result = CljVal(kind: v.kind, meta: m)
case v.kind
of ckNil: discard
of ckBool: result.boolVal = v.boolVal
of ckInt: result.intVal = v.intVal
of ckFloat: result.floatVal = v.floatVal
of ckString: result.strVal = v.strVal
of ckKeyword: result.kwName = v.kwName
of ckSymbol: result.symName = v.symName
of ckList: result.listItems = v.listItems
of ckVector: result.vecData = v.vecData
of ckMap: result.mapData = v.mapData
of ckSet: result.setData = v.setData
of ckFn: result.fnProc = v.fnProc
of ckAtom: result.atomVal = v.atomVal
of ckTransient: result.transKind = v.transKind; result.transVec = v.transVec; result.transPairs = v.transPairs
return result
proc cljAtom*(v: CljVal): CljVal =
CljVal(kind: ckAtom, atomVal: v)
@@ -950,6 +991,56 @@ proc cljSwap*(a: CljVal, f: CljVal, args: seq[CljVal] = @[]): CljVal =
if f.kind == ckFn: cljSwap(a, f.fnProc, args)
else: raise newException(CatchableError, "swap! requires a function")
# ---- Transients ----
proc cljTransient*(coll: CljVal): CljVal =
case coll.kind
of ckVector:
result = CljVal(kind: ckTransient, transKind: ckVector)
result.transVec = toSeq(coll.vecData)
of ckMap:
result = CljVal(kind: ckTransient, transKind: ckMap)
result.transPairs = pmapEntries(coll.mapData)
of ckSet:
result = CljVal(kind: ckTransient, transKind: ckVector)
result.transVec = pmapKeys(coll.setData)
else:
raise newException(CatchableError, "transient requires a collection")
proc cljPersistent*(t: CljVal): CljVal =
if t.isNil or t.kind != ckTransient:
raise newException(CatchableError, "persistent! requires a transient")
case t.transKind
of ckVector:
cljVector(t.transVec)
of ckMap:
var m = newPersistentMap[CljVal, CljVal]()
for (k, v) in t.transPairs:
m = pmapAssoc(m, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: m)
else:
raise newException(CatchableError, "persistent! requires a transient")
proc cljConjB*(t: CljVal, item: CljVal): CljVal =
if t.isNil or t.kind != ckTransient:
raise newException(CatchableError, "conj! requires a transient")
case t.transKind
of ckVector:
t.transVec.add(item)
of ckMap:
if item.kind == ckVector and item.vecData.count == 2:
t.transPairs.add((pvecNth(item.vecData, 0), pvecNth(item.vecData, 1)))
of ckSet: discard
else: raise newException(CatchableError, "conj! unsupported source type")
t
proc cljAssocB*(t: CljVal, key: CljVal, val: CljVal): CljVal =
if t.isNil or t.kind != ckTransient:
raise newException(CatchableError, "assoc! requires a transient")
if t.transKind == ckMap:
t.transPairs.add((key, val))
t
# ---- Additional functions needed by emitter ----
proc cljNotEq*(args: seq[CljVal]): CljVal =