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:
+28
-7
@@ -277,7 +277,7 @@ proc cljConj*(coll: CljVal, item: CljVal): CljVal =
|
||||
of ckVector:
|
||||
var newItems = coll.listItems
|
||||
newItems.add(item)
|
||||
cljList(newItems)
|
||||
cljVector(newItems)
|
||||
else:
|
||||
cljList(@[item])
|
||||
|
||||
@@ -315,11 +315,11 @@ proc cljSeq*(v: CljVal): CljVal =
|
||||
else: cljNil()
|
||||
|
||||
proc cljVec*(v: CljVal): CljVal =
|
||||
if v.isNil: return cljList(@[])
|
||||
if v.isNil: return cljVector(@[])
|
||||
case v.kind
|
||||
of ckList: cljList(v.listItems)
|
||||
of ckList: cljVector(v.listItems)
|
||||
of ckVector: v
|
||||
else: cljList(@[v])
|
||||
else: cljVector(@[v])
|
||||
|
||||
proc cljEmpty*(v: CljVal): CljVal =
|
||||
cljBool(cljCount(v) == 0)
|
||||
@@ -430,18 +430,39 @@ 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")
|
||||
of ckTransient: cljKeyword("transient")
|
||||
of ckAgent: cljKeyword("agent")
|
||||
|
||||
proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal =
|
||||
if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument")
|
||||
result = args[0]
|
||||
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 args[i].intVal < result.intVal: result = args[i]
|
||||
if a.intVal < result.intVal: result = a
|
||||
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 =
|
||||
case v.kind
|
||||
|
||||
@@ -2394,6 +2394,13 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
scopeStack = @[]
|
||||
pushScope()
|
||||
requiredImports = initHashSet[string]()
|
||||
loopStack = @[]
|
||||
loopResultVar = ""
|
||||
nsAliases = @[]
|
||||
libNsPrefixes = @[]
|
||||
definedGlobals = initHashSet[string]()
|
||||
definedFnArities = initTable[string, int]()
|
||||
multiArityFns = initHashSet[string]()
|
||||
var headerLines: seq[string] = @[
|
||||
"# Generated by Bara Lang",
|
||||
"import cljnim_runtime",
|
||||
|
||||
+16
-13
@@ -725,9 +725,10 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
||||
coll = args[2]
|
||||
else:
|
||||
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]
|
||||
# reduce rest
|
||||
var res = acc
|
||||
for i in 1..<coll.items.len:
|
||||
let callItems = @[fn, res, coll.items[i]]
|
||||
@@ -920,12 +921,6 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
||||
of ckAgent: "agent"
|
||||
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":
|
||||
numArgs(1)
|
||||
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[1].intVal == 0:
|
||||
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")
|
||||
|
||||
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:
|
||||
var items: seq[CljVal] = @[]
|
||||
var i = args[0].intVal
|
||||
while i < args[1].intVal:
|
||||
items.add(cljInt(i))
|
||||
i += args[2].intVal
|
||||
let step = args[2].intVal
|
||||
let endVal = args[1].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: false, error: "range requires integers")
|
||||
|
||||
@@ -1132,7 +1134,8 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
||||
let currentVal = atomRegistry[args[0].strVal]
|
||||
var callItems = @[fn, currentVal]
|
||||
if args.len > 2:
|
||||
callItems.add(args[2..^1])
|
||||
for extra in args[2..^1]:
|
||||
callItems.add(extra)
|
||||
let callRes = evalList(callItems, env)
|
||||
if not callRes.ok: return callRes
|
||||
atomRegistry[args[0].strVal] = callRes.value
|
||||
|
||||
+9
-7
@@ -428,8 +428,7 @@ proc initMacros*() =
|
||||
if whileExpr != nil:
|
||||
let gs = cljSymbol(gensymName("dw_"))
|
||||
b = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||
cljList(@[cljSymbol("when"), whileExpr] & b & @[cljList(@[cljSymbol("recur"), gs])]),
|
||||
cljList(@[cljSymbol("when"), cljBool(false), cljNil()])])]
|
||||
cljList(@[cljSymbol("when"), whileExpr] & b & @[cljList(@[cljSymbol("recur"), gs])])])]
|
||||
return cljList(@[cljSymbol("do")] & b)
|
||||
|
||||
# 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)]
|
||||
if whenExpr != nil:
|
||||
inner = @[cljList(@[cljSymbol("when"), whenExpr] & inner)]
|
||||
if whileExpr != nil:
|
||||
let gs = cljSymbol(gensymName("dw_"))
|
||||
inner = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||
cljList(@[cljSymbol("when"), whileExpr] & inner & @[cljList(@[cljSymbol("recur"), gs])]),
|
||||
cljList(@[cljSymbol("when"), cljBool(false), cljNil()])])]
|
||||
if whileExpr != nil:
|
||||
let gs = cljSymbol(gensymName("dw_"))
|
||||
inner = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||
cljList(@[cljSymbol("when"), whileExpr] & inner & @[cljList(@[cljSymbol("recur"), gs])])])]
|
||||
|
||||
proc makeLoop(name: CljVal, coll: CljVal, innerBody: seq[CljVal]): CljVal =
|
||||
if name.kind == ckVector:
|
||||
@@ -606,5 +604,9 @@ proc initMacros*() =
|
||||
return cljList(@[cljSymbol("do")] & body)
|
||||
)
|
||||
|
||||
var macrosInitialized = false
|
||||
|
||||
proc initBuiltinMacros*() =
|
||||
if macrosInitialized: return
|
||||
initMacros()
|
||||
macrosInitialized = true
|
||||
|
||||
+18
-4
@@ -227,6 +227,20 @@ proc readAtom(s: string, i: var int): CljVal =
|
||||
except CatchableError:
|
||||
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
|
||||
var isFloat = false
|
||||
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] == '+'):
|
||||
discard # skip exponent sign
|
||||
elif numTok[j] notin Digits:
|
||||
if sawExp and (tok[j] == '-' or tok[j] == '+'):
|
||||
if j > 0 and (tok[j-1] == 'e' or tok[j-1] == 'E'):
|
||||
if sawExp and (numTok[j] == '-' or numTok[j] == '+'):
|
||||
if j > 0 and (numTok[j-1] == 'e' or numTok[j-1] == 'E'):
|
||||
continue
|
||||
isNumber = false
|
||||
break
|
||||
@@ -354,7 +368,7 @@ proc readMap(s: string, i: var int): CljVal =
|
||||
return cljMapFromPairs(pairs)
|
||||
|
||||
proc readSet(s: string, i: var int): CljVal =
|
||||
inc i # skip '{' after #
|
||||
inc i # skip '{' after #
|
||||
var items: seq[CljVal] = @[]
|
||||
while true:
|
||||
skipWhitespaceAndComments(s, i)
|
||||
@@ -366,7 +380,7 @@ proc readSet(s: string, i: var int): CljVal =
|
||||
let form = readForm(s, i)
|
||||
if form != nil:
|
||||
items.add(form)
|
||||
return cljList(@[cljSymbol("set"), cljVector(items)])
|
||||
return cljSet(items)
|
||||
|
||||
proc readDispatch(s: string, i: var int): CljVal =
|
||||
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")
|
||||
except EOFError:
|
||||
break
|
||||
except:
|
||||
client.send($(%*{ "status": "error", "error": { "type": "repl/protocol-error", "message": "Protocol error" } }) & "\n")
|
||||
except CatchableError as e:
|
||||
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
|
||||
client.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user