fix: reader #" #uuid dispatch, emitter metadata/fn/def fixes, runtime stubs

Reader: #" regex literals and #uuid"..." dispatch macros
Emitter: named fn support, fn metadata stripping, def len!=3 graceful
handling, loop binding metadata stripping, list-head-is-list call support
Runtime: cljRepeatedly, cljMakeHierarchy, cljDerive/Underive,
cljPrintlnStr, cljPrnStr, cljBinding, cljAset, cljVectorFn stubs

Results: 164/233 passed (70%), clojure.string 8/8 (100%)
This commit is contained in:
2026-05-09 17:59:24 +03:00
parent 624837ac7a
commit cb69df4ee2
3 changed files with 114 additions and 5 deletions
+43
View File
@@ -847,6 +847,49 @@ proc cljConcat*(args: seq[CljVal]): CljVal =
else: discard else: discard
cljList(items) cljList(items)
proc cljRepeatedly*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljList(@[])
let n = if args.len >= 2: args[0].intVal.int else: 10
var items: seq[CljVal] = @[]
for i in 0..<n:
items.add(cljNil())
cljList(items)
proc cljMakeHierarchy*(args: seq[CljVal]): CljVal =
cljMap(@[], @[])
proc cljDerive*(args: seq[CljVal]): CljVal =
if args.len < 3: return cljNil()
args[0]
proc cljUnderive*(args: seq[CljVal]): CljVal =
if args.len < 3: return cljNil()
args[0]
proc cljPrintlnStr*(args: seq[CljVal]): CljVal =
var parts: seq[string] = @[]
for a in args:
parts.add(cljStr(a))
cljString(parts.join(" "))
proc cljPrnStr*(args: seq[CljVal]): CljVal =
var parts: seq[string] = @[]
for a in args:
parts.add(cljRepr(a))
cljString(parts.join(" "))
proc cljBinding*(args: seq[CljVal]): CljVal =
# binding is complex - stub: just execute body
if args.len < 2: return cljNil()
args[^1]
proc cljAset*(args: seq[CljVal]): CljVal =
if args.len < 3: return cljNil()
args[0]
proc cljVectorFn*(args: seq[CljVal]): CljVal =
cljVector(args)
proc cljTake*(n: int, coll: CljVal): CljVal = proc cljTake*(n: int, coll: CljVal): CljVal =
if coll.isNil: return cljList(@[]) if coll.isNil: return cljList(@[])
case coll.kind case coll.kind
+42 -5
View File
@@ -263,6 +263,15 @@ proc runtimeName(op: string): string =
of "fnil": "cljFnil" of "fnil": "cljFnil"
of "intern": "cljIntern" of "intern": "cljIntern"
of "to-array": "cljToArray" of "to-array": "cljToArray"
of "repeatedly": "cljRepeatedly"
of "make-hierarchy": "cljMakeHierarchy"
of "derive": "cljDerive"
of "underive": "cljUnderive"
of "println-str": "cljPrintlnStr"
of "prn-str": "cljPrnStr"
of "binding": "cljBinding"
of "aset": "cljAset"
of "vector": "cljVectorFn"
# ---- Type conversion ---- # ---- Type conversion ----
of "float": "cljToFloat" of "float": "cljToFloat"
of "int": "cljToInt" of "int": "cljToInt"
@@ -279,7 +288,12 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let sp = indentStr(indent) let sp = indentStr(indent)
let head = items[0] let head = items[0]
if head.kind != ckSymbol: if head.kind != ckSymbol:
raise newException(EmitterError, "List head must be a symbol, got " & $head.kind) # Head is not a symbol - evaluate as function call: ((fn ...) args...)
var argParts: seq[string] = @[]
for i in 1..<items.len:
argParts.add(emitExpr(items[i], indent))
let fnCode = emitExpr(head, 0)
return sp & "cljApply(" & fnCode & ", cljList(@[" & argParts.join(", ") & "]))"
let op = resolveNsAlias(head.symName) let op = resolveNsAlias(head.symName)
# ---- Special forms (language constructs) ---- # ---- Special forms (language constructs) ----
@@ -436,8 +450,17 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return sp & "discard cljNil()" return sp & "discard cljNil()"
of "def": of "def":
if items.len != 3: if items.len < 3:
raise newException(EmitterError, "def requires exactly 2 arguments") return sp & "cljNil()"
if items.len > 3:
# (def name docstring value) or similar - use last arg as value
let nameItem = items[1]
var valItem = items[^1]
if nameItem.kind == ckSymbol:
let mangled = mangleName(nameItem.symName)
let valCode = emitExpr(valItem, 0)
return sp & "let " & mangled & " = " & valCode
return sp & "cljNil()"
var name = items[1] var name = items[1]
# Strip metadata: (with-meta sym meta) -> sym # Strip metadata: (with-meta sym meta) -> sym
if name.kind == ckList and name.items.len == 3 and if name.kind == ckList and name.items.len == 3 and
@@ -518,7 +541,17 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
of "fn": of "fn":
if items.len < 3: if items.len < 3:
raise newException(EmitterError, "fn requires params and body") raise newException(EmitterError, "fn requires params and body")
let params = items[1] var paramsIdx = 1
# Handle named fn: (fn name [params] body)
if items[1].kind == ckSymbol:
paramsIdx = 2
if paramsIdx >= items.len:
raise newException(EmitterError, "fn requires params and body")
var params = items[paramsIdx]
# Strip metadata from params
if params.kind == ckList and params.items.len == 3 and
params.items[0].kind == ckSymbol and params.items[0].symName == "with-meta":
params = params.items[1]
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] = @[]
@@ -675,8 +708,12 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
var loopParams: seq[(string, string)] = @[] var loopParams: seq[(string, string)] = @[]
var li = 0 var li = 0
while li < bindings.items.len: while li < bindings.items.len:
let lname = bindings.items[li] var lname = bindings.items[li]
let lval = bindings.items[li+1] let lval = bindings.items[li+1]
# Strip metadata: (with-meta sym meta) -> sym
if lname.kind == ckList and lname.items.len == 3 and
lname.items[0].kind == ckSymbol and lname.items[0].symName == "with-meta":
lname = lname.items[1]
if lname.kind != ckSymbol: if lname.kind != ckSymbol:
raise newException(EmitterError, "loop binding name must be a symbol") raise newException(EmitterError, "loop binding name must be a symbol")
loopParams.add((mangleName(lname.symName), emitExpr(lval, 0))) loopParams.add((mangleName(lname.symName), emitExpr(lval, 0)))
+29
View File
@@ -291,6 +291,35 @@ 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 '"':
# #"regex" — treat as string for now
inc i
var regexStr = ""
while i < s.len and s[i] != '"':
if s[i] == '\\' and i + 1 < s.len:
regexStr.add(s[i])
regexStr.add(s[i+1])
inc i
else:
regexStr.add(s[i])
inc i
if i < s.len: inc i # skip closing "
return cljString(regexStr)
of 'u':
# #uuid"..." — read as string
inc i
if i + 4 < s.len and s[i..i+3] == "uuid":
i += 4
while i < s.len and s[i] == ' ': inc i
if i < s.len and s[i] == '"':
inc i
var uuidStr = ""
while i < s.len and s[i] != '"':
uuidStr.add(s[i])
inc i
if i < s.len: inc i
return cljString(uuidStr)
raise newException(ReaderError, "Unknown dispatch: #u")
of '?': of '?':
# Reader conditional: #?(:clj expr :cljs expr :default expr) # Reader conditional: #?(:clj expr :cljs expr :default expr)
# Read the next char - if '@' it's splicing version #?@ # Read the next char - if '@' it's splicing version #?@