fix: implement cljnim compiler recommendations (10 fixes)
- #1: Support docstrings in defn/defn- - #2: Support multi-arity defn with dispatch by args.len - #3: (:key map) keyword-as-function syntax (cljGet) - #4: & rest parameters in defn/defn- - #5: Nim interop name mangling for nim/ module calls - #6: getLibPath() checks CLJNIM_LIB_PATH env and cwd first - #7: Parse :paths from deps.edn - #8: loop+if/else discard fix with loopResult variable - #10: emitProgramLib skips when isMainModule guard
This commit is contained in:
+7
-2
@@ -3,13 +3,18 @@ import os, osproc, strutils, times
|
|||||||
import reader, emitter, types, repl, macros, deps, ai_assist, tui
|
import reader, emitter, types, repl, macros, deps, ai_assist, tui
|
||||||
|
|
||||||
proc getLibPath(): string =
|
proc getLibPath(): string =
|
||||||
|
# Check environment variable first
|
||||||
|
let envPath = getEnv("CLJNIM_LIB_PATH", "")
|
||||||
|
if envPath.len > 0 and dirExists(envPath):
|
||||||
|
return envPath
|
||||||
|
# Check current directory's lib first (project-local libs)
|
||||||
|
let candidate0 = getCurrentDir() / "lib"
|
||||||
|
if dirExists(candidate0): return candidate0
|
||||||
let appDir = getAppDir()
|
let appDir = getAppDir()
|
||||||
let candidate1 = appDir / "lib"
|
let candidate1 = appDir / "lib"
|
||||||
let candidate2 = appDir.parentDir / "lib"
|
let candidate2 = appDir.parentDir / "lib"
|
||||||
let candidate3 = getCurrentDir() / "lib"
|
|
||||||
if dirExists(candidate1): return candidate1
|
if dirExists(candidate1): return candidate1
|
||||||
if dirExists(candidate2): return candidate2
|
if dirExists(candidate2): return candidate2
|
||||||
if dirExists(candidate3): return candidate3
|
|
||||||
return "lib"
|
return "lib"
|
||||||
|
|
||||||
proc resolveNsToPath(nsName: string, searchPaths: seq[string]): string =
|
proc resolveNsToPath(nsName: string, searchPaths: seq[string]): string =
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type
|
|||||||
|
|
||||||
DepsFile* = object
|
DepsFile* = object
|
||||||
deps*: seq[Dependency]
|
deps*: seq[Dependency]
|
||||||
|
paths*: seq[string]
|
||||||
|
|
||||||
DepsError* = object of CatchableError
|
DepsError* = object of CatchableError
|
||||||
|
|
||||||
@@ -103,6 +104,11 @@ proc parseDepsFile*(filePath: string): DepsFile =
|
|||||||
for (pk, pv) in pairs:
|
for (pk, pv) in pairs:
|
||||||
if pk == "deps":
|
if pk == "deps":
|
||||||
result.deps = parseDepsMap(pv)
|
result.deps = parseDepsMap(pv)
|
||||||
|
elif pk == "paths":
|
||||||
|
if pv.kind == ckVector:
|
||||||
|
for p in pv.items:
|
||||||
|
if p.kind == ckString:
|
||||||
|
result.paths.add(p.strVal)
|
||||||
|
|
||||||
proc depsDir*(projectDir: string): string =
|
proc depsDir*(projectDir: string): string =
|
||||||
## Return the .deps directory path for a project
|
## Return the .deps directory path for a project
|
||||||
@@ -139,6 +145,11 @@ proc gitCheckout*(repoDir, sha: string): bool =
|
|||||||
proc resolveDeps*(depsFile: DepsFile, projectDir: string): seq[string] =
|
proc resolveDeps*(depsFile: DepsFile, projectDir: string): seq[string] =
|
||||||
## Download/resolve all dependencies and return search paths
|
## Download/resolve all dependencies and return search paths
|
||||||
result = @[]
|
result = @[]
|
||||||
|
# Add :paths from deps.edn
|
||||||
|
for p in depsFile.paths:
|
||||||
|
let absPath = if p.isAbsolute: p else: projectDir / p
|
||||||
|
if dirExists(absPath):
|
||||||
|
result.add(absPath)
|
||||||
for dep in depsFile.deps:
|
for dep in depsFile.deps:
|
||||||
case dep.kind
|
case dep.kind
|
||||||
of dkLocal:
|
of dkLocal:
|
||||||
|
|||||||
+236
-13
@@ -1,5 +1,5 @@
|
|||||||
# Bara Lang → Nim Emitter
|
# Bara Lang → Nim Emitter
|
||||||
import strutils, sets, algorithm
|
import strutils, sets, algorithm, sequtils
|
||||||
import types
|
import types
|
||||||
import macros
|
import macros
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ var loopStack*: seq[seq[string]] = @[]
|
|||||||
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
||||||
var scopeStack*: seq[HashSet[string]] = @[]
|
var scopeStack*: seq[HashSet[string]] = @[]
|
||||||
var definedGlobals*: HashSet[string] = initHashSet[string]()
|
var definedGlobals*: HashSet[string] = initHashSet[string]()
|
||||||
|
var multiArityFns*: HashSet[string] = initHashSet[string]()
|
||||||
|
var loopResultVar*: string = "" # Set by loop handler when result capture is needed
|
||||||
|
|
||||||
proc registerGlobal*(name: string) =
|
proc registerGlobal*(name: string) =
|
||||||
definedGlobals.incl(name)
|
definedGlobals.incl(name)
|
||||||
@@ -454,6 +456,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:
|
||||||
|
# (:key map) — keyword as function: lookup key in map
|
||||||
|
if head.kind == ckKeyword:
|
||||||
|
if items.len == 2:
|
||||||
|
return sp & "cljGet(" & emitExpr(items[1], 0) & ", cljKeyword(\"" & head.kwName & "\"))"
|
||||||
|
elif items.len == 3:
|
||||||
|
return sp & "cljGetDefault(" & emitExpr(items[1], 0) & ", cljKeyword(\"" & head.kwName & "\"), " & emitExpr(items[2], 0) & ")"
|
||||||
# Head is not a symbol - evaluate as function call: ((fn ...) args...)
|
# Head is not a symbol - evaluate as function call: ((fn ...) args...)
|
||||||
var argParts: seq[string] = @[]
|
var argParts: seq[string] = @[]
|
||||||
for i in 1..<items.len:
|
for i in 1..<items.len:
|
||||||
@@ -706,19 +714,112 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
if name.kind != ckSymbol:
|
if name.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "defn name must be a symbol")
|
raise newException(EmitterError, "defn name must be a symbol")
|
||||||
registerGlobal(name.symName)
|
registerGlobal(name.symName)
|
||||||
let params = items[2]
|
# Skip docstring if present: (defn name "doc" [params] body...)
|
||||||
|
var paramsIdx = 2
|
||||||
|
if items.len > 3 and items[2].kind == ckString:
|
||||||
|
paramsIdx = 3
|
||||||
|
let params = items[paramsIdx]
|
||||||
|
# Multi-arity defn: (defn name ([a] ...) ([a b] ...))
|
||||||
|
if params.kind == ckList:
|
||||||
|
let procName = mangleName(name.symName)
|
||||||
|
let exportMarker = if emitLibMode: "*" else: ""
|
||||||
|
# Register as multi-arity BEFORE emitting arity bodies (so internal calls wrap correctly)
|
||||||
|
multiArityFns.incl(name.symName)
|
||||||
|
var arityProcs: seq[string] = @[]
|
||||||
|
var dispatchCases: seq[string] = @[]
|
||||||
|
for arityIdx in paramsIdx..<items.len:
|
||||||
|
let arityForm = items[arityIdx]
|
||||||
|
if arityForm.kind == ckList and arityForm.items.len >= 2 and
|
||||||
|
arityForm.items[0].kind == ckVector:
|
||||||
|
let arityParams = arityForm.items[0]
|
||||||
|
let arityBody = arityForm.items[1..^1]
|
||||||
|
let paramCount = arityParams.items.len
|
||||||
|
var paramNames: seq[string] = @[]
|
||||||
|
pushScope()
|
||||||
|
for p in arityParams.items:
|
||||||
|
if p.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn params must be symbols")
|
||||||
|
addToScope(p.symName)
|
||||||
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
||||||
|
var bodyCode = ""
|
||||||
|
if arityBody.len == 1:
|
||||||
|
bodyCode = emitExpr(arityBody[0], indent + 1)
|
||||||
|
else:
|
||||||
|
bodyCode = emitBlock(arityBody, indent + 1)
|
||||||
|
popScope()
|
||||||
|
let arityName = procName & "_arity" & $paramCount
|
||||||
|
arityProcs.add(sp & "proc " & arityName & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode)
|
||||||
|
let argCall = (0..<paramCount).mapIt("args[" & $it & "]").join(", ")
|
||||||
|
dispatchCases.add(indentStr(indent + 1) & "of " & $paramCount & ": " & arityName & "(" & argCall & ")")
|
||||||
|
if arityProcs.len > 0:
|
||||||
|
var allLines: seq[string] = @[]
|
||||||
|
# Forward declaration for the dispatch proc
|
||||||
|
allLines.add(sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal")
|
||||||
|
for ap in arityProcs:
|
||||||
|
allLines.add(ap)
|
||||||
|
allLines.add(sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal =")
|
||||||
|
allLines.add(indentStr(indent + 1) & "case args.len")
|
||||||
|
for dc in dispatchCases:
|
||||||
|
allLines.add(dc)
|
||||||
|
allLines.add(indentStr(indent + 1) & "else: raise newException(CatchableError, \"Wrong number of args to " & name.symName & "\")")
|
||||||
|
# Register as multi-arity so call sites use cljApply
|
||||||
|
multiArityFns.incl(name.symName)
|
||||||
|
return allLines.join("\n")
|
||||||
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] = @[]
|
||||||
|
var hasRest = false
|
||||||
|
var restIdx = -1
|
||||||
pushScope()
|
pushScope()
|
||||||
|
# First pass: check for & rest params
|
||||||
|
for pi, p in params.items:
|
||||||
|
if p.kind == ckSymbol and p.symName == "&":
|
||||||
|
hasRest = true
|
||||||
|
restIdx = pi
|
||||||
|
break
|
||||||
|
if hasRest:
|
||||||
|
# Collect named params before &
|
||||||
|
for pi in 0..<restIdx:
|
||||||
|
let p = params.items[pi]
|
||||||
|
if p.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn params must be symbols")
|
||||||
|
addToScope(p.symName)
|
||||||
|
# The param after & is the rest param name
|
||||||
|
if restIdx + 1 < params.items.len:
|
||||||
|
let restParam = params.items[restIdx + 1]
|
||||||
|
if restParam.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn rest param must be a symbol")
|
||||||
|
addToScope(restParam.symName)
|
||||||
|
else:
|
||||||
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)
|
addToScope(p.symName)
|
||||||
paramNames.add(mangleName(p.symName) & ": CljVal")
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
||||||
let body = items[3..^1]
|
let body = items[(paramsIdx+1)..^1]
|
||||||
let procName = mangleName(name.symName)
|
let procName = mangleName(name.symName)
|
||||||
let exportMarker = if emitLibMode: "*" else: ""
|
let exportMarker = if emitLibMode: "*" else: ""
|
||||||
|
if hasRest:
|
||||||
|
# Rest params: generate proc with args: seq[CljVal]
|
||||||
|
# Register so call sites wrap args in @[...]
|
||||||
|
multiArityFns.incl(name.symName)
|
||||||
|
let namedCount = restIdx
|
||||||
|
var preambleLines: seq[string] = @[]
|
||||||
|
for pi in 0..<restIdx:
|
||||||
|
let p = params.items[pi]
|
||||||
|
preambleLines.add(indentStr(indent + 1) & "let " & mangleName(p.symName) & " = args[" & $pi & "]")
|
||||||
|
let restParamName = params.items[restIdx + 1].symName
|
||||||
|
preambleLines.add(indentStr(indent + 1) & "let " & mangleName(restParamName) & " = cljList(args[" & $namedCount & "..^1])")
|
||||||
|
var bodyCode = ""
|
||||||
|
if body.len == 0:
|
||||||
|
bodyCode = indentStr(indent + 1) & "cljNil()"
|
||||||
|
elif body.len == 1:
|
||||||
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
|
else:
|
||||||
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
|
popScope()
|
||||||
|
let preamble = preambleLines.join("\n")
|
||||||
|
return sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal =\n" & preamble & "\n" & bodyCode
|
||||||
if indent == 0:
|
if indent == 0:
|
||||||
var bodyCode = ""
|
var bodyCode = ""
|
||||||
if body.len == 0:
|
if body.len == 0:
|
||||||
@@ -751,17 +852,62 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
if name.kind != ckSymbol:
|
if name.kind != ckSymbol:
|
||||||
raise newException(EmitterError, "defn- name must be a symbol")
|
raise newException(EmitterError, "defn- name must be a symbol")
|
||||||
registerGlobal(name.symName)
|
registerGlobal(name.symName)
|
||||||
let params = items[2]
|
# Skip docstring if present: (defn- name "doc" [params] body...)
|
||||||
|
var paramsIdx = 2
|
||||||
|
if items.len > 3 and items[2].kind == ckString:
|
||||||
|
paramsIdx = 3
|
||||||
|
let params = items[paramsIdx]
|
||||||
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] = @[]
|
||||||
|
var hasRest = false
|
||||||
|
var restIdx = -1
|
||||||
pushScope()
|
pushScope()
|
||||||
|
# First pass: check for & rest params
|
||||||
|
for pi, p in params.items:
|
||||||
|
if p.kind == ckSymbol and p.symName == "&":
|
||||||
|
hasRest = true
|
||||||
|
restIdx = pi
|
||||||
|
break
|
||||||
|
if hasRest:
|
||||||
|
for pi in 0..<restIdx:
|
||||||
|
let p = params.items[pi]
|
||||||
|
if p.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn- params must be symbols")
|
||||||
|
addToScope(p.symName)
|
||||||
|
if restIdx + 1 < params.items.len:
|
||||||
|
let restParam = params.items[restIdx + 1]
|
||||||
|
if restParam.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn- rest param must be a symbol")
|
||||||
|
addToScope(restParam.symName)
|
||||||
|
else:
|
||||||
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)
|
addToScope(p.symName)
|
||||||
paramNames.add(mangleName(p.symName) & ": CljVal")
|
paramNames.add(mangleName(p.symName) & ": CljVal")
|
||||||
let body = items[3..^1]
|
let body = items[(paramsIdx+1)..^1]
|
||||||
|
let procName = mangleName(name.symName)
|
||||||
|
if hasRest:
|
||||||
|
# Rest params: generate proc with args: seq[CljVal]
|
||||||
|
multiArityFns.incl(name.symName)
|
||||||
|
let namedCount = restIdx
|
||||||
|
var preambleLines: seq[string] = @[]
|
||||||
|
for pi in 0..<restIdx:
|
||||||
|
let p = params.items[pi]
|
||||||
|
preambleLines.add(indentStr(indent + 1) & "let " & mangleName(p.symName) & " = args[" & $pi & "]")
|
||||||
|
let restParamName = params.items[restIdx + 1].symName
|
||||||
|
preambleLines.add(indentStr(indent + 1) & "let " & mangleName(restParamName) & " = cljList(args[" & $namedCount & "..^1])")
|
||||||
|
var bodyCode = ""
|
||||||
|
if body.len == 0:
|
||||||
|
bodyCode = indentStr(indent + 1) & "cljNil()"
|
||||||
|
elif body.len == 1:
|
||||||
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
|
else:
|
||||||
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
|
popScope()
|
||||||
|
let preamble = preambleLines.join("\n")
|
||||||
|
return sp & "proc " & procName & "(args: seq[CljVal]): CljVal {.used.} =\n" & preamble & "\n" & bodyCode
|
||||||
var bodyCode = ""
|
var bodyCode = ""
|
||||||
if body.len == 0:
|
if body.len == 0:
|
||||||
bodyCode = indentStr(indent + 1) & "cljNil()"
|
bodyCode = indentStr(indent + 1) & "cljNil()"
|
||||||
@@ -770,7 +916,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
else:
|
else:
|
||||||
bodyCode = emitBlock(body, indent + 1)
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
popScope()
|
popScope()
|
||||||
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
|
||||||
|
|
||||||
of "fn":
|
of "fn":
|
||||||
@@ -909,13 +1054,54 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
of "if":
|
of "if":
|
||||||
if items.len < 3 or items.len > 4:
|
if items.len < 3 or items.len > 4:
|
||||||
raise newException(EmitterError, "if requires condition, then, and optional else")
|
raise newException(EmitterError, "if requires condition, then, and optional else")
|
||||||
|
# Recursively check if an if form has control flow in any branch
|
||||||
|
proc ifHasControlFlow(form: CljVal): bool =
|
||||||
|
if form.kind != ckList or form.items.len < 3: return false
|
||||||
|
if form.items[0].kind != ckSymbol or form.items[0].symName != "if": return false
|
||||||
|
let thenCode = emitExpr(form.items[2], 0)
|
||||||
|
if thenCode.strip().contains("continue") or thenCode.strip().contains("break"):
|
||||||
|
return true
|
||||||
|
if form.items.len == 4:
|
||||||
|
let elseCode = emitExpr(form.items[3], 0)
|
||||||
|
if elseCode.strip().contains("continue") or elseCode.strip().contains("break"):
|
||||||
|
return true
|
||||||
|
# Recurse into nested if in else branch
|
||||||
|
if form.items[3].kind == ckList and form.items[3].items.len > 0 and
|
||||||
|
form.items[3].items[0].kind == ckSymbol and form.items[3].items[0].symName == "if":
|
||||||
|
return ifHasControlFlow(form.items[3])
|
||||||
|
return false
|
||||||
let condCode = emitExpr(items[1], 0)
|
let condCode = emitExpr(items[1], 0)
|
||||||
let thenCode = emitExpr(items[2], indent + 1)
|
let thenCode = emitExpr(items[2], indent + 1)
|
||||||
var ifBlock = sp & "if cljIsTruthy(" & condCode.strip() & "):\n" & thenCode
|
let thenStripped = thenCode.strip()
|
||||||
|
let thenIsControl = thenStripped.contains("continue") or thenStripped.contains("break")
|
||||||
|
let hasCF = ifHasControlFlow(items[0])
|
||||||
if items.len == 4:
|
if items.len == 4:
|
||||||
let elseCode = emitExpr(items[3], indent + 1)
|
let elseCode = emitExpr(items[3], indent + 1)
|
||||||
|
let elseStripped = elseCode.strip()
|
||||||
|
let elseIsControl = elseStripped.contains("continue") or elseStripped.contains("break")
|
||||||
|
if thenIsControl or elseIsControl:
|
||||||
|
# Direct control flow in branches — use statement form
|
||||||
|
var lines: seq[string] = @[]
|
||||||
|
lines.add(sp & "if cljIsTruthy(" & condCode.strip() & "):")
|
||||||
|
if thenIsControl:
|
||||||
|
lines.add(thenCode)
|
||||||
|
elif loopResultVar.len > 0:
|
||||||
|
lines.add(indentStr(indent + 1) & loopResultVar & " = " & thenStripped)
|
||||||
|
else:
|
||||||
|
lines.add(indentStr(indent + 1) & "discard " & thenStripped)
|
||||||
|
lines.add(sp & "else:")
|
||||||
|
if elseIsControl:
|
||||||
|
lines.add(elseCode)
|
||||||
|
elif loopResultVar.len > 0:
|
||||||
|
lines.add(indentStr(indent + 1) & loopResultVar & " = " & elseStripped)
|
||||||
|
else:
|
||||||
|
lines.add(indentStr(indent + 1) & "discard " & elseStripped)
|
||||||
|
return lines.join("\n")
|
||||||
|
var ifBlock = sp & "if cljIsTruthy(" & condCode.strip() & "):\n" & thenCode
|
||||||
ifBlock.add("\n" & sp & "else:\n" & elseCode)
|
ifBlock.add("\n" & sp & "else:\n" & elseCode)
|
||||||
return ifBlock
|
return ifBlock
|
||||||
|
var ifBlock = sp & "if cljIsTruthy(" & condCode.strip() & "):\n" & thenCode
|
||||||
|
return ifBlock
|
||||||
|
|
||||||
of "when":
|
of "when":
|
||||||
if items.len < 3:
|
if items.len < 3:
|
||||||
@@ -999,21 +1185,43 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
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)
|
||||||
lines.add(sp & "while true:")
|
|
||||||
let body = items[2..^1]
|
let body = items[2..^1]
|
||||||
|
# Check if the last form is an 'if' with control flow in branches (recursively)
|
||||||
|
proc ifHasCF(form: CljVal): bool =
|
||||||
|
if form.kind != ckList or form.items.len < 3: return false
|
||||||
|
if form.items[0].kind != ckSymbol or form.items[0].symName != "if": return false
|
||||||
|
let thenCode = emitExpr(form.items[2], 0)
|
||||||
|
if thenCode.strip().contains("continue") or thenCode.strip().contains("break"):
|
||||||
|
return true
|
||||||
|
if form.items.len == 4:
|
||||||
|
let elseCode = emitExpr(form.items[3], 0)
|
||||||
|
if elseCode.strip().contains("continue") or elseCode.strip().contains("break"):
|
||||||
|
return true
|
||||||
|
if form.items[3].kind == ckList and form.items[3].items.len > 0 and
|
||||||
|
form.items[3].items[0].kind == ckSymbol and form.items[3].items[0].symName == "if":
|
||||||
|
return ifHasCF(form.items[3])
|
||||||
|
return false
|
||||||
|
var needsResultVar = false
|
||||||
|
if body.len > 0:
|
||||||
|
needsResultVar = ifHasCF(body[^1])
|
||||||
|
if needsResultVar:
|
||||||
|
lines.add(sp & "var loopResult: CljVal = cljNil()")
|
||||||
|
loopResultVar = "loopResult"
|
||||||
|
lines.add(sp & "while true:")
|
||||||
for bi, b in body:
|
for bi, b in body:
|
||||||
let bcode = emitExpr(b, indent + 1)
|
let bcode = emitExpr(b, indent + 1)
|
||||||
let stripped = bcode.strip()
|
let stripped = bcode.strip()
|
||||||
let isLast = (bi == body.len - 1)
|
let isLast = (bi == body.len - 1)
|
||||||
let isStatement = stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
if isLast and needsResultVar:
|
||||||
|
# Last form with control flow: the if handler uses loopResultVar
|
||||||
|
lines.add(bcode)
|
||||||
|
elif isLast or stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||||
stripped.startsWith("var ") or stripped.startsWith("let ") or
|
stripped.startsWith("var ") or stripped.startsWith("let ") or
|
||||||
stripped.startsWith("proc ") or
|
stripped.startsWith("proc ") or
|
||||||
stripped.startsWith("if ") or stripped.startsWith("try:") or
|
stripped.startsWith("if ") or stripped.startsWith("try:") or
|
||||||
stripped.startsWith("while ") or stripped.contains("continue")
|
stripped.startsWith("while ") or stripped.contains("continue"):
|
||||||
if isLast or isStatement:
|
|
||||||
lines.add(bcode)
|
lines.add(bcode)
|
||||||
elif stripped.startsWith("block:"):
|
elif stripped.startsWith("block:"):
|
||||||
# Multi-line block as non-last form: discard the last line
|
|
||||||
var blockLines = bcode.split("\n")
|
var blockLines = bcode.split("\n")
|
||||||
var lastIdx = blockLines.len - 1
|
var lastIdx = blockLines.len - 1
|
||||||
while lastIdx >= 0 and blockLines[lastIdx].strip() == "":
|
while lastIdx >= 0 and blockLines[lastIdx].strip() == "":
|
||||||
@@ -1029,6 +1237,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
lines.add(indentStr(indent + 1) & "break")
|
lines.add(indentStr(indent + 1) & "break")
|
||||||
discard loopStack.pop()
|
discard loopStack.pop()
|
||||||
popScope()
|
popScope()
|
||||||
|
if needsResultVar:
|
||||||
|
lines.add(sp & "loopResult")
|
||||||
|
loopResultVar = ""
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
|
|
||||||
of "recur":
|
of "recur":
|
||||||
@@ -1737,7 +1948,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
let parts = op.replace(".", "/").split("/")
|
let parts = op.replace(".", "/").split("/")
|
||||||
if parts.len >= 3:
|
if parts.len >= 3:
|
||||||
let module = parts[1]
|
let module = parts[1]
|
||||||
var funcChain = parts[2..^1].join(".")
|
# Mangle each part of the function chain for Nim identifier validity
|
||||||
|
var mangledParts: seq[string] = @[]
|
||||||
|
for p in parts[2..^1]:
|
||||||
|
mangledParts.add(mangleName(p))
|
||||||
|
var funcChain = mangledParts.join(".")
|
||||||
# Strip ? suffix (Clojure convention) — not valid in Nim
|
# Strip ? suffix (Clojure convention) — not valid in Nim
|
||||||
if funcChain.endsWith("?"):
|
if funcChain.endsWith("?"):
|
||||||
funcChain = funcChain[0..^2]
|
funcChain = funcChain[0..^2]
|
||||||
@@ -1867,6 +2082,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
if callOp.endsWith("."):
|
if callOp.endsWith("."):
|
||||||
callOp = callOp[0..^2]
|
callOp = callOp[0..^2]
|
||||||
let mangled = mangleName(callOp)
|
let mangled = mangleName(callOp)
|
||||||
|
# Multi-arity function: wrap args in seq
|
||||||
|
if callOp in multiArityFns:
|
||||||
|
return sp & mangled & "(@[" & args.join(", ") & "])"
|
||||||
if isLocalVar(callOp):
|
if isLocalVar(callOp):
|
||||||
# Local value (may be fn, map, set, vector, keyword) — use runtime dispatch
|
# Local value (may be fn, map, set, vector, keyword) — use runtime dispatch
|
||||||
return sp & "cljCall(" & mangled & ", @[" & args.join(", ") & "])"
|
return sp & "cljCall(" & mangled & ", @[" & args.join(", ") & "])"
|
||||||
@@ -2141,6 +2359,11 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
|||||||
|
|
||||||
if mainForms.len > 0:
|
if mainForms.len > 0:
|
||||||
lines.add("")
|
lines.add("")
|
||||||
|
if emitLibMode:
|
||||||
|
# Lib mode: emit main forms at top level (no when isMainModule guard)
|
||||||
|
for form in mainForms:
|
||||||
|
lines.add(form)
|
||||||
|
else:
|
||||||
lines.add("when isMainModule:")
|
lines.add("when isMainModule:")
|
||||||
for form in mainForms:
|
for form in mainForms:
|
||||||
lines.add(form)
|
lines.add(form)
|
||||||
|
|||||||
Reference in New Issue
Block a user