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:
+56
-8
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+468
@@ -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
@@ -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
@@ -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
@@ -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(", ") & "}"
|
||||
|
||||
Reference in New Issue
Block a user