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:
+57
-2
@@ -1,6 +1,6 @@
|
|||||||
import cljnim_pvec
|
import cljnim_pvec
|
||||||
import cljnim_pmap
|
import cljnim_pmap
|
||||||
import strutils, sequtils, hashes, algorithm, os, osproc, locks
|
import strutils, sequtils, hashes, algorithm, os, osproc, locks, math
|
||||||
|
|
||||||
type
|
type
|
||||||
CljKind* = enum
|
CljKind* = enum
|
||||||
@@ -79,6 +79,8 @@ proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
|
|||||||
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
|
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
|
||||||
proc cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64)
|
proc cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64)
|
||||||
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
|
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
|
||||||
|
proc cljInf*(args: seq[CljVal]): CljVal = CljVal(kind: ckFloat, floatVal: Inf)
|
||||||
|
proc cljNaN*(args: seq[CljVal]): CljVal = CljVal(kind: ckFloat, floatVal: NaN)
|
||||||
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
|
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
|
||||||
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
|
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
|
||||||
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
|
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
|
||||||
@@ -104,6 +106,20 @@ proc cljSet*(items: seq[CljVal]): CljVal =
|
|||||||
s = pmapAssoc(s, item, true, hash(item), cljEq)
|
s = pmapAssoc(s, item, true, hash(item), cljEq)
|
||||||
CljVal(kind: ckSet, setData: s)
|
CljVal(kind: ckSet, setData: s)
|
||||||
|
|
||||||
|
proc cljHashMap*(args: seq[CljVal]): CljVal =
|
||||||
|
if args.len mod 2 != 0:
|
||||||
|
raise newException(CatchableError, "hash-map requires even number of arguments")
|
||||||
|
var m = newPersistentMap[CljVal, CljVal]()
|
||||||
|
for i in countup(0, args.len - 1, 2):
|
||||||
|
m = pmapAssoc(m, args[i], args[i+1], hash(args[i]), cljEq)
|
||||||
|
CljVal(kind: ckMap, mapData: m)
|
||||||
|
|
||||||
|
proc cljHashSet*(args: seq[CljVal]): CljVal =
|
||||||
|
var s = newPersistentMap[CljVal, bool]()
|
||||||
|
for item in args:
|
||||||
|
s = pmapAssoc(s, item, true, hash(item), cljEq)
|
||||||
|
CljVal(kind: ckSet, setData: s)
|
||||||
|
|
||||||
# ---- Display ----
|
# ---- Display ----
|
||||||
|
|
||||||
proc cljRepr*(v: CljVal): string =
|
proc cljRepr*(v: CljVal): string =
|
||||||
@@ -686,6 +702,30 @@ proc cljVec*(v: CljVal): CljVal =
|
|||||||
of ckVector: v
|
of ckVector: v
|
||||||
else: cljVector(@[v])
|
else: cljVector(@[v])
|
||||||
|
|
||||||
|
proc cljObjectArray*(n: CljVal): CljVal =
|
||||||
|
var size = 0
|
||||||
|
if not n.isNil and n.kind == ckInt:
|
||||||
|
size = n.intVal.int
|
||||||
|
var items: seq[CljVal] = @[]
|
||||||
|
for i in 0..<size:
|
||||||
|
items.add(cljNil())
|
||||||
|
return cljList(items)
|
||||||
|
|
||||||
|
proc cljSortedMap*(args: seq[CljVal]): CljVal =
|
||||||
|
# Fallback to regular hash-map
|
||||||
|
cljHashMap(args)
|
||||||
|
|
||||||
|
proc cljSortedSet*(args: seq[CljVal]): CljVal =
|
||||||
|
# Fallback to regular hash-set
|
||||||
|
cljHashSet(args)
|
||||||
|
|
||||||
|
proc cljArrayMap*(args: seq[CljVal]): CljVal =
|
||||||
|
# Fallback to regular hash-map
|
||||||
|
cljHashMap(args)
|
||||||
|
|
||||||
|
proc cljSortedQ*(v: CljVal): CljVal =
|
||||||
|
cljBool(false)
|
||||||
|
|
||||||
proc cljEmpty*(v: CljVal): CljVal =
|
proc cljEmpty*(v: CljVal): CljVal =
|
||||||
cljBool(cljCount(v).intVal == 0)
|
cljBool(cljCount(v).intVal == 0)
|
||||||
|
|
||||||
@@ -852,9 +892,24 @@ proc cljGetDefault*(m: CljVal, key: CljVal, default: CljVal): CljVal =
|
|||||||
else: default
|
else: default
|
||||||
|
|
||||||
proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal =
|
proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal =
|
||||||
if m.isNil or m.kind != ckMap:
|
if m.isNil or m.kind == ckNil:
|
||||||
return CljVal(kind: ckMap, mapData: pmapAssoc(newPersistentMap[CljVal, CljVal](), key, val, hash(key), cljEq))
|
return CljVal(kind: ckMap, mapData: pmapAssoc(newPersistentMap[CljVal, CljVal](), key, val, hash(key), cljEq))
|
||||||
|
case m.kind
|
||||||
|
of ckMap:
|
||||||
CljVal(kind: ckMap, mapData: pmapAssoc(m.mapData, key, val, hash(key), cljEq))
|
CljVal(kind: ckMap, mapData: pmapAssoc(m.mapData, key, val, hash(key), cljEq))
|
||||||
|
of ckVector:
|
||||||
|
if key.kind != ckInt:
|
||||||
|
raise newException(CatchableError, "assoc on vector requires integer index")
|
||||||
|
let idx = key.intVal
|
||||||
|
let cnt = m.vecData.count
|
||||||
|
if idx < 0 or idx > cnt:
|
||||||
|
raise newException(CatchableError, "Index out of bounds: " & $idx)
|
||||||
|
if idx == cnt:
|
||||||
|
CljVal(kind: ckVector, vecData: pvecConj(m.vecData, val))
|
||||||
|
else:
|
||||||
|
CljVal(kind: ckVector, vecData: pvecAssoc(m.vecData, idx, val))
|
||||||
|
else:
|
||||||
|
raise newException(CatchableError, "assoc expects a map or vector")
|
||||||
|
|
||||||
proc cljDissoc*(m: CljVal, key: CljVal): CljVal =
|
proc cljDissoc*(m: CljVal, key: CljVal): CljVal =
|
||||||
if m.isNil or m.kind != ckMap: return cljMap(@[], @[])
|
if m.isNil or m.kind != ckMap: return cljMap(@[], @[])
|
||||||
|
|||||||
+156
-25
@@ -7,6 +7,7 @@ var requiredImports* = initHashSet[string]()
|
|||||||
var emitLibMode* = false
|
var emitLibMode* = false
|
||||||
var loopStack*: seq[seq[string]] = @[]
|
var loopStack*: seq[seq[string]] = @[]
|
||||||
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
||||||
|
var scopeStack*: seq[HashSet[string]] = @[]
|
||||||
|
|
||||||
proc setNsAliases*(aliases: seq[(string, string)]) =
|
proc setNsAliases*(aliases: seq[(string, string)]) =
|
||||||
nsAliases = aliases
|
nsAliases = aliases
|
||||||
@@ -42,7 +43,50 @@ proc mangleName*(name: string): string =
|
|||||||
of '\'': result.add("_QUOTE_")
|
of '\'': result.add("_QUOTE_")
|
||||||
of '&': result.add("_AMP_")
|
of '&': result.add("_AMP_")
|
||||||
of '#': result.add("_HASH")
|
of '#': result.add("_HASH")
|
||||||
|
of '%': result.add("pct")
|
||||||
else: result.add(c)
|
else: result.add(c)
|
||||||
|
# Nim rejects trailing underscores
|
||||||
|
while result.len > 0 and result[^1] == '_':
|
||||||
|
result = result[0..^2]
|
||||||
|
# Nim rejects double underscores in identifiers
|
||||||
|
while result.find("__") != -1:
|
||||||
|
result = result.replace("__", "_")
|
||||||
|
# Nim 2.x only allows bare `_` as identifier starting with underscore
|
||||||
|
if result.len > 1 and result[0] == '_':
|
||||||
|
result = "x" & result
|
||||||
|
# Nim identifiers cannot start with a digit
|
||||||
|
if result.len > 0 and result[0] in {'0'..'9'}:
|
||||||
|
result = "x" & result
|
||||||
|
if result.len == 0:
|
||||||
|
result = "val"
|
||||||
|
# Nim keywords that would clash
|
||||||
|
let nimKeywords = ["in", "out", "var", "let", "proc", "func", "type", "ref", "ptr",
|
||||||
|
"object", "method", "template", "macro", "iterator", "converter",
|
||||||
|
"discard", "return", "break", "continue", "if", "else", "elif",
|
||||||
|
"when", "case", "of", "for", "while", "try", "except", "finally",
|
||||||
|
"raise", "import", "export", "from", "include", "using", "bind",
|
||||||
|
"mixin", "asm", "defer", "block", "static", "yield", "assert",
|
||||||
|
"do", "enum", "tuple", "shared", "guard", "concept", "distinct",
|
||||||
|
"interface", "lambda", "open", "quit", "result", "nil"]
|
||||||
|
if result in nimKeywords:
|
||||||
|
result = result & "1"
|
||||||
|
|
||||||
|
proc pushScope() =
|
||||||
|
scopeStack.add(initHashSet[string]())
|
||||||
|
|
||||||
|
proc popScope() =
|
||||||
|
if scopeStack.len > 0:
|
||||||
|
discard scopeStack.pop()
|
||||||
|
|
||||||
|
proc addToScope(name: string) =
|
||||||
|
if scopeStack.len > 0:
|
||||||
|
scopeStack[^1].incl(name)
|
||||||
|
|
||||||
|
proc isLocalVar(name: string): bool =
|
||||||
|
for s in scopeStack:
|
||||||
|
if name in s:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
proc emitExpr*(v: CljVal, indent: int = 0): string
|
proc emitExpr*(v: CljVal, indent: int = 0): string
|
||||||
proc emitBlock*(items: seq[CljVal], indent: int, useResult: bool = false): string
|
proc emitBlock*(items: seq[CljVal], indent: int, useResult: bool = false): string
|
||||||
@@ -51,6 +95,19 @@ proc emitQuotedForm*(v: CljVal): string
|
|||||||
proc indentStr(indent: int): string =
|
proc indentStr(indent: int): string =
|
||||||
" ".repeat(indent)
|
" ".repeat(indent)
|
||||||
|
|
||||||
|
proc indentCode(code: string, extra: int): string =
|
||||||
|
if extra <= 0: return code
|
||||||
|
let prefix = indentStr(extra)
|
||||||
|
var result = ""
|
||||||
|
var lines = code.split("\n")
|
||||||
|
for i, line in lines:
|
||||||
|
if i > 0: result.add("\n")
|
||||||
|
if line.len > 0:
|
||||||
|
result.add(prefix & line)
|
||||||
|
else:
|
||||||
|
result.add(line)
|
||||||
|
return result
|
||||||
|
|
||||||
proc runtimeName(op: string): string =
|
proc runtimeName(op: string): string =
|
||||||
case op
|
case op
|
||||||
of "+": "cljAdd"
|
of "+": "cljAdd"
|
||||||
@@ -108,6 +165,8 @@ proc runtimeName(op: string): string =
|
|||||||
of "contains?": "cljContains"
|
of "contains?": "cljContains"
|
||||||
of "select-keys": "cljSelectKeys"
|
of "select-keys": "cljSelectKeys"
|
||||||
of "merge": "cljMerge"
|
of "merge": "cljMerge"
|
||||||
|
of "hash-map": "cljHashMap"
|
||||||
|
of "hash-set": "cljHashSet"
|
||||||
of "identity": "cljIdentity"
|
of "identity": "cljIdentity"
|
||||||
of "type": "cljType"
|
of "type": "cljType"
|
||||||
of "abs": "cljAbs"
|
of "abs": "cljAbs"
|
||||||
@@ -120,6 +179,11 @@ proc runtimeName(op: string): string =
|
|||||||
of "reset!": "cljReset"
|
of "reset!": "cljReset"
|
||||||
of "swap!": "cljSwap"
|
of "swap!": "cljSwap"
|
||||||
of "zipmap": "cljZipmap"
|
of "zipmap": "cljZipmap"
|
||||||
|
of "object-array": "cljObjectArray"
|
||||||
|
of "sorted-map": "cljSortedMap"
|
||||||
|
of "sorted-set": "cljSortedSet"
|
||||||
|
of "sorted?": "cljSortedQ"
|
||||||
|
of "array-map": "cljArrayMap"
|
||||||
# ---- Type predicates ----
|
# ---- Type predicates ----
|
||||||
of "nil?": "cljIsNilP"
|
of "nil?": "cljIsNilP"
|
||||||
of "some?": "cljIsSome"
|
of "some?": "cljIsSome"
|
||||||
@@ -143,10 +207,12 @@ proc runtimeName(op: string): string =
|
|||||||
# ---- Keyword/Symbol ops ----
|
# ---- Keyword/Symbol ops ----
|
||||||
of "keyword": "cljKeywordFn"
|
of "keyword": "cljKeywordFn"
|
||||||
of "symbol": "cljSymbolFn"
|
of "symbol": "cljSymbolFn"
|
||||||
|
of "inf": "cljInf"
|
||||||
|
of "nan": "cljNaN"
|
||||||
of "name": "cljName"
|
of "name": "cljName"
|
||||||
of "namespace": "cljNamespace"
|
of "namespace": "cljNamespace"
|
||||||
of "key": "cljKey"
|
# of "key": "cljKey"
|
||||||
of "val": "cljEntryVal"
|
# of "val": "cljEntryVal"
|
||||||
# ---- File operations ----
|
# ---- File operations ----
|
||||||
of "file/read": "cljFileRead"
|
of "file/read": "cljFileRead"
|
||||||
of "file/write": "cljFileWrite"
|
of "file/write": "cljFileWrite"
|
||||||
@@ -355,7 +421,12 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
let name = items[1]
|
let name = items[1]
|
||||||
if name.kind != ckSymbol:
|
if name.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "def name must be a symbol")
|
raise newException(EmitterError, "def name must be a symbol")
|
||||||
return sp & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], indent)
|
let mangled = mangleName(name.symName)
|
||||||
|
let valCode = emitExpr(items[2], indent)
|
||||||
|
if indent == 0:
|
||||||
|
return sp & "let " & mangled & " = " & valCode
|
||||||
|
else:
|
||||||
|
return sp & "((proc (): CljVal =\n" & indentStr(indent + 1) & "let " & mangled & " = " & valCode & "\n" & indentStr(indent + 1) & mangled & ")())"
|
||||||
|
|
||||||
of "defn":
|
of "defn":
|
||||||
if items.len < 4:
|
if items.len < 4:
|
||||||
@@ -367,9 +438,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
if params.kind != ckVector:
|
if params.kind != ckVector:
|
||||||
raise newException(EmitterError, "defn params must be a vector")
|
raise newException(EmitterError, "defn params must be a vector")
|
||||||
var paramNames: seq[string] = @[]
|
var paramNames: seq[string] = @[]
|
||||||
|
pushScope()
|
||||||
for p in params.items:
|
for p in params.items:
|
||||||
if p.kind != ckSymbol:
|
if p.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "defn params must be symbols")
|
raise newException(EmitterError, "defn params must be symbols")
|
||||||
|
addToScope(p.symName)
|
||||||
paramNames.add(mangleName(p.symName) & ": CljVal")
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
||||||
let body = items[3..^1]
|
let body = items[3..^1]
|
||||||
var bodyCode = ""
|
var bodyCode = ""
|
||||||
@@ -377,6 +450,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
bodyCode = emitExpr(body[0], indent + 1)
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
else:
|
else:
|
||||||
bodyCode = emitBlock(body, indent + 1)
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
|
popScope()
|
||||||
let procName = mangleName(name.symName)
|
let procName = mangleName(name.symName)
|
||||||
let exportMarker = if emitLibMode: "*" else: ""
|
let exportMarker = if emitLibMode: "*" else: ""
|
||||||
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||||
@@ -391,9 +465,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
if params.kind != ckVector:
|
if params.kind != ckVector:
|
||||||
raise newException(EmitterError, "defn- params must be a vector")
|
raise newException(EmitterError, "defn- params must be a vector")
|
||||||
var paramNames: seq[string] = @[]
|
var paramNames: seq[string] = @[]
|
||||||
|
pushScope()
|
||||||
for p in params.items:
|
for p in params.items:
|
||||||
if p.kind != ckSymbol:
|
if p.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "defn- params must be symbols")
|
raise newException(EmitterError, "defn- params must be symbols")
|
||||||
|
addToScope(p.symName)
|
||||||
paramNames.add(mangleName(p.symName) & ": CljVal")
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
||||||
let body = items[3..^1]
|
let body = items[3..^1]
|
||||||
var bodyCode = ""
|
var bodyCode = ""
|
||||||
@@ -401,6 +477,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
bodyCode = emitExpr(body[0], indent + 1)
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
else:
|
else:
|
||||||
bodyCode = emitBlock(body, indent + 1)
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
|
popScope()
|
||||||
let procName = mangleName(name.symName)
|
let procName = mangleName(name.symName)
|
||||||
return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode
|
return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode
|
||||||
|
|
||||||
@@ -411,9 +488,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
if params.kind != ckVector:
|
if params.kind != ckVector:
|
||||||
raise newException(EmitterError, "fn params must be a vector")
|
raise newException(EmitterError, "fn params must be a vector")
|
||||||
var paramNames: seq[string] = @[]
|
var paramNames: seq[string] = @[]
|
||||||
|
pushScope()
|
||||||
for p in params.items:
|
for p in params.items:
|
||||||
if p.kind != ckSymbol:
|
if p.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "fn params must be symbols")
|
raise newException(EmitterError, "fn params must be symbols")
|
||||||
|
addToScope(p.symName)
|
||||||
paramNames.add(mangleName(p.symName) & ": CljVal")
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
||||||
let body = items[2..^1]
|
let body = items[2..^1]
|
||||||
var bodyCode = ""
|
var bodyCode = ""
|
||||||
@@ -421,7 +500,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
bodyCode = emitExpr(body[0], indent + 1)
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
else:
|
else:
|
||||||
bodyCode = emitBlock(body, indent + 1)
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
return sp & "(proc(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & ")"
|
popScope()
|
||||||
|
if bodyCode.find("\n") == -1:
|
||||||
|
return sp & "(proc (" & paramNames.join(", ") & "): CljVal = " & bodyCode.strip() & ")"
|
||||||
|
else:
|
||||||
|
return sp & "(proc (" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & ")"
|
||||||
|
|
||||||
of "let":
|
of "let":
|
||||||
if items.len < 3:
|
if items.len < 3:
|
||||||
@@ -433,12 +516,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
raise newException(EmitterError, "let bindings must have even number of elements")
|
raise newException(EmitterError, "let bindings must have even number of elements")
|
||||||
var lines: seq[string] = @[]
|
var lines: seq[string] = @[]
|
||||||
lines.add(sp & "block:")
|
lines.add(sp & "block:")
|
||||||
|
pushScope()
|
||||||
var bi = 0
|
var bi = 0
|
||||||
while bi < bindings.items.len:
|
while bi < bindings.items.len:
|
||||||
let bname = bindings.items[bi]
|
let bname = bindings.items[bi]
|
||||||
let bval = bindings.items[bi+1]
|
let bval = bindings.items[bi+1]
|
||||||
if bname.kind != ckSymbol:
|
if bname.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "let binding name must be a symbol")
|
raise newException(EmitterError, "let binding name must be a symbol")
|
||||||
|
addToScope(bname.symName)
|
||||||
lines.add(indentStr(indent + 1) & "let " & mangleName(bname.symName) & " = " & emitExpr(bval, 0))
|
lines.add(indentStr(indent + 1) & "let " & mangleName(bname.symName) & " = " & emitExpr(bval, 0))
|
||||||
bi += 2
|
bi += 2
|
||||||
let body = items[2..^1]
|
let body = items[2..^1]
|
||||||
@@ -454,6 +539,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
lines.add(bcode)
|
lines.add(bcode)
|
||||||
else:
|
else:
|
||||||
lines.add(indentStr(indent + 1) & "discard " & stripped)
|
lines.add(indentStr(indent + 1) & "discard " & stripped)
|
||||||
|
popScope()
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
|
|
||||||
of "if":
|
of "if":
|
||||||
@@ -671,6 +757,23 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
raise newException(EmitterError, "throw requires exactly 1 argument")
|
raise newException(EmitterError, "throw requires exactly 1 argument")
|
||||||
return sp & "raise newException(CatchableError, cljStr(" & emitExpr(items[1], 0) & "))"
|
return sp & "raise newException(CatchableError, cljStr(" & emitExpr(items[1], 0) & "))"
|
||||||
|
|
||||||
|
of "when-var-exists":
|
||||||
|
if items.len < 3:
|
||||||
|
raise newException(EmitterError, "when-var-exists requires a symbol and body")
|
||||||
|
let varSym = items[1]
|
||||||
|
if varSym.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "when-var-exists requires a symbol as first argument")
|
||||||
|
let symName = resolveNsAlias(varSym.symName)
|
||||||
|
let exists = runtimeName(symName).len > 0 or isLocalVar(symName)
|
||||||
|
if exists:
|
||||||
|
let body = items[2..^1]
|
||||||
|
if body.len == 1:
|
||||||
|
return emitExpr(body[0], indent)
|
||||||
|
else:
|
||||||
|
return emitBlock(body, indent)
|
||||||
|
else:
|
||||||
|
return sp & "cljNil()"
|
||||||
|
|
||||||
of "quote":
|
of "quote":
|
||||||
if items.len != 2:
|
if items.len != 2:
|
||||||
raise newException(EmitterError, "quote requires exactly 1 argument")
|
raise newException(EmitterError, "quote requires exactly 1 argument")
|
||||||
@@ -767,9 +870,18 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
return sp & "cljMapv(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
return sp & "cljMapv(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
||||||
|
|
||||||
of "apply":
|
of "apply":
|
||||||
if items.len != 3:
|
if items.len < 3:
|
||||||
raise newException(EmitterError, "apply requires exactly 2 arguments")
|
raise newException(EmitterError, "apply requires at least 2 arguments")
|
||||||
|
if items.len == 3:
|
||||||
return sp & "cljApply(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
return sp & "cljApply(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")"
|
||||||
|
else:
|
||||||
|
# (apply f x y args) -> (apply (partial f x y) args)
|
||||||
|
let fnArg = emitExpr(items[1], 0)
|
||||||
|
var partialArgs: seq[string] = @[]
|
||||||
|
for i in 2..<items.len - 1:
|
||||||
|
partialArgs.add(emitExpr(items[i], 0))
|
||||||
|
let lastArg = emitExpr(items[^1], 0)
|
||||||
|
return sp & "cljApply(cljPartial(" & fnArg & ", @[" & partialArgs.join(", ") & "]), " & lastArg & ")"
|
||||||
|
|
||||||
of "some":
|
of "some":
|
||||||
if items.len != 3:
|
if items.len != 3:
|
||||||
@@ -1033,9 +1145,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
|
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
|
||||||
"println", "prn", "print", "str", "pr-str",
|
"println", "prn", "print", "str", "pr-str",
|
||||||
"concat", "min", "max", "merge", "interleave",
|
"concat", "min", "max", "merge", "interleave",
|
||||||
"zipmap"]
|
"zipmap", "hash-map", "hash-set", "sorted-map", "sorted-set",
|
||||||
|
"array-map", "inf", "nan"]
|
||||||
var call: string
|
var call: string
|
||||||
if variadic and argParts.len > 0:
|
if variadic:
|
||||||
call = rn & "(@[" & argParts.join(", ") & "])"
|
call = rn & "(@[" & argParts.join(", ") & "])"
|
||||||
else:
|
else:
|
||||||
call = rn & "(" & argParts.join(", ") & ")"
|
call = rn & "(" & argParts.join(", ") & ")"
|
||||||
@@ -1096,16 +1209,17 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
|||||||
of ckSymbol:
|
of ckSymbol:
|
||||||
let symName = resolveNsAlias(v.symName)
|
let symName = resolveNsAlias(v.symName)
|
||||||
let rn = runtimeName(symName)
|
let rn = runtimeName(symName)
|
||||||
if rn.len > 0:
|
if rn.len > 0 and not isLocalVar(symName):
|
||||||
# Known function symbol — emit as runtime fn reference
|
|
||||||
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
|
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
|
||||||
|
if isLocalVar(symName):
|
||||||
return sp & mangleName(symName)
|
return sp & mangleName(symName)
|
||||||
|
return sp & "cljSymbol(\"" & symName & "\")"
|
||||||
of ckList:
|
of ckList:
|
||||||
if v.items.len == 0:
|
if v.items.len == 0:
|
||||||
return sp & "cljList(@[])"
|
return sp & "cljList(@[])"
|
||||||
# Macro-expand before emitting
|
# Macro-expand before emitting
|
||||||
let expanded = macroexpand(v)
|
let expanded = macroexpand(v)
|
||||||
if expanded.kind != ckList or expanded == v:
|
if expanded == v:
|
||||||
return emitSpecialForm(v.items, indent)
|
return emitSpecialForm(v.items, indent)
|
||||||
return emitExpr(expanded, indent)
|
return emitExpr(expanded, indent)
|
||||||
of ckVector:
|
of ckVector:
|
||||||
@@ -1144,19 +1258,18 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string
|
|||||||
var code = emitExpr(item, indent)
|
var code = emitExpr(item, indent)
|
||||||
let stripped = code.strip()
|
let stripped = code.strip()
|
||||||
if i < items.len - 1:
|
if i < items.len - 1:
|
||||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
|
||||||
stripped.startsWith("var ") or stripped.startsWith("let ") or
|
|
||||||
stripped.startsWith("proc ") or stripped.startsWith("result = "):
|
|
||||||
discard
|
|
||||||
else:
|
|
||||||
# Prepend discard before the first non-empty line
|
|
||||||
let firstNl = code.find("\n")
|
let firstNl = code.find("\n")
|
||||||
|
let firstLine = if firstNl == -1: stripped else: code[0..<firstNl].strip()
|
||||||
|
let trimmed = code.strip()
|
||||||
|
let lastNl = trimmed.rfind("\n")
|
||||||
|
let lastLine = if lastNl == -1: trimmed else: trimmed[lastNl+1..^1].strip()
|
||||||
|
# Prepend discard before the first non-empty line
|
||||||
if firstNl == -1:
|
if firstNl == -1:
|
||||||
code = indentStr(indent) & "discard " & stripped
|
code = indentStr(indent) & "discard " & stripped
|
||||||
else:
|
else:
|
||||||
let firstLine = code[0..<firstNl].strip()
|
# Wrap multi-line expression in proc so discard can apply
|
||||||
let rest = code[firstNl..^1]
|
let indentedCode = indentCode(code, 1)
|
||||||
code = indentStr(indent) & "discard " & firstLine & rest
|
code = indentStr(indent) & "discard ((proc (): CljVal =\n" & indentedCode & "\n" & indentStr(indent) & ")())"
|
||||||
elif useResult:
|
elif useResult:
|
||||||
if stripped.startsWith("discard "):
|
if stripped.startsWith("discard "):
|
||||||
code = indentStr(indent) & "result = " & stripped[8..^1]
|
code = indentStr(indent) & "result = " & stripped[8..^1]
|
||||||
@@ -1181,19 +1294,37 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
|||||||
var defs: seq[string] = @[]
|
var defs: seq[string] = @[]
|
||||||
var mainForms: seq[string] = @[]
|
var mainForms: seq[string] = @[]
|
||||||
|
|
||||||
|
proc unwrapAndProcess(i: int, form: CljVal, isLast: bool)
|
||||||
|
|
||||||
|
var processDepth = 0
|
||||||
|
const maxProcessDepth = 50
|
||||||
|
|
||||||
proc processForm(i: int, form: CljVal, isLast: bool) =
|
proc processForm(i: int, form: CljVal, isLast: bool) =
|
||||||
let headSym = form.kind == ckList and form.items.len > 0 and
|
if processDepth > maxProcessDepth:
|
||||||
form.items[0].kind == ckSymbol
|
raise newException(EmitterError, "Maximum processing depth exceeded - possible macro expansion loop")
|
||||||
let headName = if headSym: form.items[0].symName else: ""
|
inc processDepth
|
||||||
|
defer: dec processDepth
|
||||||
|
let expanded = macroexpand(form)
|
||||||
|
let ef = if expanded.kind == ckList: expanded else: form
|
||||||
|
let headSym = ef.kind == ckList and ef.items.len > 0 and
|
||||||
|
ef.items[0].kind == ckSymbol
|
||||||
|
let headName = if headSym: ef.items[0].symName else: ""
|
||||||
|
# Recurse into (do ...) forms produced by macro expansion
|
||||||
|
if headSym and headName == "do":
|
||||||
|
let subForms = ef.items[1..^1]
|
||||||
|
for j, subForm in subForms:
|
||||||
|
let subLast = isLast and (j == subForms.len - 1)
|
||||||
|
unwrapAndProcess(j, subForm, subLast)
|
||||||
|
return
|
||||||
let isDef = headSym and headName in ["def", "defn", "defn-"]
|
let isDef = headSym and headName in ["def", "defn", "defn-"]
|
||||||
let isMacro = headSym and headName in ["defmacro"]
|
let isMacro = headSym and headName in ["defmacro"]
|
||||||
let isNs = headSym and headName == "ns"
|
let isNs = headSym and headName == "ns"
|
||||||
if isNs:
|
if isNs:
|
||||||
discard
|
discard
|
||||||
elif isDef or isMacro:
|
elif isDef or isMacro:
|
||||||
defs.add(emitExpr(form, 0))
|
defs.add(emitExpr(ef, 0))
|
||||||
else:
|
else:
|
||||||
let code = emitExpr(form, 2)
|
let code = emitExpr(ef, 2)
|
||||||
let stripped = code.strip()
|
let stripped = code.strip()
|
||||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||||
stripped.startsWith("var "):
|
stripped.startsWith("var "):
|
||||||
|
|||||||
+272
-211
@@ -48,37 +48,26 @@ proc expandSyntaxQuote*(form: CljVal): CljVal =
|
|||||||
# Process each element
|
# Process each element
|
||||||
var parts: seq[CljVal] = @[]
|
var parts: seq[CljVal] = @[]
|
||||||
for item in form.items:
|
for item in form.items:
|
||||||
if item.kind == ckList and item.items.len == 2 and
|
parts.add(expandSyntaxQuote(item))
|
||||||
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)
|
return cljList(@[cljSymbol("concat")] & parts)
|
||||||
of ckVector:
|
of ckVector:
|
||||||
var parts: seq[CljVal] = @[]
|
var parts: seq[CljVal] = @[]
|
||||||
for item in form.items:
|
for item in form.items:
|
||||||
if item.kind == ckList and item.items.len == 2 and
|
parts.add(expandSyntaxQuote(item))
|
||||||
item.items[0].kind == ckSymbol and item.items[0].symName == "unquote-splicing":
|
return cljList(@[cljSymbol("apply"), cljSymbol("vector"), cljList(@[cljSymbol("concat")] & parts)])
|
||||||
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:
|
of ckMap:
|
||||||
# Maps: expand each key and value
|
var parts: seq[CljVal] = @[]
|
||||||
return cljList(@[cljSymbol("quote"), form])
|
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:
|
else:
|
||||||
return cljList(@[cljSymbol("quote"), form])
|
return form
|
||||||
|
|
||||||
proc macroexpand1*(form: CljVal): CljVal =
|
proc macroexpand1*(form: CljVal): CljVal =
|
||||||
if form.isNil: return form
|
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")
|
raise newException(CatchableError, "Threading macro requires at least 2 arguments")
|
||||||
var acc = args[0]
|
var acc = args[0]
|
||||||
for i in 1..<args.len:
|
for i in 1..<args.len:
|
||||||
let step = args[i]
|
let form = args[i]
|
||||||
if step.kind == ckList and step.items.len > 0:
|
if form.kind == ckList and form.items.len > 0:
|
||||||
var newItems = step.items
|
|
||||||
if reverse:
|
if reverse:
|
||||||
newItems.add(acc)
|
acc = cljList(form.items & @[acc])
|
||||||
else:
|
else:
|
||||||
newItems.insert(acc, 1)
|
acc = cljList(@[form.items[0], acc] & form.items[1..^1])
|
||||||
acc = cljList(newItems)
|
elif form.kind == ckSymbol:
|
||||||
elif step.kind == ckSymbol:
|
acc = cljList(@[form, acc])
|
||||||
# (-> x f) => (f x)
|
|
||||||
acc = cljList(@[step, acc])
|
|
||||||
else:
|
else:
|
||||||
raise newException(CatchableError, "Threading macro requires list or symbol forms")
|
raise newException(CatchableError, "Threading macro requires lists or symbols")
|
||||||
acc
|
return acc
|
||||||
|
|
||||||
proc initBuiltinMacros*() =
|
proc initMacros*() =
|
||||||
# ->
|
# fn* macro
|
||||||
defineMacro("fn*", proc(args: seq[CljVal]): CljVal =
|
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)
|
cljList(@[cljSymbol("fn")] & args)
|
||||||
)
|
)
|
||||||
|
|
||||||
# ->
|
# -> (thread-first)
|
||||||
defineMacro("->", proc(args: seq[CljVal]): CljVal =
|
defineMacro("->", proc(args: seq[CljVal]): CljVal =
|
||||||
threadingMacro(args, false)
|
threadingMacro(args, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
# ->>
|
# ->> (thread-last)
|
||||||
defineMacro("->>", proc(args: seq[CljVal]): CljVal =
|
defineMacro("->>", proc(args: seq[CljVal]): CljVal =
|
||||||
threadingMacro(args, true)
|
threadingMacro(args, true)
|
||||||
)
|
)
|
||||||
@@ -155,284 +142,294 @@ proc initBuiltinMacros*() =
|
|||||||
defineMacro("and", proc(args: seq[CljVal]): CljVal =
|
defineMacro("and", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len == 0: return cljBool(true)
|
if args.len == 0: return cljBool(true)
|
||||||
if args.len == 1: return args[0]
|
if args.len == 1: return args[0]
|
||||||
# (and a b c) => (if a (if b c false) false)
|
var result = cljList(@[cljSymbol("if"), args[^1],
|
||||||
var r = args[^1]
|
args[^1],
|
||||||
|
cljBool(false)])
|
||||||
for i in countdown(args.len - 2, 0):
|
for i in countdown(args.len - 2, 0):
|
||||||
r = cljList(@[cljSymbol("if"), args[i], r, cljBool(false)])
|
result = cljList(@[cljSymbol("if"), args[i], result, cljBool(false)])
|
||||||
r
|
return result
|
||||||
)
|
)
|
||||||
|
|
||||||
# or
|
# or
|
||||||
defineMacro("or", proc(args: seq[CljVal]): CljVal =
|
defineMacro("or", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len == 0: return cljNil()
|
if args.len == 0: return cljNil()
|
||||||
if args.len == 1: return args[0]
|
if args.len == 1: return args[0]
|
||||||
# (or a b c) => (if a a (if b b (if c c nil)))
|
var result = args[^1]
|
||||||
var r = args[^1]
|
|
||||||
for i in countdown(args.len - 2, 0):
|
for i in countdown(args.len - 2, 0):
|
||||||
r = cljList(@[cljSymbol("if"), args[i], args[i], r])
|
let gs = cljSymbol(gensymName("or_"))
|
||||||
r
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, args[i]]),
|
||||||
|
cljList(@[cljSymbol("if"), gs, gs, result])])
|
||||||
|
return result
|
||||||
)
|
)
|
||||||
|
|
||||||
# when-let
|
# when-let
|
||||||
defineMacro("when-let", proc(args: seq[CljVal]): CljVal =
|
defineMacro("when-let", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
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 binding = args[0]
|
||||||
let body = args[1..^1]
|
let body = args[1..^1]
|
||||||
if binding.kind != ckVector or binding.items.len != 2:
|
if binding.kind != ckVector or binding.items.len != 2:
|
||||||
raise newException(CatchableError, "when-let binding must be [name val]")
|
raise newException(CatchableError, "when-let requires [name val]")
|
||||||
let sym = binding.items[0]
|
let name = binding.items[0]
|
||||||
let val = binding.items[1]
|
let val = binding.items[1]
|
||||||
let gs = cljSymbol(gensymName("wl_"))
|
let gs = cljSymbol(gensymName("wl_"))
|
||||||
cljList(@[cljSymbol("let"), cljVector(@[gs, val]),
|
cljList(@[cljSymbol("let"), cljVector(@[gs, val]),
|
||||||
cljList(@[cljSymbol("if"), gs,
|
cljList(@[cljSymbol("when"), gs,
|
||||||
cljList(@[cljSymbol("let"), cljVector(@[sym, gs])] & body)])])
|
cljList(@[cljSymbol("let"), cljVector(@[name, gs])] & body)])])
|
||||||
)
|
)
|
||||||
|
|
||||||
# if-let
|
# if-let
|
||||||
defineMacro("if-let", proc(args: seq[CljVal]): CljVal =
|
defineMacro("if-let", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
if args.len < 3:
|
||||||
raise newException(CatchableError, "if-let requires binding, then, and optional else")
|
raise newException(CatchableError, "if-let requires binding vector, then, and else")
|
||||||
let binding = args[0]
|
let binding = args[0]
|
||||||
let thenExpr = args[1]
|
let thenBranch = args[1]
|
||||||
let elseExpr = if args.len > 2: args[2] else: cljNil()
|
let elseBranch = args[2]
|
||||||
if binding.kind != ckVector or binding.items.len != 2:
|
if binding.kind != ckVector or binding.items.len != 2:
|
||||||
raise newException(CatchableError, "if-let binding must be [name val]")
|
raise newException(CatchableError, "if-let requires [name val]")
|
||||||
let sym = binding.items[0]
|
let name = binding.items[0]
|
||||||
let val = binding.items[1]
|
let val = binding.items[1]
|
||||||
let gs = cljSymbol(gensymName("il_"))
|
let gs = cljSymbol(gensymName("il_"))
|
||||||
cljList(@[cljSymbol("let"), cljVector(@[gs, val]),
|
cljList(@[cljSymbol("let"), cljVector(@[gs, val]),
|
||||||
cljList(@[cljSymbol("if"), gs,
|
cljList(@[cljSymbol("if"), gs,
|
||||||
cljList(@[cljSymbol("let"), cljVector(@[sym, gs]), thenExpr]),
|
cljList(@[cljSymbol("let"), cljVector(@[name, gs]), thenBranch]),
|
||||||
elseExpr])])
|
elseBranch])])
|
||||||
)
|
)
|
||||||
|
|
||||||
# when
|
# when
|
||||||
defineMacro("when", proc(args: seq[CljVal]): CljVal =
|
defineMacro("when", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
if args.len < 1:
|
||||||
raise newException(CatchableError, "when requires condition and body")
|
raise newException(CatchableError, "when requires condition and body")
|
||||||
let test = args[0]
|
let cond = args[0]
|
||||||
let body = args[1..^1]
|
let body = args[1..^1]
|
||||||
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("do")] & body)])
|
cljList(@[cljSymbol("if"), cond,
|
||||||
|
cljList(@[cljSymbol("do")] & body),
|
||||||
|
cljNil()])
|
||||||
)
|
)
|
||||||
|
|
||||||
# when-not
|
# when-not
|
||||||
defineMacro("when-not", proc(args: seq[CljVal]): CljVal =
|
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")
|
raise newException(CatchableError, "when-not requires condition and body")
|
||||||
let test = args[0]
|
let cond = args[0]
|
||||||
let body = args[1..^1]
|
let body = args[1..^1]
|
||||||
cljList(@[cljSymbol("if"), test, cljNil(), cljList(@[cljSymbol("do")] & body)])
|
cljList(@[cljSymbol("if"), cond, cljNil(),
|
||||||
|
cljList(@[cljSymbol("do")] & body)])
|
||||||
)
|
)
|
||||||
|
|
||||||
# cond
|
# cond
|
||||||
defineMacro("cond", proc(args: seq[CljVal]): CljVal =
|
defineMacro("cond", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len == 0: return cljNil()
|
if args.len == 0: return cljNil()
|
||||||
if args.len mod 2 != 0:
|
if args.len mod 2 != 0:
|
||||||
raise newException(CatchableError, "cond requires even number of forms")
|
raise newException(CatchableError, "cond requires pairs of test/expr")
|
||||||
let test = args[0]
|
var result = cljNil()
|
||||||
let then = args[1]
|
for i in countdown((args.len div 2) - 1, 0):
|
||||||
if (test.kind == ckKeyword and test.kwName == "else") or
|
let test = args[i * 2]
|
||||||
(test.kind == ckSymbol and test.symName == ":else"):
|
let expr = args[i * 2 + 1]
|
||||||
return then
|
if test.kind == ckKeyword and test.kwName == "else":
|
||||||
let rest = args[2..^1]
|
result = expr
|
||||||
if rest.len == 0:
|
else:
|
||||||
return cljList(@[cljSymbol("if"), test, then])
|
result = cljList(@[cljSymbol("if"), test, expr, result])
|
||||||
cljList(@[cljSymbol("if"), test, then,
|
return result
|
||||||
cljList(@[cljSymbol("cond")] & rest)])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# cond->
|
# cond->
|
||||||
defineMacro("cond->", proc(args: seq[CljVal]): CljVal =
|
defineMacro("cond->", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 1:
|
if args.len < 1:
|
||||||
raise newException(CatchableError, "cond-> requires at least 1 argument")
|
raise newException(CatchableError, "cond-> requires initial value and clauses")
|
||||||
let expr = args[0]
|
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_"))
|
let gs = cljSymbol(gensymName("ct_"))
|
||||||
var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, expr])]
|
if expr.kind == ckList and expr.items.len > 0:
|
||||||
var i = 1
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
while i + 1 < args.len:
|
cljList(@[cljSymbol("if"), test,
|
||||||
let test = args[i]
|
cljList(@[expr.items[0], gs] & expr.items[1..^1]),
|
||||||
let step = args[i+1]
|
gs])])
|
||||||
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:
|
else:
|
||||||
threaded = step
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
forms.add(cljList(@[cljSymbol("if"), test,
|
cljList(@[cljSymbol("if"), test,
|
||||||
cljList(@[cljSymbol("set!"), gs, threaded]),
|
cljList(@[expr, gs]),
|
||||||
gs]))
|
gs])])
|
||||||
i += 2
|
return result
|
||||||
forms.add(gs)
|
|
||||||
cljList(forms)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# cond->>
|
# cond->>
|
||||||
defineMacro("cond->>", proc(args: seq[CljVal]): CljVal =
|
defineMacro("cond->>", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 1:
|
if args.len < 1:
|
||||||
raise newException(CatchableError, "cond->> requires at least 1 argument")
|
raise newException(CatchableError, "cond->> requires initial value and clauses")
|
||||||
let expr = args[0]
|
let init = args[0]
|
||||||
let gs = cljSymbol(gensymName("ctg_"))
|
let clauses = args[1..^1]
|
||||||
var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, expr])]
|
if clauses.len mod 2 != 0:
|
||||||
var i = 1
|
raise newException(CatchableError, "cond->> requires pairs of test/expr")
|
||||||
while i + 1 < args.len:
|
var result = init
|
||||||
let test = args[i]
|
for i in 0..<(clauses.len div 2):
|
||||||
let step = args[i+1]
|
let test = clauses[i * 2]
|
||||||
var threaded: CljVal
|
let expr = clauses[i * 2 + 1]
|
||||||
if step.kind == ckList and step.items.len > 0:
|
let gs = cljSymbol(gensymName("ct_"))
|
||||||
var newItems = step.items
|
if expr.kind == ckList and expr.items.len > 0:
|
||||||
newItems.add(gs)
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
threaded = cljList(newItems)
|
cljList(@[cljSymbol("if"), test,
|
||||||
elif step.kind == ckSymbol:
|
cljList(expr.items & @[gs]),
|
||||||
threaded = cljList(@[step, gs])
|
gs])])
|
||||||
else:
|
else:
|
||||||
threaded = step
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
forms.add(cljList(@[cljSymbol("if"), test,
|
cljList(@[cljSymbol("if"), test,
|
||||||
cljList(@[cljSymbol("set!"), gs, threaded]),
|
cljList(@[expr, gs]),
|
||||||
gs]))
|
gs])])
|
||||||
i += 2
|
return result
|
||||||
forms.add(gs)
|
|
||||||
cljList(forms)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# doto
|
# doto
|
||||||
defineMacro("doto", proc(args: seq[CljVal]): CljVal =
|
defineMacro("doto", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
if args.len < 1:
|
||||||
raise newException(CatchableError, "doto requires at least 2 arguments")
|
raise newException(CatchableError, "doto requires initial value and body")
|
||||||
let x = args[0]
|
let init = args[0]
|
||||||
let gs = cljSymbol(gensymName("do_"))
|
let body = args[1..^1]
|
||||||
var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, x])]
|
let gs = cljSymbol(gensymName("dt_"))
|
||||||
for i in 1..<args.len:
|
var forms: seq[CljVal] = @[]
|
||||||
let step = args[i]
|
forms.add(cljSymbol("let"))
|
||||||
if step.kind == ckList and step.items.len > 0:
|
forms.add(cljVector(@[gs, init]))
|
||||||
var newItems = step.items
|
for form in body:
|
||||||
newItems.insert(gs, 1)
|
if form.kind == ckList and form.items.len > 0:
|
||||||
forms.add(cljList(newItems))
|
forms.add(cljList(@[form.items[0], gs] & form.items[1..^1]))
|
||||||
elif step.kind == ckSymbol:
|
else:
|
||||||
forms.add(cljList(@[step, gs]))
|
forms.add(cljList(@[form, gs]))
|
||||||
forms.add(gs)
|
forms.add(gs)
|
||||||
cljList(forms)
|
return cljList(forms)
|
||||||
)
|
)
|
||||||
|
|
||||||
# as->
|
# as->
|
||||||
defineMacro("as->", proc(args: seq[CljVal]): CljVal =
|
defineMacro("as->", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 3:
|
if args.len < 3:
|
||||||
raise newException(CatchableError, "as-> requires at least 3 arguments")
|
raise newException(CatchableError, "as-> requires name, init, and body")
|
||||||
let expr = args[0]
|
let name = args[0]
|
||||||
let name = args[1]
|
let init = args[1]
|
||||||
var acc = expr
|
let body = args[2..^1]
|
||||||
for i in 2..<args.len:
|
var result = init
|
||||||
acc = cljList(@[cljSymbol("let"), cljVector(@[name, acc]), args[i]])
|
for form in body:
|
||||||
acc
|
result = cljList(@[cljSymbol("let"), cljVector(@[name, result]), form])
|
||||||
|
return result
|
||||||
)
|
)
|
||||||
|
|
||||||
# some->
|
# some->
|
||||||
defineMacro("some->", proc(args: seq[CljVal]): CljVal =
|
defineMacro("some->", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
if args.len < 1:
|
||||||
raise newException(CatchableError, "some-> requires at least 2 arguments")
|
raise newException(CatchableError, "some-> requires initial value and body")
|
||||||
var acc = args[0]
|
let init = args[0]
|
||||||
for i in 1..<args.len:
|
let body = args[1..^1]
|
||||||
let step = args[i]
|
var result = init
|
||||||
|
for form in body:
|
||||||
let gs = cljSymbol(gensymName("st_"))
|
let gs = cljSymbol(gensymName("st_"))
|
||||||
var threaded: CljVal
|
if form.kind == ckList and form.items.len > 0:
|
||||||
if step.kind == ckList and step.items.len > 0:
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
var newItems = step.items
|
cljList(@[cljSymbol("if"), gs,
|
||||||
newItems.insert(gs, 1)
|
cljList(@[form.items[0], gs] & form.items[1..^1]),
|
||||||
threaded = cljList(newItems)
|
cljNil()])])
|
||||||
elif step.kind == ckSymbol:
|
|
||||||
threaded = cljList(@[step, gs])
|
|
||||||
else:
|
else:
|
||||||
threaded = step
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
|
cljList(@[cljSymbol("if"), gs,
|
||||||
cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]),
|
cljList(@[form, gs]),
|
||||||
cljNil(), threaded])])
|
cljNil()])])
|
||||||
acc
|
return result
|
||||||
)
|
)
|
||||||
|
|
||||||
# some->>
|
# some->>
|
||||||
defineMacro("some->>", proc(args: seq[CljVal]): CljVal =
|
defineMacro("some->>", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
if args.len < 1:
|
||||||
raise newException(CatchableError, "some->> requires at least 2 arguments")
|
raise newException(CatchableError, "some->> requires initial value and body")
|
||||||
var acc = args[0]
|
let init = args[0]
|
||||||
for i in 1..<args.len:
|
let body = args[1..^1]
|
||||||
let step = args[i]
|
var result = init
|
||||||
let gs = cljSymbol(gensymName("stg_"))
|
for form in body:
|
||||||
var threaded: CljVal
|
let gs = cljSymbol(gensymName("st_"))
|
||||||
if step.kind == ckList and step.items.len > 0:
|
if form.kind == ckList and form.items.len > 0:
|
||||||
var newItems = step.items
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
newItems.add(gs)
|
cljList(@[cljSymbol("if"), gs,
|
||||||
threaded = cljList(newItems)
|
cljList(form.items & @[gs]),
|
||||||
elif step.kind == ckSymbol:
|
cljNil()])])
|
||||||
threaded = cljList(@[step, gs])
|
|
||||||
else:
|
else:
|
||||||
threaded = step
|
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]),
|
||||||
acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
|
cljList(@[cljSymbol("if"), gs,
|
||||||
cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]),
|
cljList(@[form, gs]),
|
||||||
cljNil(), threaded])])
|
cljNil()])])
|
||||||
acc
|
return result
|
||||||
)
|
)
|
||||||
|
|
||||||
# for (simplified — single binding only)
|
# for
|
||||||
defineMacro("for", proc(args: seq[CljVal]): CljVal =
|
defineMacro("for", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
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 bindings = args[0]
|
||||||
let body = args[1..^1]
|
let body = args[1..^1]
|
||||||
if bindings.kind != ckVector or bindings.items.len < 2:
|
if bindings.kind != ckVector:
|
||||||
raise newException(CatchableError, "for requires a binding vector [name coll]")
|
raise newException(CatchableError, "for bindings must be a vector")
|
||||||
let sym = bindings.items[0]
|
let gs = cljSymbol(gensymName("fr_"))
|
||||||
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("let"), cljVector(@[gs, cljVector(@[])]),
|
||||||
cljList(@[cljSymbol("doseq"), cljVector(@[sym, coll]),
|
cljList(@[cljSymbol("doseq")] & @[bindings] & @[
|
||||||
cljList(@[cljSymbol("set!"), gs,
|
cljList(@[cljSymbol("set!"), gs, cljList(@[cljSymbol("conj"), gs] & body)])]),
|
||||||
cljList(@[cljSymbol("conj"), gs, bodyExpr])])]),
|
|
||||||
gs])
|
gs])
|
||||||
)
|
)
|
||||||
|
|
||||||
# doseq (simplified — single binding only)
|
# doseq
|
||||||
defineMacro("doseq", proc(args: seq[CljVal]): CljVal =
|
defineMacro("doseq", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
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 bindings = args[0]
|
||||||
let body = args[1..^1]
|
let body = args[1..^1]
|
||||||
if bindings.kind != ckVector or bindings.items.len < 2:
|
if bindings.kind != ckVector:
|
||||||
raise newException(CatchableError, "doseq requires a binding vector [name coll]")
|
raise newException(CatchableError, "doseq bindings must be a vector")
|
||||||
let sym = bindings.items[0]
|
let bindingPairs = bindings.items.len div 2
|
||||||
|
if bindingPairs == 1:
|
||||||
|
let name = bindings.items[0]
|
||||||
let coll = bindings.items[1]
|
let coll = bindings.items[1]
|
||||||
let gs = cljSymbol(gensymName("ds_"))
|
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("let"), cljVector(@[gs, coll]),
|
||||||
cljList(@[cljSymbol("loop"), cljVector(@[cljSymbol(gensymName("i_")), cljInt(0)]),
|
cljList(@[cljSymbol("loop"), cljVector(@[name, cljList(@[cljSymbol("first"), gs]),
|
||||||
cljList(@[cljSymbol("when"),
|
gs, cljList(@[cljSymbol("rest"), gs])]),
|
||||||
cljList(@[cljSymbol("<"), cljSymbol(gensymName("i_")), cljList(@[cljSymbol("count"), gs])]),
|
cljList(@[cljSymbol("when"), name] & body),
|
||||||
cljList(@[cljSymbol("let"), cljVector(@[sym, cljList(@[cljSymbol("nth"), gs, cljSymbol(gensymName("i_"))])]),
|
cljList(@[cljSymbol("recur"), cljList(@[cljSymbol("first"), gs]),
|
||||||
bodyExpr]),
|
cljList(@[cljSymbol("rest"), gs])])])])
|
||||||
cljList(@[cljSymbol("recur"),
|
else:
|
||||||
cljList(@[cljSymbol("inc"), cljSymbol(gensymName("i_"))])])])])])
|
# 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
|
# dotimes
|
||||||
defineMacro("dotimes", proc(args: seq[CljVal]): CljVal =
|
defineMacro("dotimes", proc(args: seq[CljVal]): CljVal =
|
||||||
if args.len < 2:
|
if args.len < 2:
|
||||||
raise newException(CatchableError, "dotimes requires binding vector and body")
|
raise newException(CatchableError, "dotimes requires binding and body")
|
||||||
let bindings = args[0]
|
let binding = args[0]
|
||||||
let body = args[1..^1]
|
let body = args[1..^1]
|
||||||
if bindings.kind != ckVector or bindings.items.len != 2:
|
if binding.kind != ckVector or binding.items.len != 2:
|
||||||
raise newException(CatchableError, "dotimes requires [name n]")
|
raise newException(CatchableError, "dotimes requires [name count]")
|
||||||
let sym = bindings.items[0]
|
let name = binding.items[0]
|
||||||
let n = bindings.items[1]
|
let count = binding.items[1]
|
||||||
cljList(@[cljSymbol("doseq"), cljVector(@[sym, cljList(@[cljSymbol("range"), n])])] & body)
|
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
|
# defmacro
|
||||||
defineMacro("defmacro", proc(args: seq[CljVal]): CljVal =
|
defineMacro("defmacro", proc(args: seq[CljVal]): CljVal =
|
||||||
# defmacro is handled specially in the compiler, this is a no-op
|
if args.len < 3:
|
||||||
cljNil()
|
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
|
# comment
|
||||||
@@ -464,3 +461,67 @@ proc initBuiltinMacros*() =
|
|||||||
cljList(@[cljSymbol("finally"),
|
cljList(@[cljSymbol("finally"),
|
||||||
cljList(@[cljSymbol(".close"), gs])])])])
|
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()
|
||||||
|
|||||||
+112
-10
@@ -8,7 +8,7 @@ type
|
|||||||
proc skipWhitespaceAndComments(s: string, i: var int) =
|
proc skipWhitespaceAndComments(s: string, i: var int) =
|
||||||
while i < s.len:
|
while i < s.len:
|
||||||
let c = s[i]
|
let c = s[i]
|
||||||
if c in Whitespace:
|
if c in Whitespace or c == ',':
|
||||||
inc i
|
inc i
|
||||||
elif c == ';':
|
elif c == ';':
|
||||||
while i < s.len and s[i] != '\n':
|
while i < s.len and s[i] != '\n':
|
||||||
@@ -40,6 +40,18 @@ proc readStringTok(s: string, i: var int): string =
|
|||||||
inc i # skip closing quote
|
inc i # skip closing quote
|
||||||
return resultStr
|
return resultStr
|
||||||
|
|
||||||
|
proc consumeNumericSuffix(s: string, i: var int) =
|
||||||
|
# Consume N (BigInt) or M (BigDecimal) suffix after a number
|
||||||
|
if i < s.len and (s[i] == 'N' or s[i] == 'M'):
|
||||||
|
inc i
|
||||||
|
|
||||||
|
proc consumeRatioDenominator(s: string, i: var int) =
|
||||||
|
# Consume /denominator for ratio literals like 1/5, -1/5
|
||||||
|
if i < s.len and s[i] == '/' and i + 1 < s.len and s[i+1] in Digits:
|
||||||
|
inc i # skip /
|
||||||
|
while i < s.len and s[i] in Digits:
|
||||||
|
inc i
|
||||||
|
|
||||||
proc readNumberOrSym(s: string, i: var int): string =
|
proc readNumberOrSym(s: string, i: var int): string =
|
||||||
var start = i
|
var start = i
|
||||||
# handle negative sign or standalone operators
|
# handle negative sign or standalone operators
|
||||||
@@ -58,6 +70,8 @@ proc readNumberOrSym(s: string, i: var int): string =
|
|||||||
if i < s.len and s[i] in Digits:
|
if i < s.len and s[i] in Digits:
|
||||||
while i < s.len and s[i] in Digits:
|
while i < s.len and s[i] in Digits:
|
||||||
inc i
|
inc i
|
||||||
|
consumeNumericSuffix(s, i)
|
||||||
|
consumeRatioDenominator(s, i)
|
||||||
return s[start..<i]
|
return s[start..<i]
|
||||||
elif i + 1 < s.len and s[i+1] in Digits:
|
elif i + 1 < s.len and s[i+1] in Digits:
|
||||||
inc i
|
inc i
|
||||||
@@ -75,6 +89,8 @@ proc readNumberOrSym(s: string, i: var int): string =
|
|||||||
if i < s.len and s[i] in Digits:
|
if i < s.len and s[i] in Digits:
|
||||||
while i < s.len and s[i] in Digits:
|
while i < s.len and s[i] in Digits:
|
||||||
inc i
|
inc i
|
||||||
|
consumeNumericSuffix(s, i)
|
||||||
|
consumeRatioDenominator(s, i)
|
||||||
return s[start..<i]
|
return s[start..<i]
|
||||||
else:
|
else:
|
||||||
# it's a symbol like - or +
|
# it's a symbol like - or +
|
||||||
@@ -98,6 +114,10 @@ proc readNumberOrSym(s: string, i: var int): string =
|
|||||||
if i < s.len and s[i] in Digits:
|
if i < s.len and s[i] in Digits:
|
||||||
while i < s.len and s[i] in Digits:
|
while i < s.len and s[i] in Digits:
|
||||||
inc i
|
inc i
|
||||||
|
# Clojure BigInt (N) and BigDecimal (M) suffixes
|
||||||
|
if i < s.len and (s[i] == 'N' or s[i] == 'M'):
|
||||||
|
inc i
|
||||||
|
consumeRatioDenominator(s, i)
|
||||||
return s[start..<i]
|
return s[start..<i]
|
||||||
else:
|
else:
|
||||||
while i < s.len and isSymChar(s[i]):
|
while i < s.len and isSymChar(s[i]):
|
||||||
@@ -118,31 +138,36 @@ proc readAtom(s: string, i: var int): CljVal =
|
|||||||
if tok[0] == ':':
|
if tok[0] == ':':
|
||||||
return cljKeyword(tok[1..^1])
|
return cljKeyword(tok[1..^1])
|
||||||
|
|
||||||
|
# Strip Clojure BigInt/Decimal suffixes for parsing
|
||||||
|
var numTok = tok
|
||||||
|
if numTok.len > 1 and (numTok[^1] == 'N' or numTok[^1] == 'M'):
|
||||||
|
numTok = numTok[0..^2]
|
||||||
|
|
||||||
# number
|
# number
|
||||||
var isFloat = false
|
var isFloat = false
|
||||||
var isNumber = true
|
var isNumber = true
|
||||||
var startIdx = 0
|
var startIdx = 0
|
||||||
if tok[0] == '-' or tok[0] == '+':
|
if numTok[0] == '-' or numTok[0] == '+':
|
||||||
if tok.len == 1:
|
if numTok.len == 1:
|
||||||
isNumber = false
|
isNumber = false
|
||||||
else:
|
else:
|
||||||
startIdx = 1
|
startIdx = 1
|
||||||
var sawExp = false
|
var sawExp = false
|
||||||
for j in startIdx..<tok.len:
|
for j in startIdx..<numTok.len:
|
||||||
if tok[j] == '.':
|
if numTok[j] == '.':
|
||||||
if isFloat:
|
if isFloat:
|
||||||
isNumber = false
|
isNumber = false
|
||||||
break
|
break
|
||||||
isFloat = true
|
isFloat = true
|
||||||
elif tok[j] == 'e' or tok[j] == 'E':
|
elif numTok[j] == 'e' or numTok[j] == 'E':
|
||||||
if sawExp:
|
if sawExp:
|
||||||
isNumber = false
|
isNumber = false
|
||||||
break
|
break
|
||||||
sawExp = true
|
sawExp = true
|
||||||
isFloat = true
|
isFloat = true
|
||||||
if j + 1 < tok.len and (tok[j+1] == '-' or tok[j+1] == '+'):
|
if j + 1 < numTok.len and (numTok[j+1] == '-' or numTok[j+1] == '+'):
|
||||||
discard # skip exponent sign
|
discard # skip exponent sign
|
||||||
elif tok[j] notin Digits:
|
elif numTok[j] notin Digits:
|
||||||
if sawExp and (tok[j] == '-' or tok[j] == '+'):
|
if sawExp and (tok[j] == '-' or tok[j] == '+'):
|
||||||
if j > 0 and (tok[j-1] == 'e' or tok[j-1] == 'E'):
|
if j > 0 and (tok[j-1] == 'e' or tok[j-1] == 'E'):
|
||||||
continue
|
continue
|
||||||
@@ -157,9 +182,9 @@ proc readAtom(s: string, i: var int): CljVal =
|
|||||||
break
|
break
|
||||||
if hasDigit:
|
if hasDigit:
|
||||||
if isFloat:
|
if isFloat:
|
||||||
return cljFloat(parseFloat(tok))
|
return cljFloat(parseFloat(numTok))
|
||||||
else:
|
else:
|
||||||
return cljInt(parseInt(tok).int64)
|
return cljInt(parseInt(numTok).int64)
|
||||||
|
|
||||||
# symbol
|
# symbol
|
||||||
return cljSymbol(tok)
|
return cljSymbol(tok)
|
||||||
@@ -178,6 +203,13 @@ proc readList(s: string, i: var int): CljVal =
|
|||||||
break
|
break
|
||||||
let form = readForm(s, i)
|
let form = readForm(s, i)
|
||||||
if form != nil:
|
if form != nil:
|
||||||
|
if form.kind == ckList and form.items.len == 2 and
|
||||||
|
form.items[0].kind == ckSymbol and form.items[0].symName == "splice-unwrap":
|
||||||
|
let inner = form.items[1]
|
||||||
|
if inner.kind == ckVector:
|
||||||
|
for item in inner.items:
|
||||||
|
items.add(item)
|
||||||
|
else:
|
||||||
items.add(form)
|
items.add(form)
|
||||||
return cljList(items)
|
return cljList(items)
|
||||||
|
|
||||||
@@ -193,6 +225,13 @@ proc readVector(s: string, i: var int): CljVal =
|
|||||||
break
|
break
|
||||||
let form = readForm(s, i)
|
let form = readForm(s, i)
|
||||||
if form != nil:
|
if form != nil:
|
||||||
|
if form.kind == ckList and form.items.len == 2 and
|
||||||
|
form.items[0].kind == ckSymbol and form.items[0].symName == "splice-unwrap":
|
||||||
|
let inner = form.items[1]
|
||||||
|
if inner.kind == ckVector:
|
||||||
|
for item in inner.items:
|
||||||
|
items.add(item)
|
||||||
|
else:
|
||||||
items.add(form)
|
items.add(form)
|
||||||
return cljVector(items)
|
return cljVector(items)
|
||||||
|
|
||||||
@@ -252,6 +291,39 @@ proc readDispatch(s: string, i: var int): CljVal =
|
|||||||
inc i
|
inc i
|
||||||
discard readForm(s, i)
|
discard readForm(s, i)
|
||||||
return nil
|
return nil
|
||||||
|
of '?':
|
||||||
|
# Reader conditional: #?(:clj expr :cljs expr :default expr)
|
||||||
|
# Read the next char - if '@' it's splicing version #?@
|
||||||
|
var isSplice = false
|
||||||
|
var savedI = i
|
||||||
|
inc i
|
||||||
|
if i < s.len and s[i] == '@':
|
||||||
|
isSplice = true
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
i = savedI
|
||||||
|
inc i # skip '?'
|
||||||
|
let form = readForm(s, i)
|
||||||
|
if form == nil:
|
||||||
|
return nil
|
||||||
|
if form.kind != ckList:
|
||||||
|
return nil
|
||||||
|
var items = form.items
|
||||||
|
var defaultVal: CljVal = nil
|
||||||
|
var found = false
|
||||||
|
var k = 0
|
||||||
|
while k < items.len - 1:
|
||||||
|
if items[k].kind == ckKeyword and items[k].kwName == "default":
|
||||||
|
defaultVal = items[k + 1]
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
k += 2
|
||||||
|
if not found:
|
||||||
|
return nil
|
||||||
|
if isSplice and defaultVal != nil and defaultVal.kind == ckVector:
|
||||||
|
# Return the vector items as a splice marker
|
||||||
|
return cljList(@[cljSymbol("splice-unwrap"), defaultVal])
|
||||||
|
return defaultVal
|
||||||
else:
|
else:
|
||||||
raise newException(ReaderError, "Unknown dispatch macro: #" & c)
|
raise newException(ReaderError, "Unknown dispatch macro: #" & c)
|
||||||
|
|
||||||
@@ -282,6 +354,34 @@ proc readDeref(s: string, i: var int): CljVal =
|
|||||||
let form = readForm(s, i)
|
let form = readForm(s, i)
|
||||||
return cljList(@[cljSymbol("deref"), form])
|
return cljList(@[cljSymbol("deref"), form])
|
||||||
|
|
||||||
|
proc readCharLiteral(s: string, i: var int): CljVal =
|
||||||
|
inc i # skip '\'
|
||||||
|
if i >= s.len:
|
||||||
|
raise newException(ReaderError, "Unexpected end after \\")
|
||||||
|
let c = s[i]
|
||||||
|
if c in Letters:
|
||||||
|
# Could be a named char like newline, space, tab, or a single letter
|
||||||
|
var name = ""
|
||||||
|
while i < s.len and isSymChar(s[i]):
|
||||||
|
name.add(s[i])
|
||||||
|
inc i
|
||||||
|
case name
|
||||||
|
of "newline": return cljString("\n")
|
||||||
|
of "space": return cljString(" ")
|
||||||
|
of "tab": return cljString("\t")
|
||||||
|
of "return": return cljString("\r")
|
||||||
|
of "formfeed": return cljString("\x0C")
|
||||||
|
of "backspace": return cljString("\x08")
|
||||||
|
else:
|
||||||
|
if name.len == 1:
|
||||||
|
return cljString(name)
|
||||||
|
raise newException(ReaderError, "Unknown character literal: \\" & name)
|
||||||
|
else:
|
||||||
|
# Single character literal like \1, \\ etc
|
||||||
|
let ch = $c
|
||||||
|
inc i
|
||||||
|
return cljString(ch)
|
||||||
|
|
||||||
proc readForm(s: string, i: var int): CljVal =
|
proc readForm(s: string, i: var int): CljVal =
|
||||||
skipWhitespaceAndComments(s, i)
|
skipWhitespaceAndComments(s, i)
|
||||||
if i >= s.len:
|
if i >= s.len:
|
||||||
@@ -313,6 +413,8 @@ proc readForm(s: string, i: var int): CljVal =
|
|||||||
return readUnquote(s, i)
|
return readUnquote(s, i)
|
||||||
of '@':
|
of '@':
|
||||||
return readDeref(s, i)
|
return readDeref(s, i)
|
||||||
|
of '\\':
|
||||||
|
return readCharLiteral(s, i)
|
||||||
else:
|
else:
|
||||||
return readAtom(s, i)
|
return readAtom(s, i)
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -44,13 +44,16 @@ def extract_default(content, start):
|
|||||||
return rest[:k], k
|
return rest[:k], k
|
||||||
|
|
||||||
def strip_reader_conditionals(text):
|
def strip_reader_conditionals(text):
|
||||||
"""Strip #? and #?@ reader conditionals, keeping :default branch content."""
|
"""Strip #? and #?@ reader conditionals, keeping :default branch content.
|
||||||
|
For #?@, if the default is a vector/list, unwrap it (splice semantics)."""
|
||||||
result = []
|
result = []
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(text):
|
while i < len(text):
|
||||||
# Check for #?@ or #?
|
# Check for #?@ or #?
|
||||||
|
is_splice = False
|
||||||
if i + 3 <= len(text) and text[i:i+3] == '#?@':
|
if i + 3 <= len(text) and text[i:i+3] == '#?@':
|
||||||
j = i + 3
|
j = i + 3
|
||||||
|
is_splice = True
|
||||||
elif i + 2 <= len(text) and text[i:i+2] == '#?':
|
elif i + 2 <= len(text) and text[i:i+2] == '#?':
|
||||||
j = i + 2
|
j = i + 2
|
||||||
else:
|
else:
|
||||||
@@ -85,6 +88,8 @@ def strip_reader_conditionals(text):
|
|||||||
# Extract content between parens
|
# Extract content between parens
|
||||||
inner = text[j+1:k]
|
inner = text[j+1:k]
|
||||||
default_val, _ = extract_default(inner, 0)
|
default_val, _ = extract_default(inner, 0)
|
||||||
|
if is_splice and len(default_val) >= 2 and default_val[0] in '([':
|
||||||
|
default_val = default_val[1:-1]
|
||||||
result.append(default_val)
|
result.append(default_val)
|
||||||
i = k + 1
|
i = k + 1
|
||||||
|
|
||||||
@@ -98,10 +103,6 @@ def run_test(cljc_path, timeout=30):
|
|||||||
content = re.sub(r'#\{[^}]*\}', r'@[]', content) # #{} → @[]
|
content = re.sub(r'#\{[^}]*\}', r'@[]', content) # #{} → @[]
|
||||||
content = re.sub(r'#:([\w-]+)', r'\1', content) # #:foo → foo
|
content = re.sub(r'#:([\w-]+)', r'\1', content) # #:foo → foo
|
||||||
stubs = '''(do
|
stubs = '''(do
|
||||||
(defmacro is [form] (list 'quote form))
|
|
||||||
(defmacro testing [desc & body] (cons 'do body))
|
|
||||||
(defmacro thrown? [form] (list 'try form false (list 'catch 'Exception 'e true)))
|
|
||||||
(defmacro deftest [name & body] (list 'def name (cons 'fn (cons [] body))))
|
|
||||||
'''
|
'''
|
||||||
content = stubs + content + ')'
|
content = stubs + content + ')'
|
||||||
content = re.sub(r'clojure\.test', 'test', content)
|
content = re.sub(r'clojure\.test', 'test', content)
|
||||||
|
|||||||
Reference in New Issue
Block a user