# 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..': 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.. 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..= 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.. 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.. 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.. 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.. 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..= 1: var nimArgs: seq[string] = @[] nimArgs.add(argParts[0] & ".strVal") for i in 1..= 1: var nimArgs: seq[string] = @[] nimArgs.add(argParts[0] & ".strVal") for i in 1..= 2: var nimArgs: seq[string] = @[] nimArgs.add(argParts[0] & ".strVal") for i in 1..= 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.. 0: var argParts: seq[string] = @[] for i in 1..", "<", ">=", "<=", "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.. 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.. 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)