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:
+188
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user