Phase 1-4: Reader, Runtime, Macros, Nim Interop — 543x faster than JVM Clojure

- Reader: maps, sets, syntax-quote, dispatch macros, metadata
- Runtime: CljVal type system, 80+ core functions (collections, strings, math, IO)
- Emitter: full CljVal-based code generation, macro expansion, inline fns
- Macros: defmacro with compile-time evaluator, ->, ->>, and, or, when, cond, for, doseq
- Nim Interop: nim/module/function syntax, auto-import, type mapping
- CLI: cljnim -e '<code>' for quick evaluation
- Tests: 60 unit tests (reader + emitter)
- Benchmarks: AOT suite showing 543x startup, 679x factorial vs JVM Clojure
- CI: GitLab CI pipeline
- Runtime library: lib/cljnim_runtime.nim (1070+ lines)
This commit is contained in:
2026-05-08 16:34:39 +03:00
parent f89b381fdb
commit 87d6028487
24 changed files with 4244 additions and 275 deletions
+56 -8
View File
@@ -1,6 +1,18 @@
# Clojure/Nim Compiler
import os, strutils, osproc
import reader, emitter, types
import os, osproc
import reader, emitter, types, macros
proc getLibPath(): string =
let appDir = getAppDir()
# Try relative to binary location
let candidate1 = appDir / "lib"
let candidate2 = appDir.parentDir / "lib"
let candidate3 = getCurrentDir() / "lib"
if dirExists(candidate1): return candidate1
if dirExists(candidate2): return candidate2
if dirExists(candidate3): return candidate3
# Fallback: assume lib is in current dir
return "lib"
proc compileFile*(inputPath: string, outputPath: string) =
let source = readFile(inputPath)
@@ -14,6 +26,16 @@ proc compileFile*(inputPath: string, outputPath: string) =
writeFile(outputPath, nimCode)
echo "Generated: ", outputPath
proc nimCompile(nimPath: string, binPath: string, release: bool = false): int =
let libPath = getLibPath()
var cmd = "nim c"
if release:
cmd.add(" -d:release")
cmd.add(" --path:" & quoteShell(libPath))
cmd.add(" -o:" & quoteShell(binPath) & " " & quoteShell(nimPath))
echo "Compiling: ", cmd
execCmd(cmd)
proc runFile*(inputPath: string) =
let tmpDir = getTempDir()
let baseName = inputPath.splitFile.name
@@ -22,22 +44,18 @@ proc runFile*(inputPath: string) =
compileFile(inputPath, nimPath)
# Compile generated Nim code
let compileCmd = "nim c -o:" & binPath & " " & nimPath
echo "Compiling: ", compileCmd
let compileResult = execCmd(compileCmd)
let compileResult = nimCompile(nimPath, binPath)
if compileResult != 0:
stderr.writeLine("Compilation failed")
quit(1)
# Run
echo "Running: ", binPath
let runResult = execCmd(binPath)
if runResult != 0:
stderr.writeLine("Execution failed")
quit(1)
proc main() =
initBuiltinMacros()
let args = commandLineParams()
if args.len == 0:
@@ -46,10 +64,40 @@ proc main() =
echo " cljnim compile <file.clj> [output.nim] Compile to Nim"
echo " cljnim run <file.clj> Compile and run"
echo " cljnim read <file.clj> Parse and print AST"
echo " cljnim -e '<code>' Evaluate expression"
echo " cljnim -M -e '<code>' Evaluate with full runtime"
quit(0)
let cmd = args[0]
# Handle -e and -M -e flags
if cmd == "-e" or cmd == "-M":
var code = ""
var idx = 1
if cmd == "-M" and args.len > 1 and args[1] == "-e":
idx = 2
elif cmd == "-e":
idx = 1
else:
stderr.writeLine("Usage: cljnim [-M] -e '<code>'")
quit(1)
if idx >= args.len:
stderr.writeLine("Error: Missing expression after -e")
quit(1)
code = args[idx]
let tmpDir = getTempDir()
let nimPath = tmpDir / "cljnim_expr.nim"
let binPath = tmpDir / "cljnim_expr"
let forms = reader.readAll(code)
let nimCode = emitter.emitProgram(forms)
writeFile(nimPath, nimCode)
let compileResult = nimCompile(nimPath, binPath)
if compileResult != 0:
stderr.writeLine("Compilation failed")
quit(1)
let runResult = execCmd(binPath)
quit(runResult)
case cmd
of "compile":
if args.len < 2:
+456
View File
@@ -0,0 +1,456 @@
import runtime
import strutils
import sequtils
# ---- Arithmetic ----
proc cljAdd*(args: seq[CljVal]): CljVal =
var sum: int64 = 0
var sumFloat: float64 = 0.0
var isFloat = false
for a in args:
case a.kind
of ckInt: sum += a.intVal
of ckFloat:
sumFloat += a.floatVal
isFloat = true
else: raise newException(CatchableError, "+ requires numbers")
if isFloat:
cljFloat(sum.float64 + sumFloat)
else:
cljInt(sum)
proc cljMul*(args: seq[CljVal]): CljVal =
var product: int64 = 1
var productFloat: float64 = 1.0
var isFloat = false
for a in args:
case a.kind
of ckInt: product *= a.intVal
of ckFloat:
productFloat *= a.floatVal
isFloat = true
else: raise newException(CatchableError, "* requires numbers")
if isFloat:
cljFloat(product.float64 * productFloat)
else:
cljInt(product)
proc cljSub*(args: seq[CljVal]): CljVal =
if args.len == 0: raise newException(CatchableError, "- requires at least 1 argument")
if args.len == 1:
case args[0].kind
of ckInt: return cljInt(-args[0].intVal)
of ckFloat: return cljFloat(-args[0].floatVal)
else: raise newException(CatchableError, "- requires numbers")
var result: int64
var resultF: float64
var isFloat = false
case args[0].kind
of ckInt: result = args[0].intVal
of ckFloat:
resultF = args[0].floatVal
isFloat = true
else: raise newException(CatchableError, "- requires numbers")
for i in 1..<args.len:
case args[i].kind
of ckInt:
if isFloat: resultF -= args[i].intVal.float64
else: result -= args[i].intVal
of ckFloat:
if not isFloat:
resultF = result.float64 - args[i].floatVal
isFloat = true
else:
resultF -= args[i].floatVal
else: raise newException(CatchableError, "- requires numbers")
if isFloat: cljFloat(resultF)
else: cljInt(result)
proc cljDiv*(args: seq[CljVal]): CljVal =
if args.len < 2: raise newException(CatchableError, "/ requires at least 2 arguments")
var result: float64
case args[0].kind
of ckInt: result = args[0].intVal.float64
of ckFloat: result = args[0].floatVal
else: raise newException(CatchableError, "/ requires numbers")
for i in 1..<args.len:
case args[i].kind
of ckInt:
if args[i].intVal == 0: raise newException(CatchableError, "Division by zero")
result /= args[i].intVal.float64
of ckFloat:
if args[i].floatVal == 0: raise newException(CatchableError, "Division by zero")
result /= args[i].floatVal
else: raise newException(CatchableError, "/ requires numbers")
cljFloat(result)
proc cljInc*(v: CljVal): CljVal =
case v.kind
of ckInt: cljInt(v.intVal + 1)
of ckFloat: cljFloat(v.floatVal + 1.0)
else: raise newException(CatchableError, "inc requires a number")
proc cljDec*(v: CljVal): CljVal =
case v.kind
of ckInt: cljInt(v.intVal - 1)
of ckFloat: cljFloat(v.floatVal - 1.0)
else: raise newException(CatchableError, "dec requires a number")
proc cljMod*(a, b: CljVal): CljVal =
if a.kind == ckInt and b.kind == ckInt:
cljInt(a.intVal mod b.intVal)
else:
raise newException(CatchableError, "mod requires integers")
proc cljRem*(a, b: CljVal): CljVal =
if a.kind == ckInt and b.kind == ckInt:
cljInt(a.intVal rem b.intVal)
else:
raise newException(CatchableError, "rem requires integers")
# ---- Comparison ----
proc cljNumEq*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
let a = args[i-1]
let b = args[i]
if a.kind == ckInt and b.kind == ckInt:
if a.intVal != b.intVal: return cljBool(false)
elif a.kind == ckFloat and b.kind == ckFloat:
if a.floatVal != b.floatVal: return cljBool(false)
elif a.kind == ckInt and b.kind == ckFloat:
if a.intVal.float64 != b.floatVal: return cljBool(false)
elif a.kind == ckFloat and b.kind == ckInt:
if a.floatVal != b.intVal.float64: return cljBool(false)
else:
return cljBool(false)
cljBool(true)
proc cljLt*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
let a = args[i-1]
let b = args[i]
if a.kind == ckInt and b.kind == ckInt:
if a.intVal >= b.intVal: return cljBool(false)
elif a.kind == ckFloat and b.kind == ckFloat:
if a.floatVal >= b.floatVal: return cljBool(false)
else:
return cljBool(false)
cljBool(true)
proc cljGt*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
let a = args[i-1]
let b = args[i]
if a.kind == ckInt and b.kind == ckInt:
if a.intVal <= b.intVal: return cljBool(false)
elif a.kind == ckFloat and b.kind == ckFloat:
if a.floatVal <= b.floatVal: return cljBool(false)
else:
return cljBool(false)
cljBool(true)
proc cljLe*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
let a = args[i-1]
let b = args[i]
if a.kind == ckInt and b.kind == ckInt:
if a.intVal > b.intVal: return cljBool(false)
else:
return cljBool(false)
cljBool(true)
proc cljGe*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
let a = args[i-1]
let b = args[i]
if a.kind == ckInt and b.kind == ckInt:
if a.intVal < b.intVal: return cljBool(false)
else:
return cljBool(false)
cljBool(true)
# ---- Predicates ----
proc cljZero*(v: CljVal): CljVal =
case v.kind
of ckInt: cljBool(v.intVal == 0)
of ckFloat: cljBool(v.floatVal == 0.0)
else: cljBool(false)
proc cljPos*(v: CljVal): CljVal =
case v.kind
of ckInt: cljBool(v.intVal > 0)
of ckFloat: cljBool(v.floatVal > 0.0)
else: raise newException(CatchableError, "pos? requires a number")
proc cljNeg*(v: CljVal): CljVal =
case v.kind
of ckInt: cljBool(v.intVal < 0)
of ckFloat: cljBool(v.floatVal < 0.0)
else: raise newException(CatchableError, "neg? requires a number")
proc cljEven*(v: CljVal): CljVal =
if v.kind == ckInt: cljBool(v.intVal mod 2 == 0)
else: raise newException(CatchableError, "even? requires an integer")
proc cljOdd*(v: CljVal): CljVal =
if v.kind == ckInt: cljBool(v.intVal mod 2 != 0)
else: raise newException(CatchableError, "odd? requires an integer")
# ---- Collection operations ----
proc cljFirst*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
of ckList:
if v.listItems.len == 0: cljNil()
else: v.listItems[0]
of ckVector:
if v.listItems.len == 0: cljNil()
else: v.listItems[0]
else: cljNil()
proc cljRest*(v: CljVal): CljVal =
if v.isNil: return cljList(@[])
case v.kind
of ckList:
if v.listItems.len <= 1: cljList(@[])
else: cljList(v.listItems[1..^1])
of ckVector:
if v.listItems.len <= 1: cljList(@[])
else: cljList(v.listItems[1..^1])
else: cljList(@[])
proc cljNext*(v: CljVal): CljVal =
let r = cljRest(v)
if r.kind == ckList and r.listItems.len == 0:
return cljNil()
r
proc cljLast*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
of ckList:
if v.listItems.len == 0: cljNil()
else: v.listItems[^1]
of ckVector:
if v.listItems.len == 0: cljNil()
else: v.listItems[^1]
else: cljNil()
proc cljNth*(v: CljVal, n: int): CljVal =
case v.kind
of ckList:
if n < 0 or n >= v.listItems.len:
raise newException(IndexDefect, "nth: index out of range")
v.listItems[n]
of ckVector:
if n < 0 or n >= v.listItems.len:
raise newException(IndexDefect, "nth: index out of range")
v.listItems[n]
else:
raise newException(CatchableError, "nth requires a collection")
proc cljCount*(v: CljVal): int =
if v.isNil: return 0
case v.kind
of ckList: v.listItems.len
of ckVector: v.listItems.len
of ckString: v.strVal.len
else: 0
proc cljConj*(coll: CljVal, item: CljVal): CljVal =
if coll.isNil:
return cljList(@[item])
case coll.kind
of ckList:
var newItems = @[item]
newItems.add(coll.listItems)
cljList(newItems)
of ckVector:
var newItems = coll.listItems
newItems.add(item)
cljList(newItems)
else:
cljList(@[item])
proc cljCons*(item: CljVal, coll: CljVal): CljVal =
if coll.isNil or (coll.kind == ckList and coll.listItems.len == 0):
return cljList(@[item])
case coll.kind
of ckList:
var newItems = @[item]
newItems.add(coll.listItems)
cljList(newItems)
of ckVector:
var newItems = @[item]
newItems.add(coll.listItems)
cljList(newItems)
else:
cljList(@[item])
proc cljSeq*(v: CljVal): CljVal =
if v.isNil: return cljNil()
case v.kind
of ckList:
if v.listItems.len == 0: cljNil()
else: v
of ckVector:
if v.listItems.len == 0: cljNil()
else: cljList(v.listItems)
of ckString:
if v.strVal.len == 0: cljNil()
else:
var chars: seq[CljVal] = @[]
for c in v.strVal:
chars.add(cljString($c))
cljList(chars)
else: cljNil()
proc cljVec*(v: CljVal): CljVal =
if v.isNil: return cljList(@[])
case v.kind
of ckList: cljList(v.listItems)
of ckVector: v
else: cljList(@[v])
proc cljEmpty*(v: CljVal): CljVal =
cljBool(cljCount(v) == 0)
# ---- Higher-order functions ----
proc cljMap*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): seq[CljVal] =
result = @[]
for item in coll:
result.add(f(@[item]))
proc cljFilter*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): seq[CljVal] =
result = @[]
for item in coll:
let r = f(@[item])
if r.kind == ckBool and r.boolVal:
result.add(item)
proc cljReduce*(f: proc(args: seq[CljVal]): CljVal, init: CljVal, coll: seq[CljVal]): CljVal =
result = init
for item in coll:
result = f(@[result, item])
proc cljMapv*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): CljVal =
cljList(cljMap(f, coll))
proc cljSome*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): CljVal =
for item in coll:
let r = f(@[item])
if r.kind != ckBool or r.boolVal:
return r
cljNil()
proc cljEvery*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): CljVal =
for item in coll:
let r = f(@[item])
if r.kind == ckBool and not r.boolVal:
return cljBool(false)
cljBool(true)
proc cljNot*(v: CljVal): CljVal =
if v.kind == ckBool and not v.boolVal:
cljBool(true)
else:
cljBool(false)
proc cljApply*(f: proc(args: seq[CljVal]): CljVal, args: seq[CljVal]): CljVal =
f(args)
proc cljComp*(fns: seq[proc(args: seq[CljVal]): CljVal]): proc(args: seq[CljVal]): CljVal =
proc(args: seq[CljVal]): CljVal =
result = args
for i in countdown(fns.len - 1, 0):
result = fns[i](@[result])
proc cljPartial*(f: proc(args: seq[CljVal]): CljVal, partialArgs: seq[CljVal]): proc(args: seq[CljVal]): CljVal =
proc(args: seq[CljVal]): CljVal =
var allArgs = partialArgs
allArgs.add(args)
f(allArgs)
# ---- I/O ----
proc cljPrintln*(args: seq[CljVal]): CljVal =
var parts: seq[string] = @[]
for a in args:
parts.add(cljStr(a))
echo parts.join(" ")
cljNil()
proc cljPrn*(args: seq[CljVal]): CljVal =
var parts: seq[string] = @[]
for a in args:
parts.add(cljRepr(a))
echo parts.join(" ")
cljNil()
proc cljStr*(args: seq[CljVal]): CljVal =
var s = ""
for a in args:
s.add(cljStr(a))
cljString(s)
proc cljPrStr*(args: seq[CljVal]): CljVal =
var parts: seq[string] = @[]
for a in args:
parts.add(cljRepr(a))
cljString(parts.join(" "))
# ---- Misc ----
proc cljIdentity*(args: seq[CljVal]): CljVal =
if args.len == 0: cljNil()
else: args[0]
proc cljConstantly*(v: CljVal): proc(args: seq[CljVal]): CljVal =
proc(args: seq[CljVal]): CljVal = v
proc cljType*(v: CljVal): CljVal =
if v.isNil: return cljKeyword("nil")
case v.kind
of ckBool: cljKeyword("boolean")
of ckInt: cljKeyword("integer")
of ckFloat: cljKeyword("float")
of ckString: cljKeyword("string")
of ckKeyword: cljKeyword("keyword")
of ckSymbol: cljKeyword("symbol")
of ckList: cljKeyword("list")
of ckVector: cljKeyword("vector")
of ckMap: cljKeyword("map")
of ckFn: cljKeyword("function")
of ckAtom: cljKeyword("atom")
proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal =
if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument")
result = args[0]
for i in 1..<args.len:
if args[i].kind == ckInt and result.kind == ckInt:
if isMin:
if args[i].intVal < result.intVal: result = args[i]
else:
if args[i].intVal > result.intVal: result = args[i]
proc cljAbs*(v: CljVal): CljVal =
case v.kind
of ckInt: cljInt(abs(v.intVal))
of ckFloat: cljFloat(abs(v.floatVal))
else: raise newException(CatchableError, "abs requires a number")
proc cljQuot*(a, b: CljVal): CljVal =
if a.kind == ckInt and b.kind == ckInt:
cljInt(a.intVal div b.intVal)
else:
raise newException(CatchableError, "quot requires integers")
+811 -123
View File
File diff suppressed because it is too large Load Diff
+468
View File
@@ -0,0 +1,468 @@
import tables
import types
type
MacroFn* = proc(args: seq[CljVal]): CljVal
var macroTable* = initTable[string, MacroFn]()
proc defineMacro*(name: string, fn: MacroFn) =
macroTable[name] = fn
proc isMacro*(name: string): bool =
macroTable.hasKey(name)
proc expandMacro*(name: string, args: seq[CljVal]): CljVal =
macroTable[name](args)
proc expandSyntaxQuote*(form: CljVal): CljVal
proc macroexpand1*(form: CljVal): CljVal
proc macroexpand*(form: CljVal): CljVal
proc gensymCounter*: int =
var counter {.global.} = 0
inc counter
counter
proc gensymName*(prefix: string = "G__"): string =
prefix & $gensymCounter()
proc expandSyntaxQuote*(form: CljVal): CljVal =
if form.isNil: return cljNil()
case form.kind
of ckSymbol:
return cljList(@[cljSymbol("quote"), form])
of ckKeyword:
return cljList(@[cljSymbol("quote"), form])
of ckList:
if form.items.len == 0:
return cljList(@[cljSymbol("quote"), cljList(@[])])
let head = form.items[0]
if head.kind == ckSymbol:
if head.symName == "unquote":
if form.items.len != 2:
raise newException(CatchableError, "unquote requires exactly 1 argument")
return form.items[1]
if head.symName == "unquote-splicing":
raise newException(CatchableError, "unquote-splicing can only be used inside a collection")
# Process each element
var result: seq[CljVal] = @[]
var parts: seq[CljVal] = @[]
for item in form.items:
if item.kind == ckList and item.items.len == 2 and
item.items[0].kind == ckSymbol and item.items[0].symName == "unquote-splicing":
parts.add(item.items[1])
elif item.kind == ckList and item.items.len == 2 and
item.items[0].kind == ckSymbol and item.items[0].symName == "unquote":
parts.add(cljList(@[cljSymbol("list"), item.items[1]]))
else:
parts.add(cljList(@[cljSymbol("list"), expandSyntaxQuote(item)]))
if parts.len == 0:
return cljList(@[cljSymbol("quote"), cljList(@[])])
# Build (concat part1 part2 ...)
if parts.len == 1:
return parts[0]
return cljList(@[cljSymbol("concat")] & parts)
of ckVector:
var parts: seq[CljVal] = @[]
for item in form.items:
if item.kind == ckList and item.items.len == 2 and
item.items[0].kind == ckSymbol and item.items[0].symName == "unquote-splicing":
parts.add(item.items[1])
else:
parts.add(cljList(@[cljSymbol("list"), expandSyntaxQuote(item)]))
return cljList(@[cljSymbol("vec"),
if parts.len == 0: cljList(@[cljSymbol("quote"), cljList(@[])])
elif parts.len == 1: parts[0]
else: cljList(@[cljSymbol("concat")] & parts)])
of ckMap:
# Maps: expand each key and value
return cljList(@[cljSymbol("quote"), form])
else:
return cljList(@[cljSymbol("quote"), form])
proc macroexpand1*(form: CljVal): CljVal =
if form.isNil: return form
if form.kind != ckList or form.items.len == 0:
return form
let head = form.items[0]
if head.kind != ckSymbol:
return form
let name = head.symName
# Handle special forms that should not be macro-expanded
if name in ["def", "fn", "defn", "defn-", "let", "if", "when", "cond",
"do", "loop", "recur", "try", "catch", "finally", "throw",
"quote", "var", "set!", "ns", "defmacro",
"and", "or", "nil?", "range"]:
return form
# Handle syntax-quote
if name == "syntax-quote":
if form.items.len != 2:
raise newException(CatchableError, "syntax-quote requires exactly 1 argument")
return expandSyntaxQuote(form.items[1])
# Check if it's a macro
if isMacro(name):
return expandMacro(name, form.items[1..^1])
return form
proc macroexpand*(form: CljVal): CljVal =
result = form
while true:
let expanded = macroexpand1(result)
if expanded == result:
return result
result = expanded
# ---- Built-in macro implementations ----
proc threadingMacro(args: seq[CljVal], reverse: bool): CljVal =
if args.len < 2:
raise newException(CatchableError, "Threading macro requires at least 2 arguments")
var result = args[0]
for i in 1..<args.len:
let step = args[i]
if step.kind == ckList and step.items.len > 0:
var newItems = step.items
if reverse:
newItems.add(result)
else:
newItems.insert(result, 1)
result = cljList(newItems)
elif step.kind == ckSymbol:
# (-> x f) => (f x)
result = cljList(@[step, result])
else:
raise newException(CatchableError, "Threading macro requires list or symbol forms")
result
proc initBuiltinMacros*() =
# ->
defineMacro("fn*", proc(args: seq[CljVal]): CljVal =
# fn* is used internally by reader for #()
cljList(@[cljSymbol("fn")] & args)
)
# ->
defineMacro("->", proc(args: seq[CljVal]): CljVal =
threadingMacro(args, false)
)
# ->>
defineMacro("->>", proc(args: seq[CljVal]): CljVal =
threadingMacro(args, true)
)
# and
defineMacro("and", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljBool(true)
if args.len == 1: return args[0]
# (and a b c) => (if a (if b c false) false)
var r = args[^1]
for i in countdown(args.len - 2, 0):
r = cljList(@[cljSymbol("if"), args[i], r, cljBool(false)])
r
)
# or
defineMacro("or", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil()
if args.len == 1: return args[0]
# (or a b c) => (if a a (if b b (if c c nil)))
var r = args[^1]
for i in countdown(args.len - 2, 0):
r = cljList(@[cljSymbol("if"), args[i], args[i], r])
r
)
# when-let
defineMacro("when-let", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "when-let requires binding and body")
let binding = args[0]
let body = args[1..^1]
if binding.kind != ckVector or binding.items.len != 2:
raise newException(CatchableError, "when-let binding must be [name val]")
let sym = binding.items[0]
let val = binding.items[1]
let gs = cljSymbol(gensymName("wl_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, val]),
cljList(@[cljSymbol("if"), gs,
cljList(@[cljSymbol("let"), cljVector(@[sym, gs])] & body)])])
)
# if-let
defineMacro("if-let", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "if-let requires binding, then, and optional else")
let binding = args[0]
let thenExpr = args[1]
let elseExpr = if args.len > 2: args[2] else: cljNil()
if binding.kind != ckVector or binding.items.len != 2:
raise newException(CatchableError, "if-let binding must be [name val]")
let sym = binding.items[0]
let val = binding.items[1]
let gs = cljSymbol(gensymName("il_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, val]),
cljList(@[cljSymbol("if"), gs,
cljList(@[cljSymbol("let"), cljVector(@[sym, gs]), thenExpr]),
elseExpr])])
)
# when
defineMacro("when", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "when requires condition and body")
let test = args[0]
let body = args[1..^1]
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("do")] & body)])
)
# when-not
defineMacro("when-not", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "when-not requires condition and body")
let test = args[0]
let body = args[1..^1]
cljList(@[cljSymbol("if"), test, cljNil(), cljList(@[cljSymbol("do")] & body)])
)
# cond
defineMacro("cond", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil()
if args.len mod 2 != 0:
raise newException(CatchableError, "cond requires even number of forms")
let test = args[0]
let then = args[1]
if (test.kind == ckKeyword and test.kwName == "else") or
(test.kind == ckSymbol and test.symName == ":else"):
return then
let rest = args[2..^1]
if rest.len == 0:
return cljList(@[cljSymbol("if"), test, then])
cljList(@[cljSymbol("if"), test, then,
cljList(@[cljSymbol("cond")] & rest)])
)
# cond->
defineMacro("cond->", proc(args: seq[CljVal]): CljVal =
if args.len < 1:
raise newException(CatchableError, "cond-> requires at least 1 argument")
let expr = args[0]
let gs = cljSymbol(gensymName("ct_"))
var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, expr])]
var i = 1
while i + 1 < args.len:
let test = args[i]
let step = args[i+1]
var threaded: CljVal
if step.kind == ckList and step.items.len > 0:
var newItems = step.items
newItems.insert(gs, 1)
threaded = cljList(newItems)
elif step.kind == ckSymbol:
threaded = cljList(@[step, gs])
else:
threaded = step
forms.add(cljList(@[cljSymbol("if"), test,
cljList(@[cljSymbol("set!"), gs, threaded]),
gs]))
i += 2
forms.add(gs)
cljList(forms)
)
# cond->>
defineMacro("cond->>", proc(args: seq[CljVal]): CljVal =
if args.len < 1:
raise newException(CatchableError, "cond->> requires at least 1 argument")
let expr = args[0]
let gs = cljSymbol(gensymName("ctg_"))
var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, expr])]
var i = 1
while i + 1 < args.len:
let test = args[i]
let step = args[i+1]
var threaded: CljVal
if step.kind == ckList and step.items.len > 0:
var newItems = step.items
newItems.add(gs)
threaded = cljList(newItems)
elif step.kind == ckSymbol:
threaded = cljList(@[step, gs])
else:
threaded = step
forms.add(cljList(@[cljSymbol("if"), test,
cljList(@[cljSymbol("set!"), gs, threaded]),
gs]))
i += 2
forms.add(gs)
cljList(forms)
)
# doto
defineMacro("doto", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "doto requires at least 2 arguments")
let x = args[0]
let gs = cljSymbol(gensymName("do_"))
var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, x])]
for i in 1..<args.len:
let step = args[i]
if step.kind == ckList and step.items.len > 0:
var newItems = step.items
newItems.insert(gs, 1)
forms.add(cljList(newItems))
elif step.kind == ckSymbol:
forms.add(cljList(@[step, gs]))
forms.add(gs)
cljList(forms)
)
# as->
defineMacro("as->", proc(args: seq[CljVal]): CljVal =
if args.len < 3:
raise newException(CatchableError, "as-> requires at least 3 arguments")
let expr = args[0]
let name = args[1]
var result = expr
for i in 2..<args.len:
result = cljList(@[cljSymbol("let"), cljVector(@[name, result]), args[i]])
result
)
# some->
defineMacro("some->", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "some-> requires at least 2 arguments")
var result = args[0]
for i in 1..<args.len:
let step = args[i]
let gs = cljSymbol(gensymName("st_"))
var threaded: CljVal
if step.kind == ckList and step.items.len > 0:
var newItems = step.items
newItems.insert(gs, 1)
threaded = cljList(newItems)
elif step.kind == ckSymbol:
threaded = cljList(@[step, gs])
else:
threaded = step
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]),
cljNil(), threaded])])
result
)
# some->>
defineMacro("some->>", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "some->> requires at least 2 arguments")
var result = args[0]
for i in 1..<args.len:
let step = args[i]
let gs = cljSymbol(gensymName("stg_"))
var threaded: CljVal
if step.kind == ckList and step.items.len > 0:
var newItems = step.items
newItems.add(gs)
threaded = cljList(newItems)
elif step.kind == ckSymbol:
threaded = cljList(@[step, gs])
else:
threaded = step
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]),
cljNil(), threaded])])
result
)
# for (simplified — single binding only)
defineMacro("for", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "for requires binding vector and body")
let bindings = args[0]
let body = args[1..^1]
if bindings.kind != ckVector or bindings.items.len < 2:
raise newException(CatchableError, "for requires a binding vector [name coll]")
let sym = bindings.items[0]
let coll = bindings.items[1]
let bodyExpr = if body.len == 1: body[0] else: cljList(@[cljSymbol("do")] & body)
let gs = cljSymbol(gensymName("f_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, cljVector(@[])]),
cljList(@[cljSymbol("doseq"), cljVector(@[sym, coll]),
cljList(@[cljSymbol("set!"), gs,
cljList(@[cljSymbol("conj"), gs, bodyExpr])])]),
gs])
)
# doseq (simplified — single binding only)
defineMacro("doseq", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "doseq requires binding vector and body")
let bindings = args[0]
let body = args[1..^1]
if bindings.kind != ckVector or bindings.items.len < 2:
raise newException(CatchableError, "doseq requires a binding vector [name coll]")
let sym = bindings.items[0]
let coll = bindings.items[1]
let gs = cljSymbol(gensymName("ds_"))
let bodyExpr = if body.len == 1: body[0] else: cljList(@[cljSymbol("do")] & body)
cljList(@[cljSymbol("let"), cljVector(@[gs, coll]),
cljList(@[cljSymbol("loop"), cljVector(@[cljSymbol(gensymName("i_")), cljInt(0)]),
cljList(@[cljSymbol("when"),
cljList(@[cljSymbol("<"), cljSymbol(gensymName("i_")), cljList(@[cljSymbol("count"), gs])]),
cljList(@[cljSymbol("let"), cljVector(@[sym, cljList(@[cljSymbol("nth"), gs, cljSymbol(gensymName("i_"))])]),
bodyExpr]),
cljList(@[cljSymbol("recur"),
cljList(@[cljSymbol("inc"), cljSymbol(gensymName("i_"))])])])])])
)
# dotimes
defineMacro("dotimes", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "dotimes requires binding vector and body")
let bindings = args[0]
let body = args[1..^1]
if bindings.kind != ckVector or bindings.items.len != 2:
raise newException(CatchableError, "dotimes requires [name n]")
let sym = bindings.items[0]
let n = bindings.items[1]
cljList(@[cljSymbol("doseq"), cljVector(@[sym, cljList(@[cljSymbol("range"), n])])] & body)
)
# defmacro
defineMacro("defmacro", proc(args: seq[CljVal]): CljVal =
# defmacro is handled specially in the compiler, this is a no-op
cljNil()
)
# comment
defineMacro("comment", proc(args: seq[CljVal]): CljVal =
cljNil()
)
# assert
defineMacro("assert", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil()
let test = args[0]
let msg = if args.len > 1: args[1] else: cljString("Assert failed")
cljList(@[cljSymbol("when-not"), test,
cljList(@[cljSymbol("throw"), msg])])
)
# with-open
defineMacro("with-open", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "with-open requires binding and body")
let binding = args[0]
let body = args[1..^1]
if binding.kind != ckVector or binding.items.len != 2:
raise newException(CatchableError, "with-open requires [name init]")
let sym = binding.items[0]
let init = binding.items[1]
let gs = cljSymbol(gensymName("wo_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, init]),
cljList(@[cljSymbol("try")] & body & @[
cljList(@[cljSymbol("finally"),
cljList(@[cljSymbol(".close"), gs])])])])
)
+124 -8
View File
@@ -1,5 +1,5 @@
# Clojure Reader (EDN subset)
import strutils, sequtils
import strutils
import types
type
@@ -17,7 +17,7 @@ proc skipWhitespaceAndComments(s: string, i: var int) =
break
proc isSymChar(c: char): bool =
c in Letters or c in Digits or c in {'+', '-', '*', '/', '_', '?', '!', '=', '<', '>', '.', '\'', '#', '%', '&'}
c in Letters or c in Digits or c in {'+', '-', '*', '/', '_', '?', '!', '=', '<', '>', '.', '\'', '#', '%', '&', ':'}
proc readStringTok(s: string, i: var int): string =
inc i # skip opening quote
@@ -126,7 +126,9 @@ proc readList(s: string, i: var int): CljVal =
if s[i] == ')':
inc i
break
items.add(readForm(s, i))
let form = readForm(s, i)
if form != nil:
items.add(form)
return cljList(items)
proc readVector(s: string, i: var int): CljVal =
@@ -139,9 +141,97 @@ proc readVector(s: string, i: var int): CljVal =
if s[i] == ']':
inc i
break
items.add(readForm(s, i))
let form = readForm(s, i)
if form != nil:
items.add(form)
return cljVector(items)
proc readMap(s: string, i: var int): CljVal =
inc i # skip '{'
var pairs: seq[(CljVal, CljVal)] = @[]
while true:
skipWhitespaceAndComments(s, i)
if i >= s.len:
raise newException(ReaderError, "Unterminated map")
if s[i] == '}':
inc i
break
let key = readForm(s, i)
if key == nil:
continue
skipWhitespaceAndComments(s, i)
if i >= s.len or s[i] == '}':
raise newException(ReaderError, "Map must have even number of forms")
let val = readForm(s, i)
if val == nil:
raise newException(ReaderError, "Map value cannot be nil")
pairs.add((key, val))
return cljMapFromPairs(pairs)
proc readSet(s: string, i: var int): CljVal =
inc i # skip '{' after #
var items: seq[CljVal] = @[]
while true:
skipWhitespaceAndComments(s, i)
if i >= s.len:
raise newException(ReaderError, "Unterminated set")
if s[i] == '}':
inc i
break
let form = readForm(s, i)
if form != nil:
items.add(form)
return cljList(@[cljSymbol("set"), cljVector(items)])
proc readDispatch(s: string, i: var int): CljVal =
inc i # skip '#'
if i >= s.len:
raise newException(ReaderError, "Unexpected end after #")
let c = s[i]
case c
of '{':
return readSet(s, i)
of '(':
let fnBody = readList(s, i)
return cljList(@[cljSymbol("fn*"), cljVector(@[cljSymbol("%")]), fnBody])
of '\'':
inc i
let sym = readForm(s, i)
return cljList(@[cljSymbol("var"), sym])
of '_':
inc i
discard readForm(s, i)
return nil
else:
raise newException(ReaderError, "Unknown dispatch macro: #" & c)
proc readWithMeta(s: string, i: var int): CljVal =
inc i # skip '^'
let meta = readForm(s, i)
let form = readForm(s, i)
return cljList(@[cljSymbol("with-meta"), form, meta])
proc readSyntaxQuote(s: string, i: var int): CljVal =
inc i # skip '`'
let form = readForm(s, i)
return cljList(@[cljSymbol("syntax-quote"), form])
proc readUnquoteSplicing(s: string, i: var int): CljVal =
inc i # skip '~'
inc i # skip '@'
let form = readForm(s, i)
return cljList(@[cljSymbol("unquote-splicing"), form])
proc readUnquote(s: string, i: var int): CljVal =
inc i # skip '~'
let form = readForm(s, i)
return cljList(@[cljSymbol("unquote"), form])
proc readDeref(s: string, i: var int): CljVal =
inc i # skip '@'
let form = readForm(s, i)
return cljList(@[cljSymbol("deref"), form])
proc readForm(s: string, i: var int): CljVal =
skipWhitespaceAndComments(s, i)
if i >= s.len:
@@ -153,12 +243,26 @@ proc readForm(s: string, i: var int): CljVal =
return readList(s, i)
of '[':
return readVector(s, i)
of '{':
return readMap(s, i)
of '"':
return cljString(readStringTok(s, i))
of '\'':
inc i # skip quote
let form = readForm(s, i)
return cljList(@[cljSymbol("quote"), form])
of '#':
return readDispatch(s, i)
of '^':
return readWithMeta(s, i)
of '`':
return readSyntaxQuote(s, i)
of '~':
if i + 1 < s.len and s[i+1] == '@':
return readUnquoteSplicing(s, i)
return readUnquote(s, i)
of '@':
return readDeref(s, i)
else:
return readAtom(s, i)
@@ -166,15 +270,25 @@ proc readOne*(s: string, i: var int): CljVal =
skipWhitespaceAndComments(s, i)
if i >= s.len:
return nil
return readForm(s, i)
result = readForm(s, i)
while result == nil:
skipWhitespaceAndComments(s, i)
if i >= s.len:
return nil
result = readForm(s, i)
proc read*(s: string): CljVal =
var i = 0
let result = readForm(s, i)
var res: CljVal = nil
while res == nil:
skipWhitespaceAndComments(s, i)
if i >= s.len:
raise newException(ReaderError, "No form found")
res = readForm(s, i)
skipWhitespaceAndComments(s, i)
if i < s.len:
raise newException(ReaderError, "Extra input after form: " & s[i..^1])
return result
return res
proc readAll*(s: string): seq[CljVal] =
var i = 0
@@ -183,5 +297,7 @@ proc readAll*(s: string): seq[CljVal] =
skipWhitespaceAndComments(s, i)
if i >= s.len:
break
forms.add(readForm(s, i))
let form = readForm(s, i)
if form != nil:
forms.add(form)
return forms
+253
View File
@@ -0,0 +1,253 @@
import hashes
const
Bits = 5
Width = 1 shl Bits # 32
Mask = Width - 1
type
CljKind* = enum
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
ckList, ckVector, ckMap, ckFn, ckAtom
CljVal* = ref CljValObj
CljValObj = object
case kind*: CljKind
of ckNil: discard
of ckBool: boolVal*: bool
of ckInt: intVal*: int64
of ckFloat: floatVal*: float64
of ckString: strVal*: string
of ckKeyword: kwName*: string
of ckSymbol: symName*: string
of ckList: listItems*: seq[CljVal]
of ckVector: vecRoot*: VecNode
of ckMap: mapRoot*: MapNode
of ckFn: fnProc*: proc(args: seq[CljVal]): CljVal
of ckAtom: atomVal*: CljVal
VecNodeKind = enum vnLeaf, vnInternal
VecNode = ref object
case kind: VecNodeKind
of vnLeaf:
leaf: array[Width, CljVal]
of vnInternal:
children: array[Width, VecNode]
PersistentVector* = object
count*: int
shift*: int
root*: VecNode
tail*: seq[CljVal]
tailLen*: int
MapNodeKind = enum mnLeaf, mnInternal, mnCollision
MapNode = ref object
case kind: MapNodeKind
of mnLeaf:
leafKey*: CljVal
leafVal*: CljVal
of mnInternal:
bitmap*: uint32
children*: seq[MapNode]
of mnCollision:
collHash*: Hash
collPairs*: seq[(CljVal, CljVal)]
PersistentMap* = object
count*: int
root*: MapNode
hasLeaf*: bool
rootLeaf*: MapNode
# ---- Constructors ----
proc cljNil*(): CljVal = CljVal(kind: ckNil)
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
proc cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64)
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
proc cljFn*(p: proc(args: seq[CljVal]): CljVal): CljVal = CljVal(kind: ckFn, fnProc: p)
proc emptyVecNode(): VecNode = VecNode(kind: vnLeaf)
proc emptyMapNode(): MapNode = MapNode(kind: mnInternal, bitmap: 0, children: @[])
# ---- CljVal helpers ----
proc cljValHash*(v: CljVal): Hash =
case v.kind
of ckNil: hash(0)
of ckBool: hash(v.boolVal)
of ckInt: hash(v.intVal)
of ckFloat: hash(v.floatVal)
of ckString: hash(v.strVal)
of ckKeyword: hash(v.kwName) !& hash(":kw")
of ckSymbol: hash(v.symName)
else: hash(cast[uint](v))
proc `==`*(a, b: CljVal): bool =
if a.isNil and b.isNil: return true
if a.isNil or b.isNil: return false
if a.kind != b.kind: return false
case a.kind
of ckNil: true
of ckBool: a.boolVal == b.boolVal
of ckInt: a.intVal == b.intVal
of ckFloat: a.floatVal == b.floatVal
of ckString: a.strVal == b.strVal
of ckKeyword: a.kwName == b.kwName
of ckSymbol: a.symName == b.symName
else: false
# ---- Persistent Vector (HAMT) ----
proc newPersistentVector*(): PersistentVector =
PersistentVector(count: 0, shift: Bits, root: emptyVecNode(), tail: @[], tailLen: 0)
proc tailOff(count: int): int =
if count < Width: 0
else: ((count - 1) shr Bits) shl Bits
proc vecPush(v: PersistentVector, val: CljVal): PersistentVector =
if v.count - tailOff(v.count) < Width:
# room in tail
var newTail = v.tail
newTail.add(val)
return PersistentVector(count: v.count + 1, shift: v.shift, root: v.root, tail: newTail, tailLen: v.tailLen + 1)
# tail full, push into tree
let tailNode = VecNode(kind: vnLeaf, leaf: block:
var arr: array[Width, CljVal]
for i in 0..<v.tailLen: arr[i] = v.tail[i]
arr)
var newShift = v.shift
var newRoot = v.root
if (v.count shr Bits) > (1 shl v.shift):
var newRootN = VecNode(kind: vnInternal)
newRootN.children[0] = v.root
newRootN.children[1] = tailNode
newRoot = newRootN
newShift += Bits
else:
# insert tail into tree
proc insertTail(node: VecNode, level: int, parentBitmap: uint32): VecNode =
discard
# simplified: just create leaf for now
newRoot = v.root
var newTailSeq = @[val]
return PersistentVector(count: v.count + 1, shift: newShift, root: newRoot, tail: newTailSeq, tailLen: 1)
proc vecGet(v: PersistentVector, idx: int): CljVal =
if idx < 0 or idx >= v.count:
raise newException(IndexDefect, "Index out of range: " & $idx)
let to = tailOff(v.count)
if idx >= to:
return v.tail[idx - to]
var node = v.root
var level = v.shift
while level > 0:
node = node.children[(idx shr level) and Mask]
level -= Bits
return node.leaf[idx and Mask]
# ---- Simplified seq-based persistent vector ----
# For now use Nim seq with copy-on-write semantics via wrapper
type
CljVec* = ref object
data*: seq[CljVal]
CljMap* = ref object
keys*: seq[CljVal]
vals*: seq[CljVal]
proc newCljVec*(): CljVec = CljVec(data: @[])
proc newCljVec*(items: seq[CljVal]): CljVec = CljVec(data: items)
proc cljVecLen*(v: CljVec): int = v.data.len
proc cljVecGet*(v: CljVec, i: int): CljVal =
if i < 0 or i >= v.data.len:
raise newException(IndexDefect, "Index out of range: " & $i)
v.data[i]
proc cljVecConj*(v: CljVec, val: CljVal): CljVec =
var newData = v.data
newData.add(val)
newCljVec(newData)
proc cljVecAssoc*(v: CljVec, i: int, val: CljVal): CljVec =
var newData = v.data
if i == newData.len:
newData.add(val)
elif i >= 0 and i < newData.len:
newData[i] = val
else:
raise newException(IndexDefect, "Index out of range: " & $i)
newCljVec(newData)
proc newCljMap*(): CljMap = CljMap(keys: @[], vals: @[])
proc cljMapGet*(m: CljMap, key: CljVal): CljVal =
for i in 0..<m.keys.len:
if m.keys[i] == key:
return m.vals[i]
return cljNil()
proc cljMapAssoc*(m: CljMap, key: CljVal, val: CljVal): CljMap =
var newKeys = m.keys
var newVals = m.vals
for i in 0..<newKeys.len:
if newKeys[i] == key:
newVals[i] = val
return CljMap(keys: newKeys, vals: newVals)
newKeys.add(key)
newVals.add(val)
CljMap(keys: newKeys, vals: newVals)
proc cljMapDissoc*(m: CljMap, key: CljVal): CljMap =
var newKeys: seq[CljVal] = @[]
var newVals: seq[CljVal] = @[]
for i in 0..<m.keys.len:
if m.keys[i] != key:
newKeys.add(m.keys[i])
newVals.add(m.vals[i])
CljMap(keys: newKeys, vals: newVals)
proc cljMapContains*(m: CljMap, key: CljVal): bool =
for i in 0..<m.keys.len:
if m.keys[i] == key:
return true
false
proc cljMapCount*(m: CljMap): int = m.keys.len
proc cljMapKeys*(m: CljMap): seq[CljVal] = m.keys
proc cljMapVals*(m: CljMap): seq[CljVal] = m.vals
# ---- String display ----
proc cljRepr*(v: CljVal): string =
if v.isNil: return "nil"
case v.kind
of ckNil: "nil"
of ckBool: $v.boolVal
of ckInt: $v.intVal
of ckFloat: $v.floatVal
of ckString: "\"" & v.strVal & "\""
of ckKeyword: ":" & v.kwName
of ckSymbol: v.symName
of ckList: "(" & v.listItems.mapIt(cljRepr(it)).join(" ") & ")"
of ckVector: "[" & v.listItems.mapIt(cljRepr(it)).join(" ") & "]"
of ckMap: "{not-implemented}"
of ckFn: "#<fn>"
of ckAtom: "(atom " & cljRepr(v.atomVal) & ")"
proc cljStr*(v: CljVal): string =
if v.isNil: return ""
case v.kind
of ckString: v.strVal
of ckKeyword: ":" & v.kwName
of ckSymbol: v.symName
else: cljRepr(v)
+18 -7
View File
@@ -1,4 +1,4 @@
# Clojure Value Types (Nim Runtime)
# Clojure Value Types (Compiler internal representation)
import sequtils, strutils
type
@@ -16,9 +16,10 @@ type
of ckKeyword: kwName*: string
of ckSymbol: symName*: string
of ckList, ckVector: items*: seq[CljVal]
of ckMap: pairs*: seq[(CljVal, CljVal)]
of ckMap:
mapKeys*: seq[CljVal]
mapVals*: seq[CljVal]
# Constructors
proc cljNil*(): CljVal = CljVal(kind: ckNil)
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
@@ -28,10 +29,20 @@ proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
proc cljList*(items: seq[CljVal]): CljVal = CljVal(kind: ckList, items: items)
proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, items: items)
proc cljMap*(pairs: seq[(CljVal, CljVal)]): CljVal = CljVal(kind: ckMap, pairs: pairs)
# String representation (for debugging)
proc cljMap*(keys: seq[CljVal], vals: seq[CljVal]): CljVal =
CljVal(kind: ckMap, mapKeys: keys, mapVals: vals)
proc cljMapFromPairs*(pairs: seq[(CljVal, CljVal)]): CljVal =
var ks: seq[CljVal] = @[]
var vs: seq[CljVal] = @[]
for (k, v) in pairs:
ks.add(k)
vs.add(v)
cljMap(ks, vs)
proc `$`*(v: CljVal): string =
if v.isNil: return "nil"
case v.kind
of ckNil: "nil"
of ckBool: $v.boolVal
@@ -44,6 +55,6 @@ proc `$`*(v: CljVal): string =
of ckVector: "[" & v.items.mapIt($it).join(" ") & "]"
of ckMap:
var parts: seq[string] = @[]
for (k, v2) in v.pairs:
parts.add($k & " " & $v2)
for i in 0..<v.mapKeys.len:
parts.add($v.mapKeys[i] & " " & $v.mapVals[i])
"{" & parts.join(", ") & "}"