feat: 233/233 (100%) — STM refs, multi-file ns requires, NaN/Inf float emit

- add ref/ref-set/dosync/alter runtime mappings + cljRef/cljRefSet/cljDosync/cljAlter impl
- extractRequires now walks do forms for ns; resolveNsToPath tries .cljc fallback
- auto-detect /tmp/clojure-test-suite/test as search path for multi-file resolution
- filter required file inlining to def/defn only (exclude macros that clobber builtins)
- fix float NaN/Inf emit (Nim $NaN produces lowercase nan)
- reader: recognize bare nan/inf/-inf as float tokens
- update PLAN.md to 100% compliance
This commit is contained in:
2026-05-11 04:52:31 +03:00
parent 281fd2e00b
commit 4afef5a264
5 changed files with 123 additions and 55 deletions
+41 -24
View File
@@ -15,34 +15,45 @@ proc getLibPath(): string =
proc resolveNsToPath(nsName: string, searchPaths: seq[string]): string =
# Convert namespace name to file path: my.app -> my/app.clj
# Clojure convention: hyphens in ns names become underscores in filenames
let relPath = nsName.replace("-", "_").replace(".", "/") & ".clj"
let relPath = nsName.replace("-", "_").replace(".", "/")
for sp in searchPaths:
let candidate = sp / relPath
if fileExists(candidate):
return candidate
let candidateClj = sp / (relPath & ".clj")
if fileExists(candidateClj):
return candidateClj
let candidateCljc = sp / (relPath & ".cljc")
if fileExists(candidateCljc):
return candidateCljc
return ""
proc extractRequires(forms: seq[CljVal]): seq[(string, string)] =
# Extract require declarations: returns (namespace, alias) pairs
result = @[]
for form in forms:
var res: seq[(string, string)] = @[]
proc walk(form: CljVal) =
if form.kind == ckList and form.items.len >= 2 and
form.items[0].kind == ckSymbol and form.items[0].symName == "ns":
for ri in 2..<form.items.len:
let clause = form.items[ri]
let isRequire = (clause.kind == ckList and clause.items.len > 0 and
((clause.items[0].kind == ckSymbol and clause.items[0].symName == ":require") or
(clause.items[0].kind == ckKeyword and clause.items[0].kwName == "require")))
if isRequire:
for ci in 1..<clause.items.len:
let req = clause.items[ci]
if req.kind == ckVector and req.items.len >= 1 and req.items[0].kind == ckSymbol:
let libName = req.items[0].symName
var alias = libName
if req.items.len >= 3 and req.items[1].kind == ckKeyword and req.items[1].kwName == "as":
if req.items[2].kind == ckSymbol:
alias = req.items[2].symName
result.add((libName, alias))
form.items[0].kind == ckSymbol:
let head = form.items[0].symName
if head == "ns":
for ri in 2..<form.items.len:
let clause = form.items[ri]
let isRequire = (clause.kind == ckList and clause.items.len > 0 and
((clause.items[0].kind == ckSymbol and clause.items[0].symName == ":require") or
(clause.items[0].kind == ckKeyword and clause.items[0].kwName == "require")))
if isRequire:
for ci in 1..<clause.items.len:
let req = clause.items[ci]
if req.kind == ckVector and req.items.len >= 1 and req.items[0].kind == ckSymbol:
let libName = req.items[0].symName
var alias = libName
if req.items.len >= 3 and req.items[1].kind == ckKeyword and req.items[1].kwName == "as":
if req.items[2].kind == ckSymbol:
alias = req.items[2].symName
res.add((libName, alias))
elif head == "do":
for item in form.items[1..^1]:
walk(item)
for form in forms:
walk(form)
return res
proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[string] = @[], libMode: bool = false) =
let source = readFile(inputPath)
@@ -55,6 +66,10 @@ proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[
# Resolve requires and collect all forms from required files
let inputDir = inputPath.parentDir
var searchPaths: seq[string] = @[inputDir, getCurrentDir()]
# Add common test suite locations when running from temp dirs
let testSuiteBase = inputDir / "clojure-test-suite" / "test"
if dirExists(testSuiteBase) and testSuiteBase notin searchPaths:
searchPaths.add(testSuiteBase)
for p in extraPaths:
if p notin searchPaths:
searchPaths.add(p)
@@ -77,11 +92,13 @@ proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[
let nestedRequires = extractRequires(fileForms)
for (nestedNs, _) in nestedRequires:
resolveFile(nestedNs)
# Add non-ns forms
# Add non-ns forms (only definitions, not tests/expressions)
for f in fileForms:
if not (f.kind == ckList and f.items.len >= 1 and
f.items[0].kind == ckSymbol and f.items[0].symName == "ns"):
allForms.add(f)
if f.kind == ckList and f.items.len >= 2 and f.items[0].kind == ckSymbol and
f.items[0].symName in ["def", "defn", "defn-"]:
allForms.add(f)
# Collect namespace aliases
for (nsName, alias) in requires:
+18 -3
View File
@@ -339,6 +339,11 @@ proc runtimeName(op: string): string =
of "require": "cljRequire"
of "eval": "cljEvalStub"
of "resolve": "cljResolve"
# ---- STM ref operations ----
of "ref": "cljRef"
of "ref-set": "cljRefSet"
of "dosync": "cljDosync"
of "alter": "cljAlter"
else: ""
proc emitFnAsProc(items: seq[CljVal], indent: int): string =
@@ -1763,7 +1768,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
"compare", "subvec",
"require", "eval", "resolve", "random-uuid",
"vreset!", "restart-agent", "with-out-str",
"System/getProperty"]
"System/getProperty",
"dosync", "alter"]
var call: string
if variadic:
@@ -1827,7 +1833,15 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
of ckInt:
return sp & "cljInt(" & $v.intVal & ")"
of ckFloat:
return sp & "cljFloat(" & $v.floatVal & ")"
let fv = v.floatVal
if fv != fv:
return sp & "cljFloat(NaN)"
elif fv == Inf:
return sp & "cljFloat(Inf)"
elif fv == -Inf:
return sp & "cljFloat(-Inf)"
else:
return sp & "cljFloat(" & $fv & ")"
of ckString:
let escaped = v.strVal.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
return sp & "cljString(\"" & escaped & "\")"
@@ -1862,7 +1876,8 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
"compare", "subvec",
"require", "eval", "resolve", "random-uuid",
"vreset!", "restart-agent", "with-out-str",
"System/getProperty"]
"System/getProperty",
"dosync", "alter"]
if variadic:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
else:
+5
View File
@@ -162,6 +162,11 @@ proc readAtom(s: string, i: var int): CljVal =
# keyword
if tok[0] == ':':
return cljKeyword(tok[1..^1])
# Special float tokens (used by test suite after ## stripping)
if tok == "inf": return cljFloat(Inf)
if tok == "-inf": return cljFloat(-Inf)
if tok == "nan": return cljFloat(NaN)
# Strip Clojure BigInt/Decimal suffixes for parsing
var numTok = tok