Phase 7: ns declaration + multi-file compilation

T7.1: Namespace declaration parsing
  (ns my.app (:require [lib :as l])) with :require/:as support
  Hyphen-to-underscore filename convention (math-utils -> math_utils.clj)

T7.2: Multi-file compilation
  Resolves requires, recursively loads dependencies
  Inlines all defs into single Nim file
  Namespace alias resolution (mu/square -> square)

86 tests pass, all examples work.
This commit is contained in:
2026-05-08 19:37:35 +03:00
parent 19ef69bf5b
commit 66d91cd086
4 changed files with 129 additions and 8 deletions
+75 -1
View File
@@ -12,6 +12,38 @@ proc getLibPath(): string =
if dirExists(candidate3): return candidate3
return "lib"
proc resolveNsToPath(nsName: string, searchPaths: seq[string]): string =
# Convert namespace name to file path: my.app -> my/app.clj
# Clojure convention: hyphens in ns names become underscores in filenames
let relPath = nsName.replace("-", "_").replace(".", "/") & ".clj"
for sp in searchPaths:
let candidate = sp / relPath
if fileExists(candidate):
return candidate
return ""
proc extractRequires(forms: seq[CljVal]): seq[(string, string)] =
# Extract require declarations: returns (namespace, alias) pairs
result = @[]
for form in forms:
if form.kind == ckList and form.items.len >= 2 and
form.items[0].kind == ckSymbol and form.items[0].symName == "ns":
for ri in 2..<form.items.len:
let clause = form.items[ri]
let isRequire = (clause.kind == ckList and clause.items.len > 0 and
((clause.items[0].kind == ckSymbol and clause.items[0].symName == ":require") or
(clause.items[0].kind == ckKeyword and clause.items[0].kwName == "require")))
if isRequire:
for ci in 1..<clause.items.len:
let req = clause.items[ci]
if req.kind == ckVector and req.items.len >= 1 and req.items[0].kind == ckSymbol:
let libName = req.items[0].symName
var alias = libName
if req.items.len >= 3 and req.items[1].kind == ckKeyword and req.items[1].kwName == "as":
if req.items[2].kind == ckSymbol:
alias = req.items[2].symName
result.add((libName, alias))
proc compileFile*(inputPath: string, outputPath: string) =
let source = readFile(inputPath)
let forms = reader.readAll(source)
@@ -20,7 +52,49 @@ proc compileFile*(inputPath: string, outputPath: string) =
stderr.writeLine("Error: No forms found in " & inputPath)
quit(1)
let nimCode = emitter.emitProgram(forms)
# Resolve requires and collect all forms from required files
let inputDir = inputPath.parentDir
let searchPaths = @[inputDir, getCurrentDir()]
let requires = extractRequires(forms)
var allForms: seq[CljVal] = @[]
var visited: seq[string] = @[]
var nsAliases: seq[(string, string)] = @[] # (alias, namespace)
proc resolveFile(nsName: string) =
if nsName in visited: return
visited.add(nsName)
let path = resolveNsToPath(nsName, searchPaths)
if path.len == 0:
stderr.writeLine("Warning: Cannot find file for namespace: " & nsName)
return
let src = readFile(path)
let fileForms = reader.readAll(src)
# Recursively resolve nested requires
let nestedRequires = extractRequires(fileForms)
for (nestedNs, _) in nestedRequires:
resolveFile(nestedNs)
# Add non-ns forms
for f in fileForms:
if not (f.kind == ckList and f.items.len >= 1 and
f.items[0].kind == ckSymbol and f.items[0].symName == "ns"):
allForms.add(f)
# Collect namespace aliases
for (nsName, alias) in requires:
nsAliases.add((alias, nsName))
resolveFile(nsName)
# Set namespace aliases in emitter
emitter.setNsAliases(nsAliases)
# Add current file's forms (skip ns declaration)
for f in forms:
if not (f.kind == ckList and f.items.len >= 1 and
f.items[0].kind == ckSymbol and f.items[0].symName == "ns"):
allForms.add(f)
let nimCode = emitter.emitProgram(allForms)
writeFile(outputPath, nimCode)
echo "Generated: ", outputPath