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:
@@ -0,0 +1,175 @@
|
||||
import unittest
|
||||
import strutils
|
||||
import ../src/types
|
||||
import ../src/reader
|
||||
import ../src/emitter
|
||||
|
||||
suite "Emitter - Basic Values":
|
||||
test "emit nil":
|
||||
check emitExpr(cljNil()) == "cljNil()"
|
||||
|
||||
test "emit bool true":
|
||||
check emitExpr(cljBool(true)) == "cljBool(true)"
|
||||
|
||||
test "emit bool false":
|
||||
check emitExpr(cljBool(false)) == "cljBool(false)"
|
||||
|
||||
test "emit int":
|
||||
check emitExpr(cljInt(42)) == "cljInt(42)"
|
||||
|
||||
test "emit float":
|
||||
check "cljFloat(3.14)" in emitExpr(cljFloat(3.14))
|
||||
|
||||
test "emit string":
|
||||
check emitExpr(cljString("hello")) == "cljString(\"hello\")"
|
||||
|
||||
test "emit keyword":
|
||||
check "cljKeyword" in emitExpr(cljKeyword("key"))
|
||||
|
||||
test "emit symbol":
|
||||
check emitExpr(cljSymbol("foo")) == "foo"
|
||||
|
||||
test "emit mangled symbol":
|
||||
check mangleName("my-fn?") == "my_fn_Q"
|
||||
|
||||
test "emit vector":
|
||||
let v = cljVector(@[cljInt(1), cljInt(2), cljInt(3)])
|
||||
check "cljVector" in emitExpr(v)
|
||||
check "cljInt(1)" in emitExpr(v)
|
||||
|
||||
test "emit empty list":
|
||||
check emitExpr(cljList(@[])) == "cljList(@[])"
|
||||
|
||||
|
||||
suite "Emitter - Special Forms":
|
||||
test "emit println":
|
||||
let v = read("(println \"hello\")")
|
||||
let code = emitExpr(v)
|
||||
check "cljPrintln" in code
|
||||
check "discard" in code
|
||||
|
||||
test "emit def":
|
||||
let v = read("(def x 42)")
|
||||
let code = emitExpr(v)
|
||||
check "let x = cljInt(42)" in code
|
||||
|
||||
test "emit defn":
|
||||
let v = read("(defn square [x] (* x x))")
|
||||
let code = emitExpr(v)
|
||||
check "proc square" in code
|
||||
check "CljVal" in code
|
||||
|
||||
test "emit if":
|
||||
let v = read("(if true 1 2)")
|
||||
let code = emitExpr(v)
|
||||
check "cljIsTruthy" in code
|
||||
check "else:" in code
|
||||
|
||||
test "emit when":
|
||||
let v = read("(when true (println \"yes\"))")
|
||||
let code = emitExpr(v)
|
||||
check "cljIsTruthy" in code
|
||||
|
||||
test "emit let":
|
||||
let v = read("(let [x 1] x)")
|
||||
let code = emitExpr(v)
|
||||
check "block:" in code
|
||||
check "let x = cljInt(1)" in code
|
||||
|
||||
test "emit cond":
|
||||
let v = read("(cond true 1 :else 2)")
|
||||
let code = emitExpr(v)
|
||||
check "cljIsTruthy" in code
|
||||
check "else:" in code
|
||||
|
||||
test "emit fn":
|
||||
let v = read("(fn [x] x)")
|
||||
let code = emitExpr(v)
|
||||
check "proc" in code
|
||||
check "CljVal" in code
|
||||
|
||||
test "emit do":
|
||||
let v = read("(do 1 2)")
|
||||
let code = emitExpr(v)
|
||||
check "cljInt" in code
|
||||
|
||||
|
||||
suite "Emitter - Operators":
|
||||
test "emit addition":
|
||||
let v = read("(+ 1 2)")
|
||||
let code = emitExpr(v)
|
||||
check "cljAdd" in code
|
||||
|
||||
test "emit subtraction":
|
||||
let v = read("(- 5 3)")
|
||||
let code = emitExpr(v)
|
||||
check "cljSub" in code
|
||||
|
||||
test "emit multiplication":
|
||||
let v = read("(* 2 3)")
|
||||
let code = emitExpr(v)
|
||||
check "cljMul" in code
|
||||
|
||||
test "emit equality":
|
||||
let v = read("(= 1 1)")
|
||||
let code = emitExpr(v)
|
||||
check "cljNumEq" in code
|
||||
|
||||
test "emit not=":
|
||||
let v = read("(not= 1 2)")
|
||||
let code = emitExpr(v)
|
||||
check "cljNotEq" in code
|
||||
|
||||
test "emit not":
|
||||
let v = read("(not true)")
|
||||
let code = emitExpr(v)
|
||||
check "cljNot" in code
|
||||
|
||||
|
||||
suite "Emitter - Program":
|
||||
test "emitProgram with defs and main":
|
||||
let forms = readAll("(def x 10)\n(println x)")
|
||||
let prog = emitProgram(forms)
|
||||
check "let x = cljInt(10)" in prog
|
||||
check "when isMainModule:" in prog
|
||||
check "cljPrintln" in prog
|
||||
|
||||
test "emitProgram multiple defs":
|
||||
let forms = readAll("(defn add [a b] (+ a b))\n(println (add 1 2))")
|
||||
let prog = emitProgram(forms)
|
||||
check "proc add" in prog
|
||||
check "add(cljInt(1), cljInt(2))" in prog
|
||||
|
||||
|
||||
suite "Emitter - Map Literals":
|
||||
test "emit empty map":
|
||||
let v = cljMap(@[], @[])
|
||||
let code = emitExpr(v)
|
||||
check "cljMap" in code
|
||||
|
||||
test "emit map with entries":
|
||||
let v = cljMapFromPairs(@[
|
||||
(cljKeyword("a"), cljInt(1)),
|
||||
(cljKeyword("b"), cljInt(2))
|
||||
])
|
||||
let code = emitExpr(v)
|
||||
check "cljMap" in code
|
||||
|
||||
|
||||
suite "Emitter - Higher-order":
|
||||
test "emit map with symbol fn":
|
||||
let v = read("(map inc [1 2 3])")
|
||||
let code = emitExpr(v)
|
||||
check "cljMap" in code
|
||||
check "cljFn" in code
|
||||
|
||||
test "emit filter with inline fn":
|
||||
let v = read("(filter (fn [x] (> x 2)) [1 2 3])")
|
||||
let code = emitExpr(v)
|
||||
check "cljFilter" in code
|
||||
check "cljFn" in code
|
||||
|
||||
test "emit reduce":
|
||||
let v = read("(reduce + 0 [1 2 3])")
|
||||
let code = emitExpr(v)
|
||||
check "cljReduce" in code
|
||||
@@ -0,0 +1,160 @@
|
||||
import unittest
|
||||
import ../src/types
|
||||
import ../src/reader
|
||||
|
||||
suite "Reader - Basic Types":
|
||||
test "read nil":
|
||||
let v = read("nil")
|
||||
check v.kind == ckNil
|
||||
|
||||
test "read true":
|
||||
let v = read("true")
|
||||
check v.kind == ckBool
|
||||
check v.boolVal == true
|
||||
|
||||
test "read false":
|
||||
let v = read("false")
|
||||
check v.kind == ckBool
|
||||
check v.boolVal == false
|
||||
|
||||
test "read integer":
|
||||
let v = read("42")
|
||||
check v.kind == ckInt
|
||||
check v.intVal == 42
|
||||
|
||||
test "read negative integer":
|
||||
let v = read("-7")
|
||||
check v.kind == ckInt
|
||||
check v.intVal == -7
|
||||
|
||||
test "read float":
|
||||
let v = read("3.14")
|
||||
check v.kind == ckFloat
|
||||
check v.floatVal == 3.14
|
||||
|
||||
test "read string":
|
||||
let v = read("\"hello\"")
|
||||
check v.kind == ckString
|
||||
check v.strVal == "hello"
|
||||
|
||||
test "read string with escapes":
|
||||
let v = read("\"hello\\nworld\"")
|
||||
check v.kind == ckString
|
||||
check v.strVal == "hello\nworld"
|
||||
|
||||
test "read keyword":
|
||||
let v = read(":key")
|
||||
check v.kind == ckKeyword
|
||||
check v.kwName == "key"
|
||||
|
||||
test "read symbol":
|
||||
let v = read("foo")
|
||||
check v.kind == ckSymbol
|
||||
check v.symName == "foo"
|
||||
|
||||
test "read symbol with special chars":
|
||||
let v = read("my-fn?")
|
||||
check v.kind == ckSymbol
|
||||
check v.symName == "my-fn?"
|
||||
|
||||
suite "Reader - Collections":
|
||||
test "read empty list":
|
||||
let v = read("()")
|
||||
check v.kind == ckList
|
||||
check v.items.len == 0
|
||||
|
||||
test "read list":
|
||||
let v = read("(+ 1 2)")
|
||||
check v.kind == ckList
|
||||
check v.items.len == 3
|
||||
check v.items[0].kind == ckSymbol
|
||||
check v.items[0].symName == "+"
|
||||
check v.items[1].kind == ckInt
|
||||
check v.items[1].intVal == 1
|
||||
check v.items[2].kind == ckInt
|
||||
check v.items[2].intVal == 2
|
||||
|
||||
test "read vector":
|
||||
let v = read("[1 2 3]")
|
||||
check v.kind == ckVector
|
||||
check v.items.len == 3
|
||||
check v.items[0].intVal == 1
|
||||
check v.items[1].intVal == 2
|
||||
check v.items[2].intVal == 3
|
||||
|
||||
test "read empty vector":
|
||||
let v = read("[]")
|
||||
check v.kind == ckVector
|
||||
check v.items.len == 0
|
||||
|
||||
test "read map":
|
||||
let v = read("{:a 1 :b 2}")
|
||||
check v.kind == ckMap
|
||||
check v.mapKeys.len == 2
|
||||
check v.mapKeys[0].kind == ckKeyword
|
||||
check v.mapKeys[0].kwName == "a"
|
||||
check v.mapVals[0].kind == ckInt
|
||||
check v.mapVals[0].intVal == 1
|
||||
|
||||
test "read empty map":
|
||||
let v = read("{}")
|
||||
check v.kind == ckMap
|
||||
check v.mapKeys.len == 0
|
||||
|
||||
test "read nested list":
|
||||
let v = read("(+ (- 3 1) 2)")
|
||||
check v.kind == ckList
|
||||
check v.items.len == 3
|
||||
check v.items[1].kind == ckList
|
||||
check v.items[1].items.len == 3
|
||||
|
||||
suite "Reader - Quote and Syntax":
|
||||
test "read quote":
|
||||
let v = read("'foo")
|
||||
check v.kind == ckList
|
||||
check v.items.len == 2
|
||||
check v.items[0].kind == ckSymbol
|
||||
check v.items[0].symName == "quote"
|
||||
check v.items[1].kind == ckSymbol
|
||||
check v.items[1].symName == "foo"
|
||||
|
||||
test "read deref":
|
||||
let v = read("@atom")
|
||||
check v.kind == ckList
|
||||
check v.items[0].symName == "deref"
|
||||
|
||||
test "read unquote":
|
||||
let v = read("~x")
|
||||
check v.kind == ckList
|
||||
check v.items[0].symName == "unquote"
|
||||
|
||||
test "read unquote-splicing":
|
||||
let v = read("~@x")
|
||||
check v.kind == ckList
|
||||
check v.items[0].symName == "unquote-splicing"
|
||||
|
||||
suite "Reader - Comments":
|
||||
test "skip comments":
|
||||
let v = read("; comment\n42")
|
||||
check v.kind == ckInt
|
||||
check v.intVal == 42
|
||||
|
||||
test "inline comments":
|
||||
let forms = readAll("(+ 1 2) ; addition\n(- 3 1)")
|
||||
check forms.len == 2
|
||||
|
||||
suite "Reader - readAll":
|
||||
test "read multiple forms":
|
||||
let forms = readAll("1 2 3")
|
||||
check forms.len == 3
|
||||
check forms[0].intVal == 1
|
||||
check forms[1].intVal == 2
|
||||
check forms[2].intVal == 3
|
||||
|
||||
test "read empty string":
|
||||
let forms = readAll("")
|
||||
check forms.len == 0
|
||||
|
||||
test "read whitespace only":
|
||||
let forms = readAll(" \n \t ")
|
||||
check forms.len == 0
|
||||
Reference in New Issue
Block a user