feat: project build system with lib/bin separation
- Add src/project.nim: parse bara.edn manifest (name, version, lib, bins) - Add cljnim build / cljnim build --mono commands - Compile lib modules once to target/nim/lib_*.nim with exported defs - Cross-module symbol resolution via from/import nil + fully qualified names - Emitter: lib mode with * export markers, variadic seq[CljVal] handling
This commit is contained in:
+56
-9
@@ -5,6 +5,7 @@ import macros
|
||||
|
||||
var requiredImports* = initHashSet[string]()
|
||||
var emitLibMode* = false
|
||||
var emitEntryProcName* = ""
|
||||
var loopStack*: seq[seq[string]] = @[]
|
||||
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
||||
var scopeStack*: seq[HashSet[string]] = @[]
|
||||
@@ -82,6 +83,21 @@ proc clearGlobals*() =
|
||||
proc setNsAliases*(aliases: seq[(string, string)]) =
|
||||
nsAliases = aliases
|
||||
|
||||
var libNsPrefixes*: seq[string] = @[]
|
||||
|
||||
proc setLibNsPrefixes*(prefixes: seq[string]) =
|
||||
libNsPrefixes = prefixes
|
||||
|
||||
proc sanitizeNimIdent*(name: string): string
|
||||
|
||||
proc nsToNimModuleName(ns: string): string =
|
||||
result = "lib_"
|
||||
for c in ns:
|
||||
case c
|
||||
of '.', '-': result.add('_')
|
||||
of 'a'..'z', 'A'..'Z', '0'..'9', '_': result.add(c)
|
||||
else: result.add('_')
|
||||
|
||||
proc resolveNsAlias*(name: string): string =
|
||||
# Resolve mu/square -> square (strip namespace prefix if alias exists)
|
||||
let slashIdx = name.find('/')
|
||||
@@ -90,6 +106,9 @@ proc resolveNsAlias*(name: string): string =
|
||||
let suffix = name[slashIdx+1..^1]
|
||||
for (alias, ns) in nsAliases:
|
||||
if prefix == alias:
|
||||
if prefix in libNsPrefixes:
|
||||
# Return fully qualified name for lib module symbols
|
||||
return nsToNimModuleName(ns) & ".clj_" & sanitizeNimIdent(suffix)
|
||||
return suffix
|
||||
return name
|
||||
|
||||
@@ -130,6 +149,10 @@ proc sanitizeNimIdent*(name: string): string =
|
||||
result = "x" & result
|
||||
if result.len == 0:
|
||||
result = "val"
|
||||
# Avoid conflicts with runtime functions: all-uppercase short names (GET, POST, etc)
|
||||
# get a suffix so they don't collide with cljGet, cljSet, etc.
|
||||
if result.len > 0 and result.allCharsInSet({'A'..'Z', '_'}) and result.len <= 7:
|
||||
result.add("X")
|
||||
# Nim keywords that would clash
|
||||
let nimKeywords = ["in", "out", "var", "let", "proc", "func", "type", "ref", "ptr",
|
||||
"object", "method", "template", "macro", "iterator", "converter",
|
||||
@@ -732,7 +755,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false):
|
||||
let mangled = mangleName(nameItem.symName)
|
||||
registerGlobal(nameItem.symName)
|
||||
let valCode = emitExpr(valItem, 0, needsValue = true)
|
||||
return sp & "let " & mangled & " = " & valCode
|
||||
let exportMarker = if emitLibMode: "*" else: ""
|
||||
return sp & "let " & mangled & exportMarker & " = " & valCode
|
||||
return sp & "cljNil()"
|
||||
var name = items[1]
|
||||
# Strip metadata: (with-meta sym meta) -> sym
|
||||
@@ -748,6 +772,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false):
|
||||
# Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper
|
||||
let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and
|
||||
valForm.items[0].kind == ckSymbol and valForm.items[0].symName == "fn"
|
||||
let exportMarker = if emitLibMode: "*" else: ""
|
||||
if indent == 0:
|
||||
if isFnDef:
|
||||
# Emit fn as raw proc for def context
|
||||
@@ -755,9 +780,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false):
|
||||
emitLibMode = false
|
||||
let valCode = emitFnAsProc(valForm.items, 0)
|
||||
emitLibMode = oldEmitLibMode
|
||||
return sp & "let " & mangled & " = " & valCode
|
||||
return sp & "let " & mangled & exportMarker & " = " & valCode
|
||||
let valCode = emitExpr(items[2], 0, needsValue = true)
|
||||
return sp & "let " & mangled & " = " & valCode
|
||||
return sp & "let " & mangled & exportMarker & " = " & valCode
|
||||
else:
|
||||
# For nested def: use global var so it's accessible from closures
|
||||
if isFnDef:
|
||||
@@ -765,9 +790,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false):
|
||||
emitLibMode = false
|
||||
let valCode = emitFnAsProc(valForm.items, indent)
|
||||
emitLibMode = oldEmitLibMode
|
||||
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
|
||||
return sp & "var " & mangled & exportMarker & " = " & valCode.strip() & "\n" & sp & mangled
|
||||
let valCode = emitExpr(items[2], indent, needsValue = true)
|
||||
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
|
||||
return sp & "var " & mangled & exportMarker & " = " & valCode.strip() & "\n" & sp & mangled
|
||||
|
||||
of "defn":
|
||||
if items.len < 3:
|
||||
@@ -896,7 +921,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false):
|
||||
else:
|
||||
bodyCode = emitBlock(body, indent + 1)
|
||||
popScope()
|
||||
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||
var result = sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||
if emitLibMode and paramNames.len > 0 and not (name.symName in multiArityFns):
|
||||
var wrapperArgs: seq[string] = @[]
|
||||
for i in 0..<paramNames.len:
|
||||
wrapperArgs.add("args[" & $i & "]")
|
||||
result.add("\n" & sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal =\n")
|
||||
result.add(indentStr(indent + 1) & procName & "(" & wrapperArgs.join(", ") & ")\n")
|
||||
return result
|
||||
else:
|
||||
var bodyCode = ""
|
||||
if body.len == 1:
|
||||
@@ -2181,6 +2213,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false):
|
||||
var callOp = op
|
||||
if callOp.endsWith("."):
|
||||
callOp = callOp[0..^2]
|
||||
let baseOp = if callOp.contains("/"): callOp.split("/")[1] else: callOp
|
||||
let resolvedOp = resolveNsAlias(callOp)
|
||||
if resolvedOp.contains("."):
|
||||
return sp & resolvedOp & "(@[" & args.join(", ") & "])"
|
||||
let mangled = mangleName(callOp)
|
||||
# Multi-arity function: wrap args in seq
|
||||
if callOp in multiArityFns:
|
||||
@@ -2247,6 +2283,9 @@ proc emitExpr*(v: CljVal, indent: int = 0, needsValue: bool = false): string =
|
||||
return sp & "cljKeyword(\"" & v.kwName & "\")"
|
||||
of ckSymbol:
|
||||
let symName = resolveNsAlias(v.symName)
|
||||
# If symName contains a dot, it's a fully qualified lib module reference
|
||||
if symName.contains("."):
|
||||
return sp & symName
|
||||
let rn = runtimeName(symName)
|
||||
if rn.len > 0 and not isLocalVar(symName):
|
||||
# Check if the runtime function is variadic (takes seq[CljVal])
|
||||
@@ -2443,9 +2482,17 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
for form in mainForms:
|
||||
lines.add(form)
|
||||
else:
|
||||
lines.add("when isMainModule:")
|
||||
for form in mainForms:
|
||||
lines.add(form)
|
||||
if emitEntryProcName.len > 0:
|
||||
lines.add("proc " & emitEntryProcName & "*(args: seq[CljVal] = @[]): CljVal =")
|
||||
for form in mainForms:
|
||||
lines.add(form)
|
||||
lines.add("")
|
||||
lines.add("when isMainModule:")
|
||||
lines.add(" discard " & emitEntryProcName & "()")
|
||||
else:
|
||||
lines.add("when isMainModule:")
|
||||
for form in mainForms:
|
||||
lines.add(form)
|
||||
|
||||
return lines.join("\n") & "\n"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user