fix(emitter): loop in expression context via needsValue + IIFE wrapper

- Add needsValue parameter to emitExpr/emitSpecialForm to track expression context
- loop wraps in (proc(): CljVal = ... )() when needsValue=true
- Function call args, let/def values, vector/map literals pass needsValue=true
- emitProgramInternal passes needsValue=isLast for top-level forms
- Fixes: loop as function arg, loop in let RHS, loopResult at top level
This commit is contained in:
2026-05-12 12:54:00 +03:00
parent aac39a08ca
commit a1a65f1c27
2 changed files with 31 additions and 20 deletions
+1
View File
@@ -26,3 +26,4 @@ test_single_debug.py
src/tui_config
experiments/native-lib/test_client/test_client
experiments/wasm/tools/zig/
lib/bring_http.nim
+30 -20
View File
@@ -126,7 +126,7 @@ proc isLocalVar(name: string): bool =
return true
return false
proc emitExpr*(v: CljVal, indent: int = 0): string
proc emitExpr*(v: CljVal, indent: int = 0, needsValue: bool = false): string
proc emitBlock*(items: seq[CljVal], indent: int, useResult: bool = false): string
proc emitQuotedForm*(v: CljVal): string
proc emitFnAsProc*(items: seq[CljVal], indent: int): string
@@ -481,7 +481,7 @@ proc letDestructuredBindings(bindings: CljVal): CljVal =
i += 2
return cljVector(result)
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false): string =
let sp = indentStr(indent)
let head = items[0]
if head.kind != ckSymbol:
@@ -494,7 +494,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
# 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))
argParts.add(emitExpr(items[i], indent, needsValue = true))
let fnCode = emitExpr(head, 0)
return sp & "cljApply(" & fnCode & ", cljList(@[" & argParts.join(", ") & "]))"
let op = resolveNsAlias(head.symName)
@@ -695,7 +695,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if nameItem.kind == ckSymbol:
let mangled = mangleName(nameItem.symName)
registerGlobal(nameItem.symName)
let valCode = emitExpr(valItem, 0)
let valCode = emitExpr(valItem, 0, needsValue = true)
return sp & "let " & mangled & " = " & valCode
return sp & "cljNil()"
var name = items[1]
@@ -720,7 +720,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let valCode = emitFnAsProc(valForm.items, 0)
emitLibMode = oldEmitLibMode
return sp & "let " & mangled & " = " & valCode
let valCode = emitExpr(items[2], 0)
let valCode = emitExpr(items[2], 0, needsValue = true)
return sp & "let " & mangled & " = " & valCode
else:
# For nested def: use global var so it's accessible from closures
@@ -730,7 +730,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let valCode = emitFnAsProc(valForm.items, indent)
emitLibMode = oldEmitLibMode
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
let valCode = emitExpr(items[2], indent)
let valCode = emitExpr(items[2], indent, needsValue = true)
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
of "defn":
@@ -1038,7 +1038,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if bname.kind != ckSymbol:
raise newException(EmitterError, "let binding name must be a symbol, got " & $bname.kind & ": " & $bname)
addToScope(bname.symName)
let bcode = emitExpr(bval, indent + 1)
let bcode = emitExpr(bval, indent + 1, needsValue = true)
if bcode.find("\n") != -1:
# Multi-line value: use var and place first line on next line for correct indentation
lines.add(indentStr(indent + 1) & "var " & mangleName(bname.symName) & ": CljVal")
@@ -1208,7 +1208,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lname = lname.items[1]
if lname.kind != ckSymbol:
raise newException(EmitterError, "loop binding name must be a symbol")
loopParams.add((mangleName(lname.symName), lname.symName, emitExpr(lval, 0)))
loopParams.add((mangleName(lname.symName), lname.symName, emitExpr(lval, 0, needsValue = true)))
li += 2
var loopVars: seq[string] = @[]
var lines: seq[string] = @[]
@@ -1273,7 +1273,17 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if needsResultVar:
lines.add(sp & "loopResult")
loopResultVar = ""
return lines.join("\n")
let loopCode = lines.join("\n")
if needsValue:
# Wrap in IIFE for expression contexts (function args, let RHS, etc.)
var iifeLines: seq[string] = @[]
iifeLines.add(sp & "(proc(): CljVal =")
iifeLines.add(indentCode(loopCode, 1))
if not needsResultVar:
iifeLines.add(indentStr(indent + 1) & "cljNil()")
iifeLines.add(sp & ")()")
return iifeLines.join("\n")
return loopCode
of "recur":
if items.len < 2:
@@ -1749,7 +1759,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return sp & "cljNil()"
var argParts: seq[string] = @[]
for i in 2..<items.len:
argParts.add(emitExpr(items[i], indent))
argParts.add(emitExpr(items[i], indent, needsValue = true))
let mangled = mangleName(className.symName)
return sp & mangled & "(" & argParts.join(", ") & ")"
@@ -2077,7 +2087,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if rn.len > 0:
var argParts: seq[string] = @[]
for i in 1..<items.len:
argParts.add(emitExpr(items[i], indent))
argParts.add(emitExpr(items[i], indent, needsValue = true))
# Variadic functions take seq[CljVal]
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
"println", "prn", "print", "str", "pr-str",
@@ -2163,7 +2173,7 @@ proc emitQuotedForm*(v: CljVal): string =
else:
return emitExpr(v, 0)
proc emitExpr*(v: CljVal, indent: int = 0): string =
proc emitExpr*(v: CljVal, indent: int = 0, needsValue: bool = false): string =
let sp = indentStr(indent)
case v.kind
of ckNil:
@@ -2237,12 +2247,12 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
# Macro-expand before emitting
let expanded = macroexpand(v)
if expanded == v:
return emitSpecialForm(v.items, indent)
return emitExpr(expanded, indent)
return emitSpecialForm(v.items, indent, needsValue)
return emitExpr(expanded, indent, needsValue)
of ckVector:
var parts: seq[string] = @[]
for item in v.items:
parts.add(emitExpr(item, indent))
parts.add(emitExpr(item, indent, needsValue = true))
return sp & "cljVector(@[" & parts.join(", ") & "])"
of ckMap:
if v.mapKeys.len == 0:
@@ -2250,18 +2260,18 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
var keyParts: seq[string] = @[]
var valParts: seq[string] = @[]
for i in 0..<v.mapKeys.len:
keyParts.add(emitExpr(v.mapKeys[i], indent))
valParts.add(emitExpr(v.mapVals[i], indent))
keyParts.add(emitExpr(v.mapKeys[i], indent, needsValue = true))
valParts.add(emitExpr(v.mapVals[i], indent, needsValue = true))
return sp & "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])"
of ckSet:
var parts: seq[string] = @[]
for item in v.setItems:
parts.add(emitExpr(item, indent))
parts.add(emitExpr(item, indent, needsValue = true))
return sp & "cljSet(@[" & parts.join(", ") & "])"
of ckFn:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = discard cljNil())"
of ckAtom:
return sp & "cljAtom(" & emitExpr(v.atomVal, 0) & ")"
return sp & "cljAtom(" & emitExpr(v.atomVal, 0, needsValue = true) & ")"
of ckTransient:
return sp & "cljTransient(cljNil())"
of ckAgent:
@@ -2356,7 +2366,7 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
let defCode = emitExpr(ef, 0)
defs.add(defCode)
else:
let code = emitExpr(ef, 2)
let code = emitExpr(ef, 2, needsValue = isLast)
let stripped = code.strip()
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
stripped.startsWith("var ") or stripped.startsWith("while ") or