fix: general equality (=) for all types, add type predicates & collection fns, test suite docs

- Fix: = now uses cljEqual/cljMultiEqual instead of numeric-only cljNumEq,
  so (= :a :a), (= "a" "a"), (= [1 2] [1 2]), (= nil nil) etc return true
- Fix: ns forms inside unwrapped (do ...) are skipped instead of emitting
  broken Nim comments inside cljRepr()
- Fix: defmacro forms go to defs section, not wrapped in discard cljRepr()
- Add 18 type predicates: keyword?, symbol?, string?, number?, integer?,
  float?, vector?, map?, set?, list?, seq?, coll?, sequential?, fn?,
  boolean?, true?, false?, some?
- Add collection fns: second, ffirst, nfirst, peek, pop
- Add keyword/symbol ops: keyword, symbol, name, namespace, key, val
- Update eval interpreter = to handle structural collection equality
- Add test_single.py + test_vals.clj for Clojure test suite runner
- Add docs/en/07 and docs/bg/07 for cross-dialect test suite compatibility
- Update README with test suite documentation link
This commit is contained in:
2026-05-09 12:17:10 +03:00
parent ba0b300917
commit a9ad83c509
12 changed files with 777 additions and 83 deletions
+188 -2
View File
@@ -148,6 +148,86 @@ proc cljIsTrue*(v: CljVal): bool = v.kind == ckBool and v.boolVal
proc cljIsFalse*(v: CljVal): bool = v.kind == ckBool and not v.boolVal
proc cljIsTruthy*(v: CljVal): bool = not cljIsNil(v) and not cljIsFalse(v)
proc cljIsNilP*(v: CljVal): CljVal = cljBool(cljIsNil(v))
proc cljIsSome*(v: CljVal): CljVal = cljBool(not cljIsNil(v))
proc cljIsKeyword*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckKeyword)
proc cljIsSymbol*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckSymbol)
proc cljIsString*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckString)
proc cljIsNumber*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and (v.kind == ckInt or v.kind == ckFloat))
proc cljIsInteger*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckInt)
proc cljIsFloat*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckFloat)
proc cljIsVector*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckVector)
proc cljIsMap*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckMap)
proc cljIsSet*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckSet)
proc cljIsList*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckList)
proc cljIsSeq*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckList, ckVector})
proc cljIsColl*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckList, ckVector, ckMap, ckSet})
proc cljIsSequential*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckList, ckVector})
proc cljIsFn*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckFn)
proc cljIsBool*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckBool)
proc cljIsTrueP*(v: CljVal): CljVal = cljBool(cljIsTrue(v))
proc cljIsFalseP*(v: CljVal): CljVal = cljBool(cljIsFalse(v))
proc cljKeywordFn*(v: CljVal): CljVal =
if v.isNil or v.kind == ckNil: return cljNil()
case v.kind
of ckKeyword: v
of ckString: cljKeyword(v.strVal)
of ckSymbol: cljKeyword(v.symName)
else: cljNil()
proc cljSymbolFn*(v: CljVal): CljVal =
if v.isNil or v.kind == ckNil: return cljNil()
case v.kind
of ckSymbol: v
of ckString: cljSymbol(v.strVal)
of ckKeyword: cljSymbol(v.kwName)
else: cljNil()
proc cljName*(v: CljVal): CljVal =
if v.isNil or v.kind == ckNil: return cljNil()
case v.kind
of ckKeyword: cljString(v.kwName)
of ckSymbol: cljString(v.symName)
else: raise newException(CatchableError, "name requires a keyword or symbol")
proc cljNamespace*(v: CljVal): CljVal =
if v.isNil or v.kind == ckNil: return cljNil()
case v.kind
of ckKeyword:
let idx = v.kwName.find('/')
if idx >= 0: cljString(v.kwName[0..<idx])
else: cljNil()
of ckSymbol:
let idx = v.symName.find('/')
if idx >= 0: cljString(v.symName[0..<idx])
else: cljNil()
else: raise newException(CatchableError, "namespace requires a keyword or symbol")
proc cljKey*(v: CljVal): CljVal =
if v.isNil or v.kind == ckNil: return cljNil()
case v.kind
of ckVector:
if v.vecData.count >= 2: pvecNth(v.vecData, 0)
else: cljNil()
of ckList:
if v.listItems.len >= 2: v.listItems[0]
else: cljNil()
else:
raise newException(CatchableError, "key requires a map entry (vector or list of 2 items)")
proc cljEntryVal*(v: CljVal): CljVal =
if v.isNil or v.kind == ckNil: return cljNil()
case v.kind
of ckVector:
if v.vecData.count >= 2: pvecNth(v.vecData, 1)
else: cljNil()
of ckList:
if v.listItems.len >= 2: v.listItems[1]
else: cljNil()
else:
raise newException(CatchableError, "val requires a map entry (vector or list of 2 items)")
# ---- I/O ----
proc cljPrintln*(args: seq[CljVal]): CljVal =
@@ -316,6 +396,54 @@ proc cljNumEq*(args: seq[CljVal]): CljVal =
return cljBool(false)
cljBool(true)
proc cljEqual*(a, b: CljVal): bool =
## Clojure = equality: structural equality for all types, numeric equality for numbers
if a.isNil and b.isNil: return true
if a.isNil or b.isNil: return false
if a.kind != b.kind:
if a.kind == ckInt and b.kind == ckFloat:
return a.intVal.float64 == b.floatVal
if a.kind == ckFloat and b.kind == ckInt:
return a.floatVal == b.intVal.float64
return false
case a.kind
of ckNil: true
of ckBool: a.boolVal == b.boolVal
of ckInt: a.intVal == b.intVal
of ckFloat: a.floatVal == b.floatVal
of ckString: a.strVal == b.strVal
of ckKeyword: a.kwName == b.kwName
of ckSymbol: a.symName == b.symName
of ckList:
if a.listItems.len != b.listItems.len: return false
for i in 0..<a.listItems.len:
if not cljEqual(a.listItems[i], b.listItems[i]): return false
true
of ckVector:
if a.vecData.count != b.vecData.count: return false
for i in 0..<a.vecData.count:
if not cljEqual(a.vecData.pvecNth(i), b.vecData.pvecNth(i)): return false
true
of ckMap:
if a.mapData.count != b.mapData.count: return false
for (k, v) in a.mapData.pmapItems:
if not b.mapData.pmapContains(k, hash(k), cljEqual): return false
if not cljEqual(v, b.mapData.pmapGet(k, cljNil(), hash(k), cljEqual)): return false
true
of ckSet:
if a.setData.count != b.setData.count: return false
for (k, _) in a.setData.pmapItems:
if not b.setData.pmapContains(k, hash(k), cljEqual): return false
true
else: false
proc cljMultiEqual*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
if not cljEqual(args[i-1], args[i]):
return cljBool(false)
cljBool(true)
proc cljLt*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
@@ -434,6 +562,15 @@ proc cljNext*(v: CljVal): CljVal =
return cljNil()
r
proc cljSecond*(v: CljVal): CljVal =
cljFirst(cljRest(v))
proc cljFfirst*(v: CljVal): CljVal =
cljFirst(cljFirst(v))
proc cljNfirst*(v: CljVal): CljVal =
cljNext(cljFirst(v))
proc cljLast*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
@@ -496,6 +633,31 @@ proc cljDisj*(s: CljVal, item: CljVal): CljVal =
return cljSet(@[])
CljVal(kind: ckSet, setData: pmapDissoc(s.setData, item, hash(item), cljEq))
proc cljPeek*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
of ckList:
if v.listItems.len == 0: cljNil()
else: v.listItems[0]
of ckVector:
if v.vecData.count == 0: cljNil()
else: pvecNth(v.vecData, v.vecData.count - 1)
else: cljNil()
proc cljPop*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
of ckList:
if v.listItems.len <= 1: cljList(@[])
else: cljList(v.listItems[1..^1])
of ckVector:
if v.vecData.count <= 1: cljVector(@[])
else:
var items = toSeq(v.vecData)
items.setLen(items.len - 1)
cljVector(items)
else: cljNil()
proc cljSeq*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
@@ -1129,7 +1291,7 @@ proc cljAssocB*(t: CljVal, key: CljVal, val: CljVal): CljVal =
# ---- Additional functions needed by emitter ----
proc cljNotEq*(args: seq[CljVal]): CljVal =
cljNot(cljNumEq(args))
cljNot(cljMultiEqual(args))
proc cljGetIn*(m: CljVal, keys: CljVal, default: CljVal = nil): CljVal =
if keys.isNil or keys.kind != ckList:
@@ -1410,4 +1572,28 @@ proc cljGitLog*(n: CljVal = cljInt(5)): CljVal =
for line in gitOut.splitLines():
if line.len > 0:
items.add(cljString(line))
cljVector(items)
proc cljZipmap*(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "zipmap requires 2 arguments")
let keys = args[0]
let vals = args[1]
if keys.kind != ckVector and keys.kind != ckList:
raise newException(CatchableError, "zipmap: first argument must be seqable")
var kitems: seq[CljVal] = @[]
var vitems: seq[CljVal] = @[]
case keys.kind
of ckVector: kitems = keys.vecData.toSeq()
of ckList: kitems = keys.listItems
else: discard
case vals.kind
of ckVector: vitems = vals.vecData.toSeq()
of ckList: vitems = vals.listItems
else: discard
var mk: seq[CljVal] = @[]
var mv: seq[CljVal] = @[]
let n = min(kitems.len, vitems.len)
for i in 0..<n:
mk.add(kitems[i])
mv.add(vitems[i])
return cljMap(mk, mv)