fix: clojure test suite compatibility — 145/225 passing (64%)

Reader: #? reader conditionals with :default, #?@ splicing, char literals,
N/M suffix and ratio parsing, comma as whitespace, number parsing fix
for negative BigInt/Decimal/Ratio literals

Emitter: mangleName trailing/double underscore fixes, scope stack for
local var tracking, symbol-as-value emits cljSymbol() not bare identifier,
macro-expanded do-unwrap in processForm, emitBlock multiline wrap,
indentCode helper, when-var-exists emitter form, variadic apply fix,
fn proc spacing fix, def indent fix

Macros: deftest, is, testing, thrown?, are, when-var-exists builtins;
rewritten and/or using gensym; improved for/doseq/dotimes; cond->
and cond->> threading fixes; threading macro insert fixes

Runtime: sorted-map, sorted-set, sorted?, array-map, object-array,
hash-map, hash-set, inf, nan; assoc extended to vectors

Test runner: #?@ splice unwrapping, removed conflicting defmacro stubs
This commit is contained in:
2026-05-09 16:14:44 +03:00
parent a9ad83c509
commit e6859568b3
5 changed files with 615 additions and 265 deletions
+276 -215
View File
@@ -48,37 +48,26 @@ proc expandSyntaxQuote*(form: CljVal): CljVal =
# Process each element
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]
parts.add(expandSyntaxQuote(item))
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)])
parts.add(expandSyntaxQuote(item))
return cljList(@[cljSymbol("apply"), cljSymbol("vector"), cljList(@[cljSymbol("concat")] & parts)])
of ckMap:
# Maps: expand each key and value
return cljList(@[cljSymbol("quote"), form])
var parts: seq[CljVal] = @[]
for i in 0..<form.mapKeys.len:
parts.add(expandSyntaxQuote(form.mapKeys[i]))
parts.add(expandSyntaxQuote(form.mapVals[i]))
return cljList(@[cljSymbol("apply"), cljSymbol("hash-map"), cljList(@[cljSymbol("concat")] & parts)])
of ckSet:
var parts: seq[CljVal] = @[]
for item in form.setItems:
parts.add(expandSyntaxQuote(item))
return cljList(@[cljSymbol("apply"), cljSymbol("hash-set"), cljList(@[cljSymbol("concat")] & parts)])
else:
return cljList(@[cljSymbol("quote"), form])
return form
proc macroexpand1*(form: CljVal): CljVal =
if form.isNil: return form
@@ -119,34 +108,32 @@ proc threadingMacro(args: seq[CljVal], reverse: bool): CljVal =
raise newException(CatchableError, "Threading macro requires at least 2 arguments")
var acc = 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
let form = args[i]
if form.kind == ckList and form.items.len > 0:
if reverse:
newItems.add(acc)
acc = cljList(form.items & @[acc])
else:
newItems.insert(acc, 1)
acc = cljList(newItems)
elif step.kind == ckSymbol:
# (-> x f) => (f x)
acc = cljList(@[step, acc])
acc = cljList(@[form.items[0], acc] & form.items[1..^1])
elif form.kind == ckSymbol:
acc = cljList(@[form, acc])
else:
raise newException(CatchableError, "Threading macro requires list or symbol forms")
acc
raise newException(CatchableError, "Threading macro requires lists or symbols")
return acc
proc initBuiltinMacros*() =
# ->
proc initMacros*() =
# fn* macro
defineMacro("fn*", proc(args: seq[CljVal]): CljVal =
# fn* is used internally by reader for #()
if args.len < 2:
raise newException(CatchableError, "fn* requires params and body")
cljList(@[cljSymbol("fn")] & args)
)
# ->
# -> (thread-first)
defineMacro("->", proc(args: seq[CljVal]): CljVal =
threadingMacro(args, false)
)
# ->>
# ->> (thread-last)
defineMacro("->>", proc(args: seq[CljVal]): CljVal =
threadingMacro(args, true)
)
@@ -155,284 +142,294 @@ proc initBuiltinMacros*() =
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]
var result = cljList(@[cljSymbol("if"), args[^1],
args[^1],
cljBool(false)])
for i in countdown(args.len - 2, 0):
r = cljList(@[cljSymbol("if"), args[i], r, cljBool(false)])
r
result = cljList(@[cljSymbol("if"), args[i], result, cljBool(false)])
return result
)
# 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]
var result = args[^1]
for i in countdown(args.len - 2, 0):
r = cljList(@[cljSymbol("if"), args[i], args[i], r])
r
let gs = cljSymbol(gensymName("or_"))
result = cljList(@[cljSymbol("let"), cljVector(@[gs, args[i]]),
cljList(@[cljSymbol("if"), gs, gs, result])])
return result
)
# when-let
defineMacro("when-let", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "when-let requires binding and body")
raise newException(CatchableError, "when-let requires binding vector 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]
raise newException(CatchableError, "when-let requires [name val]")
let name = 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)])])
cljList(@[cljSymbol("when"), gs,
cljList(@[cljSymbol("let"), cljVector(@[name, 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")
if args.len < 3:
raise newException(CatchableError, "if-let requires binding vector, then, and else")
let binding = args[0]
let thenExpr = args[1]
let elseExpr = if args.len > 2: args[2] else: cljNil()
let thenBranch = args[1]
let elseBranch = args[2]
if binding.kind != ckVector or binding.items.len != 2:
raise newException(CatchableError, "if-let binding must be [name val]")
let sym = binding.items[0]
raise newException(CatchableError, "if-let requires [name val]")
let name = 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])])
cljList(@[cljSymbol("let"), cljVector(@[name, gs]), thenBranch]),
elseBranch])])
)
# when
defineMacro("when", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
if args.len < 1:
raise newException(CatchableError, "when requires condition and body")
let test = args[0]
let cond = args[0]
let body = args[1..^1]
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("do")] & body)])
cljList(@[cljSymbol("if"), cond,
cljList(@[cljSymbol("do")] & body),
cljNil()])
)
# when-not
defineMacro("when-not", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
if args.len < 1:
raise newException(CatchableError, "when-not requires condition and body")
let test = args[0]
let cond = args[0]
let body = args[1..^1]
cljList(@[cljSymbol("if"), test, cljNil(), cljList(@[cljSymbol("do")] & body)])
cljList(@[cljSymbol("if"), cond, 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)])
raise newException(CatchableError, "cond requires pairs of test/expr")
var result = cljNil()
for i in countdown((args.len div 2) - 1, 0):
let test = args[i * 2]
let expr = args[i * 2 + 1]
if test.kind == ckKeyword and test.kwName == "else":
result = expr
else:
result = cljList(@[cljSymbol("if"), test, expr, result])
return result
)
# 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])
raise newException(CatchableError, "cond-> requires initial value and clauses")
let init = args[0]
let clauses = args[1..^1]
if clauses.len mod 2 != 0:
raise newException(CatchableError, "cond-> requires pairs of test/expr")
var result = init
for i in 0..<(clauses.len div 2):
let test = clauses[i * 2]
let expr = clauses[i * 2 + 1]
let gs = cljSymbol(gensymName("ct_"))
if expr.kind == ckList and expr.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), test,
cljList(@[expr.items[0], gs] & expr.items[1..^1]),
gs])])
else:
threaded = step
forms.add(cljList(@[cljSymbol("if"), test,
cljList(@[cljSymbol("set!"), gs, threaded]),
gs]))
i += 2
forms.add(gs)
cljList(forms)
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), test,
cljList(@[expr, gs]),
gs])])
return result
)
# 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])
raise newException(CatchableError, "cond->> requires initial value and clauses")
let init = args[0]
let clauses = args[1..^1]
if clauses.len mod 2 != 0:
raise newException(CatchableError, "cond->> requires pairs of test/expr")
var result = init
for i in 0..<(clauses.len div 2):
let test = clauses[i * 2]
let expr = clauses[i * 2 + 1]
let gs = cljSymbol(gensymName("ct_"))
if expr.kind == ckList and expr.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), test,
cljList(expr.items & @[gs]),
gs])])
else:
threaded = step
forms.add(cljList(@[cljSymbol("if"), test,
cljList(@[cljSymbol("set!"), gs, threaded]),
gs]))
i += 2
forms.add(gs)
cljList(forms)
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), test,
cljList(@[expr, gs]),
gs])])
return result
)
# 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]))
if args.len < 1:
raise newException(CatchableError, "doto requires initial value and body")
let init = args[0]
let body = args[1..^1]
let gs = cljSymbol(gensymName("dt_"))
var forms: seq[CljVal] = @[]
forms.add(cljSymbol("let"))
forms.add(cljVector(@[gs, init]))
for form in body:
if form.kind == ckList and form.items.len > 0:
forms.add(cljList(@[form.items[0], gs] & form.items[1..^1]))
else:
forms.add(cljList(@[form, gs]))
forms.add(gs)
cljList(forms)
return 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 acc = expr
for i in 2..<args.len:
acc = cljList(@[cljSymbol("let"), cljVector(@[name, acc]), args[i]])
acc
raise newException(CatchableError, "as-> requires name, init, and body")
let name = args[0]
let init = args[1]
let body = args[2..^1]
var result = init
for form in body:
result = cljList(@[cljSymbol("let"), cljVector(@[name, result]), form])
return result
)
# some->
defineMacro("some->", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "some-> requires at least 2 arguments")
var acc = args[0]
for i in 1..<args.len:
let step = args[i]
if args.len < 1:
raise newException(CatchableError, "some-> requires initial value and body")
let init = args[0]
let body = args[1..^1]
var result = init
for form in body:
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])
if form.kind == ckList and form.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), gs,
cljList(@[form.items[0], gs] & form.items[1..^1]),
cljNil()])])
else:
threaded = step
acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]),
cljNil(), threaded])])
acc
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), gs,
cljList(@[form, gs]),
cljNil()])])
return result
)
# some->>
defineMacro("some->>", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "some->> requires at least 2 arguments")
var acc = 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])
if args.len < 1:
raise newException(CatchableError, "some->> requires initial value and body")
let init = args[0]
let body = args[1..^1]
var result = init
for form in body:
let gs = cljSymbol(gensymName("st_"))
if form.kind == ckList and form.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), gs,
cljList(form.items & @[gs]),
cljNil()])])
else:
threaded = step
acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]),
cljNil(), threaded])])
acc
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
cljList(@[cljSymbol("if"), gs,
cljList(@[form, gs]),
cljNil()])])
return result
)
# for (simplified — single binding only)
# for
defineMacro("for", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "for requires binding vector and body")
raise newException(CatchableError, "for requires bindings 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_"))
if bindings.kind != ckVector:
raise newException(CatchableError, "for bindings must be a vector")
let gs = cljSymbol(gensymName("fr_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, cljVector(@[])]),
cljList(@[cljSymbol("doseq"), cljVector(@[sym, coll]),
cljList(@[cljSymbol("set!"), gs,
cljList(@[cljSymbol("conj"), gs, bodyExpr])])]),
cljList(@[cljSymbol("doseq")] & @[bindings] & @[
cljList(@[cljSymbol("set!"), gs, cljList(@[cljSymbol("conj"), gs] & body)])]),
gs])
)
# doseq (simplified — single binding only)
# doseq
defineMacro("doseq", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "doseq requires binding vector and body")
raise newException(CatchableError, "doseq requires bindings 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_"))])])])])])
if bindings.kind != ckVector:
raise newException(CatchableError, "doseq bindings must be a vector")
let bindingPairs = bindings.items.len div 2
if bindingPairs == 1:
let name = bindings.items[0]
let coll = bindings.items[1]
let gs = cljSymbol(gensymName("ds_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, coll]),
cljList(@[cljSymbol("loop"), cljVector(@[name, cljList(@[cljSymbol("first"), gs]),
gs, cljList(@[cljSymbol("rest"), gs])]),
cljList(@[cljSymbol("when"), name] & body),
cljList(@[cljSymbol("recur"), cljList(@[cljSymbol("first"), gs]),
cljList(@[cljSymbol("rest"), gs])])])])
else:
# Nested doseq
let name = bindings.items[0]
let coll = bindings.items[1]
let restBindings = cljVector(bindings.items[2..^1])
cljList(@[cljSymbol("doseq"), cljVector(@[name, coll]),
cljList(@[cljSymbol("doseq"), restBindings] & body)])
)
# 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]
raise newException(CatchableError, "dotimes requires binding and body")
let binding = 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)
if binding.kind != ckVector or binding.items.len != 2:
raise newException(CatchableError, "dotimes requires [name count]")
let name = binding.items[0]
let count = binding.items[1]
let gs = cljSymbol(gensymName("dt_"))
cljList(@[cljSymbol("let"), cljVector(@[gs, count]),
cljList(@[cljSymbol("loop"), cljVector(@[name, cljInt(0)]),
cljList(@[cljSymbol("when"), cljList(@[cljSymbol("<"), name, gs])] & body),
cljList(@[cljSymbol("recur"), cljList(@[cljSymbol("+"), name, cljInt(1)])])])])
)
# defmacro
defineMacro("defmacro", proc(args: seq[CljVal]): CljVal =
# defmacro is handled specially in the compiler, this is a no-op
cljNil()
if args.len < 3:
raise newException(CatchableError, "defmacro requires name, params, and body")
let name = args[0]
let params = args[1]
let body = args[2..^1]
cljList(@[cljSymbol("def"), name,
cljList(@[cljSymbol("fn"), params] & body)])
)
# comment
@@ -464,3 +461,67 @@ proc initBuiltinMacros*() =
cljList(@[cljSymbol("finally"),
cljList(@[cljSymbol(".close"), gs])])])])
)
# clojure.test macros
defineMacro("deftest", proc(args: seq[CljVal]): CljVal =
if args.len == 0:
raise newException(CatchableError, "deftest requires name and body")
let name = args[0]
let body = args[1..^1]
cljList(@[cljSymbol("def"), name,
cljList(@[cljSymbol("fn"), cljVector(@[])] & body)])
)
defineMacro("is", proc(args: seq[CljVal]): CljVal =
# is is a no-op for compilation tests
cljNil()
)
defineMacro("testing", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil()
cljList(@[cljSymbol("do")] & args[1..^1])
)
defineMacro("thrown?", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil()
let form = args[0]
cljList(@[cljSymbol("try"), form, cljBool(false),
cljList(@[cljSymbol("catch"), cljSymbol("Exception"), cljSymbol("e"), cljBool(true)])])
)
defineMacro("are", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "are requires argv and test cases")
let argv = args[0]
let testExpr = args[1]
let exprs = args[2..^1]
if argv.kind != ckVector:
raise newException(CatchableError, "are argv must be a vector")
let arity = argv.items.len
if exprs.len mod arity != 0:
raise newException(CatchableError, "are test cases must match argv arity")
var isForms: seq[CljVal] = @[]
for i in countup(0, exprs.len - arity, arity):
var bindings: seq[CljVal] = @[]
for j in 0..<arity:
bindings.add(argv.items[j])
bindings.add(exprs[i+j])
let letForm = cljList(@[cljSymbol("let"), cljVector(bindings),
cljList(@[cljSymbol("is"), testExpr])])
isForms.add(letForm)
if isForms.len == 1:
return isForms[0]
return cljList(@[cljSymbol("do")] & isForms)
)
defineMacro("when-var-exists", proc(args: seq[CljVal]): CljVal =
if args.len < 2:
raise newException(CatchableError, "when-var-exists requires symbol and body")
let body = args[1..^1]
if body.len == 1:
return body[0]
return cljList(@[cljSymbol("do")] & body)
)
proc initBuiltinMacros*() =
initMacros()