fix: 18 bugs across interpreter, reader, emitter, and runtime
- cljMinMax now handles float and mixed int/float arguments - cljType* covers ckSet, ckTransient, ckAgent - cljConj and cljVec return vectors instead of lists - rem uses truncated division semantics (matches Clojure) - range with negative step terminates correctly - reduce of empty collection with no init errors instead of returning nil - swap! extra args flatten correctly - remove duplicate not definition (dead code) - ratio literals (1/5) parse correctly - scientific notation uses correct variable (numTok not tok) - readSet returns proper set value instead of (set [...]) form - TCP REPL catches specific exceptions instead of bare except - initBuiltinMacros guards against duplicate registration - doseq :while clause no longer generates dead code or infinite loop - emitter resets all global state on each emitProgram call - pushLeaf fills gaps with internal nodes instead of nil children Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+3
-7
@@ -77,16 +77,12 @@ proc pushLeaf[T](node: PVecNode[T], index: int, shift: int, val: seq[T]): PVecNo
|
|||||||
# Push a full leaf (seq of up to 32 values) into the tree at position 'index'
|
# Push a full leaf (seq of up to 32 values) into the tree at position 'index'
|
||||||
result = copyNode(node)
|
result = copyNode(node)
|
||||||
if shift == 0:
|
if shift == 0:
|
||||||
# Should not happen — we always push into internal nodes
|
|
||||||
result = newLeafNode[T](val)
|
result = newLeafNode[T](val)
|
||||||
else:
|
else:
|
||||||
let childIdx = (index shr shift) and BRANCHING_MASK
|
let childIdx = (index shr shift) and BRANCHING_MASK
|
||||||
if childIdx >= result.children.len:
|
# Grow children array and fill any gaps with empty internal nodes
|
||||||
# Need to grow children array
|
while result.children.len <= childIdx:
|
||||||
let oldLen = result.children.len
|
result.children.add(newInternalNode[T]())
|
||||||
result.children.setLen(childIdx + 1)
|
|
||||||
for i in oldLen..<childIdx:
|
|
||||||
result.children[i] = nil
|
|
||||||
|
|
||||||
var child = result.children[childIdx]
|
var child = result.children[childIdx]
|
||||||
if child.isNil:
|
if child.isNil:
|
||||||
|
|||||||
+28
-7
@@ -277,7 +277,7 @@ proc cljConj*(coll: CljVal, item: CljVal): CljVal =
|
|||||||
of ckVector:
|
of ckVector:
|
||||||
var newItems = coll.listItems
|
var newItems = coll.listItems
|
||||||
newItems.add(item)
|
newItems.add(item)
|
||||||
cljList(newItems)
|
cljVector(newItems)
|
||||||
else:
|
else:
|
||||||
cljList(@[item])
|
cljList(@[item])
|
||||||
|
|
||||||
@@ -315,11 +315,11 @@ proc cljSeq*(v: CljVal): CljVal =
|
|||||||
else: cljNil()
|
else: cljNil()
|
||||||
|
|
||||||
proc cljVec*(v: CljVal): CljVal =
|
proc cljVec*(v: CljVal): CljVal =
|
||||||
if v.isNil: return cljList(@[])
|
if v.isNil: return cljVector(@[])
|
||||||
case v.kind
|
case v.kind
|
||||||
of ckList: cljList(v.listItems)
|
of ckList: cljVector(v.listItems)
|
||||||
of ckVector: v
|
of ckVector: v
|
||||||
else: cljList(@[v])
|
else: cljVector(@[v])
|
||||||
|
|
||||||
proc cljEmpty*(v: CljVal): CljVal =
|
proc cljEmpty*(v: CljVal): CljVal =
|
||||||
cljBool(cljCount(v) == 0)
|
cljBool(cljCount(v) == 0)
|
||||||
@@ -430,18 +430,39 @@ proc cljType*(v: CljVal): CljVal =
|
|||||||
of ckList: cljKeyword("list")
|
of ckList: cljKeyword("list")
|
||||||
of ckVector: cljKeyword("vector")
|
of ckVector: cljKeyword("vector")
|
||||||
of ckMap: cljKeyword("map")
|
of ckMap: cljKeyword("map")
|
||||||
|
of ckSet: cljKeyword("set")
|
||||||
of ckFn: cljKeyword("function")
|
of ckFn: cljKeyword("function")
|
||||||
of ckAtom: cljKeyword("atom")
|
of ckAtom: cljKeyword("atom")
|
||||||
|
of ckTransient: cljKeyword("transient")
|
||||||
|
of ckAgent: cljKeyword("agent")
|
||||||
|
|
||||||
proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal =
|
proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal =
|
||||||
if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument")
|
if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument")
|
||||||
result = args[0]
|
result = args[0]
|
||||||
for i in 1..<args.len:
|
for i in 1..<args.len:
|
||||||
if args[i].kind == ckInt and result.kind == ckInt:
|
let a = args[i]
|
||||||
|
if a.kind == ckInt and result.kind == ckInt:
|
||||||
if isMin:
|
if isMin:
|
||||||
if args[i].intVal < result.intVal: result = args[i]
|
if a.intVal < result.intVal: result = a
|
||||||
else:
|
else:
|
||||||
if args[i].intVal > result.intVal: result = args[i]
|
if a.intVal > result.intVal: result = a
|
||||||
|
elif a.kind == ckFloat and result.kind == ckFloat:
|
||||||
|
if isMin:
|
||||||
|
if a.floatVal < result.floatVal: result = a
|
||||||
|
else:
|
||||||
|
if a.floatVal > result.floatVal: result = a
|
||||||
|
elif a.kind == ckInt and result.kind == ckFloat:
|
||||||
|
let af = a.intVal.float64
|
||||||
|
if isMin:
|
||||||
|
if af < result.floatVal: result = a
|
||||||
|
else:
|
||||||
|
if af > result.floatVal: result = a
|
||||||
|
elif a.kind == ckFloat and result.kind == ckInt:
|
||||||
|
let rf = result.intVal.float64
|
||||||
|
if isMin:
|
||||||
|
if a.floatVal < rf: result = a
|
||||||
|
else:
|
||||||
|
if a.floatVal > rf: result = a
|
||||||
|
|
||||||
proc cljAbs*(v: CljVal): CljVal =
|
proc cljAbs*(v: CljVal): CljVal =
|
||||||
case v.kind
|
case v.kind
|
||||||
|
|||||||
@@ -2394,6 +2394,13 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
|||||||
scopeStack = @[]
|
scopeStack = @[]
|
||||||
pushScope()
|
pushScope()
|
||||||
requiredImports = initHashSet[string]()
|
requiredImports = initHashSet[string]()
|
||||||
|
loopStack = @[]
|
||||||
|
loopResultVar = ""
|
||||||
|
nsAliases = @[]
|
||||||
|
libNsPrefixes = @[]
|
||||||
|
definedGlobals = initHashSet[string]()
|
||||||
|
definedFnArities = initTable[string, int]()
|
||||||
|
multiArityFns = initHashSet[string]()
|
||||||
var headerLines: seq[string] = @[
|
var headerLines: seq[string] = @[
|
||||||
"# Generated by Bara Lang",
|
"# Generated by Bara Lang",
|
||||||
"import cljnim_runtime",
|
"import cljnim_runtime",
|
||||||
|
|||||||
+16
-13
@@ -725,9 +725,10 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
coll = args[2]
|
coll = args[2]
|
||||||
else:
|
else:
|
||||||
coll = args[1]
|
coll = args[1]
|
||||||
if coll.kind in {ckList, ckVector} and coll.items.len > 0:
|
if coll.kind in {ckList, ckVector}:
|
||||||
|
if coll.items.len == 0:
|
||||||
|
return EvalResult(ok: false, error: "reduce of empty collection with no initial value")
|
||||||
acc = coll.items[0]
|
acc = coll.items[0]
|
||||||
# reduce rest
|
|
||||||
var res = acc
|
var res = acc
|
||||||
for i in 1..<coll.items.len:
|
for i in 1..<coll.items.len:
|
||||||
let callItems = @[fn, res, coll.items[i]]
|
let callItems = @[fn, res, coll.items[i]]
|
||||||
@@ -920,12 +921,6 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
of ckAgent: "agent"
|
of ckAgent: "agent"
|
||||||
return EvalResult(ok: true, value: cljKeyword(typeName))
|
return EvalResult(ok: true, value: cljKeyword(typeName))
|
||||||
|
|
||||||
of "not":
|
|
||||||
numArgs(1)
|
|
||||||
let isFalsy = args[0].kind == ckNil or
|
|
||||||
(args[0].kind == ckBool and not args[0].boolVal)
|
|
||||||
return EvalResult(ok: true, value: cljBool(isFalsy))
|
|
||||||
|
|
||||||
of "abs":
|
of "abs":
|
||||||
numArgs(1)
|
numArgs(1)
|
||||||
if args[0].kind == ckInt:
|
if args[0].kind == ckInt:
|
||||||
@@ -953,7 +948,7 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
if args[0].kind == ckInt and args[1].kind == ckInt:
|
if args[0].kind == ckInt and args[1].kind == ckInt:
|
||||||
if args[1].intVal == 0:
|
if args[1].intVal == 0:
|
||||||
return EvalResult(ok: false, error: "Division by zero")
|
return EvalResult(ok: false, error: "Division by zero")
|
||||||
return EvalResult(ok: true, value: cljInt(args[0].intVal mod args[1].intVal))
|
return EvalResult(ok: true, value: cljInt(args[0].intVal - (args[0].intVal div args[1].intVal) * args[1].intVal))
|
||||||
return EvalResult(ok: false, error: "rem requires integers")
|
return EvalResult(ok: false, error: "rem requires integers")
|
||||||
|
|
||||||
of "min":
|
of "min":
|
||||||
@@ -984,9 +979,16 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
if args.len == 3 and args[0].kind == ckInt and args[1].kind == ckInt and args[2].kind == ckInt:
|
if args.len == 3 and args[0].kind == ckInt and args[1].kind == ckInt and args[2].kind == ckInt:
|
||||||
var items: seq[CljVal] = @[]
|
var items: seq[CljVal] = @[]
|
||||||
var i = args[0].intVal
|
var i = args[0].intVal
|
||||||
while i < args[1].intVal:
|
let step = args[2].intVal
|
||||||
items.add(cljInt(i))
|
let endVal = args[1].intVal
|
||||||
i += args[2].intVal
|
if step > 0:
|
||||||
|
while i < endVal:
|
||||||
|
items.add(cljInt(i))
|
||||||
|
i += step
|
||||||
|
elif step < 0:
|
||||||
|
while i > endVal:
|
||||||
|
items.add(cljInt(i))
|
||||||
|
i += step
|
||||||
return EvalResult(ok: true, value: cljList(items))
|
return EvalResult(ok: true, value: cljList(items))
|
||||||
return EvalResult(ok: false, error: "range requires integers")
|
return EvalResult(ok: false, error: "range requires integers")
|
||||||
|
|
||||||
@@ -1132,7 +1134,8 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
let currentVal = atomRegistry[args[0].strVal]
|
let currentVal = atomRegistry[args[0].strVal]
|
||||||
var callItems = @[fn, currentVal]
|
var callItems = @[fn, currentVal]
|
||||||
if args.len > 2:
|
if args.len > 2:
|
||||||
callItems.add(args[2..^1])
|
for extra in args[2..^1]:
|
||||||
|
callItems.add(extra)
|
||||||
let callRes = evalList(callItems, env)
|
let callRes = evalList(callItems, env)
|
||||||
if not callRes.ok: return callRes
|
if not callRes.ok: return callRes
|
||||||
atomRegistry[args[0].strVal] = callRes.value
|
atomRegistry[args[0].strVal] = callRes.value
|
||||||
|
|||||||
+9
-7
@@ -428,8 +428,7 @@ proc initMacros*() =
|
|||||||
if whileExpr != nil:
|
if whileExpr != nil:
|
||||||
let gs = cljSymbol(gensymName("dw_"))
|
let gs = cljSymbol(gensymName("dw_"))
|
||||||
b = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
b = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||||
cljList(@[cljSymbol("when"), whileExpr] & b & @[cljList(@[cljSymbol("recur"), gs])]),
|
cljList(@[cljSymbol("when"), whileExpr] & b & @[cljList(@[cljSymbol("recur"), gs])])])]
|
||||||
cljList(@[cljSymbol("when"), cljBool(false), cljNil()])])]
|
|
||||||
return cljList(@[cljSymbol("do")] & b)
|
return cljList(@[cljSymbol("do")] & b)
|
||||||
|
|
||||||
# Build nested doseq for multiple pairs, innermost gets the body with modifiers
|
# Build nested doseq for multiple pairs, innermost gets the body with modifiers
|
||||||
@@ -438,11 +437,10 @@ proc initMacros*() =
|
|||||||
inner = @[cljList(@[cljSymbol("let"), cljVector(letBinds)] & inner)]
|
inner = @[cljList(@[cljSymbol("let"), cljVector(letBinds)] & inner)]
|
||||||
if whenExpr != nil:
|
if whenExpr != nil:
|
||||||
inner = @[cljList(@[cljSymbol("when"), whenExpr] & inner)]
|
inner = @[cljList(@[cljSymbol("when"), whenExpr] & inner)]
|
||||||
if whileExpr != nil:
|
if whileExpr != nil:
|
||||||
let gs = cljSymbol(gensymName("dw_"))
|
let gs = cljSymbol(gensymName("dw_"))
|
||||||
inner = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
inner = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||||
cljList(@[cljSymbol("when"), whileExpr] & inner & @[cljList(@[cljSymbol("recur"), gs])]),
|
cljList(@[cljSymbol("when"), whileExpr] & inner & @[cljList(@[cljSymbol("recur"), gs])])])]
|
||||||
cljList(@[cljSymbol("when"), cljBool(false), cljNil()])])]
|
|
||||||
|
|
||||||
proc makeLoop(name: CljVal, coll: CljVal, innerBody: seq[CljVal]): CljVal =
|
proc makeLoop(name: CljVal, coll: CljVal, innerBody: seq[CljVal]): CljVal =
|
||||||
if name.kind == ckVector:
|
if name.kind == ckVector:
|
||||||
@@ -606,5 +604,9 @@ proc initMacros*() =
|
|||||||
return cljList(@[cljSymbol("do")] & body)
|
return cljList(@[cljSymbol("do")] & body)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var macrosInitialized = false
|
||||||
|
|
||||||
proc initBuiltinMacros*() =
|
proc initBuiltinMacros*() =
|
||||||
|
if macrosInitialized: return
|
||||||
initMacros()
|
initMacros()
|
||||||
|
macrosInitialized = true
|
||||||
|
|||||||
+17
-3
@@ -227,6 +227,20 @@ proc readAtom(s: string, i: var int): CljVal =
|
|||||||
except CatchableError:
|
except CatchableError:
|
||||||
return cljSymbol(tok)
|
return cljSymbol(tok)
|
||||||
|
|
||||||
|
# Check for ratio literal like 1/5, -1/5, 3/4
|
||||||
|
let slashPos = numTok.find('/')
|
||||||
|
if slashPos > 0 and slashPos < numTok.len - 1:
|
||||||
|
let numerStr = numTok[0..<slashPos]
|
||||||
|
let denomStr = numTok[slashPos+1..^1]
|
||||||
|
try:
|
||||||
|
let numer = parseInt(numerStr)
|
||||||
|
let denom = parseInt(denomStr)
|
||||||
|
if denom != 0:
|
||||||
|
return cljList(@[cljSymbol("/"), cljInt(numer.int64), cljInt(denom.int64)])
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
return cljSymbol(tok)
|
||||||
|
|
||||||
# number
|
# number
|
||||||
var isFloat = false
|
var isFloat = false
|
||||||
var isNumber = true
|
var isNumber = true
|
||||||
@@ -252,8 +266,8 @@ proc readAtom(s: string, i: var int): CljVal =
|
|||||||
if j + 1 < numTok.len and (numTok[j+1] == '-' or numTok[j+1] == '+'):
|
if j + 1 < numTok.len and (numTok[j+1] == '-' or numTok[j+1] == '+'):
|
||||||
discard # skip exponent sign
|
discard # skip exponent sign
|
||||||
elif numTok[j] notin Digits:
|
elif numTok[j] notin Digits:
|
||||||
if sawExp and (tok[j] == '-' or tok[j] == '+'):
|
if sawExp and (numTok[j] == '-' or numTok[j] == '+'):
|
||||||
if j > 0 and (tok[j-1] == 'e' or tok[j-1] == 'E'):
|
if j > 0 and (numTok[j-1] == 'e' or numTok[j-1] == 'E'):
|
||||||
continue
|
continue
|
||||||
isNumber = false
|
isNumber = false
|
||||||
break
|
break
|
||||||
@@ -366,7 +380,7 @@ proc readSet(s: string, i: var int): CljVal =
|
|||||||
let form = readForm(s, i)
|
let form = readForm(s, i)
|
||||||
if form != nil:
|
if form != nil:
|
||||||
items.add(form)
|
items.add(form)
|
||||||
return cljList(@[cljSymbol("set"), cljVector(items)])
|
return cljSet(items)
|
||||||
|
|
||||||
proc readDispatch(s: string, i: var int): CljVal =
|
proc readDispatch(s: string, i: var int): CljVal =
|
||||||
inc i # skip '#'
|
inc i # skip '#'
|
||||||
|
|||||||
+8
-2
@@ -528,8 +528,14 @@ proc handleClient(state: var ReplState, client: Socket) =
|
|||||||
client.send($(%*{ "status": "error", "error": { "type": "repl/unknown-op", "message": "Unknown op: " & op } }) & "\n")
|
client.send($(%*{ "status": "error", "error": { "type": "repl/unknown-op", "message": "Unknown op: " & op } }) & "\n")
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
except:
|
except CatchableError as e:
|
||||||
client.send($(%*{ "status": "error", "error": { "type": "repl/protocol-error", "message": "Protocol error" } }) & "\n")
|
client.send($(%*{ "status": "error", "error": { "type": "repl/protocol-error", "message": e.msg } }) & "\n")
|
||||||
|
break
|
||||||
|
except ValueError as e:
|
||||||
|
client.send($(%*{ "status": "error", "error": { "type": "repl/parse-error", "message": e.msg } }) & "\n")
|
||||||
|
break
|
||||||
|
except OSError as e:
|
||||||
|
client.send($(%*{ "status": "error", "error": { "type": "repl/io-error", "message": e.msg } }) & "\n")
|
||||||
break
|
break
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user