Files
bara-lang/tests/test_macros.nim
T
dimgigov ba0b300917 feat: add fullscreen TUI and project updates
- New TUI screens: Main Menu, Compile, Run, REPL, AI Generator, AI Settings, Help
- AI configuration persisted in ~/.config/cljnim/config.json
- Added illwill dependency for terminal UI
- Updated experiments, examples, docs, and core modules
2026-05-09 01:53:23 +03:00

355 lines
11 KiB
Nim

import unittest, tables
import ../src/types
import ../src/macros
# Helper: check that a value is a list with given head symbol
proc isListWithHead(v: CljVal, head: string): bool =
v.kind == ckList and v.items.len > 0 and v.items[0].kind == ckSymbol and v.items[0].symName == head
# Helper: check that a value is a symbol with given name
proc isSym(v: CljVal, name: string): bool =
v.kind == ckSymbol and v.symName == name
suite "Macros - initBuiltinMacros":
setup:
initBuiltinMacros()
suite "Macros - -> thread-first":
setup:
initBuiltinMacros()
test "-> with symbol step":
let form = cljList(@[cljSymbol("->"), cljInt(5), cljSymbol("inc")])
let expanded = macroexpand(form)
check expanded.isListWithHead("inc")
check expanded.items.len == 2
check expanded.items[1].kind == ckInt
check expanded.items[1].intVal == 5
test "-> with list step":
let form = cljList(@[cljSymbol("->"), cljInt(5),
cljList(@[cljSymbol("+"), cljInt(3)])])
let expanded = macroexpand(form)
check expanded.isListWithHead("+")
check expanded.items.len == 3
check expanded.items[1].kind == ckInt
check expanded.items[1].intVal == 5
check expanded.items[2].kind == ckInt
check expanded.items[2].intVal == 3
test "-> chained":
let form = cljList(@[cljSymbol("->"), cljInt(5),
cljSymbol("inc"),
cljList(@[cljSymbol("*"), cljInt(2)])])
let expanded = macroexpand(form)
check expanded.isListWithHead("*")
check expanded.items.len == 3
check expanded.items[1].isListWithHead("inc")
check expanded.items[1].items.len == 2
check expanded.items[1].items[1].intVal == 5
check expanded.items[2].intVal == 2
test "-> too few args raises":
let form = cljList(@[cljSymbol("->"), cljInt(5)])
expect CatchableError:
discard macroexpand(form)
suite "Macros - ->> thread-last":
setup:
initBuiltinMacros()
test "->> with list step":
let form = cljList(@[cljSymbol("->>"), cljInt(5),
cljList(@[cljSymbol("+"), cljInt(3)])])
let expanded = macroexpand(form)
check expanded.isListWithHead("+")
check expanded.items.len == 3
check expanded.items[1].intVal == 3
check expanded.items[2].intVal == 5
test "->> chained":
let form = cljList(@[cljSymbol("->>"), cljInt(5),
cljList(@[cljSymbol("conj"), cljInt(1)])])
let expanded = macroexpand(form)
check expanded.isListWithHead("conj")
check expanded.items[1].intVal == 1
check expanded.items[2].intVal == 5
suite "Macros - and (not expanded by macroexpand)":
setup:
initBuiltinMacros()
test "and is left untouched by macroexpand":
let form = cljList(@[cljSymbol("and"), cljSymbol("a"), cljSymbol("b")])
let expanded = macroexpand(form)
check expanded.isListWithHead("and")
check expanded.items.len == 3
test "and macro implementation directly":
let andFn = macroTable["and"]
let result = andFn(@[cljSymbol("a"), cljSymbol("b")])
check result.isListWithHead("if")
check result.items.len == 4
check result.items[1].isSym("a")
check result.items[2].isSym("b")
check result.items[3].kind == ckBool
check result.items[3].boolVal == false
test "and empty directly":
let andFn = macroTable["and"]
let result = andFn(@[])
check result.kind == ckBool
check result.boolVal == true
test "and single directly":
let andFn = macroTable["and"]
let result = andFn(@[cljSymbol("x")])
check result.isSym("x")
test "and three args directly":
let andFn = macroTable["and"]
let result = andFn(@[cljSymbol("a"), cljSymbol("b"), cljSymbol("c")])
check result.isListWithHead("if")
check result.items[1].isSym("a")
check result.items[2].isListWithHead("if")
check result.items[3].kind == ckBool
check result.items[3].boolVal == false
suite "Macros - or (not expanded by macroexpand)":
setup:
initBuiltinMacros()
test "or is left untouched by macroexpand":
let form = cljList(@[cljSymbol("or"), cljSymbol("a"), cljSymbol("b")])
let expanded = macroexpand(form)
check expanded.isListWithHead("or")
test "or empty directly":
let orFn = macroTable["or"]
let result = orFn(@[])
check result.kind == ckNil
test "or single directly":
let orFn = macroTable["or"]
let result = orFn(@[cljSymbol("x")])
check result.isSym("x")
test "or two args directly":
let orFn = macroTable["or"]
let result = orFn(@[cljSymbol("a"), cljSymbol("b")])
check result.isListWithHead("if")
check result.items.len == 4
check result.items[1].isSym("a")
check result.items[2].isSym("a")
check result.items[3].isSym("b")
suite "Macros - when (not expanded by macroexpand)":
setup:
initBuiltinMacros()
test "when is left untouched by macroexpand":
let form = cljList(@[cljSymbol("when"), cljSymbol("x"), cljInt(1)])
let expanded = macroexpand(form)
check expanded.isListWithHead("when")
test "when macro directly":
let whenFn = macroTable["when"]
let result = whenFn(@[cljSymbol("x"), cljInt(1), cljInt(2)])
check result.isListWithHead("if")
check result.items.len == 3
check result.items[1].isSym("x")
check result.items[2].isListWithHead("do")
check result.items[2].items.len == 3
test "when too few args raises":
expect CatchableError:
discard macroTable["when"](@[cljSymbol("x")])
suite "Macros - when-not":
setup:
initBuiltinMacros()
test "when-not expands to if":
let form = cljList(@[cljSymbol("when-not"), cljSymbol("x"), cljInt(1)])
let expanded = macroexpand(form)
check expanded.isListWithHead("if")
check expanded.items.len == 4
check expanded.items[1].isSym("x")
check expanded.items[2].kind == ckNil
check expanded.items[3].isListWithHead("do")
suite "Macros - cond (not expanded by macroexpand)":
setup:
initBuiltinMacros()
test "cond is left untouched by macroexpand":
let form = cljList(@[cljSymbol("cond"), cljSymbol("a"), cljInt(1)])
let expanded = macroexpand(form)
check expanded.isListWithHead("cond")
test "cond empty directly":
let condFn = macroTable["cond"]
let result = condFn(@[])
check result.kind == ckNil
test "cond single clause directly":
let condFn = macroTable["cond"]
let result = condFn(@[cljSymbol("a"), cljInt(1)])
check result.isListWithHead("if")
check result.items.len == 3
check result.items[1].isSym("a")
check result.items[2].intVal == 1
test "cond two clauses directly":
let condFn = macroTable["cond"]
let result = condFn(@[cljSymbol("a"), cljInt(1), cljSymbol("b"), cljInt(2)])
check result.isListWithHead("if")
check result.items.len == 4
check result.items[1].isSym("a")
check result.items[2].intVal == 1
check result.items[3].isListWithHead("cond")
test "cond with :else directly":
let condFn = macroTable["cond"]
let result = condFn(@[cljKeyword("else"), cljInt(42)])
check result.kind == ckInt
check result.intVal == 42
test "cond odd args raises":
expect CatchableError:
discard macroTable["cond"](@[cljSymbol("a")])
suite "Macros - as->":
setup:
initBuiltinMacros()
test "as-> basic":
let form = cljList(@[cljSymbol("as->"), cljInt(5), cljSymbol("x"),
cljList(@[cljSymbol("inc"), cljSymbol("x")])])
let expanded = macroexpand(form)
check expanded.isListWithHead("let")
check expanded.items.len == 3
check expanded.items[1].kind == ckVector
check expanded.items[1].items.len == 2
check expanded.items[1].items[0].isSym("x")
check expanded.items[1].items[1].intVal == 5
check expanded.items[2].isListWithHead("inc")
suite "Macros - doto":
setup:
initBuiltinMacros()
test "doto creates let":
let form = cljList(@[cljSymbol("doto"), cljString("hello"),
cljList(@[cljSymbol("toUpper")])])
let expanded = macroexpand(form)
check expanded.isListWithHead("let")
check expanded.items.len == 4 # let, [gs "hello"], (toUpper gs), gs
check expanded.items[1].kind == ckVector
check expanded.items[1].items.len == 2
check expanded.items[1].items[1].kind == ckString
suite "Macros - some->":
setup:
initBuiltinMacros()
test "some-> wraps in nil? check":
let form = cljList(@[cljSymbol("some->"), cljInt(5),
cljList(@[cljSymbol("inc")])])
let expanded = macroexpand(form)
check expanded.isListWithHead("let")
check expanded.items.len == 3
check expanded.items[2].isListWithHead("if")
check expanded.items[2].items[1].isListWithHead("nil?")
suite "Macros - some->>":
setup:
initBuiltinMacros()
test "some->> appends to list":
let form = cljList(@[cljSymbol("some->>"), cljInt(5),
cljList(@[cljSymbol("conj"), cljInt(1)])])
let expanded = macroexpand(form)
check expanded.isListWithHead("let")
suite "Macros - when-let":
setup:
initBuiltinMacros()
test "when-let expands via macroexpand":
let form = cljList(@[cljSymbol("when-let"),
cljVector(@[cljSymbol("x"), cljInt(5)]),
cljInt(42)])
let expanded = macroexpand(form)
check expanded.isListWithHead("let")
test "when-let macro directly":
let whenLetFn = macroTable["when-let"]
let result = whenLetFn(@[
cljVector(@[cljSymbol("x"), cljInt(5)]),
cljInt(42)])
check result.isListWithHead("let")
check result.items.len == 3
suite "Macros - macroexpand1":
setup:
initBuiltinMacros()
test "macroexpand1 returns form for non-macro":
let form = cljList(@[cljSymbol("+"), cljInt(1), cljInt(2)])
let expanded = macroexpand1(form)
check expanded.isListWithHead("+")
test "macroexpand1 expands -> macro":
let form = cljList(@[cljSymbol("->"), cljInt(5), cljSymbol("inc")])
let expanded = macroexpand1(form)
check expanded.isListWithHead("inc")
test "macroexpand1 leaves special forms alone":
let form = cljList(@[cljSymbol("def"), cljSymbol("x"), cljInt(1)])
let expanded = macroexpand1(form)
check expanded.isListWithHead("def")
test "macroexpand1 leaves and alone":
let form = cljList(@[cljSymbol("and"), cljSymbol("a"), cljSymbol("b")])
let expanded = macroexpand1(form)
check expanded.isListWithHead("and")
suite "Macros - macroexpand":
setup:
initBuiltinMacros()
test "macroexpand fully expands chained ->":
let form = cljList(@[cljSymbol("->"), cljInt(5),
cljSymbol("inc"),
cljList(@[cljSymbol("*"), cljInt(2)])])
let expanded = macroexpand(form)
check expanded.isListWithHead("*")
check expanded.items[1].isListWithHead("inc")
suite "Macros - syntax-quote":
setup:
initBuiltinMacros()
test "syntax-quote on symbol returns quoted symbol":
let form = cljList(@[cljSymbol("syntax-quote"), cljSymbol("x")])
let expanded = macroexpand1(form)
check expanded.isListWithHead("quote")
check expanded.items[1].isSym("x")
test "syntax-quote on list returns concat":
let form = cljList(@[cljSymbol("syntax-quote"),
cljList(@[cljSymbol("inc"), cljSymbol("x")])])
let expanded = macroexpand1(form)
check expanded.isListWithHead("concat")
suite "Macros - fn*":
setup:
initBuiltinMacros()
test "fn* wraps to fn":
let form = cljList(@[cljSymbol("fn*"), cljVector(@[cljSymbol("x")]), cljSymbol("x")])
let expanded = macroexpand1(form)
check expanded.isListWithHead("fn")
check expanded.items.len == 3