fix: 230/233 (98.7%) — reader :default preference, cond/new/definterface, runtime stubs, seq overloads
- Reader: prefer :default over :clj in reader conditionals
- Emitter: add cond, definterface, new special forms
- Emitter: ns handler registers namespace aliases from :require
- Emitter: when body always discarded (fixes type mismatch with else)
- Emitter: try/catch maps clojure.lang.ExceptionInfo → ExInfo, scope catch vars
- Emitter: def inside proc uses var (not {.global.}), removed debug output
- Runtime: cljRandomUuid, cljSystemGetProperty, cljVresetB, cljRestartAgent
- Runtime: cljRequire, cljEvalStub, cljResolve no-op stubs
- Runtime: Boolean/Object/Integer/Long/Float/Double constructors
- Runtime: seq overloads for cljAssoc, cljDissoc, cljGet, cljExInfo, cljTransduce
- Unit tests: 78/80 (was 77/80)
- 3 remaining: not_eq (cross-namespace), add_watch/remove_watch (def hoisting)
This commit is contained in:
@@ -1,30 +1,26 @@
|
||||
# Clojure/Nim — Test Suite Compliance Plan
|
||||
|
||||
## Current Status: 233/233 (100%)
|
||||
## Current Status: 230/233 (98.7%)
|
||||
|
||||
| Component | Pass | Total | % |
|
||||
|---|---|---|---|
|
||||
| clojure.string | 8 | 8 | 100% |
|
||||
| clojure.core | 225 | 225 | 100% |
|
||||
| **Total** | **233** | **233** | **100%** |
|
||||
| clojure.core | 222 | 225 | 98.7% |
|
||||
| **Total** | **230** | **233** | **98.7%** |
|
||||
|
||||
## Progress This Session (2026-05-11)
|
||||
## Unit Tests: 78/80 pass (97.5%)
|
||||
|
||||
**Started: 210/233 (90%) → Current: 233/233 (100%) — +23 tests**
|
||||
- `emit symbol` — returns `cljSymbol(...)` instead of raw name
|
||||
- `emit mangled symbol` — same issue
|
||||
|
||||
### Key Changes Made
|
||||
## 3 Remaining Compliance Failures
|
||||
|
||||
- **Reader**: Removed debug stderr lines causing crashes in readList
|
||||
- **Reader**: Added `##Inf`, `##-Inf`, `##NaN` dispatch macro handling (was returning nil, causing arity mismatches in `are` macro)
|
||||
- **Reader**: Added `#?@` splice-unwrap support in `readAll` (top-level splicing)
|
||||
- **Reader**: Reader conditionals now prefer `:clj` platform branch, falling back to `:default`
|
||||
- **Reader**: Fixed hex literal overflow for `-0x8000000000000000` (returns int64 min)
|
||||
- **Macros**: `doseq` macro now handles `:when`, `:let`, `:while` keywords
|
||||
- **Macros**: `(when false)` in doseq while-loop has body (was invalid)
|
||||
- **Macros**: `expandSyntaxQuote` handles `unquote-splicing` inside collections (returns form directly for concat splicing)
|
||||
|
||||
### Pre-existing Issues (not from this session)
|
||||
- Emitter test: `emit symbol` returns `cljSymbol(...)` instead of raw name
|
||||
- Emitter test: `emit mangled symbol` similar issue
|
||||
- Emitter test: `emit cond` generates wrong Nim code
|
||||
| # | Test | Error | Root Cause |
|
||||
|---|---|---|---|
|
||||
| 1 | `not_eq` | `undeclared 'eq_SLASH_tests'` | Cross-namespace: `eq/tests` fn from `clojure.core-test.eq` not loaded |
|
||||
| 2 | `add_watch` | `undeclared 'testvar_a'` | `(def testvar-a 0)` inside deftest body — Nim requires var decl before use in closures |
|
||||
| 3 | `remove_watch` | `undeclared 'watchable'` | Same — `(def watchable 0)` inside deftest body, referenced in closures |
|
||||
|
||||
All 3 require deeper architectural changes:
|
||||
- Multi-file compilation or inline required namespaces
|
||||
- Top-level def hoisting across closure boundaries
|
||||
|
||||
@@ -1338,6 +1338,10 @@ proc cljGet*(m: CljVal, key: CljVal): CljVal =
|
||||
else: cljNil()
|
||||
else: cljNil()
|
||||
|
||||
proc cljGet*(args: seq[CljVal]): CljVal =
|
||||
if args.len < 2: return cljNil()
|
||||
cljGet(args[0], args[1])
|
||||
|
||||
proc cljGetDefault*(m: CljVal, key: CljVal, default: CljVal): CljVal =
|
||||
if m.isNil: return default
|
||||
case m.kind
|
||||
@@ -1352,6 +1356,13 @@ proc cljExInfo*(msg: CljVal, data: CljVal): CljVal =
|
||||
ex.exData = data
|
||||
raise ex
|
||||
|
||||
proc cljExInfo*(args: seq[CljVal]): CljVal =
|
||||
var msg = if args.len > 0: args[0] else: cljString("")
|
||||
var data = if args.len > 1: args[1] else: cljNil()
|
||||
var ex = newException(ExInfo, cljStr(msg))
|
||||
ex.exData = data
|
||||
raise ex
|
||||
|
||||
proc cljExData*(ex: CljVal): CljVal =
|
||||
if ex.isNil: return cljNil()
|
||||
case ex.kind
|
||||
@@ -1403,10 +1414,18 @@ proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal =
|
||||
else:
|
||||
raise newException(CatchableError, "assoc expects a map or vector")
|
||||
|
||||
proc cljAssoc*(args: seq[CljVal]): CljVal =
|
||||
if args.len < 3: raise newException(CatchableError, "assoc requires 3 arguments")
|
||||
cljAssoc(args[0], args[1], args[2])
|
||||
|
||||
proc cljDissoc*(m: CljVal, key: CljVal): CljVal =
|
||||
if m.isNil or m.kind != ckMap: return cljMap(@[], @[])
|
||||
CljVal(kind: ckMap, mapData: pmapDissoc(m.mapData, key, hash(key), cljEq))
|
||||
|
||||
proc cljDissoc*(args: seq[CljVal]): CljVal =
|
||||
if args.len < 2: return cljMap(@[], @[])
|
||||
cljDissoc(args[0], args[1])
|
||||
|
||||
proc cljContains*(m: CljVal, key: CljVal): CljVal =
|
||||
if m.isNil: return cljBool(false)
|
||||
case m.kind
|
||||
@@ -1523,6 +1542,10 @@ proc cljTransduce*(xform: CljVal, f: CljVal, init: CljVal, coll: CljVal): CljVal
|
||||
# Fallback: just return init
|
||||
return init
|
||||
|
||||
proc cljTransduce*(args: seq[CljVal]): CljVal =
|
||||
if args.len < 4: return cljNil()
|
||||
cljTransduce(args[0], args[1], args[2], args[3])
|
||||
|
||||
proc cljMapv*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal =
|
||||
if coll.isNil: return cljVector(@[])
|
||||
case coll.kind
|
||||
@@ -2246,3 +2269,90 @@ proc cljZipmap*(args: seq[CljVal]): CljVal =
|
||||
mk.add(kitems[i])
|
||||
mv.add(vitems[i])
|
||||
return cljMap(mk, mv)
|
||||
|
||||
# ---- Missing stubs ----
|
||||
|
||||
proc cljRandomUuid*(args: seq[CljVal]): CljVal =
|
||||
randomize()
|
||||
var uuid = ""
|
||||
for i in 0..<32:
|
||||
uuid.add("0123456789abcdef"[rand(15)])
|
||||
if i in {7, 11, 15, 19}: uuid.add('-')
|
||||
cljString(uuid)
|
||||
|
||||
proc cljSystemGetProperty*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljNil()
|
||||
let key = if args[0].kind == ckString: args[0].strVal else: ""
|
||||
case key
|
||||
of "line.separator": cljString("\n")
|
||||
of "file.separator": cljString("/")
|
||||
of "path.separator": cljString(":")
|
||||
of "os.name": cljString("linux")
|
||||
of "os.arch": cljString("x86_64")
|
||||
of "java.version": cljString("0")
|
||||
else: cljString("")
|
||||
|
||||
proc cljVresetB*(args: seq[CljVal]): CljVal =
|
||||
if args.len < 2: return cljNil()
|
||||
if args[0].kind == ckAtom:
|
||||
args[0].atomVal = args[1]
|
||||
args[1]
|
||||
else:
|
||||
raise newException(CatchableError, "vreset! requires a volatile")
|
||||
|
||||
proc cljRestartAgent*(args: seq[CljVal]): CljVal =
|
||||
if args.len < 2: return cljNil()
|
||||
if args[0].kind == ckAgent:
|
||||
args[0].agentVal = args[1]
|
||||
args[0].agentBusy = false
|
||||
args[0]
|
||||
else:
|
||||
raise newException(CatchableError, "restart-agent requires an agent")
|
||||
|
||||
proc cljRequire*(args: seq[CljVal]): CljVal =
|
||||
cljNil()
|
||||
|
||||
proc cljEvalStub*(args: seq[CljVal]): CljVal =
|
||||
cljNil()
|
||||
|
||||
proc cljResolve*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljNil()
|
||||
cljNil()
|
||||
|
||||
proc Boolean*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljBool(false)
|
||||
let v = args[0]
|
||||
if v.kind == ckBool: v
|
||||
elif v.kind == ckString: cljBool(v.strVal.toLowerAscii() == "true")
|
||||
elif v.kind == ckInt: cljBool(v.intVal != 0)
|
||||
elif v.kind == ckFloat: cljBool(v.floatVal != 0.0)
|
||||
else: cljBool(cljIsTruthy(v))
|
||||
|
||||
proc Boolean*(v: CljVal): CljVal =
|
||||
Boolean(@[v])
|
||||
|
||||
proc Object*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: cljKeyword("Object")
|
||||
else: args[0]
|
||||
|
||||
proc Object*(): CljVal =
|
||||
cljKeyword("Object")
|
||||
|
||||
proc Integer*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljInt(0)
|
||||
cljToInt(args)
|
||||
|
||||
proc Integer*(v: CljVal): CljVal =
|
||||
cljToInt(@[v])
|
||||
|
||||
proc Long*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljInt(0)
|
||||
cljToInt(args)
|
||||
|
||||
proc Float*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljFloat(0.0)
|
||||
cljToFloat(args)
|
||||
|
||||
proc Double*(args: seq[CljVal]): CljVal =
|
||||
if args.len == 0: return cljFloat(0.0)
|
||||
cljToFloat(args)
|
||||
|
||||
+118
-46
@@ -329,6 +329,16 @@ proc runtimeName(op: string): string =
|
||||
of "boolean": "cljToBool"
|
||||
of "num": "cljToInt"
|
||||
of "number": "cljToInt"
|
||||
of "int?": "cljIsInteger"
|
||||
of "Thread/sleep": "cljSleep"
|
||||
of "System/getProperty": "cljSystemGetProperty"
|
||||
of "random-uuid": "cljRandomUuid"
|
||||
of "vreset!": "cljVresetB"
|
||||
of "restart-agent": "cljRestartAgent"
|
||||
of "with-out-str": "cljWithOutStr"
|
||||
of "require": "cljRequire"
|
||||
of "eval": "cljEvalStub"
|
||||
of "resolve": "cljResolve"
|
||||
else: ""
|
||||
|
||||
proc emitFnAsProc(items: seq[CljVal], indent: int): string =
|
||||
@@ -414,10 +424,6 @@ proc letDestructuredBindings(bindings: CljVal): CljVal =
|
||||
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let sp = indentStr(indent)
|
||||
let head = items[0]
|
||||
if head.kind == ckSymbol:
|
||||
stderr.writeLine("DEBUG emitSpecialForm head=", head.symName)
|
||||
if head.kind == ckSymbol and head.symName == "fn*":
|
||||
stderr.writeLine("DEBUG emitSpecialForm fn* reached, op=", resolveNsAlias(head.symName))
|
||||
if head.kind != ckSymbol:
|
||||
# Head is not a symbol - evaluate as function call: ((fn ...) args...)
|
||||
var argParts: seq[string] = @[]
|
||||
@@ -438,12 +444,16 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
raise newException(EmitterError, "ns name must be a symbol")
|
||||
var lines: seq[string] = @[]
|
||||
lines.add(sp & "# Namespace: " & nsName.symName)
|
||||
# Process require clauses
|
||||
# Process require clauses — register aliases
|
||||
var newAliases: seq[(string, string)] = @[]
|
||||
for ri in 2..<items.len:
|
||||
let clause = 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")))
|
||||
let isImport = (clause.kind == ckList and clause.items.len > 0 and
|
||||
((clause.items[0].kind == ckSymbol and clause.items[0].symName == ":import") or
|
||||
(clause.items[0].kind == ckKeyword and clause.items[0].kwName == "import")))
|
||||
if isRequire:
|
||||
for ci in 1..<clause.items.len:
|
||||
let req = clause.items[ci]
|
||||
@@ -455,9 +465,38 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
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
|
||||
# Also check for :refer
|
||||
var referNames: seq[string] = @[]
|
||||
var ri2 = 1
|
||||
while ri2 < req.items.len:
|
||||
if req.items[ri2].kind == ckKeyword and req.items[ri2].kwName == "refer":
|
||||
if ri2 + 1 < req.items.len and req.items[ri2+1].kind == ckVector:
|
||||
for rn in req.items[ri2+1].items:
|
||||
if rn.kind == ckSymbol:
|
||||
referNames.add(rn.symName)
|
||||
ri2 += 2
|
||||
elif req.items[ri2].kind == ckKeyword and req.items[ri2].kwName in ["as", "refer-macros"]:
|
||||
ri2 += 2
|
||||
else:
|
||||
ri2 += 1
|
||||
newAliases.add((alias, libName.symName))
|
||||
for rn in referNames:
|
||||
newAliases.add((rn, rn))
|
||||
# Convert namespace to file path: my.app -> my/app
|
||||
let filePath = libName.symName.replace(".", "/") & ".clj"
|
||||
lines.add(sp & "# require: " & libName.symName & " as " & alias & " from " & filePath)
|
||||
if isImport:
|
||||
# Skip :import clauses
|
||||
discard
|
||||
# Merge new aliases with existing
|
||||
for (a, ns) in newAliases:
|
||||
var found = false
|
||||
for (ea, ens) in nsAliases:
|
||||
if ea == a:
|
||||
found = true
|
||||
break
|
||||
if not found:
|
||||
nsAliases.add((a, ns))
|
||||
return lines.join("\n")
|
||||
|
||||
of "defmacro":
|
||||
@@ -605,9 +644,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
# Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper
|
||||
let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and
|
||||
valForm.items[0].kind == ckSymbol and valForm.items[0].symName == "fn"
|
||||
stderr.writeLine("DEBUG def mangled=", mangled, " indent=", indent, " items.len=", items.len, " isFnDef=", isFnDef)
|
||||
if indent == 0:
|
||||
stderr.writeLine("DEBUG def indent=0 branch")
|
||||
if isFnDef:
|
||||
# Emit fn as raw proc for def context
|
||||
let oldEmitLibMode = emitLibMode
|
||||
@@ -618,17 +655,15 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let valCode = emitExpr(items[2], 0)
|
||||
return sp & "let " & mangled & " = " & valCode
|
||||
else:
|
||||
stderr.writeLine("DEBUG def indent>0 branch")
|
||||
# For nested def: use global var so it's accessible from closures
|
||||
if isFnDef:
|
||||
let oldEmitLibMode = emitLibMode
|
||||
emitLibMode = false
|
||||
let valCode = emitFnAsProc(valForm.items, indent)
|
||||
emitLibMode = oldEmitLibMode
|
||||
return sp & "var " & mangled & " {.global.} = " & valCode.strip() & "\n" & sp & mangled
|
||||
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
|
||||
let valCode = emitExpr(items[2], indent)
|
||||
stderr.writeLine("DEBUG def about to return var global for ", mangled)
|
||||
return sp & "var " & mangled & " {.global.} = " & valCode.strip() & "\n" & sp & mangled
|
||||
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
|
||||
|
||||
of "defn":
|
||||
if items.len < 3:
|
||||
@@ -706,7 +741,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode
|
||||
|
||||
of "fn":
|
||||
stderr.writeLine("DEBUG emitSpecialForm fn indent=", indent)
|
||||
if items.len < 2:
|
||||
raise newException(EmitterError, "fn requires params")
|
||||
var paramsIdx = 1
|
||||
@@ -757,7 +791,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if fnName.len > 0 or argsLines.len > 0 or bodyCode.find("\n") != -1:
|
||||
# Wrap in IIFE to avoid indentation issues when used as an argument
|
||||
var wrapperLines: seq[string] = @[]
|
||||
stderr.writeLine("DEBUG fn indent=", indent, " spLen=", sp.len, " indent+1Len=", indentStr(indent+1).len)
|
||||
wrapperLines.add(sp & "((proc (): CljVal =")
|
||||
var tempName: string
|
||||
if fnName.len > 0:
|
||||
@@ -861,16 +894,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
for bi, b in body:
|
||||
let bcode = emitExpr(b, indent + 1)
|
||||
let stripped = bcode.strip()
|
||||
let isLast = (bi == body.len - 1)
|
||||
let isStatement = stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("var ") or stripped.startsWith("let ") or
|
||||
stripped.startsWith("proc ") or
|
||||
stripped.startsWith("if ") or stripped.startsWith("try:") or
|
||||
stripped.startsWith("while ") or stripped.contains("continue")
|
||||
if isLast or isStatement:
|
||||
if isStatement:
|
||||
lines.add(bcode)
|
||||
elif stripped.startsWith("block:"):
|
||||
# Multi-line block as non-last form: discard the last line
|
||||
var blockLines = bcode.split("\n")
|
||||
var lastIdx = blockLines.len - 1
|
||||
while lastIdx >= 0 and blockLines[lastIdx].strip() == "":
|
||||
@@ -887,6 +918,26 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
lines.add(indentStr(indent + 1) & "discard cljNil()")
|
||||
return lines.join("\n")
|
||||
|
||||
of "cond":
|
||||
if items.len < 3 or items.len mod 2 != 1:
|
||||
raise newException(EmitterError, "cond requires test/expr pairs")
|
||||
# Build nested if-elif chain
|
||||
var lines: seq[string] = @[]
|
||||
var first = true
|
||||
var ci = 1
|
||||
while ci < items.len - 1:
|
||||
let testExpr = items[ci]
|
||||
let thenExpr = items[ci + 1]
|
||||
let condCode = emitExpr(testExpr, 0).strip()
|
||||
let thenCode = emitExpr(thenExpr, indent + 1)
|
||||
if first:
|
||||
lines.add(sp & "if cljIsTruthy(" & condCode & "):\n" & thenCode)
|
||||
first = false
|
||||
else:
|
||||
lines.add(sp & "elif cljIsTruthy(" & condCode & "):\n" & thenCode)
|
||||
ci += 2
|
||||
return lines.join("\n")
|
||||
|
||||
of "loop":
|
||||
if items.len < 3:
|
||||
raise newException(EmitterError, "loop requires bindings and body")
|
||||
@@ -989,7 +1040,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
var nameIdx = 2
|
||||
var bodyIdx = 3
|
||||
if form.items[1].kind == ckSymbol and form.items.len >= 4:
|
||||
exTypeStr = form.items[1].symName
|
||||
let rawType = form.items[1].symName
|
||||
# Map known Clojure exception types to Nim
|
||||
if rawType in ["clojure.lang.ExceptionInfo", "ExceptionInfo"]:
|
||||
exTypeStr = "ExInfo"
|
||||
elif rawType in ["Throwable", "Exception", "java.lang.Exception", "java.lang.Throwable"]:
|
||||
exTypeStr = "CatchableError"
|
||||
else:
|
||||
exTypeStr = rawType
|
||||
elif form.items[1].kind == ckKeyword:
|
||||
if form.items[1].kwName == "default":
|
||||
exTypeStr = "CatchableError"
|
||||
@@ -1035,6 +1093,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
lines.add(indentStr(indent + 1) & "let exPtr" & exVar & " = getCurrentException()")
|
||||
lines.add(indentStr(indent + 1) & "if exPtr" & exVar & " of ExInfo:")
|
||||
lines.add(indentStr(indent + 2) & "discard cljAssoc(" & exVar & ", cljKeyword(\"data\"), (ref ExInfo)(exPtr" & exVar & ").exData)")
|
||||
pushScope()
|
||||
addToScope(exVar)
|
||||
for i, eb in exBody:
|
||||
let isLast = (i == exBody.len - 1)
|
||||
let code = emitExpr(eb, indent + 1)
|
||||
@@ -1051,6 +1111,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
lines.add(code)
|
||||
else:
|
||||
lines.add(indentStr(indent + 1) & "discard " & stripped)
|
||||
popScope()
|
||||
if finallyBody.len > 0:
|
||||
lines.add(sp & "finally:")
|
||||
for fb in finallyBody:
|
||||
@@ -1172,7 +1233,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let fnArg = items[1]
|
||||
let collArg = emitExpr(items[2], 0)
|
||||
let isFn = fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]
|
||||
stderr.writeLine("DEBUG filter isFn=", isFn, " symName=", (if fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol: fnArg.items[0].symName else: "N/A"))
|
||||
if isFn:
|
||||
let fnParams = fnArg.items[1]
|
||||
let fnBody = fnArg.items[2..^1]
|
||||
@@ -1298,6 +1358,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
return sp & "cljVar(" & emitExpr(target, 0) & ")"
|
||||
return sp & "cljVar(" & emitExpr(target, 0) & ")"
|
||||
|
||||
of "definterface":
|
||||
if items.len < 2:
|
||||
raise newException(EmitterError, "definterface requires a name")
|
||||
let iname = items[1]
|
||||
if iname.kind != ckSymbol:
|
||||
raise newException(EmitterError, "definterface name must be a symbol")
|
||||
return sp & "discard cljProtocol(\"" & iname.symName & "\")"
|
||||
|
||||
of "defprotocol":
|
||||
if items.len < 2:
|
||||
raise newException(EmitterError, "defprotocol requires a name")
|
||||
@@ -1337,6 +1405,19 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
raise newException(EmitterError, "deftype name must be a symbol")
|
||||
return sp & "let " & mangleName(tname.symName) & " = cljTypeConstructor(\"" & tname.symName & "\", cljVector(@[]))"
|
||||
|
||||
of "new":
|
||||
# Java-style constructor: (new ClassName args...) — emit as constructor call
|
||||
if items.len < 2:
|
||||
return sp & "cljNil()"
|
||||
let className = items[1]
|
||||
if className.kind != ckSymbol:
|
||||
return sp & "cljNil()"
|
||||
var argParts: seq[string] = @[]
|
||||
for i in 2..<items.len:
|
||||
argParts.add(emitExpr(items[i], indent))
|
||||
let mangled = mangleName(className.symName)
|
||||
return sp & mangled & "(" & argParts.join(", ") & ")"
|
||||
|
||||
of "defmulti":
|
||||
if items.len < 3:
|
||||
raise newException(EmitterError, "defmulti requires name and dispatch function")
|
||||
@@ -1656,8 +1737,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if rn.len > 0:
|
||||
var argParts: seq[string] = @[]
|
||||
for i in 1..<items.len:
|
||||
if items[i].kind == ckList and items[i].items.len > 0 and items[i].items[0].kind == ckSymbol and items[i].items[0].symName in ["fn", "fn*"]:
|
||||
stderr.writeLine("DEBUG runtimeCall fn arg indent=", indent)
|
||||
argParts.add(emitExpr(items[i], indent))
|
||||
# Variadic functions take seq[CljVal]
|
||||
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
|
||||
@@ -1676,7 +1755,16 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
"println-str", "prn-str", "binding", "aset",
|
||||
"volatile!", "deliver", "doall", "dorun",
|
||||
"to-array", "into-array", "vector", "rand", "rand-int",
|
||||
"rand-nth", "random-sample"]
|
||||
"rand-nth", "random-sample",
|
||||
"assoc", "dissoc", "get", "get-in", "update", "assoc-in",
|
||||
"contains?", "select-keys", "keys", "vals",
|
||||
"disj", "peek", "pop",
|
||||
"transduce", "ex-info",
|
||||
"compare", "subvec",
|
||||
"require", "eval", "resolve", "random-uuid",
|
||||
"vreset!", "restart-agent", "with-out-str",
|
||||
"System/getProperty"]
|
||||
|
||||
var call: string
|
||||
if variadic:
|
||||
call = rn & "(@[" & argParts.join(", ") & "])"
|
||||
@@ -1766,7 +1854,15 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
"println-str", "prn-str", "binding", "aset",
|
||||
"volatile!", "deliver", "doall", "dorun",
|
||||
"to-array", "into-array", "vector", "rand", "rand-int",
|
||||
"rand-nth", "random-sample"]
|
||||
"rand-nth", "random-sample",
|
||||
"assoc", "dissoc", "get", "get-in", "update", "assoc-in",
|
||||
"contains?", "select-keys", "keys", "vals",
|
||||
"disj", "peek", "pop",
|
||||
"transduce", "ex-info",
|
||||
"compare", "subvec",
|
||||
"require", "eval", "resolve", "random-uuid",
|
||||
"vreset!", "restart-agent", "with-out-str",
|
||||
"System/getProperty"]
|
||||
if variadic:
|
||||
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
|
||||
else:
|
||||
@@ -1816,16 +1912,10 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
|
||||
var lines: seq[string] = @[]
|
||||
for i, item in items:
|
||||
var code = emitExpr(item, indent)
|
||||
if code.contains("testvar_a"):
|
||||
stderr.writeLine("DEBUG emitBlock indent=", indent, " code=", code.repr)
|
||||
let stripped = code.strip()
|
||||
if i < items.len - 1:
|
||||
let firstNl = code.find("\n")
|
||||
let firstLine = if firstNl == -1: stripped else: code[0..<firstNl].strip()
|
||||
let trimmed = code.strip()
|
||||
let lastNl = trimmed.rfind("\n")
|
||||
let lastLine = if lastNl == -1: trimmed else: trimmed[lastNl+1..^1].strip()
|
||||
# Prepend discard before the first non-empty line
|
||||
if firstNl == -1:
|
||||
if stripped.startsWith("let ") or stripped.startsWith("proc ") or stripped.startsWith("var ") or
|
||||
stripped.startsWith("discard "):
|
||||
@@ -1833,11 +1923,8 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
|
||||
else:
|
||||
code = indentStr(indent) & "discard " & stripped
|
||||
else:
|
||||
# Wrap multi-line expression in proc so discard can apply
|
||||
let indentedCode = indentCode(code, 1)
|
||||
code = indentStr(indent) & "discard ((proc (): CljVal =\n" & indentedCode & "\n" & indentStr(indent) & ")())"
|
||||
if code.contains("testvar_a"):
|
||||
stderr.writeLine("DEBUG emitBlock after wrap has var=", code.contains("var testvar_a"), " has let=", code.contains("let testvar_a"))
|
||||
elif useResult:
|
||||
if stripped.startsWith("discard "):
|
||||
code = indentStr(indent) & "result = " & stripped[8..^1]
|
||||
@@ -1853,9 +1940,6 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
|
||||
return lines.join("\n")
|
||||
|
||||
proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
stderr.writeLine("DEBUG emitProgramInternal forms.len=", forms.len)
|
||||
if forms.len > 0 and forms[0].kind == ckList and forms[0].items.len > 0 and forms[0].items[0].kind == ckSymbol:
|
||||
stderr.writeLine("DEBUG emitProgramInternal forms[0]=", forms[0].items[0].symName, " forms[0].len=", forms[0].items.len)
|
||||
scopeStack = @[]
|
||||
pushScope()
|
||||
requiredImports = initHashSet[string]()
|
||||
@@ -1880,7 +1964,6 @@ 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]
|
||||
stderr.writeLine("DEBUG processOneForm do subForms.len=", subForms.len)
|
||||
for j in countdown(subForms.len - 1, 0):
|
||||
let subLast = isLast and (j == subForms.len - 1)
|
||||
worklist.add((subForms[j], subLast))
|
||||
@@ -1888,19 +1971,13 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
let isDef = headSym and headName in ["def", "defn", "defn-", "defprotocol", "defrecord", "deftype", "defmulti", "defmethod", "var"]
|
||||
let isMacro = headSym and headName in ["defmacro"]
|
||||
let isNs = headSym and headName == "ns"
|
||||
if headName == "def" and ($form).contains("testvar"):
|
||||
stderr.writeLine("DEBUG processOneForm testvar isDef=", isDef, " headName=", headName)
|
||||
if isNs:
|
||||
discard
|
||||
elif isDef or isMacro:
|
||||
let defCode = emitExpr(ef, 0)
|
||||
if defCode.contains("testvar_a"):
|
||||
stderr.writeLine("DEBUG defs adding testvar_a code=", defCode.repr)
|
||||
defs.add(defCode)
|
||||
else:
|
||||
let code = emitExpr(ef, 2)
|
||||
if code.contains("testvar_a"):
|
||||
stderr.writeLine("DEBUG emitProgramInternal adding testvar_a to mainForms")
|
||||
let stripped = code.strip()
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("var "):
|
||||
@@ -1919,10 +1996,8 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
worklist.add((subForms[j], subLast))
|
||||
elif form.kind == ckList and form.items.len == 2 and
|
||||
form.items[0].kind == ckSymbol and form.items[0].symName == "splice-unwrap":
|
||||
stderr.writeLine("DEBUG unwrapOneForm splice-unwrap")
|
||||
let inner = form.items[1]
|
||||
if inner.kind == ckVector:
|
||||
stderr.writeLine("DEBUG unwrapOneForm splice-unwrap vector len=", inner.items.len)
|
||||
for j in countdown(inner.items.len - 1, 0):
|
||||
let subLast = isLast and (j == inner.items.len - 1)
|
||||
worklist.add((inner.items[j], subLast))
|
||||
@@ -1932,14 +2007,11 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
processOneForm(form, isLast)
|
||||
|
||||
for i in countdown(forms.len - 1, 0):
|
||||
if forms[i].kind == ckList and forms[i].items.len > 0 and forms[i].items[0].kind == ckSymbol and forms[i].items[0].symName == "def" and ($forms[i]).contains("testvar"):
|
||||
stderr.writeLine("DEBUG forms adding testvar to worklist")
|
||||
worklist.add((forms[i], i == forms.len - 1))
|
||||
|
||||
while worklist.len > 0:
|
||||
let (item, itemIsLast) = worklist.pop()
|
||||
if item.kind == ckList and item.items.len > 0 and item.items[0].kind == ckSymbol:
|
||||
stderr.writeLine("DEBUG worklist popping ", item.items[0].symName)
|
||||
unwrapOneForm(item, itemIsLast)
|
||||
|
||||
var lines = headerLines
|
||||
|
||||
+3
-3
@@ -450,10 +450,10 @@ proc readDispatch(s: string, i: var int): CljVal =
|
||||
else: discard
|
||||
k += 2
|
||||
var resultVal: CljVal = nil
|
||||
if cljVal != nil:
|
||||
resultVal = cljVal
|
||||
elif defaultVal != nil:
|
||||
if defaultVal != nil:
|
||||
resultVal = defaultVal
|
||||
elif cljVal != nil:
|
||||
resultVal = cljVal
|
||||
else:
|
||||
return nil
|
||||
if isSplice and resultVal != nil and resultVal.kind == ckVector:
|
||||
|
||||
Reference in New Issue
Block a user