Initial commit

This commit is contained in:
2026-05-08 14:51:47 +03:00
commit f89b381fdb
12 changed files with 1085 additions and 0 deletions
+272
View File
@@ -0,0 +1,272 @@
# Clojure → Nim Emitter
import strutils, sequtils
import types
type
EmitterError* = object of CatchableError
proc mangleName*(name: string): string =
## Convert Clojure symbol to valid Nim identifier
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("_DIV")
of '=': result.add("_EQ")
of '>': result.add("_GT")
of '<': result.add("_LT")
of '.': result.add('_')
of '\'': result.add("_QUOTE")
else: result.add(c)
proc emitExpr*(v: CljVal, indent: int = 0): string
proc emitBlock(items: seq[CljVal], indent: int): string =
if items.len == 0:
return "discard"
var lines: seq[string] = @[]
for i, item in items:
lines.add(emitExpr(item, indent))
return lines.join("\n")
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let spaces = " ".repeat(indent)
let head = items[0]
if head.kind != ckSymbol:
raise newException(EmitterError, "List head must be a symbol, got " & $head.kind)
let op = head.symName
case op
of "+", "-", "*", "/", "=", ">", "<":
if items.len < 3:
raise newException(EmitterError, op & " requires at least 2 arguments")
var parts: seq[string] = @[]
let nimOp = case op
of "=": "=="
else: op
for i in 1..<items.len:
parts.add(emitExpr(items[i], indent))
return spaces & parts.join(" " & nimOp & " ")
of "println":
if items.len < 2:
raise newException(EmitterError, "println requires at least 1 argument")
var parts: seq[string] = @[]
for i in 1..<items.len:
parts.add(emitExpr(items[i], indent))
return spaces & "echo " & parts.join(", ")
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 spaces & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], indent)
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) & ": auto")
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)
var typedParams: seq[string] = @[]
for p in params.items:
typedParams.add(mangleName(p.symName) & ": int")
if typedParams.len > 0:
return spaces & "proc " & procName & "(" & typedParams.join(", ") & "): int =\n" & bodyCode
else:
return spaces & "proc " & procName & "(): int =\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) & ": auto")
let body = items[2..^1]
var bodyCode = ""
if body.len == 1:
bodyCode = emitExpr(body[0], indent + 1)
else:
bodyCode = emitBlock(body, indent + 1)
var typedParams: seq[string] = @[]
for p in params.items:
typedParams.add(mangleName(p.symName) & ": int")
if typedParams.len > 0:
return spaces & "proc(" & typedParams.join(", ") & "): int =\n" & bodyCode
else:
return spaces & "proc(): int =\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("block:")
var bindLines: seq[string] = @[]
var i = 0
while i < bindings.items.len:
let name = bindings.items[i]
let val = bindings.items[i+1]
if name.kind != ckSymbol:
raise newException(EmitterError, "let binding name must be a symbol")
bindLines.add(mangleName(name.symName) & " = " & emitExpr(val, 0).strip())
i += 2
lines.add(" ".repeat(indent + 1) & "let")
for bl in bindLines:
lines.add(" ".repeat(indent + 2) & bl)
let body = items[2..^1]
for j, b in body:
let bcode = emitExpr(b, indent + 1).strip()
if j == body.len - 1:
lines.add(" ".repeat(indent + 1) & bcode)
else:
if bcode.startsWith("echo ") or bcode.startsWith("discard "):
lines.add(" ".repeat(indent + 1) & bcode)
else:
lines.add(" ".repeat(indent + 1) & "discard " & bcode)
return spaces & 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], indent)
let thenCode = emitExpr(items[2], indent + 1)
var result = spaces & "if " & condCode & ":\n" & " ".repeat(indent + 1) & thenCode
if items.len == 4:
let elseCode = emitExpr(items[3], indent + 1)
result.add("\n" & spaces & "else:\n" & " ".repeat(indent + 1) & elseCode)
return result
of "do":
if items.len < 2:
return spaces & "discard"
return emitBlock(items[1..^1], indent)
of "quote":
if items.len != 2:
raise newException(EmitterError, "quote requires exactly 1 argument")
let quoted = items[1]
case quoted.kind
of ckSymbol:
return spaces & "cljSymbol(\"" & quoted.symName & "\")"
of ckList:
var parts: seq[string] = @[]
for item in quoted.items:
parts.add(emitExpr(item, indent))
return spaces & "cljList(@[" & parts.join(", ") & "])"
of ckVector:
var parts: seq[string] = @[]
for item in quoted.items:
parts.add(emitExpr(item, indent))
return spaces & "cljVector(@[" & parts.join(", ") & "])"
else:
return emitExpr(quoted, indent)
else:
# Function call
var args: seq[string] = @[]
for i in 1..<items.len:
args.add(emitExpr(items[i], indent))
return spaces & mangleName(op) & "(" & args.join(", ") & ")"
proc emitExpr*(v: CljVal, indent: int = 0): string =
let spaces = " ".repeat(indent)
case v.kind
of ckNil:
return spaces & "nil"
of ckBool:
return spaces & $v.boolVal
of ckInt:
return spaces & $v.intVal
of ckFloat:
return spaces & $v.floatVal
of ckString:
return spaces & "\"" & v.strVal & "\""
of ckKeyword:
return spaces & "cljKeyword(\"" & v.kwName & "\")"
of ckSymbol:
return spaces & mangleName(v.symName)
of ckList:
if v.items.len == 0:
return spaces & "cljList(@[])"
return emitSpecialForm(v.items, indent)
of ckVector:
var parts: seq[string] = @[]
for item in v.items:
parts.add(emitExpr(item, indent))
return spaces & "@[" & parts.join(", ") & "]"
of ckMap:
raise newException(EmitterError, "Maps not yet supported in emitter")
proc emitProgram*(forms: seq[CljVal]): string =
var headerLines: seq[string] = @[
"# Generated by Clojure/Nim",
"import strutils, sequtils",
"",
"# Runtime helpers",
"proc cljSymbol(name: string): auto = name",
"proc cljKeyword(name: string): auto = \":\" & name",
"proc cljList(items: seq[auto]): auto = items",
"proc cljVector(items: seq[auto]): auto = items",
"",
]
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"]
if isDef:
defs.add(emitExpr(form, 0))
else:
let code = emitExpr(form, 2).strip()
if code.startsWith("echo ") or code.startsWith("discard ") or code.startsWith("block:"):
mainForms.add(code)
elif i == forms.len - 1:
# Last form - echo its result
mainForms.add("echo " & code)
else:
mainForms.add("discard " & code)
var lines = headerLines
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"