refactor: remove QBE backend, improve bootstrap compiler and stdlib

Bootstrap compiler improvements:
- Extract findStdlibDir() to remove hardcoded path and deduplicate
- Extract prepareProject()/mergeProject() to share code between cmdCheck/cmdBuild
- Fix C backend to emit warning on unknown types instead of silent 'int'
- Clean up all unused imports across 7 modules
- Makefile: add strip, debug target, clean-all, selfhost strip

QBE removal:
- Remove vendor/qbe/ (74K lines)
- Remove compiler/selfhost/qbe_backend.bux, nim_backend.bux

Stdlib improvements:
- Add Result.bux + Option.bux modules
- Fix Json.bux memory leak (removed double String_Copy)
- Add missing imports in Crypto.bux (Alloc/Free) and Test.bux (PrintLine/PrintInt)
- Clean stale buxc_debug binary
This commit is contained in:
2026-06-06 00:51:11 +03:00
parent 7b32cad3e9
commit 0dade151d2
146 changed files with 183 additions and 74747 deletions
+8 -3
View File
@@ -1,5 +1,5 @@
import std/[strformat, strutils, tables]
import hir, types, token, source_location
import std/[strformat, strutils]
import hir, types, token
type
CBackend* = object
@@ -107,7 +107,12 @@ proc typeToC*(be: var CBackend, typ: Type): string =
else: return typ.name
of tkTuple: return "void*" # TODO: proper tuple struct
of tkFunc: return "void*" # TODO: function pointer
else: return "int"
else:
when defined(release):
return "void*"
else:
stderr.writeLine("warning: C backend: unknown type kind " & $typ.kind & ", using void*")
return "void*"
proc operatorToC(op: TokenKind): string =
case op
+61 -112
View File
@@ -1,5 +1,5 @@
import std/[os, strutils, terminal, strformat, osproc, sets]
import lexer, parser, ast, sema, manifest, hir, hir_lower, c_backend, types, scope
import lexer, parser, ast, sema, manifest, hir_lower, c_backend
type
ColorMode* = enum
@@ -257,22 +257,47 @@ proc getDeclName(d: Decl): string
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl]
proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl]
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
proc findStdlibDir(root: string): string =
let searchPaths = @[
getAppDir() / ".." / "library",
getAppDir() / "library",
root / "library",
]
for path in searchPaths:
if dirExists(path):
return path
return ""
type
ProjectContext = object
root: string
man: Manifest
stdlibDir: string
stdlibDecls: seq[Decl]
depDecls: seq[Decl]
allModuleItems: seq[Decl]
hasMain: bool
proc prepareProject(root: string, useColor: bool, opts: GlobalOptions): (ProjectContext, int) =
var pctx: ProjectContext
pctx.root = root
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
return (pctx, 1)
pctx.man = loadManifest(manifestPath)
let srcDir = root / "src"
if not dirExists(srcDir):
printError("no src/ directory found", useColor)
return 1
return (pctx, 1)
# Phase 1: Parse all .bux files and collect declarations
var allModuleItems: seq[Decl] = @[]
var foundMain = false
pctx.stdlibDir = findStdlibDir(root)
pctx.stdlibDecls = collectStdlibDecls(pctx.stdlibDir)
let lock = loadLockfile(root / "bux.lock")
pctx.depDecls = collectDepDecls(lock, root, opts)
pctx.allModuleItems = @[]
pctx.hasMain = false
for kind, path in walkDir(srcDir):
if kind == pcFile and path.endsWith(".bux"):
let source = readFile(path)
@@ -281,50 +306,42 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
printError(&"lex errors in {path}", useColor)
for d in lexRes.diagnostics:
echo $d
return 1
return (pctx, 1)
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0:
printError(&"parse errors in {path}", useColor)
for d in parseRes.diagnostics:
echo &"error: {d.message} at {d.loc}"
return 1
# Flatten declarations from module wrappers
return (pctx, 1)
for decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
allModuleItems.add(sub)
pctx.allModuleItems.add(sub)
else:
allModuleItems.add(decl)
pctx.allModuleItems.add(decl)
if splitFile(path).name == "Main":
foundMain = true
pctx.hasMain = true
if not foundMain:
if not pctx.hasMain:
printError("no Main.bux found in src/", useColor)
return 1
return (pctx, 1)
# Phase 2: Merge with stdlib and check
var stdlibDir = ""
let stdlibSearchPaths = @[
getAppDir() / ".." / "library",
getAppDir() / "library",
getCurrentDir() / "library",
"/home/ziko/z-git/bux/bux/library",
]
for path in stdlibSearchPaths:
if dirExists(path):
stdlibDir = path
break
let stdlibDecls = collectStdlibDecls(stdlibDir)
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
return (pctx, 0)
proc mergeProject(pctx: ProjectContext): Module =
let stdlibAndDeps = mergeDecls(pctx.stdlibDecls, pctx.depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, pctx.allModuleItems)
var unifiedModule = newModule("main")
unifiedModule.items = mergedItems
return unifiedModule
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let (pctx, status) = prepareProject(root, useColor, opts)
if status != 0:
return status
let unifiedModule = mergeProject(pctx)
let semaRes = analyze(unifiedModule)
if semaRes.hasErrors:
printError("type errors in project", useColor)
@@ -332,7 +349,6 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
let sev = if d.severity == sdsError: "error" else: "warning"
echo &"{sev}: {d.message} at {d.loc}"
return 1
if not opts.quiet:
printInfo("check passed", useColor)
return 0
@@ -417,82 +433,16 @@ proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] =
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
let srcDir = root / "src"
if not dirExists(srcDir):
printError("no src/ directory found", useColor)
return 1
let (pctx, status) = prepareProject(root, useColor, opts)
if status != 0:
return status
# Create build directory
let buildDir = root / "build"
if not dirExists(buildDir):
createDir(buildDir)
# Find stdlib directory
var stdlibDir = ""
let stdlibSearchPaths = @[
getAppDir() / ".." / "library",
getAppDir() / "library",
root / "library",
"/home/ziko/z-git/bux/bux/library",
]
for path in stdlibSearchPaths:
if dirExists(path):
stdlibDir = path
break
# Collect stdlib declarations once
let stdlibDecls = collectStdlibDecls(stdlibDir)
# Collect dependency declarations from lockfile
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
# Phase 1: Parse all .bux files and collect declarations
var allModuleItems: seq[Decl] = @[]
var foundMain = false
for kind, path in walkDir(srcDir):
if kind == pcFile and path.endsWith(".bux"):
let source = readFile(path)
let lexRes = tokenize(source, path)
if lexRes.hasErrors:
printError(&"lex errors in {path}", useColor)
for d in lexRes.diagnostics:
echo $d
return 1
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0:
printError(&"parse errors in {path}", useColor)
for d in parseRes.diagnostics:
echo &"error: {d.message} at {d.loc}"
return 1
# Flatten: extract declarations from module wrappers
for decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
allModuleItems.add(sub)
else:
allModuleItems.add(decl)
if splitFile(path).name == "Main":
foundMain = true
if not foundMain:
printError("no Main.bux found in src/", useColor)
return 1
# Phase 2: Merge stdlib + deps + project (later shadow earlier)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
# Create unified module
var unifiedModule = newModule("main")
unifiedModule.items = mergedItems
let unifiedModule = mergeProject(pctx)
# Phase 3: Sema + HIR + C codegen on unified module
let (semaRes, semaCtx) = analyzeFull(unifiedModule)
@@ -512,13 +462,13 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
writeFile(cFile, allCCode)
# Copy runtime and stdlib files
let stdlibDir = pctx.stdlibDir
let runtimeDst = buildDir / "runtime.c"
let ioDst = buildDir / "io.c"
if stdlibDir == "":
printError("stdlib directory not found", useColor)
return 1
# Copy runtime.c
let runtimeSrc = stdlibDir / "runtime" / "runtime.c"
if fileExists(runtimeSrc):
copyFile(runtimeSrc, runtimeDst)
@@ -526,7 +476,6 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
printError("runtime.c not found in library/runtime/", useColor)
return 1
# Copy io.c
let ioSrc = stdlibDir / "runtime" / "io.c"
if fileExists(ioSrc):
copyFile(ioSrc, ioDst)
@@ -535,7 +484,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
return 1
# Compile with cc
let outputName = if man.name != "": man.name else: "bux_out"
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
let outputFile = buildDir / outputName
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
if opts.verbose:
@@ -552,10 +501,10 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let buildRes = cmdBuild(args, opts)
if buildRes != 0:
return buildRes
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let man = loadManifest(root / "bux.toml")
let outputName = if man.name != "": man.name else: "bux_out"
let outputFile = root / "build" / outputName
+1 -1
View File
@@ -1,4 +1,4 @@
import std/[tables, sets, strformat, strutils]
import std/[tables, sets, strutils]
import ast, types, token, source_location, hir, sema, scope
type
+1 -1
View File
@@ -1,4 +1,4 @@
import std/[strutils, os, tables, sequtils, strformat]
import std/[strutils, os, tables, strformat]
type
PackageType* = enum
+2 -2
View File
@@ -1,5 +1,5 @@
import std/[strformat, sequtils, strutils]
import token, source_location, lexer, ast
import std/strutils
import token, source_location, ast
type
ParserDiagnosticSeverity* = enum
+1 -1
View File
@@ -1,5 +1,5 @@
import std/tables
import types, ast, source_location
import types, ast
type
SymbolKind* = enum
+1 -1
View File
@@ -1,4 +1,4 @@
import std/[strformat, tables, sequtils, strutils]
import std/[strformat, tables, strutils]
import ast, types, scope, source_location, token
type
+2 -1
View File
@@ -1,4 +1,5 @@
import std/[sequtils, strformat, strutils]
import std/[sequtils, strutils]
import token
type
TypeKind* = enum
-812
View File
@@ -1,812 +0,0 @@
// nim_backend.bux — Nim transpiler backend
// Generates Nim code from the HIR. Much simpler than C backend.
import Std::String;
import Std::Io::{Print, PrintLine};
module NimBackend {
// ---------------------------------------------------------------------------
// Type → Nim type name
// ---------------------------------------------------------------------------
func NimBackend_TypeToNim(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
if kind == tyChar16 { return "uint16"; }
if kind == tyChar32 { return "uint32"; }
if kind == tyStr { return "string"; }
if kind == tyInt8 { return "int8"; }
if kind == tyInt16 { return "int16"; }
if kind == tyInt32 { return "int32"; }
if kind == tyInt64 { return "int64"; }
if kind == tyInt { return "int"; }
if kind == tyUInt8 { return "uint8"; }
if kind == tyUInt16 { return "uint16"; }
if kind == tyUInt32 { return "uint32"; }
if kind == tyUInt64 { return "uint64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat32 { return "float32"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyPointer { return "pointer"; }
return "int";
}
// ---------------------------------------------------------------------------
// Operator → Nim
// ---------------------------------------------------------------------------
func NimBackend_OpToNim(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
if op == tkSlash { return "/"; }
if op == tkPercent { return "%"; }
if op == tkEq { return "=="; }
if op == tkNe { return "!="; }
if op == tkLt { return "<"; }
if op == tkLe { return "<="; }
if op == tkGt { return ">"; }
if op == tkGe { return ">="; }
if op == tkAmpAmp { return "and"; }
if op == tkPipePipe { return "or"; }
if op == tkBang { return "not"; }
if op == tkAmp { return "and"; }
if op == tkPipe { return "or"; }
if op == tkCaret { return "xor"; }
if op == tkShl { return "shl"; }
if op == tkShr { return "shr"; }
if op == tkAssign { return "="; }
return "?";
}
// ---------------------------------------------------------------------------
// Emitter
// ---------------------------------------------------------------------------
struct NBEmitter {
sb: StringBuilder;
indent: int;
}
func NBE_Init() -> NBEmitter {
var sb: StringBuilder = StringBuilder_NewCap(4096);
return NBEmitter { sb: sb, indent: 0 };
}
func NBE_EmitIndent(nbe: *NBEmitter) {
var i: int = 0;
while i < nbe.indent {
StringBuilder_Append(&nbe.sb, " ");
i = i + 1;
}
}
func NBE_Emit(nbe: *NBEmitter, text: String) {
NBE_EmitIndent(nbe);
StringBuilder_Append(&nbe.sb, text);
}
func NBE_EmitLine(nbe: *NBEmitter, text: String) {
NBE_Emit(nbe, text);
StringBuilder_Append(&nbe.sb, "\n");
}
func NBE_Build(nbe: *NBEmitter) -> String {
return StringBuilder_Build(&nbe.sb);
}
func NBE_Append(nbe: *NBEmitter, text: String) {
StringBuilder_Append(&nbe.sb, text);
}
func NBE_AppendLine(nbe: *NBEmitter, text: String) {
StringBuilder_Append(&nbe.sb, text);
StringBuilder_Append(&nbe.sb, "\n");
}
func NBE_AppendChar(nbe: *NBEmitter, c: char8) {
var tmp: *char8 = bux_alloc(2) as *char8;
tmp[0] = c;
tmp[1] = 0 as char8;
StringBuilder_Append(&nbe.sb, tmp);
}
// ---------------------------------------------------------------------------
// Expression emission
// ---------------------------------------------------------------------------
func NBE_EmitExpr(nbe: *NBEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
// Literal
if kind == hLit {
if node.intValue == tkNull {
NBE_Append(nbe, "nil");
} else if node.intValue == tkStringLiteral {
// Nim string: emit quoted with escapes
NBE_Append(nbe, "\"");
var s: String = node.strValue;
if s != null as String {
var slen: int = bux_strlen(s) as int;
var start: int = 0;
var end: int = slen;
// Strip outer quotes if present
if slen >= 2 {
var fc: char8 = s[0];
var lc: char8 = s[slen - 1];
if fc == 34 as char8 && lc == 34 as char8 {
start = 1;
end = slen - 1;
}
}
var i: int = start;
while i < end {
var c: char8 = s[i];
if c == 10 as char8 { NBE_Append(nbe, "\\n"); } // \n
else if c == 13 as char8 { NBE_Append(nbe, "\\r"); } // \r
else if c == 9 as char8 { NBE_Append(nbe, "\\t"); } // \t
else if c == 34 as char8 { NBE_Append(nbe, "\\\""); } // \"
else if c == 92 as char8 { NBE_Append(nbe, "\\\\"); } // \\
else { NBE_AppendChar(nbe, c); }
i = i + 1;
}
}
NBE_Append(nbe, "\"");
} else if node.intValue == tkBoolLiteral {
NBE_Append(nbe, node.strValue);
} else {
NBE_Append(nbe, node.strValue);
}
return;
}
// Variable
if kind == hVar {
NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue));
return;
}
// Binary
if kind == hBinary {
// Special case: String == null → check for empty string
if node.intValue == tkEq || node.intValue == tkNe {
var isNullCmp: bool = false;
var other: *HirNode = null as *HirNode;
if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull {
isNullCmp = true;
other = node.child2;
}
if !isNullCmp && node.child2 != null as *HirNode && node.child2.kind == hLit && node.child2.intValue == tkNull {
isNullCmp = true;
other = node.child1;
}
if isNullCmp && other != null as *HirNode && other.typeKind == tyStr {
if node.intValue == tkEq {
NBE_EmitExpr(nbe, other);
NBE_Append(nbe, " == \"\"");
} else {
NBE_EmitExpr(nbe, other);
NBE_Append(nbe, " != \"\"");
}
return;
}
}
if node.child1 != null as *HirNode {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " ");
}
// Emit operator only if we have a left operand
if node.child1 != null as *HirNode {
NBE_Append(nbe, NimBackend_OpToNim(node.intValue));
NBE_Append(nbe, " ");
}
NBE_EmitExpr(nbe, node.child2);
return;
}
// Unary
if kind == hUnary {
// Skip address-of operator (&) — Nim uses automatic dereference
if node.intValue == tkAmp {
NBE_EmitExpr(nbe, node.child1);
return;
}
NBE_Append(nbe, NimBackend_OpToNim(node.intValue));
NBE_Append(nbe, "(");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ")");
return;
}
// Call
if kind == hCall {
var fname: String = node.strValue;
// Translate StringBuilder calls to Nim native operations
if String_Eq(fname, "StringBuilder_Append") {
// sb = sb & text (string concatenation)
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " & ");
NBE_EmitExpr(nbe, node.child2);
return;
}
if String_Eq(fname, "StringBuilder_NewCap") {
// Just empty string
NBE_Append(nbe, "\"\"");
return;
}
if String_Eq(fname, "StringBuilder_Build") {
// Just return the string field
NBE_EmitExpr(nbe, node.child1);
return;
}
NBE_Append(nbe, NimBackend_EscapeIdent(fname));
NBE_Append(nbe, "(");
if node.child1 != null as *HirNode {
NBE_EmitExpr(nbe, node.child1);
}
if node.child2 != null as *HirNode {
NBE_Append(nbe, ", ");
NBE_EmitExpr(nbe, node.child2);
}
NBE_Append(nbe, ")");
return;
}
// Field access: obj.field → obj.field (Nim auto-dereferences)
if kind == hFieldPtr || kind == hArrowField {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ".");
NBE_Append(nbe, node.strValue);
return;
}
// Index: arr[idx]
if kind == hIndexPtr {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, "[");
NBE_EmitExpr(nbe, node.child2);
NBE_Append(nbe, "]");
return;
}
// Cast: expr as Type
if kind == hCast {
// Special case: null as Type → appropriate default
if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull {
var typeName: String = node.typeName;
if !String_Eq(typeName, "") {
var converted: String = NimBackend_ConvertTypeName(typeName);
if String_Eq(converted, "string") {
NBE_Append(nbe, "\"\"");
return;
}
if String_Eq(converted, "pointer") {
NBE_Append(nbe, "nil");
return;
}
}
// For pointer types (ending in * or "ptr "), emit nil
var tlen: int = bux_strlen(typeName) as int;
if tlen > 1 {
var lastCh: char8 = typeName[tlen - 1];
if lastCh == 42 as char8 { // '*'
NBE_Append(nbe, "nil");
return;
}
}
}
NBE_Append(nbe, "cast[");
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, NimBackend_ConvertTypeName(node.typeName));
} else {
NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind));
}
NBE_Append(nbe, "](");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ")");
return;
}
// Is
if kind == hIs {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " is ");
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, node.typeName);
}
return;
}
// SizeOf
if kind == hSizeOf {
NBE_Append(nbe, "sizeof(");
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, node.typeName);
} else {
NBE_Append(nbe, "int");
}
NBE_Append(nbe, ")");
return;
}
// Alloca: variable declaration (used within Store for full decl)
if kind == hAlloca {
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.typeName));
} else {
NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind));
}
NBE_Append(nbe, " ");
NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue));
return;
}
// Assign: target = value
if kind == hAssign {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child2);
return;
}
// Store: assignment or declaration with init
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration: var x: Type = value
NBE_Append(nbe, "var ");
NBE_Append(nbe, NimBackend_EscapeIdent(node.child1.strValue));
if !String_Eq(node.child1.typeName, "") {
NBE_Append(nbe, ": ");
NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.child1.typeName));
}
if node.child2 != null as *HirNode {
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child2);
}
return;
}
// Plain assignment
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child2);
return;
}
// If
if kind == hIf {
NBE_Append(nbe, "if ");
NBE_EmitExpr(nbe, node.child1);
NBE_AppendLine(nbe, ":");
if node.child2 != null as *HirNode {
nbe.indent = nbe.indent + 1;
if node.child2.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child2);
nbe.indent = nbe.indent - 1;
} else {
nbe.indent = nbe.indent + 1;
NBE_EmitLine(nbe, "discard");
nbe.indent = nbe.indent - 1;
}
if node.child3 != null as *HirNode {
NBE_EmitIndent(nbe); // indent else at same level as if
NBE_AppendLine(nbe, "else:");
nbe.indent = nbe.indent + 1;
if node.child3.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child3);
nbe.indent = nbe.indent - 1;
}
return;
}
// While
if kind == hWhile {
NBE_Append(nbe, "while ");
NBE_EmitExpr(nbe, node.child1);
NBE_AppendLine(nbe, ":");
if node.child2 != null as *HirNode {
nbe.indent = nbe.indent + 1;
if node.child2.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child2);
nbe.indent = nbe.indent - 1;
}
return;
}
// Loop (infinite)
if kind == hLoop {
NBE_AppendLine(nbe, "while true:");
if node.child1 != null as *HirNode {
nbe.indent = nbe.indent + 1;
if node.child1.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child1);
nbe.indent = nbe.indent - 1;
}
return;
}
// Break / Continue
if kind == hBreak {
NBE_AppendLine(nbe, "break");
return;
}
if kind == hContinue {
NBE_AppendLine(nbe, "continue");
return;
}
// Return
if kind == hReturn {
NBE_Append(nbe, "return ");
if node.child1 != null as *HirNode {
if node.child1.kind == hBinary {
NBE_Append(nbe, "(");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ")");
} else {
NBE_EmitExpr(nbe, node.child1);
}
}
return;
}
// Struct init: TypeName { field: value, ... }
if kind == hStructInit {
NBE_Append(nbe, node.strValue);
NBE_Append(nbe, "(");
// Emit fields (chained via child3)
var field: *HirNode = node.child3;
var first: bool = true;
while field != null as *HirNode {
if !first { NBE_Append(nbe, ", "); }
NBE_Append(nbe, field.strValue);
NBE_Append(nbe, ": ");
NBE_EmitExpr(nbe, field.child1);
first = false;
field = field.child3;
}
NBE_Append(nbe, ")");
return;
}
// Load: dereference — in Nim just emit the pointer expression
if kind == hLoad {
NBE_EmitExpr(nbe, node.child1);
return;
}
// Block — sequence of statements
if kind == hBlock {
var child: *HirNode = node.child1;
if child == null as *HirNode {
NBE_EmitIndent(nbe);
NBE_AppendLine(nbe, "discard");
return;
}
// First pass: emit statements, merging continuation lines
while child != null as *HirNode {
NBE_EmitIndent(nbe);
// Check if this is a return/binary that has continuation siblings
if child.kind == hReturn && child.child1 != null as *HirNode {
// Emit return and open paren before expression
NBE_Append(nbe, "return (");
NBE_EmitExpr(nbe, child.child1);
child = child.child3;
// Merge all continuation siblings
while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode {
NBE_Append(nbe, " ");
NBE_EmitExpr(nbe, child);
child = child.child3;
}
NBE_AppendLine(nbe, ")");
} else {
// Wrap with discard for non-control-flow statements
if child.kind == hCall || child.kind == hAssign || child.kind == hStore {
NBE_Append(nbe, "discard (");
NBE_EmitExpr(nbe, child);
NBE_Append(nbe, ")");
} else {
NBE_EmitExpr(nbe, child);
}
child = child.child3;
// Merge continuation siblings for other statement types
while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode {
NBE_Append(nbe, " ");
NBE_EmitExpr(nbe, child);
child = child.child3;
}
NBE_AppendLine(nbe, "");
}
}
return;
}
// Match — simplified
if kind == hMatch {
NBE_EmitLine(nbe, "# match (simplified)");
return;
}
// Spawn
if kind == hSpawn {
NBE_Append(nbe, "spawn ");
NBE_Append(nbe, node.strValue);
NBE_Append(nbe, "(");
if node.child1 != null as *HirNode {
NBE_EmitExpr(nbe, node.child1);
}
NBE_Append(nbe, ")");
return;
}
// Await
if kind == hAwait {
NBE_Append(nbe, "await ");
NBE_EmitExpr(nbe, node.child1);
return;
}
// Fallback
NBE_Append(nbe, "nil");
}
// ---------------------------------------------------------------------------
// Function declaration emission
// ---------------------------------------------------------------------------
func NBE_EmitFuncDecl(nbe: *NBEmitter, f: *HirFunc) {
// Return type
var retType: String = f.retTypeName;
if String_Eq(retType, "") || String_Eq(retType, "void") {
// No return type annotation needed
} else if String_Eq(retType, "int") {
NBE_Append(nbe, "int");
} else if String_Eq(retType, "bool") {
NBE_Append(nbe, "bool");
} else if String_Eq(retType, "string") || String_Eq(retType, "String") {
NBE_Append(nbe, "string");
} else {
NBE_Append(nbe, retType);
}
}
// ---------------------------------------------------------------------------
// Convert type name from Bux to Nim
// ---------------------------------------------------------------------------
func NimBackend_EscapeIdent(name: String) -> String {
if name == null as String || String_Eq(name, "") { return "x"; }
// Nim keywords that need escaping
if String_Eq(name, "block") { return "`block`"; }
if String_Eq(name, "type") { return "`type`"; }
if String_Eq(name, "object") { return "`object`"; }
if String_Eq(name, "proc") { return "`proc`"; }
if String_Eq(name, "var") { return "`var`"; }
if String_Eq(name, "let") { return "`let`"; }
if String_Eq(name, "const") { return "`const`"; }
if String_Eq(name, "from") { return "`from`"; }
if String_Eq(name, "import") { return "`import`"; }
if String_Eq(name, "export") { return "`export`"; }
if String_Eq(name, "include") { return "`include`"; }
if String_Eq(name, "iterator") { return "`iterator`"; }
if String_Eq(name, "method") { return "`method`"; }
if String_Eq(name, "func") { return "`func`"; }
if String_Eq(name, "string") { return "`string`"; }
if String_Eq(name, "int") { return "`int`"; }
if String_Eq(name, "bool") { return "`bool`"; }
if String_Eq(name, "float") { return "`float`"; }
if String_Eq(name, "char") { return "`char`"; }
if String_Eq(name, "ptr") { return "`ptr`"; }
if String_Eq(name, "ref") { return "`ref`"; }
if String_Eq(name, "nil") { return "`nil`"; }
if String_Eq(name, "true") { return "`true`"; }
if String_Eq(name, "false") { return "`false`"; }
if String_Eq(name, "and") { return "`and`"; }
if String_Eq(name, "or") { return "`or`"; }
if String_Eq(name, "not") { return "`not`"; }
if String_Eq(name, "xor") { return "`xor`"; }
if String_Eq(name, "shl") { return "`shl`"; }
if String_Eq(name, "shr") { return "`shr`"; }
if String_Eq(name, "div") { return "`div`"; }
if String_Eq(name, "mod") { return "`mod`"; }
if String_Eq(name, "is") { return "`is`"; }
if String_Eq(name, "of") { return "`of`"; }
if String_Eq(name, "in") { return "`in`"; }
if String_Eq(name, "out") { return "`out`"; }
if String_Eq(name, "static") { return "`static`"; }
if String_Eq(name, "enum") { return "`enum`"; }
if String_Eq(name, "tuple") { return "`tuple`"; }
if String_Eq(name, "concept") { return "`concept`"; }
if String_Eq(name, "distinct") { return "`distinct`"; }
if String_Eq(name, "Result") { return "`Result`"; }
return name;
}
func NimBackend_ConvertTypeNameParam(tn: String) -> String {
// For function parameters: strip pointer indirection, Nim passes by ref
if tn == null as String || String_Eq(tn, "") { return "int"; }
if String_Eq(tn, "String") { return "string"; }
if String_Eq(tn, "bool") { return "bool"; }
if String_Eq(tn, "int") { return "int"; }
if String_Eq(tn, "int64") { return "int64"; }
if String_Eq(tn, "uint32") { return "uint32"; }
if String_Eq(tn, "uint") { return "uint"; }
if String_Eq(tn, "float64") { return "float64"; }
// Pointer types → ptr types in Nim
if !String_Eq(tn, "") {
var tlen: int = bux_strlen(tn) as int;
if tlen > 1 {
var lastCh: char8 = tn[tlen - 1];
if lastCh == 42 as char8 {
var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint);
if String_Eq(base, "void") { return "pointer"; }
return String_Concat("ptr ", base);
}
}
}
return tn;
}
func NimBackend_ConvertTypeName(tn: String) -> String {
if tn == null as String || String_Eq(tn, "") { return "int"; }
if String_Eq(tn, "String") { return "string"; }
if String_Eq(tn, "bool") { return "bool"; }
if String_Eq(tn, "int") { return "int"; }
if String_Eq(tn, "int64") { return "int64"; }
if String_Eq(tn, "uint32") { return "uint32"; }
if String_Eq(tn, "uint") { return "uint"; }
if String_Eq(tn, "float64") { return "float64"; }
// Convert pointer types: "Foo*" → "ptr Foo"
if !String_Eq(tn, "") {
var tlen: int = bux_strlen(tn) as int;
if tlen > 1 {
var lastCh: char8 = tn[tlen - 1];
if lastCh == 42 as char8 { // '*'
// Extract base name (without *)
var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint);
if String_Eq(base, "void") { return "pointer"; }
return String_Concat("ptr ", base);
}
}
}
return tn;
}
// ---------------------------------------------------------------------------
// Generate complete Nim module
// ---------------------------------------------------------------------------
func NimBackend_Generate(mod: *HirModule) -> String {
let nbe: *NBEmitter = bux_alloc(sizeof(NBEmitter)) as *NBEmitter;
nbe.sb = StringBuilder_NewCap(8192);
nbe.indent = 0;
// Header
NBE_AppendLine(nbe, "# Generated by Bux Nim Backend");
NBE_AppendLine(nbe, "");
NBE_AppendLine(nbe, "import std/strutils");
NBE_AppendLine(nbe, "import std/tables");
NBE_AppendLine(nbe, "import std/sequtils");
NBE_AppendLine(nbe, "import std/os");
NBE_AppendLine(nbe, "");
// Type definitions — skip builtins that Nim already has
NBE_AppendLine(nbe, "# Type aliases");
NBE_AppendLine(nbe, "type");
nbe.indent = nbe.indent + 1;
NBE_EmitLine(nbe, "String* = string");
NBE_EmitLine(nbe, "char8* = char");
// StringBuild is just a string in Nim
NBE_EmitLine(nbe, "StringBuilder* = string");
// Close type block
nbe.indent = nbe.indent - 1;
NBE_AppendLine(nbe, "");
NBE_AppendLine(nbe, "");
// Struct definitions
if mod.structCount > 0 {
NBE_AppendLine(nbe, "# Struct types");
NBE_AppendLine(nbe, "type");
var si: int = 0;
while si < mod.structCount {
NBE_Append(nbe, " ");
NBE_Append(nbe, NimBackend_EscapeIdent(mod.structs[si].name));
NBE_AppendLine(nbe, " = object");
var fi: int = 0;
while fi < mod.structs[si].fieldCount {
var fname: String = mod.structs[si].fields[fi].name;
if !String_Eq(fname, "") {
NBE_Append(nbe, " ");
NBE_Append(nbe, NimBackend_EscapeIdent(fname));
NBE_Append(nbe, ": ");
var ft: String = mod.structs[si].fields[fi].typeName;
NBE_Append(nbe, NimBackend_ConvertTypeName(ft));
NBE_AppendLine(nbe, "");
}
fi = fi + 1;
}
si = si + 1;
}
NBE_AppendLine(nbe, "");
}
// Const definitions
if mod.constCount > 0 {
NBE_AppendLine(nbe, "# Constants");
var ci: int = 0;
while ci < mod.constCount {
NBE_Emit(nbe, "const ");
NBE_Append(nbe, mod.consts[ci].name);
NBE_Append(nbe, " = ");
NBE_Append(nbe, String_FromInt(mod.consts[ci].value as int64));
NBE_AppendLine(nbe, "");
ci = ci + 1;
}
NBE_AppendLine(nbe, "");
}
// Extern declarations — skip (Nim doesn't need them)
// Bux extern functions are C runtime functions that Nim can't directly call.
// Instead, we use Nim native equivalents (e.g., len() instead of bux_strlen).
// Function definitions (reverse order: dependencies first)
var i: int = mod.funcCount;
while i > 0 {
i = i - 1;
let f: *HirFunc = &mod.funcs[i];
if f.body == null as *HirNode { continue; }
NBE_Emit(nbe, "proc ");
NBE_Append(nbe, NimBackend_EscapeIdent(f.name));
NBE_Append(nbe, "(");
// Parameters
var pi: int = 0;
var pnames: *HirParam = &f.param0;
while pi < f.paramCount {
if pi > 0 { NBE_Append(nbe, ", "); }
NBE_Append(nbe, NimBackend_EscapeIdent(pnames[pi].name));
NBE_Append(nbe, ": ");
var pt: String = pnames[pi].typeName;
NBE_Append(nbe, NimBackend_ConvertTypeNameParam(pt));
pi = pi + 1;
}
NBE_Append(nbe, "): ");
// Return type
var rtn: String = NimBackend_ConvertTypeNameParam(f.retTypeName);
if String_Eq(rtn, "") || String_Eq(rtn, "void") {
NBE_AppendLine(nbe, "void =");
} else {
NBE_Append(nbe, rtn);
NBE_AppendLine(nbe, " =");
}
// Body
nbe.indent = nbe.indent + 1;
if f.body != null as *HirNode && f.body.kind != hBlock {
NBE_EmitIndent(nbe);
}
NBE_EmitExpr(nbe, f.body);
nbe.indent = nbe.indent - 1;
NBE_AppendLine(nbe, "");
NBE_AppendLine(nbe, "");
}
// Main entry point
NBE_AppendLine(nbe, "# Entry point");
NBE_AppendLine(nbe, "when isMainModule:");
NBE_EmitLine(nbe, " var args: seq[string] = @[]");
NBE_EmitLine(nbe, " var argCount = paramCount()");
NBE_EmitLine(nbe, " discard Main()");
NBE_EmitLine(nbe, "");
let result: String = NBE_Build(nbe);
return result;
}
} // module NimBackend
File diff suppressed because it is too large Load Diff