feat: 80/80 unit tests, real-world examples (FizzBuzz, WordFreq)
- Emitter: def registry (definedGlobals) for symbol emit distinction - Emitter: cond emits return statements - Emitter: reduce wraps named functions in closure adapter - Emitter: clojure.string/* function mappings (split, lower-case, etc.) - Emitter: statement detection for echo cljRepr wrapping - Runtime: cljGet supports default value (3 args) - Runtime: cljStrSplit supports regex patterns via re module - Examples: fizzbuzz.clj and wordfreq.clj both compile and run correctly - Tests: emit symbol / emit mangled symbol now pass (80/80) - Compliance: 233/233 (100%) maintained
This commit is contained in:
+83
-6
@@ -8,6 +8,13 @@ var emitLibMode* = false
|
||||
var loopStack*: seq[seq[string]] = @[]
|
||||
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
||||
var scopeStack*: seq[HashSet[string]] = @[]
|
||||
var definedGlobals*: HashSet[string] = initHashSet[string]()
|
||||
|
||||
proc registerGlobal*(name: string) =
|
||||
definedGlobals.incl(name)
|
||||
|
||||
proc clearGlobals*() =
|
||||
definedGlobals = initHashSet[string]()
|
||||
|
||||
proc setNsAliases*(aliases: seq[(string, string)]) =
|
||||
nsAliases = aliases
|
||||
@@ -247,6 +254,18 @@ proc runtimeName(op: string): string =
|
||||
of "git/push": "cljGitPush"
|
||||
of "git/diff": "cljGitDiff"
|
||||
of "git/log": "cljGitLog"
|
||||
# ---- clojure.string operations ----
|
||||
of "clojure.string/split": "cljStrSplit"
|
||||
of "clojure.string/lower-case": "cljStrLower"
|
||||
of "clojure.string/upper-case": "cljStrUpper"
|
||||
of "clojure.string/trim": "cljStrTrim"
|
||||
of "clojure.string/join": "cljStrJoin"
|
||||
of "clojure.string/replace": "cljStrReplace"
|
||||
of "clojure.string/includes?": "cljStrIncludes"
|
||||
of "clojure.string/starts-with?": "cljStrStartsWith"
|
||||
of "clojure.string/ends-with?": "cljStrEndsWith"
|
||||
of "clojure.string/blank?": "cljStrBlank"
|
||||
of "clojure.string/reverse": "cljStrReverse"
|
||||
of "disj": "cljDisj"
|
||||
of "peek": "cljPeek"
|
||||
of "pop": "cljPop"
|
||||
@@ -633,6 +652,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
var valItem = items[^1]
|
||||
if nameItem.kind == ckSymbol:
|
||||
let mangled = mangleName(nameItem.symName)
|
||||
registerGlobal(nameItem.symName)
|
||||
let valCode = emitExpr(valItem, 0)
|
||||
return sp & "let " & mangled & " = " & valCode
|
||||
return sp & "cljNil()"
|
||||
@@ -645,6 +665,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
raise newException(EmitterError, "def name must be a symbol")
|
||||
let mangled = mangleName(name.symName)
|
||||
addToScope(name.symName)
|
||||
registerGlobal(name.symName)
|
||||
let valForm = items[2]
|
||||
# Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper
|
||||
let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and
|
||||
@@ -679,6 +700,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
name = name.items[1]
|
||||
if name.kind != ckSymbol:
|
||||
raise newException(EmitterError, "defn name must be a symbol")
|
||||
registerGlobal(name.symName)
|
||||
let params = items[2]
|
||||
if params.kind != ckVector:
|
||||
raise newException(EmitterError, "defn params must be a vector")
|
||||
@@ -723,6 +745,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
name = name.items[1]
|
||||
if name.kind != ckSymbol:
|
||||
raise newException(EmitterError, "defn- name must be a symbol")
|
||||
registerGlobal(name.symName)
|
||||
let params = items[2]
|
||||
if params.kind != ckVector:
|
||||
raise newException(EmitterError, "defn- params must be a vector")
|
||||
@@ -936,10 +959,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let condCode = emitExpr(testExpr, 0).strip()
|
||||
let thenCode = emitExpr(thenExpr, indent + 1)
|
||||
if first:
|
||||
lines.add(sp & "if cljIsTruthy(" & condCode & "):\n" & thenCode)
|
||||
lines.add(sp & "if cljIsTruthy(" & condCode & "):\n" & sp & " return " & thenCode.strip())
|
||||
first = false
|
||||
else:
|
||||
lines.add(sp & "elif cljIsTruthy(" & condCode & "):\n" & thenCode)
|
||||
lines.add(sp & "elif cljIsTruthy(" & condCode & "):\n" & sp & " return " & thenCode.strip())
|
||||
ci += 2
|
||||
return lines.join("\n")
|
||||
|
||||
@@ -1265,13 +1288,59 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
# (reduce f coll) — first element as init
|
||||
let fnArg = items[1]
|
||||
let collArg = emitExpr(items[2], 0)
|
||||
return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", cljNil(), " & collArg & ")"
|
||||
let isFn = fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]
|
||||
let isSymbol = fnArg.kind == ckSymbol
|
||||
if isFn:
|
||||
let fnParams = fnArg.items[1]
|
||||
let fnBody = fnArg.items[2..^1]
|
||||
var pNames: seq[string] = @[]
|
||||
for p in fnParams.items:
|
||||
pNames.add(mangleName(p.symName))
|
||||
var bodyExpr = ""
|
||||
if fnBody.len == 1:
|
||||
bodyExpr = emitExpr(fnBody[0], 0).strip()
|
||||
else:
|
||||
var parts: seq[string] = @[]
|
||||
for b in fnBody:
|
||||
parts.add(emitExpr(b, 0).strip())
|
||||
bodyExpr = parts.join("; ")
|
||||
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; let " & pNames[1] & " = args[1]; " & bodyExpr & ")"
|
||||
return sp & "cljReduce(cljFn(" & fnProc & "), cljNil(), " & collArg & ")"
|
||||
elif isSymbol:
|
||||
let mangled = mangleName(fnArg.symName)
|
||||
let wrapper = "proc(args: seq[CljVal]): CljVal = " & mangled & "(args[0], args[1])"
|
||||
return sp & "cljReduce(cljFn(" & wrapper & "), cljNil(), " & collArg & ")"
|
||||
else:
|
||||
return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", cljNil(), " & collArg & ")"
|
||||
else:
|
||||
# (reduce f init coll)
|
||||
let fnArg = items[1]
|
||||
let initArg = emitExpr(items[2], 0)
|
||||
let collArg = emitExpr(items[3], 0)
|
||||
return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", " & initArg & ", " & collArg & ")"
|
||||
let isFn = fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]
|
||||
let isSymbol = fnArg.kind == ckSymbol
|
||||
if isFn:
|
||||
let fnParams = fnArg.items[1]
|
||||
let fnBody = fnArg.items[2..^1]
|
||||
var pNames: seq[string] = @[]
|
||||
for p in fnParams.items:
|
||||
pNames.add(mangleName(p.symName))
|
||||
var bodyExpr = ""
|
||||
if fnBody.len == 1:
|
||||
bodyExpr = emitExpr(fnBody[0], 0).strip()
|
||||
else:
|
||||
var parts: seq[string] = @[]
|
||||
for b in fnBody:
|
||||
parts.add(emitExpr(b, 0).strip())
|
||||
bodyExpr = parts.join("; ")
|
||||
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; let " & pNames[1] & " = args[1]; " & bodyExpr & ")"
|
||||
return sp & "cljReduce(cljFn(" & fnProc & "), " & initArg & ", " & collArg & ")"
|
||||
elif isSymbol:
|
||||
let mangled = mangleName(fnArg.symName)
|
||||
let wrapper = "proc(args: seq[CljVal]): CljVal = " & mangled & "(args[0], args[1])"
|
||||
return sp & "cljReduce(cljFn(" & wrapper & "), " & initArg & ", " & collArg & ")"
|
||||
else:
|
||||
return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", " & initArg & ", " & collArg & ")"
|
||||
|
||||
of "mapv":
|
||||
if items.len != 3:
|
||||
@@ -1369,6 +1438,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let iname = items[1]
|
||||
if iname.kind != ckSymbol:
|
||||
raise newException(EmitterError, "definterface name must be a symbol")
|
||||
registerGlobal(iname.symName)
|
||||
return sp & "discard cljProtocol(\"" & iname.symName & "\")"
|
||||
|
||||
of "defprotocol":
|
||||
@@ -1377,6 +1447,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let pname = items[1]
|
||||
if pname.kind != ckSymbol:
|
||||
raise newException(EmitterError, "defprotocol name must be a symbol")
|
||||
registerGlobal(pname.symName)
|
||||
return sp & "let " & mangleName(pname.symName) & " = cljProtocol(\"" & pname.symName & "\")"
|
||||
|
||||
of "defrecord":
|
||||
@@ -1385,6 +1456,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let rname = items[1]
|
||||
if rname.kind != ckSymbol:
|
||||
raise newException(EmitterError, "defrecord name must be a symbol")
|
||||
registerGlobal(rname.symName)
|
||||
let mangled = mangleName(rname.symName)
|
||||
let fields = items[2]
|
||||
var fieldNames: seq[string] = @[]
|
||||
@@ -1408,6 +1480,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let tname = items[1]
|
||||
if tname.kind != ckSymbol:
|
||||
raise newException(EmitterError, "deftype name must be a symbol")
|
||||
registerGlobal(tname.symName)
|
||||
return sp & "let " & mangleName(tname.symName) & " = cljTypeConstructor(\"" & tname.symName & "\", cljVector(@[]))"
|
||||
|
||||
of "new":
|
||||
@@ -1762,7 +1835,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
"to-array", "into-array", "vector", "rand", "rand-int",
|
||||
"rand-nth", "random-sample",
|
||||
"assoc", "dissoc", "get", "get-in", "update", "assoc-in",
|
||||
"contains?", "select-keys", "keys", "vals",
|
||||
"contains?", "select-keys",
|
||||
"disj", "peek", "pop",
|
||||
"transduce", "ex-info",
|
||||
"compare", "subvec",
|
||||
@@ -1884,6 +1957,8 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args[0]))"
|
||||
if isLocalVar(symName):
|
||||
return sp & mangleName(symName)
|
||||
if symName in definedGlobals:
|
||||
return sp & mangleName(symName)
|
||||
return sp & "cljSymbol(\"" & symName & "\")"
|
||||
of ckList:
|
||||
if v.items.len == 0:
|
||||
@@ -2013,7 +2088,9 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
let code = emitExpr(ef, 2)
|
||||
let stripped = code.strip()
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("var "):
|
||||
stripped.startsWith("var ") or stripped.startsWith("while ") or
|
||||
stripped.startsWith("if ") or stripped.startsWith("block:") or
|
||||
stripped.startsWith("for ") or stripped.startsWith("try"):
|
||||
mainForms.add(code)
|
||||
elif isLast:
|
||||
mainForms.add(indentStr(2) & "echo cljRepr(" & stripped & ")")
|
||||
|
||||
Reference in New Issue
Block a user