chore: emitter/runtime improvements from previous session

This commit is contained in:
2026-05-11 01:49:48 +03:00
parent 075e5f6df0
commit e5897125fd
2 changed files with 523 additions and 84 deletions
+195 -3
View File
@@ -11,6 +11,9 @@ type
fn: CljVal fn: CljVal
args: seq[CljVal] args: seq[CljVal]
ExInfo* = object of CatchableError
exData*: CljVal
CljVal* = ref CljValObj CljVal* = ref CljValObj
CljValObj = object CljValObj = object
meta*: CljVal meta*: CljVal
@@ -110,6 +113,24 @@ proc cljSet*(items: seq[CljVal]): CljVal =
s = pmapAssoc(s, item, true, hash(item), cljEq) s = pmapAssoc(s, item, true, hash(item), cljEq)
CljVal(kind: ckSet, setData: s) CljVal(kind: ckSet, setData: s)
proc cljSet*(coll: CljVal): CljVal =
var s = newPersistentMap[CljVal, bool]()
if coll.isNil: return CljVal(kind: ckSet, setData: s)
case coll.kind
of ckList:
for item in coll.listItems:
s = pmapAssoc(s, item, true, hash(item), cljEq)
of ckVector:
for item in toSeq(coll.vecData):
s = pmapAssoc(s, item, true, hash(item), cljEq)
of ckSet:
return coll
of ckMap:
for key in pmapKeys(coll.mapData):
s = pmapAssoc(s, key, true, hash(key), cljEq)
else: discard
CljVal(kind: ckSet, setData: s)
proc cljHashMap*(args: seq[CljVal]): CljVal = proc cljHashMap*(args: seq[CljVal]): CljVal =
if args.len mod 2 != 0: if args.len mod 2 != 0:
raise newException(CatchableError, "hash-map requires even number of arguments") raise newException(CatchableError, "hash-map requires even number of arguments")
@@ -185,6 +206,47 @@ proc cljIsColl*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckLi
proc cljIsSequential*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckList, ckVector}) proc cljIsSequential*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckList, ckVector})
proc cljIsFn*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckFn) proc cljIsFn*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckFn)
proc cljIsVar*(v: CljVal): CljVal = cljBool(false) proc cljIsVar*(v: CljVal): CljVal = cljBool(false)
proc cljParseUuid*(s: CljVal): CljVal =
if s.kind != ckString:
raise newException(CatchableError, "Invalid UUID string")
let str = s.strVal
if str.len == 0:
return cljNil()
# Simple UUID validation: 8-4-4-4-12 hex digits
var parts: seq[string] = @[]
var current = ""
for c in str:
if c == '-':
parts.add(current)
current = ""
else:
current.add(c)
parts.add(current)
if parts.len == 5 and
parts[0].len == 8 and parts[1].len == 4 and parts[2].len == 4 and
parts[3].len == 4 and parts[4].len == 12:
for p in parts:
for c in p:
if c notin {'0'..'9', 'a'..'f', 'A'..'F'}:
return cljNil()
return cljString(str.toLowerAscii())
return cljNil()
proc cljIsUuid*(v: CljVal): CljVal =
if v.kind != ckString:
return cljBool(false)
let str = v.strVal
var parts = str.split('-')
if parts.len == 5 and
parts[0].len == 8 and parts[1].len == 4 and parts[2].len == 4 and
parts[3].len == 4 and parts[4].len == 12:
for p in parts:
for c in p:
if c notin {'0'..'9', 'a'..'f', 'A'..'F'}:
return cljBool(false)
return cljBool(true)
return cljBool(false)
proc cljIsIfn*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckFn, ckKeyword, ckSymbol, ckMap, ckSet, ckVector}) proc cljIsIfn*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind in {ckFn, ckKeyword, ckSymbol, ckMap, ckSet, ckVector})
proc cljIsBool*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckBool) proc cljIsBool*(v: CljVal): CljVal = cljBool(not cljIsNil(v) and v.kind == ckBool)
proc cljIsTrueP*(v: CljVal): CljVal = cljBool(cljIsTrue(v)) proc cljIsTrueP*(v: CljVal): CljVal = cljBool(cljIsTrue(v))
@@ -772,10 +834,20 @@ proc cljSortedMap*(args: seq[CljVal]): CljVal =
# Fallback to regular hash-map # Fallback to regular hash-map
cljHashMap(args) cljHashMap(args)
proc cljSortedMapBy*(args: seq[CljVal]): CljVal =
# Fallback to regular hash-map (ignores comparator)
if args.len < 1: return cljMap(@[], @[])
cljHashMap(args[1..^1])
proc cljSortedSet*(args: seq[CljVal]): CljVal = proc cljSortedSet*(args: seq[CljVal]): CljVal =
# Fallback to regular hash-set # Fallback to regular hash-set
cljHashSet(args) cljHashSet(args)
proc cljSortedSetBy*(args: seq[CljVal]): CljVal =
# Fallback to regular hash-set (ignores comparator)
if args.len < 1: return cljSet(@[])
cljHashSet(args[1..^1])
proc cljArrayMap*(args: seq[CljVal]): CljVal = proc cljArrayMap*(args: seq[CljVal]): CljVal =
# Fallback to regular hash-map # Fallback to regular hash-map
cljHashMap(args) cljHashMap(args)
@@ -845,6 +917,14 @@ proc cljToArray*(args: seq[CljVal]): CljVal =
for c in v.strVal: items.add(cljString($c)) for c in v.strVal: items.add(cljString($c))
return cljVector(items) return cljVector(items)
else: return cljVector(@[v]) else: return cljVector(@[v])
proc cljIntoArray*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljVector(@[])
if args.len == 1:
return cljToArray(@[args[0]])
# Ignore type argument, convert last collection argument to vector
return cljToArray(@[args[^1]])
proc cljVolatileBang*(args: seq[CljVal]): CljVal = proc cljVolatileBang*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil() if args.len == 0: return cljNil()
# volatile! creates a mutable holder - stub as atom # volatile! creates a mutable holder - stub as atom
@@ -948,6 +1028,19 @@ proc cljDescendants*(args: seq[CljVal]): CljVal =
if args.len < 1: return cljNil() if args.len < 1: return cljNil()
cljSet(@[]) cljSet(@[])
proc cljUseFixtures*(args: seq[CljVal]): CljVal =
cljNil()
proc cljAddWatch*(args: seq[CljVal]): CljVal =
cljNil()
proc cljRemoveWatch*(args: seq[CljVal]): CljVal =
cljNil()
proc cljAlterVarRoot*(args: seq[CljVal]): CljVal =
if args.len >= 2: return args[1]
cljNil()
proc cljParents*(args: seq[CljVal]): CljVal = proc cljParents*(args: seq[CljVal]): CljVal =
if args.len < 1: return cljNil() if args.len < 1: return cljNil()
cljSet(@[]) cljSet(@[])
@@ -1254,6 +1347,42 @@ proc cljGetDefault*(m: CljVal, key: CljVal, default: CljVal): CljVal =
else: default else: default
else: default else: default
proc cljExInfo*(msg: CljVal, data: CljVal): CljVal =
var ex = newException(ExInfo, cljStr(msg))
ex.exData = data
raise ex
proc cljExData*(ex: CljVal): CljVal =
if ex.isNil: return cljNil()
case ex.kind
of ckMap:
let data = cljGet(ex, cljKeyword("data"))
if data.kind == ckNil:
return ex
return data
else: return cljNil()
proc cljCall*(f: CljVal, args: seq[CljVal]): CljVal =
if f.isNil: return cljNil()
case f.kind
of ckFn:
if args.len > 0: return f.fnProc(args)
else: return f.fnProc(@[])
of ckMap:
if args.len > 0: return cljGet(f, args[0])
else: return cljNil()
of ckSet:
if args.len > 0: return cljBool(pmapContains(f.setData, args[0], hash(args[0]), cljEq))
else: return cljBool(false)
of ckVector:
if args.len > 0: return cljNth(f, args[0])
else: return cljNil()
of ckKeyword:
if args.len > 0: return cljGet(args[0], f)
else: return cljNil()
else:
raise newException(CatchableError, "Cannot call value of type " & $f.kind)
proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal = proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal =
if m.isNil or m.kind == ckNil: if m.isNil or m.kind == ckNil:
return CljVal(kind: ckMap, mapData: pmapAssoc(newPersistentMap[CljVal, CljVal](), key, val, hash(key), cljEq)) return CljVal(kind: ckMap, mapData: pmapAssoc(newPersistentMap[CljVal, CljVal](), key, val, hash(key), cljEq))
@@ -1340,6 +1469,29 @@ proc cljMap*(f: CljVal, coll: CljVal): CljVal =
if f.kind == ckFn: cljMap(f.fnProc, coll) if f.kind == ckFn: cljMap(f.fnProc, coll)
else: raise newException(CatchableError, "map requires a function") else: raise newException(CatchableError, "map requires a function")
proc cljMapN*(f: CljVal, colls: seq[CljVal]): CljVal =
if f.kind != ckFn: raise newException(CatchableError, "map requires a function")
let fn = f.fnProc
var seqs: seq[seq[CljVal]] = newSeq[seq[CljVal]]()
for coll in colls:
if coll.isNil:
seqs.add(@[])
else:
case coll.kind
of ckList: seqs.add(coll.listItems)
of ckVector: seqs.add(toSeq(coll.vecData))
else: seqs.add(@[])
if seqs.len == 0: return cljList(@[])
var res = newSeq[CljVal]()
var i = 0
while true:
var args = newSeq[CljVal]()
for s in seqs:
if i >= s.len: return cljList(res)
args.add(s[i])
res.add(fn(args))
i += 1
proc cljFilter*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal = proc cljFilter*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal =
if coll.isNil: return cljList(@[]) if coll.isNil: return cljList(@[])
case coll.kind case coll.kind
@@ -1362,6 +1514,15 @@ proc cljReduce*(f: CljVal, init: CljVal, coll: CljVal): CljVal =
if f.kind == ckFn: cljReduce(f.fnProc, init, coll) if f.kind == ckFn: cljReduce(f.fnProc, init, coll)
else: raise newException(CatchableError, "reduce requires a function") else: raise newException(CatchableError, "reduce requires a function")
proc cljTransduce*(xform: CljVal, f: CljVal, init: CljVal, coll: CljVal): CljVal =
# Stub: apply xform to f and reduce
if xform.kind == ckFn and f.kind == ckFn:
let rf = xform.fnProc(@[f])
if rf.kind == ckFn:
return cljReduce(rf, init, coll)
# Fallback: just return init
return init
proc cljMapv*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal = proc cljMapv*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal =
if coll.isNil: return cljVector(@[]) if coll.isNil: return cljVector(@[])
case coll.kind case coll.kind
@@ -1590,9 +1751,32 @@ proc cljAtom*(args: seq[CljVal]): CljVal =
i += 2 i += 2
a a
proc cljPromise*(): CljVal =
cljAtom(cljNil())
proc cljPromise*(args: seq[CljVal]): CljVal = proc cljPromise*(args: seq[CljVal]): CljVal =
cljAtom(cljNil()) cljAtom(cljNil())
proc cljForce*(v: CljVal): CljVal =
if v.kind == ckAtom: v.atomVal
else: v
proc cljSleep*(ms: CljVal): CljVal =
cljNil()
proc cljTap*(args: seq[CljVal]): CljVal =
if args.len >= 1: return args[0]
cljNil()
proc cljFutureCancel*(v: CljVal): CljVal =
cljNil()
proc cljAddTap*(args: seq[CljVal]): CljVal =
cljNil()
proc cljRemoveTap*(args: seq[CljVal]): CljVal =
cljNil()
proc cljFuture*(args: seq[CljVal]): CljVal = proc cljFuture*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljAtom(cljNil()) if args.len == 0: return cljAtom(cljNil())
let f = args[0] let f = args[0]
@@ -1621,7 +1805,7 @@ proc cljReset*(a: CljVal, v: CljVal): CljVal =
else: else:
raise newException(CatchableError, "reset! requires an atom") raise newException(CatchableError, "reset! requires an atom")
proc cljSwap*(a: CljVal, f: proc(args: seq[CljVal]): CljVal, args: seq[CljVal]): CljVal = proc cljSwapImpl(a: CljVal, f: proc(args: seq[CljVal]): CljVal, args: seq[CljVal]): CljVal =
if a.kind == ckAtom: if a.kind == ckAtom:
var fargs = @[a.atomVal] var fargs = @[a.atomVal]
fargs.add(args) fargs.add(args)
@@ -1630,10 +1814,18 @@ proc cljSwap*(a: CljVal, f: proc(args: seq[CljVal]): CljVal, args: seq[CljVal]):
else: else:
raise newException(CatchableError, "swap! requires an atom") raise newException(CatchableError, "swap! requires an atom")
proc cljSwap*(a: CljVal, f: CljVal, args: seq[CljVal] = @[]): CljVal = proc cljSwap*(args: seq[CljVal]): CljVal =
if f.kind == ckFn: cljSwap(a, f.fnProc, args) if args.len < 2: raise newException(CatchableError, "swap! requires at least 2 arguments")
let a = args[0]
let f = args[1]
let rest = if args.len > 2: args[2..^1] else: @[]
if f.kind == ckFn: cljSwapImpl(a, f.fnProc, rest)
else: raise newException(CatchableError, "swap! requires a function") else: raise newException(CatchableError, "swap! requires a function")
proc cljVswap*(args: seq[CljVal]): CljVal =
# Treat volatile like atom for now
cljSwap(args)
# ---- Agents ---- # ---- Agents ----
proc cljAgent*(v: CljVal): CljVal = proc cljAgent*(v: CljVal): CljVal =
+328 -81
View File
@@ -1,5 +1,5 @@
# Clojure → Nim Emitter # Clojure → Nim Emitter
import strutils, sets import strutils, sets, algorithm
import types import types
import macros import macros
@@ -67,7 +67,7 @@ proc mangleName*(name: string): string =
"raise", "import", "export", "from", "include", "using", "bind", "raise", "import", "export", "from", "include", "using", "bind",
"mixin", "asm", "defer", "block", "static", "yield", "assert", "mixin", "asm", "defer", "block", "static", "yield", "assert",
"do", "enum", "tuple", "shared", "guard", "concept", "distinct", "do", "enum", "tuple", "shared", "guard", "concept", "distinct",
"interface", "lambda", "open", "quit", "result", "nil"] "interface", "lambda", "open", "quit", "result", "nil", "end"]
if result in nimKeywords: if result in nimKeywords:
result = result & "1" result = result & "1"
@@ -182,8 +182,26 @@ proc runtimeName(op: string): string =
of "zipmap": "cljZipmap" of "zipmap": "cljZipmap"
of "object-array": "cljObjectArray" of "object-array": "cljObjectArray"
of "sorted-map": "cljSortedMap" of "sorted-map": "cljSortedMap"
of "sorted-map-by": "cljSortedMapBy"
of "sorted-set": "cljSortedSet" of "sorted-set": "cljSortedSet"
of "sorted-set-by": "cljSortedSetBy"
of "sorted?": "cljSortedQ" of "sorted?": "cljSortedQ"
of "use-fixtures": "cljUseFixtures"
of "transduce": "cljTransduce"
of "vswap!": "cljVswap"
of "split": "cljStrSplit"
of "str/split": "cljStrSplit"
of "add-watch": "cljAddWatch"
of "remove-watch": "cljRemoveWatch"
of "ex-info": "cljExInfo"
of "ex-data": "cljExData"
of "alter-var-root": "cljAlterVarRoot"
of "force": "cljForce"
of "sleep": "cljSleep"
of "tap>": "cljTap"
of "future-cancel": "cljFutureCancel"
of "add-tap": "cljAddTap"
of "remove-tap": "cljRemoveTap"
of "array-map": "cljArrayMap" of "array-map": "cljArrayMap"
of "rseq": "cljRseq" of "rseq": "cljRseq"
of "list": "cljListEmpty" of "list": "cljListEmpty"
@@ -262,6 +280,8 @@ proc runtimeName(op: string): string =
of "deliver": "cljDeliver" of "deliver": "cljDeliver"
of "var?": "cljIsVar" of "var?": "cljIsVar"
of "ifn?": "cljIsIfn" of "ifn?": "cljIsIfn"
of "parse-uuid": "cljParseUuid"
of "uuid?": "cljIsUuid"
of "ancestors": "cljAncestors" of "ancestors": "cljAncestors"
of "descendants": "cljDescendants" of "descendants": "cljDescendants"
of "parents": "cljParents" of "parents": "cljParents"
@@ -283,6 +303,7 @@ proc runtimeName(op: string): string =
of "fnil": "cljFnil" of "fnil": "cljFnil"
of "intern": "cljIntern" of "intern": "cljIntern"
of "to-array": "cljToArray" of "to-array": "cljToArray"
of "into-array": "cljIntoArray"
of "repeatedly": "cljRepeatedly" of "repeatedly": "cljRepeatedly"
of "make-hierarchy": "cljMakeHierarchy" of "make-hierarchy": "cljMakeHierarchy"
of "derive": "cljDerive" of "derive": "cljDerive"
@@ -339,14 +360,64 @@ proc emitFnAsProc(items: seq[CljVal], indent: int): string =
else: else:
bodyCode = emitBlock(body, indent + 1) bodyCode = emitBlock(body, indent + 1)
popScope() popScope()
let sp = indentStr(indent)
if bodyCode.find("\n") == -1: if bodyCode.find("\n") == -1:
return "(proc (" & paramNames.join(", ") & "): CljVal = " & bodyCode.strip() & ")" return sp & "(proc (" & paramNames.join(", ") & "): CljVal = " & bodyCode.strip() & ")"
else: else:
return "(proc (" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & ")" return sp & "(proc (" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & ")"
proc letDestructuredBindings(bindings: CljVal): CljVal =
# Expand let bindings with vector/map destructuring into simple symbol bindings
if bindings.kind != ckVector:
return bindings
var result: seq[CljVal] = @[]
var i = 0
while i < bindings.items.len:
let bname = bindings.items[i]
let bval = bindings.items[i+1]
if bname.kind == ckSymbol:
result.add(bname)
result.add(bval)
elif bname.kind == ckVector:
let tmp = cljSymbol("ds_" & $i)
result.add(tmp)
result.add(bval)
for j in 0..<bname.items.len:
if bname.items[j].kind == ckSymbol:
result.add(bname.items[j])
result.add(cljList(@[cljSymbol("nth"), tmp, cljInt(j)]))
elif bname.kind == ckMap:
let tmp = cljSymbol("ds_" & $i)
result.add(tmp)
result.add(bval)
var hasAs = false
var asName: CljVal = nil
var j = 0
while j < bname.mapKeys.len:
let key = bname.mapKeys[j]
let val = bname.mapVals[j]
if key.kind == ckKeyword and key.kwName == "keys" and val.kind == ckVector:
for k in 0..<val.items.len:
if val.items[k].kind == ckSymbol:
result.add(val.items[k])
result.add(cljList(@[cljSymbol("get"), tmp, cljKeyword(val.items[k].symName)]))
elif key.kind == ckKeyword and key.kwName == "as":
hasAs = true
asName = val
j += 1
if hasAs and asName != nil and asName.kind == ckSymbol:
result.add(asName)
result.add(tmp)
i += 2
return cljVector(result)
proc emitSpecialForm(items: seq[CljVal], indent: int): string = proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let sp = indentStr(indent) let sp = indentStr(indent)
let head = items[0] 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: if head.kind != ckSymbol:
# Head is not a symbol - evaluate as function call: ((fn ...) args...) # Head is not a symbol - evaluate as function call: ((fn ...) args...)
var argParts: seq[string] = @[] var argParts: seq[string] = @[]
@@ -529,11 +600,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if name.kind != ckSymbol: if name.kind != ckSymbol:
raise newException(EmitterError, "def name must be a symbol") raise newException(EmitterError, "def name must be a symbol")
let mangled = mangleName(name.symName) let mangled = mangleName(name.symName)
addToScope(name.symName)
let valForm = items[2] let valForm = items[2]
# Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper # Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper
let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and
valForm.items[0].kind == ckSymbol and valForm.items[0].symName == "fn" 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: if indent == 0:
stderr.writeLine("DEBUG def indent=0 branch")
if isFnDef: if isFnDef:
# Emit fn as raw proc for def context # Emit fn as raw proc for def context
let oldEmitLibMode = emitLibMode let oldEmitLibMode = emitLibMode
@@ -544,15 +618,17 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let valCode = emitExpr(items[2], 0) let valCode = emitExpr(items[2], 0)
return sp & "let " & mangled & " = " & valCode return sp & "let " & mangled & " = " & valCode
else: else:
# For nested def: wrap in proc and use consistent indentation stderr.writeLine("DEBUG def indent>0 branch")
# For nested def: use global var so it's accessible from closures
if isFnDef: if isFnDef:
let oldEmitLibMode = emitLibMode let oldEmitLibMode = emitLibMode
emitLibMode = false emitLibMode = false
let valCode = emitFnAsProc(valForm.items, indent + 2) let valCode = emitFnAsProc(valForm.items, indent)
emitLibMode = oldEmitLibMode emitLibMode = oldEmitLibMode
return sp & "((proc (): CljVal =\n" & indentStr(indent + 1) & "let " & mangled & " = " & valCode.strip() & "\n" & indentStr(indent + 1) & mangled & ")())" return sp & "var " & mangled & " {.global.} = " & valCode.strip() & "\n" & sp & mangled
let valCode = emitExpr(items[2], indent + 2) let valCode = emitExpr(items[2], indent)
return sp & "((proc (): CljVal =\n" & indentStr(indent + 1) & "let " & mangled & " = " & valCode.strip() & "\n" & indentStr(indent + 1) & mangled & ")())" stderr.writeLine("DEBUG def about to return var global for ", mangled)
return sp & "var " & mangled & " {.global.} = " & valCode.strip() & "\n" & sp & mangled
of "defn": of "defn":
if items.len < 3: if items.len < 3:
@@ -574,7 +650,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
addToScope(p.symName) addToScope(p.symName)
paramNames.add(mangleName(p.symName) & ": CljVal") paramNames.add(mangleName(p.symName) & ": CljVal")
let body = items[3..^1] let body = items[3..^1]
popScope()
let procName = mangleName(name.symName) let procName = mangleName(name.symName)
let exportMarker = if emitLibMode: "*" else: "" let exportMarker = if emitLibMode: "*" else: ""
if indent == 0: if indent == 0:
@@ -585,6 +660,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
bodyCode = emitExpr(body[0], indent + 1) bodyCode = emitExpr(body[0], indent + 1)
else: else:
bodyCode = emitBlock(body, indent + 1) bodyCode = emitBlock(body, indent + 1)
popScope()
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
else: else:
var bodyCode = "" var bodyCode = ""
@@ -592,9 +668,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
bodyCode = emitExpr(body[0], indent + 2) bodyCode = emitExpr(body[0], indent + 2)
else: else:
bodyCode = emitBlock(body, indent + 2) bodyCode = emitBlock(body, indent + 2)
popScope()
return sp & "(block:\n" & return sp & "(block:\n" &
indentStr(indent + 1) & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & "\n" & indentStr(indent + 1) & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & "\n" &
indentStr(indent + 1) & procName & ")" indentStr(indent + 1) & "cljNil())"
of "defn-": of "defn-":
if items.len < 3: if items.len < 3:
@@ -629,12 +706,15 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode
of "fn": of "fn":
stderr.writeLine("DEBUG emitSpecialForm fn indent=", indent)
if items.len < 2: if items.len < 2:
raise newException(EmitterError, "fn requires params") raise newException(EmitterError, "fn requires params")
var paramsIdx = 1 var paramsIdx = 1
var fnName = ""
# Handle named fn: (fn name [params] body) # Handle named fn: (fn name [params] body)
if items[1].kind == ckSymbol: if items[1].kind == ckSymbol:
paramsIdx = 2 paramsIdx = 2
fnName = items[1].symName
if paramsIdx >= items.len: if paramsIdx >= items.len:
raise newException(EmitterError, "fn requires params and body") raise newException(EmitterError, "fn requires params and body")
var params = items[paramsIdx] var params = items[paramsIdx]
@@ -647,6 +727,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let paramCount = params.items.len let paramCount = params.items.len
pushScope() pushScope()
var paramNames: seq[string] = @[] var paramNames: seq[string] = @[]
if fnName.len > 0:
addToScope(fnName)
for p in params.items: for p in params.items:
if p.kind != ckSymbol: if p.kind != ckSymbol:
raise newException(EmitterError, "fn params must be symbols") raise newException(EmitterError, "fn params must be symbols")
@@ -664,27 +746,40 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if paramCount > 0: if paramCount > 0:
for i in 0..<paramCount: for i in 0..<paramCount:
argsLines.add(indentStr(indent + 1) & "let " & paramNames[i] & " = args[" & $i & "]") argsLines.add(indentStr(indent + 1) & "let " & paramNames[i] & " = args[" & $i & "]")
var fnBodyLines: seq[string] = @[]
if argsLines.len > 0: if argsLines.len > 0:
# Multi-line: let bindings on separate lines, then body
var allLines: seq[string] = @[]
for a in argsLines: for a in argsLines:
allLines.add(a) fnBodyLines.add(a)
if bodyCode.find("\n") == -1: if bodyCode.find("\n") == -1:
allLines.add(indentStr(indent + 1) & bodyCode.strip()) fnBodyLines.add(indentStr(indent + 1) & bodyCode.strip())
else:
allLines.add(bodyCode)
return sp & "cljFn(proc(args: seq[CljVal]): CljVal =\n" & allLines.join("\n") & ")"
else: else:
# No params - simple case fnBodyLines.add(bodyCode)
if bodyCode.find("\n") == -1: if fnName.len > 0 or argsLines.len > 0 or bodyCode.find("\n") != -1:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & bodyCode.strip() & ")" # 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:
tempName = mangleName(fnName)
else: else:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal =\n" & bodyCode & ")" tempName = "fnResult"
wrapperLines.add(indentStr(indent + 1) & "var " & tempName & ": CljVal")
wrapperLines.add(indentStr(indent + 1) & tempName & " = cljFn(proc(args: seq[CljVal]): CljVal =")
let indentedFnBody = indentCode(fnBodyLines.join("\n"), 1)
for line in indentedFnBody.split("\n"):
wrapperLines.add(line)
wrapperLines.add(indentStr(indent + 1) & ")")
wrapperLines.add(indentStr(indent + 1) & tempName)
wrapperLines.add(sp & ")())")
return wrapperLines.join("\n")
else:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & bodyCode.strip() & ")"
of "let": of "let":
if items.len < 3: if items.len < 3:
raise newException(EmitterError, "let requires bindings and body") raise newException(EmitterError, "let requires bindings and body")
let bindings = items[1] var bindings = letDestructuredBindings(items[1])
if bindings.kind != ckVector: if bindings.kind != ckVector:
raise newException(EmitterError, "let bindings must be a vector") raise newException(EmitterError, "let bindings must be a vector")
if bindings.items.len mod 2 != 0: if bindings.items.len mod 2 != 0:
@@ -697,9 +792,20 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let bname = bindings.items[bi] let bname = bindings.items[bi]
let bval = bindings.items[bi+1] let bval = bindings.items[bi+1]
if bname.kind != ckSymbol: if bname.kind != ckSymbol:
raise newException(EmitterError, "let binding name must be a symbol") raise newException(EmitterError, "let binding name must be a symbol, got " & $bname.kind & ": " & $bname)
addToScope(bname.symName) addToScope(bname.symName)
lines.add(indentStr(indent + 1) & "let " & mangleName(bname.symName) & " = " & emitExpr(bval, indent + 1)) let bcode = emitExpr(bval, indent + 1)
if bcode.find("\n") != -1:
# Multi-line value: use var and place first line on next line for correct indentation
lines.add(indentStr(indent + 1) & "var " & mangleName(bname.symName) & ": CljVal")
var bcodeLines = bcode.split("\n")
for j, line in bcodeLines:
if j == 0:
lines.add(indentStr(indent + 1) & mangleName(bname.symName) & " = " & line)
else:
lines.add(line)
else:
lines.add(indentStr(indent + 1) & "let " & mangleName(bname.symName) & " = " & bcode)
bi += 2 bi += 2
let body = items[2..^1] let body = items[2..^1]
for j, b in body: for j, b in body:
@@ -708,7 +814,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lines.add(bcode) lines.add(bcode)
else: else:
let stripped = bcode.strip() let stripped = bcode.strip()
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or if stripped.contains("\n"):
# Multi-line expression as non-last form: wrap in proc
let indentedCode = indentCode(bcode, 1)
lines.add(indentStr(indent + 1) & "discard ((proc (): CljVal =\n" & indentedCode & "\n" & indentStr(indent + 1) & ")())")
elif stripped.startsWith("echo ") or stripped.startsWith("discard ") or
stripped.startsWith("if ") or stripped.startsWith("try:") or stripped.startsWith("if ") or stripped.startsWith("try:") or
stripped.startsWith("var ") or stripped.startsWith("let ") or stripped.startsWith("var ") or stripped.startsWith("let ") or
stripped.startsWith("proc ") or stripped.startsWith("while "): stripped.startsWith("proc ") or stripped.startsWith("while "):
@@ -751,13 +861,26 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
for bi, b in body: for bi, b in body:
let bcode = emitExpr(b, indent + 1) let bcode = emitExpr(b, indent + 1)
let stripped = bcode.strip() let stripped = bcode.strip()
let isLast = (bi == body.len - 1)
let isStatement = stripped.startsWith("echo ") or stripped.startsWith("discard ") or let isStatement = stripped.startsWith("echo ") or stripped.startsWith("discard ") or
stripped.startsWith("var ") or stripped.startsWith("let ") or stripped.startsWith("var ") or stripped.startsWith("let ") or
stripped.startsWith("proc ") or stripped.startsWith("block:") or stripped.startsWith("proc ") or
stripped.startsWith("if ") or stripped.startsWith("try:") or stripped.startsWith("if ") or stripped.startsWith("try:") or
stripped.startsWith("while ") or stripped.contains("continue") stripped.startsWith("while ") or stripped.contains("continue")
if isStatement: if isLast or isStatement:
lines.add(bcode) 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() == "":
lastIdx.dec
if lastIdx >= 0:
let lastLine = blockLines[lastIdx].strip()
if not lastLine.startsWith("discard ") and not lastLine.startsWith("result = "):
let prefixLen = blockLines[lastIdx].len - blockLines[lastIdx].strip().len
blockLines[lastIdx] = blockLines[lastIdx][0..<prefixLen] & "discard " & lastLine
lines.add(blockLines.join("\n"))
else: else:
lines.add(indentStr(indent + 1) & "discard " & stripped) lines.add(indentStr(indent + 1) & "discard " & stripped)
lines.add(sp & "else:") lines.add(sp & "else:")
@@ -794,8 +917,31 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
loopStack.add(loopVars) loopStack.add(loopVars)
lines.add(sp & "while true:") lines.add(sp & "while true:")
let body = items[2..^1] let body = items[2..^1]
for b in body: for bi, b in body:
lines.add(emitExpr(b, indent + 1)) 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:
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() == "":
lastIdx.dec
if lastIdx >= 0:
let lastLine = blockLines[lastIdx].strip()
if not lastLine.startsWith("discard ") and not lastLine.startsWith("result = "):
let prefixLen = blockLines[lastIdx].len - blockLines[lastIdx].strip().len
blockLines[lastIdx] = blockLines[lastIdx][0..<prefixLen] & "discard " & lastLine
lines.add(blockLines.join("\n"))
else:
lines.add(indentStr(indent + 1) & "discard " & stripped)
lines.add(indentStr(indent + 1) & "break") lines.add(indentStr(indent + 1) & "break")
discard loopStack.pop() discard loopStack.pop()
popScope() popScope()
@@ -838,15 +984,25 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if form.kind == ckList and form.items.len > 0 and form.items[0].kind == ckSymbol: if form.kind == ckList and form.items.len > 0 and form.items[0].kind == ckSymbol:
if form.items[0].symName == "catch": if form.items[0].symName == "catch":
if form.items.len < 3: if form.items.len < 3:
raise newException(EmitterError, "catch requires exception type, name, and body") raise newException(EmitterError, "catch requires at least name and body")
let exType = form.items[1]
var exTypeStr = "CatchableError" var exTypeStr = "CatchableError"
if exType.kind == ckSymbol: var nameIdx = 2
exTypeStr = exType.symName var bodyIdx = 3
let exName = form.items[2] if form.items[1].kind == ckSymbol and form.items.len >= 4:
exTypeStr = form.items[1].symName
elif form.items[1].kind == ckKeyword:
if form.items[1].kwName == "default":
exTypeStr = "CatchableError"
else:
exTypeStr = form.items[1].kwName
else:
# (catch name body...)
nameIdx = 1
bodyIdx = 2
let exName = form.items[nameIdx]
if exName.kind != ckSymbol: if exName.kind != ckSymbol:
raise newException(EmitterError, "catch name must be a symbol") raise newException(EmitterError, "catch name must be a symbol")
catchClauses.add((exTypeStr, mangleName(exName.symName), form.items[3..^1])) catchClauses.add((exTypeStr, mangleName(exName.symName), form.items[bodyIdx..^1]))
elif form.items[0].symName == "finally": elif form.items[0].symName == "finally":
finallyBody = form.items[1..^1] finallyBody = form.items[1..^1]
else: else:
@@ -876,6 +1032,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lines.add(sp & "except " & exType & ":") lines.add(sp & "except " & exType & ":")
lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])") lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])")
lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))") lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))")
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)")
for i, eb in exBody: for i, eb in exBody:
let isLast = (i == exBody.len - 1) let isLast = (i == exBody.len - 1)
let code = emitExpr(eb, indent + 1) let code = emitExpr(eb, indent + 1)
@@ -945,41 +1104,76 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return emitExpr(quoted, indent) return emitExpr(quoted, indent)
of "map": of "map":
if items.len != 3: if items.len < 3:
raise newException(EmitterError, "map requires exactly 2 arguments (f, coll)") raise newException(EmitterError, "map requires at least 2 arguments (f, coll)")
let fnArg = items[1] let fnArg = items[1]
let collArg = emitExpr(items[2], 0) let isVariadic = items.len > 3
if fnArg.kind == ckSymbol: var collArgs: seq[string] = @[]
let rn = runtimeName(fnArg.symName) for idx in 2..<items.len:
if rn.len > 0: collArgs.add(emitExpr(items[idx], 0))
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args)), " & collArg & ")" if isVariadic:
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args[0])), " & collArg & ")" # Multiple collections: use cljMapN
elif fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName == "fn": let collList = "@[" & collArgs.join(", ") & "]"
# Inline fn: (map (fn [x] ...) coll) if fnArg.kind == ckSymbol:
let fnParams = fnArg.items[1] let rn = runtimeName(fnArg.symName)
let fnBody = fnArg.items[2..^1] if rn.len > 0:
var pNames: seq[string] = @[] return sp & "cljMapN(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args)), " & collList & ")"
for p in fnParams.items: return sp & "cljMapN(cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args)), " & collList & ")"
pNames.add(mangleName(p.symName)) elif fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]:
var bodyExpr = "" let fnParams = fnArg.items[1]
if fnBody.len == 1: let fnBody = fnArg.items[2..^1]
bodyExpr = emitExpr(fnBody[0], 0).strip() var pNames: seq[string] = @[]
for p in fnParams.items:
pNames.add(mangleName(p.symName))
var bodyExpr = ""
if fnBody.len == 1:
bodyExpr = emitExpr(fnBody[0], 0).strip()
else:
var parts: seq[string] = @[]
for b in fnBody:
parts.add(emitExpr(b, 0).strip())
bodyExpr = parts.join("; ")
var letBindings = ""
for j in 0..<pNames.len:
letBindings.add("let " & pNames[j] & " = args[" & $j & "]; ")
let fnProc = "proc(args: seq[CljVal]): CljVal = (" & letBindings & bodyExpr & ")"
return sp & "cljMapN(cljFn(" & fnProc & "), " & collList & ")"
else: else:
var parts: seq[string] = @[] return sp & "cljMapN(" & emitExpr(fnArg, 0) & ", " & collList & ")"
for b in fnBody:
parts.add(emitExpr(b, 0).strip())
bodyExpr = parts.join("; ")
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")"
return sp & "cljMap(cljFn(" & fnProc & "), " & collArg & ")"
else: else:
return sp & "cljMap(" & emitExpr(fnArg, 0) & ", " & collArg & ")" let collArg = collArgs[0]
if fnArg.kind == ckSymbol:
let rn = runtimeName(fnArg.symName)
if rn.len > 0:
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args[0])), " & collArg & ")"
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args[0])), " & collArg & ")"
elif fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]:
let fnParams = fnArg.items[1]
let fnBody = fnArg.items[2..^1]
var pNames: seq[string] = @[]
for p in fnParams.items:
pNames.add(mangleName(p.symName))
var bodyExpr = ""
if fnBody.len == 1:
bodyExpr = emitExpr(fnBody[0], 0).strip()
else:
var parts: seq[string] = @[]
for b in fnBody:
parts.add(emitExpr(b, 0).strip())
bodyExpr = parts.join("; ")
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")"
return sp & "cljMap(cljFn(" & fnProc & "), " & collArg & ")"
else:
return sp & "cljMap(" & emitExpr(fnArg, 0) & ", " & collArg & ")"
of "filter": of "filter":
if items.len != 3: if items.len != 3:
raise newException(EmitterError, "filter requires exactly 2 arguments") raise newException(EmitterError, "filter requires exactly 2 arguments")
let fnArg = items[1] let fnArg = items[1]
let collArg = emitExpr(items[2], 0) let collArg = emitExpr(items[2], 0)
if fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName == "fn": 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 fnParams = fnArg.items[1]
let fnBody = fnArg.items[2..^1] let fnBody = fnArg.items[2..^1]
var pNames: seq[string] = @[] var pNames: seq[string] = @[]
@@ -997,7 +1191,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")" let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")"
return sp & "cljFilter(cljFn(" & fnProc & "), " & collArg & ")" return sp & "cljFilter(cljFn(" & fnProc & "), " & collArg & ")"
else: else:
return sp & "cljFilter(" & emitExpr(fnArg, 0) & ", " & collArg & ")" return sp & "cljFilter(" & emitExpr(fnArg, indent) & ", " & collArg & ")"
of "reduce": of "reduce":
if items.len < 3 or items.len > 4: if items.len < 3 or items.len > 4:
@@ -1057,7 +1251,27 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
of "partial": of "partial":
var parts: seq[string] = @[] var parts: seq[string] = @[]
for i in 1..<items.len: for i in 1..<items.len:
parts.add(emitExpr(items[i], 0)) 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*"]:
let fnParams = items[i].items[1]
let fnBody = items[i].items[2..^1]
var pNames: seq[string] = @[]
for p in fnParams.items:
pNames.add(mangleName(p.symName))
var bodyExpr = ""
if fnBody.len == 1:
bodyExpr = emitExpr(fnBody[0], 0).strip()
else:
var bodyParts: seq[string] = @[]
for b in fnBody:
bodyParts.add(emitExpr(b, 0).strip())
bodyExpr = bodyParts.join("; ")
var letBindings = ""
for j in 0..<pNames.len:
letBindings.add("let " & pNames[j] & " = args[" & $j & "]; ")
let fnProc = "proc(args: seq[CljVal]): CljVal = (" & letBindings & bodyExpr & ")"
parts.add("cljFn(" & fnProc & ")")
else:
parts.add(emitExpr(items[i], 0))
return sp & "cljPartial(" & parts[0] & ", @[" & parts[1..^1].join(", ") & "])" return sp & "cljPartial(" & parts[0] & ", @[" & parts[1..^1].join(", ") & "])"
of "juxt": of "juxt":
@@ -1215,8 +1429,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return emitExpr(fnForm, indent) return emitExpr(fnForm, indent)
of "bound-fn*": of "bound-fn*":
let fnForm = cljList(@[cljSymbol("fn")] & items[1..^1]) if items.len < 2:
return emitExpr(fnForm, indent) raise newException(EmitterError, "bound-fn* requires a function argument")
return emitExpr(items[1], indent)
of "promise": of "promise":
if items.len < 1: return sp & "cljPromise()" if items.len < 1: return sp & "cljPromise()"
@@ -1322,10 +1537,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
parts.add(emitExpr(item, 0)) parts.add(emitExpr(item, 0))
return sp & "cljSet(@[" & parts.join(", ") & "])" return sp & "cljSet(@[" & parts.join(", ") & "])"
of ckList: of ckList:
var parts: seq[string] = @[] return sp & "cljSet(" & emitExpr(arg, 0) & ")"
for item in arg.items:
parts.add(emitExpr(item, 0))
return sp & "cljSet(@[" & parts.join(", ") & "])"
else: else:
return sp & "cljSet(@[" & emitExpr(arg, 0) & "])" return sp & "cljSet(@[" & emitExpr(arg, 0) & "])"
@@ -1444,12 +1656,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if rn.len > 0: if rn.len > 0:
var argParts: seq[string] = @[] var argParts: seq[string] = @[]
for i in 1..<items.len: 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)) argParts.add(emitExpr(items[i], indent))
# Variadic functions take seq[CljVal] # Variadic functions take seq[CljVal]
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
"println", "prn", "print", "str", "pr-str", "println", "prn", "print", "str", "pr-str",
"atom", "concat", "min", "max", "merge", "interleave", "atom", "concat", "min", "max", "merge", "interleave",
"zipmap", "hash-map", "hash-set", "sorted-map", "sorted-set", "cons", "zipmap", "hash-map", "hash-set", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "cons", "use-fixtures", "vswap!", "swap!", "tap>", "add-tap", "remove-tap", "add-watch", "remove-watch", "alter-var-root",
"array-map", "inf", "nan", "list", "array-map", "inf", "nan", "list",
"float", "int", "double", "long", "short", "byte", "float", "int", "double", "long", "short", "byte",
"boolean", "num", "number", "boolean", "num", "number",
@@ -1461,7 +1675,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
"drop-last", "shuffle", "repeatedly", "fnil", "intern", "drop-last", "shuffle", "repeatedly", "fnil", "intern",
"println-str", "prn-str", "binding", "aset", "println-str", "prn-str", "binding", "aset",
"volatile!", "deliver", "doall", "dorun", "volatile!", "deliver", "doall", "dorun",
"to-array", "vector", "rand", "rand-int", "to-array", "into-array", "vector", "rand", "rand-int",
"rand-nth", "random-sample"] "rand-nth", "random-sample"]
var call: string var call: string
if variadic: if variadic:
@@ -1480,8 +1694,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
callOp = callOp[0..^2] callOp = callOp[0..^2]
let mangled = mangleName(callOp) let mangled = mangleName(callOp)
if isLocalVar(callOp): if isLocalVar(callOp):
# Local cljFn value: call via .fnProc # Local value (may be fn, map, set, vector, keyword) — use runtime dispatch
return sp & mangled & ".fnProc(@[" & args.join(", ") & "])" return sp & "cljCall(" & mangled & ", @[" & args.join(", ") & "])"
return sp & mangled & "(" & args.join(", ") & ")" return sp & mangled & "(" & args.join(", ") & ")"
proc emitQuotedForm*(v: CljVal): string = proc emitQuotedForm*(v: CljVal): string =
@@ -1539,7 +1753,7 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
let variadic = symName in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", let variadic = symName in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
"println", "prn", "print", "str", "pr-str", "println", "prn", "print", "str", "pr-str",
"atom", "concat", "min", "max", "merge", "interleave", "atom", "concat", "min", "max", "merge", "interleave",
"zipmap", "hash-map", "hash-set", "sorted-map", "sorted-set", "cons", "zipmap", "hash-map", "hash-set", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "cons", "use-fixtures", "vswap!", "swap!", "tap>", "add-tap", "remove-tap", "add-watch", "remove-watch", "alter-var-root",
"array-map", "inf", "nan", "list", "array-map", "inf", "nan", "list",
"float", "int", "double", "long", "short", "byte", "float", "int", "double", "long", "short", "byte",
"boolean", "num", "number", "boolean", "num", "number",
@@ -1551,7 +1765,7 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
"drop-last", "shuffle", "repeatedly", "fnil", "intern", "drop-last", "shuffle", "repeatedly", "fnil", "intern",
"println-str", "prn-str", "binding", "aset", "println-str", "prn-str", "binding", "aset",
"volatile!", "deliver", "doall", "dorun", "volatile!", "deliver", "doall", "dorun",
"to-array", "vector", "rand", "rand-int", "to-array", "into-array", "vector", "rand", "rand-int",
"rand-nth", "random-sample"] "rand-nth", "random-sample"]
if variadic: if variadic:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))" return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
@@ -1602,6 +1816,8 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
var lines: seq[string] = @[] var lines: seq[string] = @[]
for i, item in items: for i, item in items:
var code = emitExpr(item, indent) var code = emitExpr(item, indent)
if code.contains("testvar_a"):
stderr.writeLine("DEBUG emitBlock indent=", indent, " code=", code.repr)
let stripped = code.strip() let stripped = code.strip()
if i < items.len - 1: if i < items.len - 1:
let firstNl = code.find("\n") let firstNl = code.find("\n")
@@ -1620,6 +1836,8 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
# Wrap multi-line expression in proc so discard can apply # Wrap multi-line expression in proc so discard can apply
let indentedCode = indentCode(code, 1) let indentedCode = indentCode(code, 1)
code = indentStr(indent) & "discard ((proc (): CljVal =\n" & indentedCode & "\n" & indentStr(indent) & ")())" 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: elif useResult:
if stripped.startsWith("discard "): if stripped.startsWith("discard "):
code = indentStr(indent) & "result = " & stripped[8..^1] code = indentStr(indent) & "result = " & stripped[8..^1]
@@ -1635,6 +1853,11 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
return lines.join("\n") return lines.join("\n")
proc emitProgramInternal(forms: seq[CljVal]): string = 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]() requiredImports = initHashSet[string]()
var headerLines: seq[string] = @[ var headerLines: seq[string] = @[
"# Generated by Clojure/Nim", "# Generated by Clojure/Nim",
@@ -1657,6 +1880,7 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
# Recurse into (do ...) forms produced by macro expansion # Recurse into (do ...) forms produced by macro expansion
if headSym and headName == "do": if headSym and headName == "do":
let subForms = ef.items[1..^1] let subForms = ef.items[1..^1]
stderr.writeLine("DEBUG processOneForm do subForms.len=", subForms.len)
for j in countdown(subForms.len - 1, 0): for j in countdown(subForms.len - 1, 0):
let subLast = isLast and (j == subForms.len - 1) let subLast = isLast and (j == subForms.len - 1)
worklist.add((subForms[j], subLast)) worklist.add((subForms[j], subLast))
@@ -1664,12 +1888,19 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
let isDef = headSym and headName in ["def", "defn", "defn-", "defprotocol", "defrecord", "deftype", "defmulti", "defmethod", "var"] let isDef = headSym and headName in ["def", "defn", "defn-", "defprotocol", "defrecord", "deftype", "defmulti", "defmethod", "var"]
let isMacro = headSym and headName in ["defmacro"] let isMacro = headSym and headName in ["defmacro"]
let isNs = headSym and headName == "ns" let isNs = headSym and headName == "ns"
if headName == "def" and ($form).contains("testvar"):
stderr.writeLine("DEBUG processOneForm testvar isDef=", isDef, " headName=", headName)
if isNs: if isNs:
discard discard
elif isDef or isMacro: elif isDef or isMacro:
defs.add(emitExpr(ef, 0)) let defCode = emitExpr(ef, 0)
if defCode.contains("testvar_a"):
stderr.writeLine("DEBUG defs adding testvar_a code=", defCode.repr)
defs.add(defCode)
else: else:
let code = emitExpr(ef, 2) let code = emitExpr(ef, 2)
if code.contains("testvar_a"):
stderr.writeLine("DEBUG emitProgramInternal adding testvar_a to mainForms")
let stripped = code.strip() let stripped = code.strip()
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
stripped.startsWith("var "): stripped.startsWith("var "):
@@ -1686,14 +1917,29 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
for j in countdown(subForms.len - 1, 0): for j in countdown(subForms.len - 1, 0):
let subLast = isLast and (j == subForms.len - 1) let subLast = isLast and (j == subForms.len - 1)
worklist.add((subForms[j], subLast)) 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))
else:
processOneForm(form, isLast)
else: else:
processOneForm(form, isLast) processOneForm(form, isLast)
for i, form in forms: for i in countdown(forms.len - 1, 0):
worklist.add((form, i == forms.len - 1)) 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: while worklist.len > 0:
let (item, itemIsLast) = worklist.pop() 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) unwrapOneForm(item, itemIsLast)
var lines = headerLines var lines = headerLines
@@ -1701,7 +1947,8 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
for imp in requiredImports: for imp in requiredImports:
lines.add("import " & imp) lines.add("import " & imp)
lines.add("") lines.add("")
lines.add(defs) for d in defs:
lines.add(d)
if mainForms.len > 0: if mainForms.len > 0:
lines.add("") lines.add("")