ba0b300917
- New TUI screens: Main Menu, Compile, Run, REPL, AI Generator, AI Settings, Help - AI configuration persisted in ~/.config/cljnim/config.json - Added illwill dependency for terminal UI - Updated experiments, examples, docs, and core modules
1117 lines
42 KiB
Nim
1117 lines
42 KiB
Nim
# Clojure → Nim Emitter
|
|
import strutils, sets
|
|
import types
|
|
import macros
|
|
|
|
var requiredImports* = initHashSet[string]()
|
|
var emitLibMode* = false
|
|
var loopStack*: seq[seq[string]] = @[]
|
|
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
|
|
|
proc setNsAliases*(aliases: seq[(string, string)]) =
|
|
nsAliases = aliases
|
|
|
|
proc resolveNsAlias*(name: string): string =
|
|
# Resolve mu/square -> square (strip namespace prefix if alias exists)
|
|
let slashIdx = name.find('/')
|
|
if slashIdx < 0: return name
|
|
let prefix = name[0..<slashIdx]
|
|
let suffix = name[slashIdx+1..^1]
|
|
for (alias, ns) in nsAliases:
|
|
if prefix == alias:
|
|
return suffix
|
|
return name
|
|
|
|
type
|
|
EmitterError* = object of CatchableError
|
|
|
|
proc mangleName*(name: string): string =
|
|
result = ""
|
|
for c in name:
|
|
case c
|
|
of '-': result.add('_')
|
|
of '?': result.add("_Q")
|
|
of '!': result.add("_B")
|
|
of '*': result.add("_STAR_")
|
|
of '+': result.add("_PLUS_")
|
|
of '/': result.add("_SLASH_")
|
|
of '=': result.add("_EQ_")
|
|
of '>': result.add("_GT_")
|
|
of '<': result.add("_LT_")
|
|
of '.': result.add('_')
|
|
of '\'': result.add("_QUOTE_")
|
|
of '&': result.add("_AMP_")
|
|
else: result.add(c)
|
|
|
|
proc emitExpr*(v: CljVal, indent: int = 0): string
|
|
proc emitBlock*(items: seq[CljVal], indent: int): string
|
|
|
|
proc indentStr(indent: int): string =
|
|
" ".repeat(indent)
|
|
|
|
proc runtimeName(op: string): string =
|
|
case op
|
|
of "+": "cljAdd"
|
|
of "-": "cljSub"
|
|
of "*": "cljMul"
|
|
of "/": "cljDiv"
|
|
of "=": "cljNumEq"
|
|
of ">": "cljGt"
|
|
of "<": "cljLt"
|
|
of ">=": "cljGe"
|
|
of "<=": "cljLe"
|
|
of "not=": "cljNotEq"
|
|
of "not": "cljNot"
|
|
of "println": "cljPrintln"
|
|
of "prn": "cljPrn"
|
|
of "str": "cljStrConcat"
|
|
of "pr-str": "cljPrStrConcat"
|
|
of "inc": "cljInc"
|
|
of "dec": "cljDec"
|
|
of "zero?": "cljZero"
|
|
of "pos?": "cljPos"
|
|
of "neg?": "cljNeg"
|
|
of "even?": "cljEven"
|
|
of "odd?": "cljOdd"
|
|
of "first": "cljFirst"
|
|
of "rest": "cljRest"
|
|
of "next": "cljNext"
|
|
of "last": "cljLast"
|
|
of "nth": "cljNth"
|
|
of "count": "cljCount"
|
|
of "conj": "cljConj"
|
|
of "cons": "cljCons"
|
|
of "seq": "cljSeq"
|
|
of "vec": "cljVec"
|
|
of "empty?": "cljEmpty"
|
|
of "concat": "cljConcat"
|
|
of "take": "cljTake"
|
|
of "drop": "cljDrop"
|
|
of "reverse": "cljReverse"
|
|
of "sort": "cljSort"
|
|
of "distinct": "cljDistinct"
|
|
of "flatten": "cljFlatten"
|
|
of "partition": "cljPartition"
|
|
of "frequencies": "cljFrequencies"
|
|
of "get": "cljGet"
|
|
of "get-in": "cljGetIn"
|
|
of "assoc": "cljAssoc"
|
|
of "dissoc": "cljDissoc"
|
|
of "keys": "cljKeys"
|
|
of "vals": "cljVals"
|
|
of "contains?": "cljContains"
|
|
of "select-keys": "cljSelectKeys"
|
|
of "merge": "cljMerge"
|
|
of "identity": "cljIdentity"
|
|
of "type": "cljType"
|
|
of "abs": "cljAbs"
|
|
of "mod": "cljMod"
|
|
of "min": "cljMin"
|
|
of "max": "cljMax"
|
|
of "apply": "cljApply"
|
|
of "atom": "cljAtom"
|
|
of "deref": "cljDeref"
|
|
of "reset!": "cljReset"
|
|
of "swap!": "cljSwap"
|
|
# ---- File operations ----
|
|
of "file/read": "cljFileRead"
|
|
of "file/write": "cljFileWrite"
|
|
of "file/append": "cljFileAppend"
|
|
of "file/ls": "cljFileLs"
|
|
of "file/exists?": "cljFileExists"
|
|
# ---- Git operations ----
|
|
of "git/status": "cljGitStatus"
|
|
of "git/commit": "cljGitCommit"
|
|
of "git/push": "cljGitPush"
|
|
of "git/diff": "cljGitDiff"
|
|
of "git/log": "cljGitLog"
|
|
of "disj": "cljDisj"
|
|
of "slurp": "cljFileRead"
|
|
of "spit": "cljFileWrite"
|
|
of "read-line": "cljReadLine"
|
|
of "repeat": "cljRepeat"
|
|
of "cycle": "cljCycle"
|
|
of "iterate": "cljIterate"
|
|
of "interleave": "cljInterleave"
|
|
of "quot": "cljQuot"
|
|
of "rem": "cljRem"
|
|
of "instance?": "cljInstanceP"
|
|
of "meta": "cljMeta"
|
|
of "with-meta": "cljWithMeta"
|
|
of "transient": "cljTransient"
|
|
of "persistent!": "cljPersistent"
|
|
of "conj!": "cljConjB"
|
|
of "assoc!": "cljAssocB"
|
|
# ---- Agent operations ----
|
|
of "agent": "cljAgent"
|
|
of "send": "cljAgentSend"
|
|
of "await": "cljAgentAwait"
|
|
of "agent-error": "cljAgentError"
|
|
of "shutdown-agents": "cljAgentShutdown"
|
|
# ---- Channel operations (core.async) ----
|
|
of "chan": "cljChan"
|
|
of "close!": "cljChanClose"
|
|
else: ""
|
|
|
|
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|
let sp = indentStr(indent)
|
|
let head = items[0]
|
|
if head.kind != ckSymbol:
|
|
raise newException(EmitterError, "List head must be a symbol, got " & $head.kind)
|
|
let op = resolveNsAlias(head.symName)
|
|
|
|
# ---- Special forms (language constructs) ----
|
|
case op
|
|
of "ns":
|
|
# Namespace declaration: (ns my.app (:require [other.lib :as lib]))
|
|
if items.len < 2:
|
|
raise newException(EmitterError, "ns requires a namespace name")
|
|
let nsName = items[1]
|
|
if nsName.kind != ckSymbol:
|
|
raise newException(EmitterError, "ns name must be a symbol")
|
|
var lines: seq[string] = @[]
|
|
lines.add(sp & "# Namespace: " & nsName.symName)
|
|
# Process require clauses
|
|
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")))
|
|
if isRequire:
|
|
for ci in 1..<clause.items.len:
|
|
let req = clause.items[ci]
|
|
if req.kind == ckVector and req.items.len >= 1:
|
|
let libName = req.items[0]
|
|
if libName.kind == ckSymbol:
|
|
var alias = libName.symName
|
|
# Check for :as
|
|
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
|
|
# 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)
|
|
return lines.join("\n")
|
|
|
|
of "defmacro":
|
|
# Register user-defined macro
|
|
if items.len < 4:
|
|
raise newException(EmitterError, "defmacro requires name, params, and body")
|
|
let name = items[1]
|
|
if name.kind != ckSymbol:
|
|
raise newException(EmitterError, "defmacro name must be a symbol")
|
|
let macroName = name.symName
|
|
let macroParams = items[2]
|
|
let macroBody = items[3..^1]
|
|
defineMacro(macroName, proc(args: seq[CljVal]): CljVal =
|
|
# Build env: param name -> arg value
|
|
var env: seq[(string, CljVal)] = @[]
|
|
if macroParams.kind == ckVector:
|
|
for i in 0..<macroParams.items.len:
|
|
if macroParams.items[i].kind == ckSymbol:
|
|
let pName = macroParams.items[i].symName
|
|
if pName == "&":
|
|
if i + 1 < macroParams.items.len:
|
|
let restName = macroParams.items[i+1].symName
|
|
env.add((restName, cljList(args[i..^1])))
|
|
break
|
|
elif i < args.len:
|
|
env.add((pName, args[i]))
|
|
# Mini-evaluator for macro body
|
|
proc eval(form: CljVal): CljVal =
|
|
if form.isNil: return cljNil()
|
|
case form.kind
|
|
of ckSymbol:
|
|
for (n, v) in env:
|
|
if form.symName == n: return v
|
|
return form
|
|
of ckKeyword: return form
|
|
of ckInt: return form
|
|
of ckFloat: return form
|
|
of ckString: return form
|
|
of ckBool: return form
|
|
of ckNil: return form
|
|
of ckVector:
|
|
var newItems: seq[CljVal] = @[]
|
|
for item in form.items:
|
|
newItems.add(eval(item))
|
|
return cljVector(newItems)
|
|
of ckList:
|
|
if form.items.len == 0: return cljList(@[])
|
|
let head = form.items[0]
|
|
if head.kind == ckSymbol:
|
|
let hName = head.symName
|
|
# quote
|
|
if hName == "quote" and form.items.len == 2:
|
|
return form.items[1]
|
|
# list
|
|
if hName == "list":
|
|
var evaluated: seq[CljVal] = @[]
|
|
for i in 1..<form.items.len:
|
|
evaluated.add(eval(form.items[i]))
|
|
return cljList(evaluated)
|
|
# cons
|
|
if hName == "cons" and form.items.len == 3:
|
|
let fst = eval(form.items[1])
|
|
let rst = eval(form.items[2])
|
|
var res = @[fst]
|
|
if rst.kind == ckList:
|
|
res.add(rst.items)
|
|
return cljList(res)
|
|
# concat
|
|
if hName == "concat":
|
|
var res: seq[CljVal] = @[]
|
|
for i in 1..<form.items.len:
|
|
let v = eval(form.items[i])
|
|
if v.kind == ckList:
|
|
res.add(v.items)
|
|
return cljList(res)
|
|
# vec
|
|
if hName == "vec" and form.items.len == 2:
|
|
let v = eval(form.items[1])
|
|
if v.kind == ckList: return cljVector(v.items)
|
|
return v
|
|
# conj
|
|
if hName == "conj" and form.items.len == 3:
|
|
let coll = eval(form.items[2])
|
|
let item = eval(form.items[1])
|
|
if coll.kind == ckList:
|
|
var newItems = @[item]
|
|
newItems.add(coll.items)
|
|
return cljList(newItems)
|
|
return coll
|
|
# str
|
|
if hName == "str":
|
|
var s = ""
|
|
for i in 1..<form.items.len:
|
|
let v = eval(form.items[i])
|
|
case v.kind
|
|
of ckString: s.add(v.strVal)
|
|
else: s.add($v)
|
|
return cljString(s)
|
|
# Apply: evaluate all items, first must be fn
|
|
var evalItems: seq[CljVal] = @[]
|
|
for item in form.items:
|
|
evalItems.add(eval(item))
|
|
return cljList(evalItems)
|
|
# Non-symbol head: evaluate all
|
|
var evalItems: seq[CljVal] = @[]
|
|
for item in form.items:
|
|
evalItems.add(eval(item))
|
|
return cljList(evalItems)
|
|
else: return form
|
|
# Evaluate body
|
|
if macroBody.len == 1:
|
|
return eval(macroBody[0])
|
|
var resultItems: seq[CljVal] = @[cljSymbol("do")]
|
|
for b in macroBody:
|
|
resultItems.add(eval(b))
|
|
cljList(resultItems)
|
|
)
|
|
return sp & "discard cljNil()"
|
|
|
|
of "def":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "def requires exactly 2 arguments")
|
|
let name = items[1]
|
|
if name.kind != ckSymbol:
|
|
raise newException(EmitterError, "def name must be a symbol")
|
|
return sp & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], 0)
|
|
|
|
of "defn":
|
|
if items.len < 4:
|
|
raise newException(EmitterError, "defn requires name, params, and body")
|
|
let name = items[1]
|
|
if name.kind != ckSymbol:
|
|
raise newException(EmitterError, "defn name must be a symbol")
|
|
let params = items[2]
|
|
if params.kind != ckVector:
|
|
raise newException(EmitterError, "defn params must be a vector")
|
|
var paramNames: seq[string] = @[]
|
|
for p in params.items:
|
|
if p.kind != ckSymbol:
|
|
raise newException(EmitterError, "defn params must be symbols")
|
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
|
let body = items[3..^1]
|
|
var bodyCode = ""
|
|
if body.len == 1:
|
|
bodyCode = emitExpr(body[0], indent + 1)
|
|
else:
|
|
bodyCode = emitBlock(body, indent + 1)
|
|
# In a proc body, replace "discard " with "result = " so that
|
|
# if/when branches return their last expression properly.
|
|
bodyCode = bodyCode.replace("discard ", "result = ")
|
|
let procName = mangleName(name.symName)
|
|
let exportMarker = if emitLibMode: "*" else: ""
|
|
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
|
|
|
of "defn-":
|
|
if items.len < 4:
|
|
raise newException(EmitterError, "defn- requires name, params, and body")
|
|
let name = items[1]
|
|
if name.kind != ckSymbol:
|
|
raise newException(EmitterError, "defn- name must be a symbol")
|
|
let params = items[2]
|
|
if params.kind != ckVector:
|
|
raise newException(EmitterError, "defn- params must be a vector")
|
|
var paramNames: seq[string] = @[]
|
|
for p in params.items:
|
|
if p.kind != ckSymbol:
|
|
raise newException(EmitterError, "defn- params must be symbols")
|
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
|
let body = items[3..^1]
|
|
var bodyCode = ""
|
|
if body.len == 1:
|
|
bodyCode = emitExpr(body[0], indent + 1)
|
|
else:
|
|
bodyCode = emitBlock(body, indent + 1)
|
|
let procName = mangleName(name.symName)
|
|
return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode
|
|
|
|
of "fn":
|
|
if items.len < 3:
|
|
raise newException(EmitterError, "fn requires params and body")
|
|
let params = items[1]
|
|
if params.kind != ckVector:
|
|
raise newException(EmitterError, "fn params must be a vector")
|
|
var paramNames: seq[string] = @[]
|
|
for p in params.items:
|
|
if p.kind != ckSymbol:
|
|
raise newException(EmitterError, "fn params must be symbols")
|
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
|
let body = items[2..^1]
|
|
var bodyCode = ""
|
|
if body.len == 1:
|
|
bodyCode = emitExpr(body[0], indent + 1)
|
|
else:
|
|
bodyCode = emitBlock(body, indent + 1)
|
|
return sp & "(proc(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & ")"
|
|
|
|
of "let":
|
|
if items.len < 3:
|
|
raise newException(EmitterError, "let requires bindings and body")
|
|
let bindings = items[1]
|
|
if bindings.kind != ckVector:
|
|
raise newException(EmitterError, "let bindings must be a vector")
|
|
if bindings.items.len mod 2 != 0:
|
|
raise newException(EmitterError, "let bindings must have even number of elements")
|
|
var lines: seq[string] = @[]
|
|
lines.add(sp & "block:")
|
|
var bi = 0
|
|
while bi < bindings.items.len:
|
|
let bname = bindings.items[bi]
|
|
let bval = bindings.items[bi+1]
|
|
if bname.kind != ckSymbol:
|
|
raise newException(EmitterError, "let binding name must be a symbol")
|
|
lines.add(indentStr(indent + 1) & "let " & mangleName(bname.symName) & " = " & emitExpr(bval, 0))
|
|
bi += 2
|
|
let body = items[2..^1]
|
|
for j, b in body:
|
|
let bcode = emitExpr(b, indent + 1)
|
|
if j == body.len - 1:
|
|
lines.add(bcode)
|
|
else:
|
|
let stripped = bcode.strip()
|
|
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
|
stripped.startsWith("block:") or stripped.startsWith("if ") or
|
|
stripped.startsWith("try:"):
|
|
lines.add(bcode)
|
|
else:
|
|
lines.add(indentStr(indent + 1) & "discard " & stripped)
|
|
return lines.join("\n")
|
|
|
|
of "if":
|
|
if items.len < 3 or items.len > 4:
|
|
raise newException(EmitterError, "if requires condition, then, and optional else")
|
|
let condCode = emitExpr(items[1], 0)
|
|
let thenCode = emitExpr(items[2], indent + 1)
|
|
let thenStripped = thenCode.strip()
|
|
var thenFinal = thenCode
|
|
if not (thenStripped.startsWith("echo ") or thenStripped.startsWith("discard ") or
|
|
thenStripped.startsWith("block:") or thenStripped.startsWith("if ") or
|
|
thenStripped.startsWith("try:") or thenStripped.startsWith("var ") or
|
|
thenStripped.startsWith("let ") or thenStripped.startsWith("proc ") or
|
|
thenStripped.contains("\n") or thenStripped.contains(" = ")):
|
|
thenFinal = indentStr(indent + 1) & "discard " & thenStripped
|
|
var ifBlock = sp & "if cljIsTruthy(" & condCode & "):\n" & thenFinal
|
|
if items.len == 4:
|
|
let elseCode = emitExpr(items[3], indent + 1)
|
|
let elseStripped = elseCode.strip()
|
|
var elseFinal = elseCode
|
|
if not (elseStripped.startsWith("echo ") or elseStripped.startsWith("discard ") or
|
|
elseStripped.startsWith("block:") or elseStripped.startsWith("if ") or
|
|
elseStripped.startsWith("try:") or elseStripped.startsWith("var ") or
|
|
elseStripped.startsWith("let ") or elseStripped.startsWith("proc ") or
|
|
elseStripped.contains("\n") or elseStripped.contains(" = ")):
|
|
elseFinal = indentStr(indent + 1) & "discard " & elseStripped
|
|
ifBlock.add("\n" & sp & "else:\n" & elseFinal)
|
|
return ifBlock
|
|
|
|
of "when":
|
|
if items.len < 3:
|
|
raise newException(EmitterError, "when requires condition and body")
|
|
let condCode = emitExpr(items[1], 0)
|
|
var lines: seq[string] = @[]
|
|
lines.add(sp & "if cljIsTruthy(" & condCode & "):")
|
|
let body = items[2..^1]
|
|
for b in body:
|
|
lines.add(emitExpr(b, indent + 1))
|
|
return lines.join("\n")
|
|
|
|
of "when-let":
|
|
if items.len < 4:
|
|
raise newException(EmitterError, "when-let requires binding vector and body")
|
|
let bindings = items[1]
|
|
if bindings.kind != ckVector or bindings.items.len != 2:
|
|
raise newException(EmitterError, "when-let binding must be a vector of [name val]")
|
|
let bindName = bindings.items[0]
|
|
let bindVal = bindings.items[1]
|
|
if bindName.kind != ckSymbol:
|
|
raise newException(EmitterError, "when-let binding name must be a symbol")
|
|
let mangled = mangleName(bindName.symName)
|
|
var lines: seq[string] = @[]
|
|
lines.add(sp & "block:")
|
|
lines.add(indentStr(indent + 1) & "let " & mangled & " = " & emitExpr(bindVal, 0))
|
|
lines.add(indentStr(indent + 1) & "if not cljIsNil(" & mangled & "):")
|
|
let body = items[2..^1]
|
|
for b in body:
|
|
lines.add(emitExpr(b, indent + 2))
|
|
return lines.join("\n")
|
|
|
|
of "if-let":
|
|
if items.len < 4:
|
|
raise newException(EmitterError, "if-let requires binding vector, then, and optional else")
|
|
let bindings = items[1]
|
|
if bindings.kind != ckVector or bindings.items.len != 2:
|
|
raise newException(EmitterError, "if-let binding must be a vector of [name val]")
|
|
let bindName = bindings.items[0]
|
|
let bindVal = bindings.items[1]
|
|
if bindName.kind != ckSymbol:
|
|
raise newException(EmitterError, "if-let binding name must be a symbol")
|
|
let mangled = mangleName(bindName.symName)
|
|
var lines: seq[string] = @[]
|
|
lines.add(sp & "block:")
|
|
lines.add(indentStr(indent + 1) & "let " & mangled & " = " & emitExpr(bindVal, 0))
|
|
lines.add(indentStr(indent + 1) & "if not cljIsNil(" & mangled & "):")
|
|
lines.add(emitExpr(items[2], indent + 2))
|
|
if items.len == 4:
|
|
lines.add(indentStr(indent + 1) & "else:")
|
|
lines.add(emitExpr(items[3], indent + 2))
|
|
return lines.join("\n")
|
|
|
|
of "cond":
|
|
if items.len < 3 or items.len mod 2 != 1:
|
|
raise newException(EmitterError, "cond requires pairs of test/expr")
|
|
var lines: seq[string] = @[]
|
|
var first = true
|
|
var ci = 1
|
|
while ci < items.len:
|
|
let testExpr = items[ci]
|
|
let thenExpr = items[ci+1]
|
|
if (testExpr.kind == ckSymbol and testExpr.symName == ":else") or
|
|
(testExpr.kind == ckKeyword and testExpr.kwName == "else"):
|
|
lines.add(sp & "else:")
|
|
lines.add(emitExpr(thenExpr, indent + 1))
|
|
elif first:
|
|
lines.add(sp & "if cljIsTruthy(" & emitExpr(testExpr, 0) & "):")
|
|
lines.add(emitExpr(thenExpr, indent + 1))
|
|
first = false
|
|
else:
|
|
lines.add(sp & "elif cljIsTruthy(" & emitExpr(testExpr, 0) & "):")
|
|
lines.add(emitExpr(thenExpr, indent + 1))
|
|
ci += 2
|
|
return lines.join("\n")
|
|
|
|
of "loop":
|
|
if items.len < 3:
|
|
raise newException(EmitterError, "loop requires bindings and body")
|
|
let bindings = items[1]
|
|
if bindings.kind != ckVector:
|
|
raise newException(EmitterError, "loop bindings must be a vector")
|
|
if bindings.items.len mod 2 != 0:
|
|
raise newException(EmitterError, "loop bindings must have even number of elements")
|
|
var loopParams: seq[(string, string)] = @[]
|
|
var li = 0
|
|
while li < bindings.items.len:
|
|
let lname = bindings.items[li]
|
|
let lval = bindings.items[li+1]
|
|
if lname.kind != ckSymbol:
|
|
raise newException(EmitterError, "loop binding name must be a symbol")
|
|
loopParams.add((mangleName(lname.symName), emitExpr(lval, 0)))
|
|
li += 2
|
|
var loopVars: seq[string] = @[]
|
|
var lines: seq[string] = @[]
|
|
for (lpName, lpVal) in loopParams:
|
|
lines.add(sp & "var " & lpName & ": CljVal = " & lpVal)
|
|
loopVars.add(lpName)
|
|
loopStack.add(loopVars)
|
|
lines.add(sp & "while true:")
|
|
let body = items[2..^1]
|
|
for b in body:
|
|
lines.add(emitExpr(b, indent + 1))
|
|
lines.add(indentStr(indent + 1) & "break")
|
|
discard loopStack.pop()
|
|
return lines.join("\n")
|
|
|
|
of "recur":
|
|
if items.len < 2:
|
|
raise newException(EmitterError, "recur requires arguments")
|
|
if loopStack.len == 0:
|
|
raise newException(EmitterError, "recur outside of loop")
|
|
let loopVars = loopStack[^1]
|
|
if items.len - 1 != loopVars.len:
|
|
raise newException(EmitterError, "recur requires " & $loopVars.len & " arguments, got " & $(items.len - 1))
|
|
var lines: seq[string] = @[]
|
|
for ri in 1..<items.len:
|
|
lines.add(sp & loopVars[ri-1] & " = " & emitExpr(items[ri], 0))
|
|
lines.add(sp & "continue")
|
|
return lines.join("\n")
|
|
|
|
of "do":
|
|
if items.len < 2:
|
|
return sp & "discard cljNil()"
|
|
return emitBlock(items[1..^1], indent)
|
|
|
|
of "try":
|
|
if items.len < 2:
|
|
raise newException(EmitterError, "try requires body")
|
|
var bodyForms: seq[CljVal] = @[]
|
|
var catchClauses: seq[(string, string, seq[CljVal])] = @[]
|
|
var finallyBody: seq[CljVal] = @[]
|
|
var ti = 1
|
|
while ti < items.len:
|
|
let form = items[ti]
|
|
if form.kind == ckList and form.items.len > 0 and form.items[0].kind == ckSymbol:
|
|
if form.items[0].symName == "catch":
|
|
if form.items.len < 3:
|
|
raise newException(EmitterError, "catch requires exception type, name, and body")
|
|
let exType = form.items[1]
|
|
var exTypeStr = "CatchableError"
|
|
if exType.kind == ckSymbol:
|
|
exTypeStr = exType.symName
|
|
let exName = form.items[2]
|
|
if exName.kind != ckSymbol:
|
|
raise newException(EmitterError, "catch name must be a symbol")
|
|
catchClauses.add((exTypeStr, mangleName(exName.symName), form.items[3..^1]))
|
|
elif form.items[0].symName == "finally":
|
|
finallyBody = form.items[1..^1]
|
|
else:
|
|
bodyForms.add(form)
|
|
else:
|
|
bodyForms.add(form)
|
|
inc ti
|
|
var lines: seq[string] = @[]
|
|
lines.add(sp & "try:")
|
|
for b in bodyForms:
|
|
lines.add(emitExpr(b, indent + 1))
|
|
for (exType, exVar, exBody) in catchClauses:
|
|
lines.add(sp & "except " & exType & ":")
|
|
lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])")
|
|
lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))")
|
|
for eb in exBody:
|
|
lines.add(emitExpr(eb, indent + 1))
|
|
if finallyBody.len > 0:
|
|
lines.add(sp & "finally:")
|
|
for fb in finallyBody:
|
|
lines.add(emitExpr(fb, indent + 1))
|
|
return lines.join("\n")
|
|
|
|
of "throw":
|
|
if items.len != 2:
|
|
raise newException(EmitterError, "throw requires exactly 1 argument")
|
|
return sp & "raise newException(CatchableError, cljStr(" & emitExpr(items[1], 0) & "))"
|
|
|
|
of "quote":
|
|
if items.len != 2:
|
|
raise newException(EmitterError, "quote requires exactly 1 argument")
|
|
let quoted = items[1]
|
|
case quoted.kind
|
|
of ckSymbol:
|
|
return sp & "cljSymbol(\"" & quoted.symName & "\")"
|
|
of ckList:
|
|
var parts: seq[string] = @[]
|
|
for item in quoted.items:
|
|
parts.add(emitExpr(item, 0))
|
|
return sp & "cljList(@[" & parts.join(", ") & "])"
|
|
of ckVector:
|
|
var parts: seq[string] = @[]
|
|
for item in quoted.items:
|
|
parts.add(emitExpr(item, 0))
|
|
return sp & "cljVector(@[" & parts.join(", ") & "])"
|
|
else:
|
|
return emitExpr(quoted, indent)
|
|
|
|
of "map":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "map requires exactly 2 arguments (f, coll)")
|
|
let fnArg = items[1]
|
|
let collArg = emitExpr(items[2], 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)), " & 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 == "fn":
|
|
# Inline fn: (map (fn [x] ...) coll)
|
|
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":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "filter requires exactly 2 arguments")
|
|
let fnArg = items[1]
|
|
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 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("; ")
|
|
# Wrap single-param fn as seq[CljVal] version
|
|
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")"
|
|
return sp & "cljFilter(cljFn(" & fnProc & "), " & collArg & ")"
|
|
else:
|
|
return sp & "cljFilter(" & emitExpr(fnArg, 0) & ", " & collArg & ")"
|
|
|
|
of "reduce":
|
|
if items.len < 3 or items.len > 4:
|
|
raise newException(EmitterError, "reduce requires 2 or 3 arguments")
|
|
if items.len == 3:
|
|
# (reduce f coll) — first element as init
|
|
let fnArg = items[1]
|
|
let collArg = emitExpr(items[2], 0)
|
|
return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", cljNil(), " & collArg & ")"
|
|
else:
|
|
# (reduce f init coll)
|
|
let fnArg = items[1]
|
|
let initArg = emitExpr(items[2], 0)
|
|
let collArg = emitExpr(items[3], 0)
|
|
return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", " & initArg & ", " & collArg & ")"
|
|
|
|
of "mapv":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "mapv requires exactly 2 arguments")
|
|
return sp & "cljMapv(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
|
|
of "apply":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "apply requires exactly 2 arguments")
|
|
return sp & "cljApply(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
|
|
of "some":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "some requires exactly 2 arguments")
|
|
return sp & "cljSome(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
|
|
of "every?":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "every? requires exactly 2 arguments")
|
|
return sp & "cljEvery(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
|
|
of "into":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "into requires exactly 2 arguments")
|
|
return sp & "cljInto(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
|
|
of "comp":
|
|
var parts: seq[string] = @[]
|
|
for i in 1..<items.len:
|
|
parts.add(emitExpr(items[i], 0))
|
|
return sp & "cljComp(@[" & parts.join(", ") & "])"
|
|
|
|
of "partial":
|
|
var parts: seq[string] = @[]
|
|
for i in 1..<items.len:
|
|
parts.add(emitExpr(items[i], 0))
|
|
return sp & "cljPartial(" & parts[0] & ", @[" & parts[1..^1].join(", ") & "])"
|
|
|
|
of "juxt":
|
|
var parts: seq[string] = @[]
|
|
for i in 1..<items.len:
|
|
parts.add(emitExpr(items[i], 0))
|
|
return sp & "cljJuxt(@[" & parts.join(", ") & "])"
|
|
|
|
of "complement":
|
|
if items.len != 2:
|
|
raise newException(EmitterError, "complement requires exactly 1 argument")
|
|
return sp & "cljComplement(" & emitExpr(items[1], 0) & ")"
|
|
|
|
of "constantly":
|
|
if items.len != 2:
|
|
raise newException(EmitterError, "constantly requires exactly 1 argument")
|
|
return sp & "cljConstantly(" & emitExpr(items[1], 0) & ")"
|
|
|
|
of "group-by":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "group-by requires exactly 2 arguments")
|
|
return sp & "cljGroupBy(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
|
|
of "subs":
|
|
if items.len < 3 or items.len > 4:
|
|
raise newException(EmitterError, "subs requires 2 or 3 arguments")
|
|
if items.len == 3:
|
|
return sp & "cljSubs(" & emitExpr(items[1], 0) & ", cljInt(" & emitExpr(items[2], 0) & ").intVal)"
|
|
else:
|
|
return sp & "cljSubsRange(" & emitExpr(items[1], 0) & ", cljInt(" & emitExpr(items[2], 0) & ").intVal, cljInt(" & emitExpr(items[3], 0) & ").intVal)"
|
|
|
|
of "get-in":
|
|
if items.len < 3 or items.len > 4:
|
|
raise newException(EmitterError, "get-in requires 2 or 3 arguments")
|
|
if items.len == 3:
|
|
return sp & "cljGetIn(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
else:
|
|
return sp & "cljGetIn(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & emitExpr(items[3], 0) & ")"
|
|
|
|
of "update":
|
|
if items.len < 4:
|
|
raise newException(EmitterError, "update requires at least 3 arguments")
|
|
var fArg: string
|
|
if items[3].kind == ckSymbol:
|
|
let rn = runtimeName(items[3].symName)
|
|
if rn.len > 0:
|
|
fArg = "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
|
|
else:
|
|
fArg = mangleName(items[3].symName)
|
|
else:
|
|
fArg = emitExpr(items[3], 0)
|
|
var extraArgs: seq[string] = @[]
|
|
for i in 4..<items.len:
|
|
extraArgs.add(emitExpr(items[i], 0))
|
|
if extraArgs.len > 0:
|
|
return sp & "cljUpdate(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & fArg & ", @[" & extraArgs.join(", ") & "])"
|
|
else:
|
|
return sp & "cljUpdate(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & fArg & ")"
|
|
|
|
of "assoc-in":
|
|
if items.len != 4:
|
|
raise newException(EmitterError, "assoc-in requires exactly 3 arguments")
|
|
return sp & "cljAssocIn(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & emitExpr(items[3], 0) & ")"
|
|
|
|
of "and":
|
|
if items.len < 2: return sp & "cljBool(true)"
|
|
if items.len == 2: return emitExpr(items[1], indent)
|
|
# (and a b c) => ternary: if a then (if b then c else false) else false
|
|
# Generate as nested inline if
|
|
let lastExpr = emitExpr(items[^1], 0)
|
|
var r = lastExpr
|
|
for i in countdown(items.len - 2, 1):
|
|
r = "(if cljIsTruthy(" & emitExpr(items[i], 0) & "): " & r & " else: cljBool(false))"
|
|
return sp & r
|
|
|
|
of "or":
|
|
if items.len < 2: return sp & "cljNil()"
|
|
if items.len == 2: return emitExpr(items[1], indent)
|
|
# (or a b c) => ternary: if a then a else (if b then b else c)
|
|
let lastExpr = emitExpr(items[^1], 0)
|
|
var r = lastExpr
|
|
for i in countdown(items.len - 2, 1):
|
|
let argCode = emitExpr(items[i], 0)
|
|
r = "(if cljIsTruthy(" & argCode & "): " & argCode & " else: " & r & ")"
|
|
return sp & r
|
|
|
|
of "nil?":
|
|
if items.len != 2:
|
|
raise newException(EmitterError, "nil? requires exactly 1 argument")
|
|
return sp & "cljBool(cljIsNil(" & emitExpr(items[1], 0) & "))"
|
|
|
|
of "set!":
|
|
if items.len != 3:
|
|
raise newException(EmitterError, "set! requires exactly 2 arguments")
|
|
let target = items[1]
|
|
if target.kind != ckSymbol:
|
|
raise newException(EmitterError, "set! target must be a symbol")
|
|
return sp & mangleName(target.symName) & " = " & emitExpr(items[2], 0)
|
|
|
|
of "set":
|
|
if items.len != 2:
|
|
raise newException(EmitterError, "set requires exactly 1 argument")
|
|
let arg = items[1]
|
|
case arg.kind
|
|
of ckVector:
|
|
var parts: seq[string] = @[]
|
|
for item in arg.items:
|
|
parts.add(emitExpr(item, 0))
|
|
return sp & "cljSet(@[" & parts.join(", ") & "])"
|
|
of ckList:
|
|
var parts: seq[string] = @[]
|
|
for item in arg.items:
|
|
parts.add(emitExpr(item, 0))
|
|
return sp & "cljSet(@[" & parts.join(", ") & "])"
|
|
else:
|
|
return sp & "cljSet(@[" & emitExpr(arg, 0) & "])"
|
|
|
|
of "range":
|
|
if items.len < 1 or items.len > 4:
|
|
raise newException(EmitterError, "range requires 0, 1, 2, or 3 arguments")
|
|
if items.len == 1:
|
|
return sp & "cljList(@[])"
|
|
elif items.len == 2:
|
|
return sp & "cljRange(" & emitExpr(items[1], 0) & ")"
|
|
elif items.len == 3:
|
|
return sp & "cljRange(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
|
else:
|
|
return sp & "cljRange3(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & emitExpr(items[3], 0) & ")"
|
|
|
|
of "iterate":
|
|
if items.len != 4:
|
|
raise newException(EmitterError, "iterate requires 3 arguments (n, f, x)")
|
|
let fnArg = items[2]
|
|
let nArg = emitExpr(items[1], 0)
|
|
let xArg = emitExpr(items[3], 0)
|
|
if fnArg.kind == ckSymbol:
|
|
let rn = runtimeName(fnArg.symName)
|
|
if rn.len > 0:
|
|
return sp & "cljIterate(" & nArg & ", cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args)), " & xArg & ")"
|
|
return sp & "cljIterate(" & nArg & ", cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args[0])), " & xArg & ")"
|
|
else:
|
|
return sp & "cljIterate(" & nArg & ", " & emitExpr(fnArg, 0) & ", " & xArg & ")"
|
|
|
|
else:
|
|
# ---- Nim interop: nim/module/function ----
|
|
if op.startsWith("nim/") or op.startsWith("nim."):
|
|
let parts = op.replace(".", "/").split("/")
|
|
if parts.len >= 3:
|
|
let module = parts[1]
|
|
var funcChain = parts[2..^1].join(".")
|
|
# Strip ? suffix (Clojure convention) — not valid in Nim
|
|
if funcChain.endsWith("?"):
|
|
funcChain = funcChain[0..^2]
|
|
var argParts: seq[string] = @[]
|
|
for i in 1..<items.len:
|
|
argParts.add(emitExpr(items[i], 0))
|
|
# Known Nim module interop patterns
|
|
case module
|
|
of "math":
|
|
requiredImports.incl("math")
|
|
var nimArgs: seq[string] = @[]
|
|
for a in argParts:
|
|
nimArgs.add(a & ".floatVal")
|
|
return sp & "cljFloat(" & funcChain & "(" & nimArgs.join(", ") & "))"
|
|
of "strutils":
|
|
requiredImports.incl("strutils")
|
|
let strFns = ["endsWith", "startsWith", "contains", "find"]
|
|
let strRetFns = ["replace", "strip", "toLower", "toUpper", "join"]
|
|
let strIntFns = ["repeat", "count"]
|
|
if funcChain in strFns:
|
|
if argParts.len >= 1:
|
|
var nimArgs: seq[string] = @[]
|
|
nimArgs.add(argParts[0] & ".strVal")
|
|
for i in 1..<argParts.len:
|
|
nimArgs.add(argParts[i] & ".strVal")
|
|
return sp & "cljBool(" & funcChain & "(" & nimArgs.join(", ") & "))"
|
|
elif funcChain in strRetFns:
|
|
if argParts.len >= 1:
|
|
var nimArgs: seq[string] = @[]
|
|
nimArgs.add(argParts[0] & ".strVal")
|
|
for i in 1..<argParts.len:
|
|
nimArgs.add(argParts[i] & ".strVal")
|
|
return sp & "cljString(" & funcChain & "(" & nimArgs.join(", ") & "))"
|
|
elif funcChain in strIntFns:
|
|
# repeat(s, n), count(s, sub) — first arg string, rest int
|
|
if argParts.len >= 2:
|
|
var nimArgs: seq[string] = @[]
|
|
nimArgs.add(argParts[0] & ".strVal")
|
|
for i in 1..<argParts.len:
|
|
nimArgs.add(argParts[i] & ".intVal")
|
|
return sp & "cljString(" & funcChain & "(" & nimArgs.join(", ") & "))"
|
|
elif funcChain == "split":
|
|
if argParts.len >= 2:
|
|
return sp & "cljList(" & funcChain & "(" & argParts[0] & ".strVal, " & argParts[1] & ".strVal).mapIt(cljString(it)))"
|
|
return sp & funcChain & "(" & argParts.join(", ") & ")"
|
|
of "times":
|
|
requiredImports.incl("times")
|
|
return sp & funcChain & "(" & argParts.join(", ") & ")"
|
|
of "os":
|
|
requiredImports.incl("os")
|
|
return sp & "cljString(" & funcChain & "(" & argParts.join(", ") & "))"
|
|
of "system":
|
|
return sp & funcChain & "(" & argParts.join(", ") & ")"
|
|
else:
|
|
requiredImports.incl(module)
|
|
return sp & funcChain & "(" & argParts.join(", ") & ")"
|
|
elif parts.len == 2:
|
|
return sp & "cljString(\"" & parts[1] & "\")"
|
|
|
|
# ---- C FFI import: (c/import "header" :fn1 :fn2) ----
|
|
if op == "c/import" or op == "c-ffi/import":
|
|
if items.len < 2:
|
|
raise newException(EmitterError, "c/import requires at least a header path")
|
|
let header = items[1]
|
|
if header.kind != ckString:
|
|
raise newException(EmitterError, "c/import header must be a string")
|
|
let headerPath = header.strVal
|
|
var ffiProcs: seq[string] = @[]
|
|
for i in 2..<items.len:
|
|
if items[i].kind == ckKeyword:
|
|
let fnName = items[i].kwName
|
|
ffiProcs.add("proc " & fnName & "*(): clong {.importc, header: \"" & headerPath & "\".}")
|
|
elif items[i].kind == ckSymbol:
|
|
let fnName = items[i].symName
|
|
ffiProcs.add("proc " & fnName & "*(): clong {.importc, header: \"" & headerPath & "\".}")
|
|
return ffiProcs.join("\n" & sp)
|
|
|
|
# ---- Known runtime functions ----
|
|
let rn = runtimeName(op)
|
|
if rn.len > 0:
|
|
var argParts: seq[string] = @[]
|
|
for i in 1..<items.len:
|
|
argParts.add(emitExpr(items[i], 0))
|
|
# Variadic functions take seq[CljVal]
|
|
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
|
|
"println", "prn", "print", "str", "pr-str",
|
|
"concat", "min", "max", "merge", "interleave"]
|
|
var call: string
|
|
if variadic and argParts.len > 0:
|
|
call = rn & "(@[" & argParts.join(", ") & "])"
|
|
else:
|
|
call = rn & "(" & argParts.join(", ") & ")"
|
|
if op in ["println", "prn", "print"]:
|
|
return sp & "discard " & call
|
|
return sp & call
|
|
|
|
# ---- User-defined function call ----
|
|
var args: seq[string] = @[]
|
|
for i in 1..<items.len:
|
|
args.add(emitExpr(items[i], 0))
|
|
return sp & mangleName(op) & "(" & args.join(", ") & ")"
|
|
|
|
proc emitExpr*(v: CljVal, indent: int = 0): string =
|
|
let sp = indentStr(indent)
|
|
case v.kind
|
|
of ckNil:
|
|
return sp & "cljNil()"
|
|
of ckBool:
|
|
return sp & "cljBool(" & $v.boolVal & ")"
|
|
of ckInt:
|
|
return sp & "cljInt(" & $v.intVal & ")"
|
|
of ckFloat:
|
|
return sp & "cljFloat(" & $v.floatVal & ")"
|
|
of ckString:
|
|
return sp & "cljString(\"" & v.strVal.replace("\"", "\\\"") & "\")"
|
|
of ckKeyword:
|
|
return sp & "cljKeyword(\"" & v.kwName & "\")"
|
|
of ckSymbol:
|
|
let symName = resolveNsAlias(v.symName)
|
|
let rn = runtimeName(symName)
|
|
if rn.len > 0:
|
|
# Known function symbol — emit as runtime fn reference
|
|
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
|
|
return sp & mangleName(symName)
|
|
of ckList:
|
|
if v.items.len == 0:
|
|
return sp & "cljList(@[])"
|
|
# Macro-expand before emitting
|
|
let expanded = macroexpand(v)
|
|
if expanded.kind != ckList or expanded == v:
|
|
return emitSpecialForm(v.items, indent)
|
|
return emitExpr(expanded, indent)
|
|
of ckVector:
|
|
var parts: seq[string] = @[]
|
|
for item in v.items:
|
|
parts.add(emitExpr(item, 0))
|
|
return sp & "cljVector(@[" & parts.join(", ") & "])"
|
|
of ckMap:
|
|
if v.mapKeys.len == 0:
|
|
return sp & "cljMap(@[], @[])"
|
|
var keyParts: seq[string] = @[]
|
|
var valParts: seq[string] = @[]
|
|
for i in 0..<v.mapKeys.len:
|
|
keyParts.add(emitExpr(v.mapKeys[i], 0))
|
|
valParts.add(emitExpr(v.mapVals[i], 0))
|
|
return sp & "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])"
|
|
of ckSet:
|
|
var parts: seq[string] = @[]
|
|
for item in v.setItems:
|
|
parts.add(emitExpr(item, 0))
|
|
return sp & "cljSet(@[" & parts.join(", ") & "])"
|
|
of ckFn:
|
|
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = discard cljNil())"
|
|
of ckAtom:
|
|
return sp & "cljAtom(" & emitExpr(v.atomVal, 0) & ")"
|
|
of ckTransient:
|
|
return sp & "cljTransient(cljNil())"
|
|
of ckAgent:
|
|
return sp & "cljAgent(" & emitExpr(v.agentVal, 0) & ")"
|
|
|
|
proc emitBlock(items: seq[CljVal], indent: int): string =
|
|
if items.len == 0:
|
|
return indentStr(indent) & "discard cljNil()"
|
|
var lines: seq[string] = @[]
|
|
for item in items:
|
|
lines.add(emitExpr(item, indent))
|
|
return lines.join("\n")
|
|
|
|
proc emitProgramInternal(forms: seq[CljVal]): string =
|
|
requiredImports = initHashSet[string]()
|
|
var headerLines: seq[string] = @[
|
|
"# Generated by Clojure/Nim",
|
|
"import cljnim_runtime",
|
|
]
|
|
|
|
var defs: seq[string] = @[]
|
|
var mainForms: seq[string] = @[]
|
|
|
|
for i, form in forms:
|
|
let isDef = form.kind == ckList and form.items.len > 0 and
|
|
form.items[0].kind == ckSymbol and
|
|
form.items[0].symName in ["def", "defn", "defn-"]
|
|
if isDef:
|
|
defs.add(emitExpr(form, 0))
|
|
else:
|
|
let code = emitExpr(form, 2)
|
|
let stripped = code.strip()
|
|
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
|
stripped.startsWith("block:") or stripped.startsWith("if ") or
|
|
stripped.startsWith("try:") or stripped.startsWith("var "):
|
|
mainForms.add(code)
|
|
elif i == forms.len - 1:
|
|
mainForms.add(indentStr(2) & "echo cljRepr(" & stripped & ")")
|
|
else:
|
|
mainForms.add(indentStr(2) & "discard cljRepr(" & stripped & ")")
|
|
|
|
var lines = headerLines
|
|
# Add collected Nim imports
|
|
for imp in requiredImports:
|
|
lines.add("import " & imp)
|
|
lines.add("")
|
|
lines.add(defs)
|
|
|
|
if mainForms.len > 0:
|
|
lines.add("")
|
|
lines.add("when isMainModule:")
|
|
for form in mainForms:
|
|
lines.add(form)
|
|
|
|
return lines.join("\n") & "\n"
|
|
|
|
proc emitProgram*(forms: seq[CljVal]): string =
|
|
emitLibMode = false
|
|
emitProgramInternal(forms)
|
|
|
|
proc emitProgramLib*(forms: seq[CljVal]): string =
|
|
emitLibMode = true
|
|
emitProgramInternal(forms)
|