fix: clj_ prefix for identifiers + try/catch/finally fixes

- #9: Add clj_ prefix to all mangled identifiers to avoid Nim
  case-insensitive name conflicts (parseQueryString ≡ parse_query_string)
- #11: Fix finally block - discard expression results
- #11: Fix catch handler - use original name for scope tracking
- Fix loop handler - use original name for addToScope
- Update emitter tests for clj_ prefix
This commit is contained in:
2026-05-12 00:48:50 +03:00
parent e36bf4e362
commit fb8928e830
2 changed files with 26 additions and 16 deletions
+19 -9
View File
@@ -79,6 +79,8 @@ proc mangleName*(name: string): string =
"interface", "lambda", "open", "quit", "result", "nil", "end"] "interface", "lambda", "open", "quit", "result", "nil", "end"]
if result in nimKeywords: if result in nimKeywords:
result = result & "1" result = result & "1"
# Prefix to avoid conflicts with Nim identifiers (case-insensitive + underscore ignoring)
result = "clj_" & result
proc pushScope() = proc pushScope() =
scopeStack.add(initHashSet[string]()) scopeStack.add(initHashSet[string]())
@@ -1165,7 +1167,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
raise newException(EmitterError, "loop bindings must be a vector") raise newException(EmitterError, "loop bindings must be a vector")
if bindings.items.len mod 2 != 0: if bindings.items.len mod 2 != 0:
raise newException(EmitterError, "loop bindings must have even number of elements") raise newException(EmitterError, "loop bindings must have even number of elements")
var loopParams: seq[(string, string)] = @[] var loopParams: seq[(string, string, string)] = @[] # (mangled, original, value)
var li = 0 var li = 0
while li < bindings.items.len: while li < bindings.items.len:
var lname = bindings.items[li] var lname = bindings.items[li]
@@ -1175,13 +1177,13 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lname = lname.items[1] 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), lname.symName, emitExpr(lval, 0)))
li += 2 li += 2
var loopVars: seq[string] = @[] var loopVars: seq[string] = @[]
var lines: seq[string] = @[] var lines: seq[string] = @[]
pushScope() pushScope()
for (lpName, lpVal) in loopParams: for (lpName, lpOrig, lpVal) in loopParams:
addToScope(lpName) addToScope(lpOrig)
lines.add(sp & "var " & lpName & ": CljVal = " & lpVal) lines.add(sp & "var " & lpName & ": CljVal = " & lpVal)
loopVars.add(lpName) loopVars.add(lpName)
loopStack.add(loopVars) loopStack.add(loopVars)
@@ -1271,7 +1273,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if items.len < 2: if items.len < 2:
raise newException(EmitterError, "try requires body") raise newException(EmitterError, "try requires body")
var bodyForms: seq[CljVal] = @[] var bodyForms: seq[CljVal] = @[]
var catchClauses: seq[(string, string, seq[CljVal])] = @[] var catchClauses: seq[(string, string, string, seq[CljVal])] = @[] # (exType, mangledName, origName, body)
var finallyBody: seq[CljVal] = @[] var finallyBody: seq[CljVal] = @[]
var ti = 1 var ti = 1
while ti < items.len: while ti < items.len:
@@ -1304,7 +1306,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let exName = form.items[nameIdx] let exName = form.items[nameIdx]
if exName.kind != ckSymbol: if exName.kind != ckSymbol:
raise newException(EmitterError, "catch name must be a symbol") raise newException(EmitterError, "catch name must be a symbol")
catchClauses.add((exTypeStr, mangleName(exName.symName), form.items[bodyIdx..^1])) catchClauses.add((exTypeStr, mangleName(exName.symName), exName.symName, form.items[bodyIdx..^1]))
elif form.items[0].symName == "finally": elif form.items[0].symName == "finally":
finallyBody = form.items[1..^1] finallyBody = form.items[1..^1]
else: else:
@@ -1330,7 +1332,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lines.add(code) lines.add(code)
else: else:
lines.add(indentStr(indent + 1) & "discard " & stripped) lines.add(indentStr(indent + 1) & "discard " & stripped)
for (exType, exVar, exBody) in catchClauses: for (exType, exVar, exOrig, exBody) in catchClauses:
lines.add(sp & "except " & exType & ":") lines.add(sp & "except " & exType & ":")
lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])") lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])")
lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))") lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))")
@@ -1338,7 +1340,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lines.add(indentStr(indent + 1) & "if exPtr" & exVar & " of ExInfo:") lines.add(indentStr(indent + 1) & "if exPtr" & exVar & " of ExInfo:")
lines.add(indentStr(indent + 2) & "discard cljAssoc(" & exVar & ", cljKeyword(\"data\"), (ref ExInfo)(exPtr" & exVar & ").exData)") lines.add(indentStr(indent + 2) & "discard cljAssoc(" & exVar & ", cljKeyword(\"data\"), (ref ExInfo)(exPtr" & exVar & ").exData)")
pushScope() pushScope()
addToScope(exVar) addToScope(exOrig)
for i, eb in exBody: for i, eb in exBody:
let isLast = (i == exBody.len - 1) let isLast = (i == exBody.len - 1)
let code = emitExpr(eb, indent + 1) let code = emitExpr(eb, indent + 1)
@@ -1359,7 +1361,15 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if finallyBody.len > 0: if finallyBody.len > 0:
lines.add(sp & "finally:") lines.add(sp & "finally:")
for fb in finallyBody: for fb in finallyBody:
lines.add(emitExpr(fb, indent + 1)) let fbCode = emitExpr(fb, indent + 1)
let fbStripped = fbCode.strip()
if fbStripped.startsWith("echo ") or fbStripped.startsWith("discard ") or
fbStripped.startsWith("var ") or fbStripped.startsWith("let ") or
fbStripped.startsWith("proc ") or fbStripped.startsWith("if ") or
fbStripped.startsWith("try:") or fbStripped.startsWith("while "):
lines.add(fbCode)
else:
lines.add(indentStr(indent + 1) & "discard " & fbStripped)
return lines.join("\n") return lines.join("\n")
of "throw": of "throw":
+7 -7
View File
@@ -43,13 +43,13 @@ suite "Emitter - Basic Values":
registerGlobal("x") registerGlobal("x")
let v = cljSymbol("x") let v = cljSymbol("x")
let code = emitExpr(v) let code = emitExpr(v)
check code == "x" check code == "clj_x"
test "emit mangled symbol": test "emit mangled symbol":
registerGlobal("my-var") registerGlobal("my-var")
let v = cljSymbol("my-var") let v = cljSymbol("my-var")
let code = emitExpr(v) let code = emitExpr(v)
check code == "my_var" check code == "clj_my_var"
test "emit vector": test "emit vector":
let v = cljVector(@[cljInt(1), cljInt(2)]) let v = cljVector(@[cljInt(1), cljInt(2)])
@@ -70,12 +70,12 @@ suite "Emitter - Special Forms":
test "emit def": test "emit def":
let v = read("(def x 42)") let v = read("(def x 42)")
let code = emitExpr(v) let code = emitExpr(v)
check "let x" in code check "let clj_x" in code
test "emit defn": test "emit defn":
let v = read("(defn square [x] (* x x))") let v = read("(defn square [x] (* x x))")
let code = emitExpr(v) let code = emitExpr(v)
check "proc square" in code check "proc clj_square" in code
test "emit if": test "emit if":
let v = read("(if true 1 2)") let v = read("(if true 1 2)")
@@ -161,14 +161,14 @@ suite "Emitter - Program":
test "emitProgram with defs and main": test "emitProgram with defs and main":
let forms = readAll("(def x 42) (println x)") let forms = readAll("(def x 42) (println x)")
let code = emitProgram(forms) let code = emitProgram(forms)
check "let x" in code check "let clj_x" in code
check "when isMainModule" in code check "when isMainModule" in code
test "emitProgram multiple defs": test "emitProgram multiple defs":
let forms = readAll("(def a 1) (def b 2)") let forms = readAll("(def a 1) (def b 2)")
let code = emitProgram(forms) let code = emitProgram(forms)
check "let a" in code check "let clj_a" in code
check "let b" in code check "let clj_b" in code
suite "Emitter - Map Literals": suite "Emitter - Map Literals":
test "emit empty map": test "emit empty map":