fix: 70% test suite pass rate (164/233)

Emitter: iterative worklist-based form processing (no more stack overflow),
type conversion functions (float/int/double/long/short/byte/boolean),
metadata stripping in def/defn/defn-, to-array runtime mapping

Macros: namespace-qualified macro resolution (t/deftest -> deftest),
when-var-exists macro restored, are/testing/is/thrown? built-in macros

Runtime: cljToFloat, cljToInt, cljToBool, cljToArray conversion wrappers,
cljVolatileBang, cljDeliver, cljDoall, cljDorun, cljDropLast,
cljShuffle, cljFnil, cljIntern stubs

Results: 164/233 passed (70%)
  clojure.string: 8/8 (100%)
  clojure.core: 156/225 (69%)
This commit is contained in:
2026-05-09 17:33:12 +03:00
parent e6859568b3
commit 624837ac7a
3 changed files with 172 additions and 24 deletions
+57 -23
View File
@@ -253,6 +253,26 @@ proc runtimeName(op: string): string =
# ---- Channel operations (core.async) ----
of "chan": "cljChan"
of "close!": "cljChanClose"
of "volatile!": "cljVolatileBang"
of "volatile-mutable": "cljVolatileMutableQ"
of "deliver": "cljDeliver"
of "doall": "cljDoall"
of "dorun": "cljDorun"
of "drop-last": "cljDropLast"
of "shuffle": "cljShuffle"
of "fnil": "cljFnil"
of "intern": "cljIntern"
of "to-array": "cljToArray"
# ---- Type conversion ----
of "float": "cljToFloat"
of "int": "cljToInt"
of "double": "cljToFloat"
of "long": "cljToInt"
of "short": "cljToInt"
of "byte": "cljToInt"
of "boolean": "cljToBool"
of "num": "cljToInt"
of "number": "cljToInt"
else: ""
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
@@ -418,20 +438,30 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
of "def":
if items.len != 3:
raise newException(EmitterError, "def requires exactly 2 arguments")
let name = items[1]
var name = items[1]
# Strip metadata: (with-meta sym meta) -> sym
if name.kind == ckList and name.items.len == 3 and
name.items[0].kind == ckSymbol and name.items[0].symName == "with-meta":
name = name.items[1]
if name.kind != ckSymbol:
raise newException(EmitterError, "def name must be a symbol")
let mangled = mangleName(name.symName)
let valCode = emitExpr(items[2], indent)
if indent == 0:
let valCode = emitExpr(items[2], 0)
return sp & "let " & mangled & " = " & valCode
else:
return sp & "((proc (): CljVal =\n" & indentStr(indent + 1) & "let " & mangled & " = " & valCode & "\n" & indentStr(indent + 1) & mangled & ")())"
# For nested def: wrap in proc and use consistent indentation
let valCode = emitExpr(items[2], indent + 2)
return sp & "((proc (): CljVal =\n" & indentStr(indent + 1) & "let " & mangled & " = " & valCode.strip() & "\n" & indentStr(indent + 1) & mangled & ")())"
of "defn":
if items.len < 4:
raise newException(EmitterError, "defn requires name, params, and body")
let name = items[1]
var name = items[1]
# Strip metadata: (with-meta sym meta) -> sym
if name.kind == ckList and name.items.len == 3 and
name.items[0].kind == ckSymbol and name.items[0].symName == "with-meta":
name = name.items[1]
if name.kind != ckSymbol:
raise newException(EmitterError, "defn name must be a symbol")
let params = items[2]
@@ -458,7 +488,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
of "defn-":
if items.len < 4:
raise newException(EmitterError, "defn- requires name, params, and body")
let name = items[1]
var name = items[1]
# Strip metadata: (with-meta sym meta) -> sym
if name.kind == ckList and name.items.len == 3 and
name.items[0].kind == ckSymbol and name.items[0].symName == "with-meta":
name = name.items[1]
if name.kind != ckSymbol:
raise newException(EmitterError, "defn- name must be a symbol")
let params = items[2]
@@ -1146,7 +1180,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
"println", "prn", "print", "str", "pr-str",
"concat", "min", "max", "merge", "interleave",
"zipmap", "hash-map", "hash-set", "sorted-map", "sorted-set",
"array-map", "inf", "nan"]
"array-map", "inf", "nan",
"float", "int", "double", "long", "short", "byte",
"boolean", "num", "number"]
var call: string
if variadic:
call = rn & "(@[" & argParts.join(", ") & "])"
@@ -1294,16 +1330,11 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
var defs: seq[string] = @[]
var mainForms: seq[string] = @[]
proc unwrapAndProcess(i: int, form: CljVal, isLast: bool)
# Iterative worklist-based form processing (avoids deep recursion)
type WorkItem = tuple[form: CljVal, isLast: bool]
var worklist: seq[WorkItem] = @[]
var processDepth = 0
const maxProcessDepth = 50
proc processForm(i: int, form: CljVal, isLast: bool) =
if processDepth > maxProcessDepth:
raise newException(EmitterError, "Maximum processing depth exceeded - possible macro expansion loop")
inc processDepth
defer: dec processDepth
proc processOneForm(form: CljVal, isLast: bool) =
let expanded = macroexpand(form)
let ef = if expanded.kind == ckList: expanded else: form
let headSym = ef.kind == ckList and ef.items.len > 0 and
@@ -1312,9 +1343,9 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
# Recurse into (do ...) forms produced by macro expansion
if headSym and headName == "do":
let subForms = ef.items[1..^1]
for j, subForm in subForms:
for j in countdown(subForms.len - 1, 0):
let subLast = isLast and (j == subForms.len - 1)
unwrapAndProcess(j, subForm, subLast)
worklist.add((subForms[j], subLast))
return
let isDef = headSym and headName in ["def", "defn", "defn-"]
let isMacro = headSym and headName in ["defmacro"]
@@ -1334,19 +1365,22 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
else:
mainForms.add(indentStr(2) & "discard cljRepr(" & stripped & ")")
proc unwrapAndProcess(i: int, form: CljVal, isLast: bool) =
# Unwrap top-level (do ...) forms so each sub-form is processed individually
proc unwrapOneForm(form: CljVal, isLast: bool) =
if form.kind == ckList and form.items.len > 0 and
form.items[0].kind == ckSymbol and form.items[0].symName == "do":
let subForms = form.items[1..^1]
for j, subForm in subForms:
for j in countdown(subForms.len - 1, 0):
let subLast = isLast and (j == subForms.len - 1)
unwrapAndProcess(j, subForm, subLast)
worklist.add((subForms[j], subLast))
else:
processForm(i, form, isLast)
processOneForm(form, isLast)
for i, form in forms:
unwrapAndProcess(i, form, i == forms.len - 1)
worklist.add((form, i == forms.len - 1))
while worklist.len > 0:
let (item, itemIsLast) = worklist.pop()
unwrapOneForm(item, itemIsLast)
var lines = headerLines
# Add collected Nim imports
+7 -1
View File
@@ -88,9 +88,14 @@ proc macroexpand1*(form: CljVal): CljVal =
if form.items.len != 2:
raise newException(CatchableError, "syntax-quote requires exactly 1 argument")
return expandSyntaxQuote(form.items[1])
# Check if it's a macro
# Check if it's a macro (try full name, then bare name without namespace prefix)
if isMacro(name):
return expandMacro(name, form.items[1..^1])
let slashPos = name.find('/')
if slashPos > 0:
let bareName = name[slashPos+1..^1]
if isMacro(bareName):
return expandMacro(bareName, form.items[1..^1])
return form
proc macroexpand*(form: CljVal): CljVal =
@@ -514,6 +519,7 @@ proc initMacros*() =
return cljList(@[cljSymbol("do")] & isForms)
)
# when-var-exists: always expands body (var check is compile-time in emitter)
defineMacro("when-var-exists", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "when-var-exists requires symbol and body")