Files
bux-lang/src/cli.bux
T
dimgigov 9e515c157d feat(pkg): add basic package manager (add, remove, fetch, build deps)
- manifest.bux: parse [dependencies] section with up to 8 deps
- manifest.bux: Manifest_AddDep, Manifest_RemoveDep, Manifest_ToString
- cli.bux: bux add <name> <url> — adds dependency to bux.toml
- cli.bux: bux remove <name> — removes dependency from bux.toml
- cli.bux: bux fetch — git clone/pull dependencies into deps/
- cli.bux: Cli_BuildProject now merges deps/<name>/src/*.bux before user code
- Selfhost bootstrap loop verified: C output deterministic ✓
2026-06-10 19:18:36 +03:00

1205 lines
40 KiB
Plaintext

// cli.bux — CLI driver for the Bux self-hosting compiler
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
module Cli {
extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_getcwd() -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_path_parent(path: String) -> String;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int;
func ReadFile(path: String) -> String {
return bux_read_file(path);
}
func WriteFile(path: String, content: String) -> bool {
return bux_write_file(path, content);
}
func FileExists(path: String) -> bool {
return bux_file_exists(path) != 0;
}
func DirExists(path: String) -> bool {
return bux_dir_exists(path) != 0;
}
// Import the compiler pipeline
// In self-hosting mode, these are compiled together from src/
// ---------------------------------------------------------------------------
// Compile a single .bux source file
// ---------------------------------------------------------------------------
func Cli_Compile(source: String, sourceName: String) -> String {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors:");
var i: int = 0;
while i < Lexer_DiagCount(lex) {
Print(" ");
PrintLine(lex.diags[i].message);
i = i + 1;
}
return "";
}
// Phase 2: Parse
PrintLine(" Parsing...");
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return "";
}
PrintLine(" Parse done");
// Flatten module wrappers: find module decl and hoist its children
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkModule && decl.childDecl1 != null as *Decl {
mod.firstItem = decl.childDecl1;
break;
}
decl = decl.childDecl2;
}
// Phase 3: Semantic analysis
PrintLine(" Sema...");
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors:");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return "";
}
PrintLine(" Sema done");
// Phase 4: HIR lowering
PrintLine(" HIR lowering...");
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
if hirMod == null as *HirModule {
PrintLine("HIR lowering failed");
return "";
}
Print("HIR funcCount=");
PrintInt(hirMod.funcCount);
// Phase 5: C code generation
let cCode: String = CBackend_Generate(hirMod);
// Cleanup
Lexer_Free(lex);
Sema_Free(sema);
return cCode;
}
// ---------------------------------------------------------------------------
// Build command
// ---------------------------------------------------------------------------
func Cli_Build(srcPath: String, outPath: String) -> int {
// If building the standard project entry point, use full project build
// which merges stdlib and supports multi-file projects
if String_Eq(srcPath, "src/Main.bux") || String_Eq(srcPath, "src/main.bux") {
let rc: int = Cli_BuildProject(".");
if rc == 0 && !String_Eq(outPath, "build/main") {
// Rename output if custom path was requested
bux_system(String_Concat(String_Concat("mv build/main ", outPath), " 2>/dev/null || true"));
}
return rc;
}
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
Print("Error: cannot read source file: ");
PrintLine(srcPath);
return 1;
}
Print("Compiling ");
PrintLine(srcPath);
let cCode: String = Cli_Compile(source, srcPath);
if String_Eq(cCode, "") {
PrintLine("Compilation failed");
return 1;
}
// Write C output
let cFile: String = String_Concat(outPath, ".c");
let ok: bool = WriteFile(cFile, cCode);
if !ok {
PrintLine("Error: cannot write output file");
return 1;
}
Print(" → C written to ");
PrintLine(cFile);
// Find runtime.c and io.c for linking (rt/ directory)
var rtPath: String = "rt/runtime.c";
var ioPath: String = "rt/io.c";
if !FileExists(rtPath) {
rtPath = "../rt/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "../rt/io.c";
}
if !FileExists(rtPath) {
rtPath = "../../rt/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "../../rt/io.c";
}
// Compile with cc
PrintLine("Compiling C...");
var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&cmdBuf, outPath);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, cFile);
StringBuilder_Append(&cmdBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&cmdBuf, rtPath);
StringBuilder_Append(&cmdBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&cmdBuf, ioPath);
StringBuilder_Append(&cmdBuf, " ");
}
StringBuilder_Append(&cmdBuf, "-lm");
StringBuilder_Append(&cmdBuf, " -lcrypto");
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print(" → Binary: ");
PrintLine(outPath);
return 0;
}
// ---------------------------------------------------------------------------
// Check command (compile only, no output)
// ---------------------------------------------------------------------------
func Cli_Check(srcPath: String) -> int {
Print("Check: ");
PrintLine(srcPath);
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") {
PrintLine("Error: cannot read source file");
return 1;
}
PrintLine(" File read OK");
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors found");
return 1;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return 1;
}
PrintLine(" Parse done");
// Flatten module wrappers
var decl2: *Decl = mod.firstItem;
while decl2 != null as *Decl {
if decl2.kind == dkModule && decl2.childDecl1 != null as *Decl {
mod.firstItem = decl2.childDecl1;
break;
}
decl2 = decl2.childDecl2;
}
// Phase 3: Sema
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(" col ");
PrintInt(sema.diags[i].column as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
var dc: *Decl = mod.firstItem;
while dc != null as *Decl {
if dc.kind == dkFunc {
Print("check func ");
PrintLine(dc.strValue);
}
dc = dc.childDecl2;
}
PrintLine("Check passed");
return 0;
}
// ---------------------------------------------------------------------------
// Project build — compile all .bux files in src/ directory
// ---------------------------------------------------------------------------
func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
Print("Lex errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print("Parse failed for ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 3: Semantic analysis
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
Print("Sema errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
return hirMod;
}
// ---------------------------------------------------------------------------
// Stdlib discovery and merging
// ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String {
var path: String = bux_path_join(projectDir, "lib");
if DirExists(path) { return path; }
path = bux_path_join(projectDir, "../lib");
if DirExists(path) { return path; }
path = bux_path_join(projectDir, "../../lib");
if DirExists(path) { return path; }
return "";
}
func Cli_DeclName(decl: *Decl) -> String {
if decl == null as *Decl { return ""; }
return decl.strValue;
}
func Cli_NameInList(name: String, names: *String, count: int) -> bool {
if String_Eq(name, "") { return false; }
var i: int = 0;
while i < count {
if String_Eq(names[i], name) {
return true;
}
i = i + 1;
}
return false;
}
func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
let next: *Decl = decl.childDecl2;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl && count < maxCount {
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") {
names[count] = iname;
count = count + 1;
}
inner = inner.childDecl2;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") {
names[count] = dname;
count = count + 1;
}
}
decl = next;
}
return count;
}
// Helper: convert single char int to String
func String_FromChar(c: int) -> String {
var buf: *char8 = bux_alloc(2) as *char8;
buf[0] = c as char8;
buf[1] = 0 as char8;
return buf as String;
}
// Collect stdlib module paths imported by user code.
// Returns number of unique stdlib files to merge (paths written into outPaths).
func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
if decl.kind == dkUse {
let path: String = decl.usePath;
// Only handle Std::* imports
if String_StartsWith(path, "Std::") {
// Extract module name after Std:: (e.g., "Std::Io" -> "Io")
var j: int = 5; // skip "Std::"
var modName: String = "";
while j < 100 {
let c: int = path[j] as int;
if c == 0 || c == 58 { break; } // null or ':'
modName = String_Concat(modName, String_FromChar(c));
j = j + 1;
}
if !String_Eq(modName, "") {
let filePath: String = bux_path_join(stdlibDir, String_Concat(modName, ".bux"));
// Check for duplicates
var dup: bool = false;
var di: int = 0;
while di < count {
if String_Eq(outPaths[di], filePath) { dup = true; }
di = di + 1;
}
if !dup {
outPaths[count] = filePath;
count = count + 1;
}
}
}
}
decl = decl.childDecl2;
}
return count;
}
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
if !FileExists(path) {
Print("Error: stdlib file not found: ");
PrintLine(path);
return 0;
}
let source: String = ReadFile(path);
if source == null as String || String_Eq(source, "") { return 0; }
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 { return 0; }
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module { return 0; }
var added: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
inner.childDecl2 = null as *Decl;
if target.firstItem == null as *Decl {
target.firstItem = inner;
} else {
var last: *Decl = target.firstItem;
while last.childDecl2 != null as *Decl {
last = last.childDecl2;
}
last.childDecl2 = inner;
}
target.itemCount = target.itemCount + 1;
added = added + 1;
}
inner = innerNext;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
decl.childDecl2 = null as *Decl;
if target.firstItem == null as *Decl {
target.firstItem = decl;
} else {
var last: *Decl = target.firstItem;
while last.childDecl2 != null as *Decl {
last = last.childDecl2;
}
last.childDecl2 = decl;
}
target.itemCount = target.itemCount + 1;
added = added + 1;
}
}
decl = next;
}
return added;
}
func Cli_CopyModuleDecls(target: *Module, source: *Module) {
if source == null as *Module || source.firstItem == null as *Decl { return; }
// Find last item in target
var targetLast: *Decl = null as *Decl;
if target.firstItem != null as *Decl {
targetLast = target.firstItem;
while targetLast.childDecl2 != null as *Decl {
targetLast = targetLast.childDecl2;
}
}
var decl: *Decl = source.firstItem;
var limit: int = 0;
while decl != null as *Decl && limit < 10000 {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
if target.firstItem == null as *Decl {
target.firstItem = decl;
targetLast = decl;
} else {
targetLast.childDecl2 = decl;
targetLast = decl;
}
target.itemCount = target.itemCount + 1;
decl = next;
limit = limit + 1;
}
source.firstItem = null as *Decl;
source.itemCount = 0;
}
// ---------------------------------------------------------------------------
// New — create a new Bux project
// ---------------------------------------------------------------------------
func Cli_New(name: String) -> int {
var root: String = name;
// If name starts with /, use as absolute path; otherwise join with cwd
if !String_StartsWith(name, "/") {
let cwd: String = bux_getcwd();
root = bux_path_join(cwd, name);
}
// Extract package name from path (last component)
var pkgName: String = name;
if String_StartsWith(name, "/") {
var i: int = 0;
while i < 256 {
let c: int = name[i] as int;
if c == 0 { break; }
i = i + 1;
}
var j: int = i - 1;
while j >= 0 {
let c: int = name[j] as int;
if c == 47 {
pkgName = "";
var k: int = j + 1;
while k < i {
pkgName = String_Concat(pkgName, String_FromChar(name[k] as int));
k = k + 1;
}
break;
}
j = j - 1;
}
}
if DirExists(root) {
Print("Error: directory '"); Print(pkgName); PrintLine("' already exists");
return 1;
}
let srcDir: String = bux_path_join(root, "src");
let mk1: int = bux_mkdir_if_needed(root);
let mk2: int = bux_mkdir_if_needed(srcDir);
let tomlPath: String = bux_path_join(root, "bux.toml");
let tomlContent: String = "[Package]\nName = \"";
tomlContent = String_Concat(tomlContent, pkgName);
tomlContent = String_Concat(tomlContent, "\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n");
let w1: bool = WriteFile(tomlPath, tomlContent);
let mainPath: String = bux_path_join(srcDir, "Main.bux");
let mainContent: String = "import Std::Io::PrintLine;\n\nfunc Main() -> int {\n PrintLine(\"Hello, Bux!\");\n return 0;\n}\n";
let w2: bool = WriteFile(mainPath, mainContent);
if !w1 || !w2 || mk1 != 0 || mk2 != 0 {
Print("Error: could not create project '"); Print(pkgName); PrintLine("'");
return 1;
}
Print("Created Bux package '"); Print(pkgName); PrintLine("'");
return 0;
}
// ---------------------------------------------------------------------------
// Init — initialize a Bux project in the current directory
// ---------------------------------------------------------------------------
func Cli_Init() -> int {
let cwd: String = bux_getcwd();
let tomlPath: String = bux_path_join(cwd, "bux.toml");
if FileExists(tomlPath) {
PrintLine("Error: bux.toml already exists");
return 1;
}
let parent: String = bux_path_parent(cwd);
var name: String = "";
if !String_Eq(parent, "/") {
var i: int = 0;
while i < 256 {
let c: int = cwd[i] as int;
if c == 0 { break; }
i = i + 1;
}
// Extract last path component
var j: int = i - 1;
while j >= 0 {
let c: int = cwd[j] as int;
if c == 47 { // '/'
var k: int = j + 1;
while k < i {
name = String_Concat(name, String_FromChar(cwd[k] as int));
k = k + 1;
}
break;
}
j = j - 1;
}
if String_Eq(name, "") { name = "untitled"; }
} else {
name = "untitled";
}
let tomlContent: String = "[Package]\nName = \"";
tomlContent = String_Concat(tomlContent, name);
tomlContent = String_Concat(tomlContent, "\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n");
discard WriteFile(tomlPath, tomlContent);
let srcDir: String = bux_path_join(cwd, "src");
if !DirExists(srcDir) {
discard bux_mkdir_if_needed(srcDir);
}
Print("Initialized Bux package '"); Print(name); PrintLine("'");
return 0;
}
// ---------------------------------------------------------------------------
// Test command — build and run tests
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Add — add a dependency to bux.toml
// ---------------------------------------------------------------------------
func Cli_Add(pkgName: String, url: String) -> int {
let cwd: String = bux_getcwd();
let tomlPath: String = bux_path_join(cwd, "bux.toml");
if !FileExists(tomlPath) {
PrintLine("Error: no bux.toml found");
return 1;
}
var man: Manifest = Manifest_Load(tomlPath);
if Manifest_HasDep(man, pkgName) {
Print("Error: dependency '"); Print(pkgName); PrintLine("' already exists");
return 1;
}
let added: bool = Manifest_AddDep(&man, pkgName, url);
if !added {
PrintLine("Error: cannot add dependency (max 8)");
return 1;
}
let tomlContent: String = Manifest_ToString(man);
if !WriteFile(tomlPath, tomlContent) {
PrintLine("Error: cannot write bux.toml");
return 1;
}
Print("Added dependency: "); Print(pkgName); Print(" = "); PrintLine(url);
return 0;
}
// ---------------------------------------------------------------------------
// Remove — remove a dependency from bux.toml
// ---------------------------------------------------------------------------
func Cli_Remove(pkgName: String) -> int {
let cwd: String = bux_getcwd();
let tomlPath: String = bux_path_join(cwd, "bux.toml");
if !FileExists(tomlPath) {
PrintLine("Error: no bux.toml found");
return 1;
}
var man: Manifest = Manifest_Load(tomlPath);
if !Manifest_HasDep(man, pkgName) {
Print("Error: dependency '"); Print(pkgName); PrintLine("' not found");
return 1;
}
let removed: bool = Manifest_RemoveDep(&man, pkgName);
if !removed {
PrintLine("Error: cannot remove dependency");
return 1;
}
let tomlContent: String = Manifest_ToString(man);
if !WriteFile(tomlPath, tomlContent) {
PrintLine("Error: cannot write bux.toml");
return 1;
}
Print("Removed dependency: "); PrintLine(pkgName);
return 0;
}
// ---------------------------------------------------------------------------
// Fetch — download dependencies into deps/
// ---------------------------------------------------------------------------
func Cli_Fetch() -> int {
let cwd: String = bux_getcwd();
let tomlPath: String = bux_path_join(cwd, "bux.toml");
if !FileExists(tomlPath) {
PrintLine("Error: no bux.toml found");
return 1;
}
let man: Manifest = Manifest_Load(tomlPath);
if man.depCount == 0 {
PrintLine("No dependencies to fetch");
return 0;
}
let depsDir: String = bux_path_join(cwd, "deps");
discard bux_mkdir_if_needed(depsDir);
var i: int = 0;
while i < man.depCount {
var depName: String = "";
var depUrl: String = "";
if i == 0 { depName = man.depName0; depUrl = man.depUrl0; }
else if i == 1 { depName = man.depName1; depUrl = man.depUrl1; }
else if i == 2 { depName = man.depName2; depUrl = man.depUrl2; }
else if i == 3 { depName = man.depName3; depUrl = man.depUrl3; }
else if i == 4 { depName = man.depName4; depUrl = man.depUrl4; }
else if i == 5 { depName = man.depName5; depUrl = man.depUrl5; }
else if i == 6 { depName = man.depName6; depUrl = man.depUrl6; }
else if i == 7 { depName = man.depName7; depUrl = man.depUrl7; }
let depPath: String = bux_path_join(depsDir, depName);
if DirExists(depPath) {
Print("Updating "); PrintLine(depName);
let cmd: String = String_Concat("cd \"", String_Concat(depPath, "\" && git pull --quiet 2>/dev/null"));
discard bux_system(cmd);
} else {
Print("Fetching "); Print(depName); Print(" from "); PrintLine(depUrl);
let cmd: String = String_Concat("git clone --quiet \"", String_Concat(depUrl, String_Concat("\" \"", String_Concat(depPath, "\""))));
let rc: int = bux_system(cmd);
if rc != 0 {
Print("Error: failed to fetch "); PrintLine(depName);
}
}
i = i + 1;
}
PrintLine("Fetch complete");
return 0;
}
// ---------------------------------------------------------------------------
// Fmt command — format source files
// ---------------------------------------------------------------------------
func Cli_Fmt(dir: String) -> int {
// If dir is a file, format just that file
if FileExists(dir) {
Print("Formatting ");
PrintLine(dir);
if Fmt_FormatFile(dir) {
PrintLine(" OK");
return 0;
}
return 1;
}
// Otherwise format all .bux files in directory
Print("Formatting ");
PrintLine(dir);
var fileCount: int = 0;
let files: *String = bux_list_dir(dir, ".bux", &fileCount);
if fileCount == 0 {
PrintLine("No .bux files found");
return 1;
}
var i: int = 0;
var okCount: int = 0;
while i < fileCount {
Print(" ");
PrintLine(files[i]);
if Fmt_FormatFile(files[i]) {
okCount = okCount + 1;
}
i = i + 1;
}
Print("Formatted "); PrintInt(okCount as int64); Print("/"); PrintInt(fileCount as int64); PrintLine(" files");
return 0;
}
func Cli_Test(projectDir: String) -> int {
Print("Testing project: ");
PrintLine(projectDir);
// Build the project first
let rc: int = Cli_BuildProject(projectDir);
if rc != 0 {
PrintLine("Test build failed");
return rc;
}
// Run the resulting binary
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") { outName = "bux_out"; }
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
if !FileExists(outBin) {
Print("Error: test binary not found: ");
PrintLine(outBin);
return 1;
}
let result: int = bux_system(outBin);
if result == 0 {
PrintLine("Tests passed");
} else {
Print("Tests failed (exit code ");
PrintInt(result as int64);
PrintLine(")");
}
return result;
}
func Cli_BuildProject(projectDir: String) -> int {
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let srcDir: String = bux_path_join(projectDir, "src");
if !DirExists(srcDir) {
Print("Error: no src/ directory in ");
PrintLine(projectDir);
return 1;
}
Print("Scanning ");
PrintLine(srcDir);
// List all .bux files in src/
var fileCount: int = 0;
let files: *String = bux_list_dir(srcDir, ".bux", &fileCount);
if fileCount == 0 {
PrintLine("Error: no .bux files found in src/");
return 1;
}
Print("Found ");
PrintInt(fileCount);
PrintLine(" source files");
// Create user merged module (collect user decls first)
let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module;
userMerged.name = "main";
userMerged.path = "";
userMerged.itemCount = 0;
userMerged.firstItem = null as *Decl;
// Parse each file and merge declarations into userMerged module
var i: int = 0;
while i < fileCount {
let path: String = files[i];
Print(" Parsing ");
PrintLine(path);
let source: String = ReadFile(path);
if String_Eq(source, "") {
Print(" Error: cannot read ");
PrintLine(path);
return 1;
}
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 {
Print(" Lex errors in ");
PrintLine(path);
return 1;
}
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print(" Parse failed for ");
PrintLine(path);
return 1;
}
// Merge declarations from this module into userMerged
var decl: *Decl = mod.firstItem;
var fileDeclCount: int = 0;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
// Flatten module declarations
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
// Find last item to append (preserve original order)
var last: *Decl = userMerged.firstItem;
if last != null as *Decl {
while last.childDecl2 != null as *Decl {
last = last.childDecl2;
}
}
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
inner.childDecl2 = null as *Decl;
if userMerged.firstItem == null as *Decl {
userMerged.firstItem = inner;
last = inner;
} else {
last.childDecl2 = inner;
last = inner;
}
userMerged.itemCount = userMerged.itemCount + 1;
fileDeclCount = fileDeclCount + 1;
inner = innerNext;
}
} else {
decl.childDecl2 = null as *Decl;
var last2: *Decl = userMerged.firstItem;
if last2 != null as *Decl {
while last2.childDecl2 != null as *Decl {
last2 = last2.childDecl2;
}
last2.childDecl2 = decl;
} else {
userMerged.firstItem = decl;
}
userMerged.itemCount = userMerged.itemCount + 1;
fileDeclCount = fileDeclCount + 1;
}
decl = next;
}
Print(" -> ");
PrintInt(fileDeclCount as int64);
PrintLine(" decls merged");
i = i + 1;
}
// Collect user declaration names for shadow detection
let maxNames: int = 2048;
let userNames: *String = bux_alloc(maxNames * 8) as *String;
let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames);
// Create final merged module
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
merged.name = "main";
merged.path = "";
merged.itemCount = 0;
merged.firstItem = null as *Decl;
// Find and merge ALL stdlib declarations
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") {
Print("Stdlib found: ");
PrintLine(stdlibDir);
// List all .bux files in lib/
var libCount: int = 0;
let libFiles: *String = bux_list_dir(stdlibDir, ".bux", &libCount);
var stdAdded: int = 0;
if libCount > 0 {
var si: int = 0;
while si < libCount {
Print(" Merging ");
PrintLine(libFiles[si]);
let added: int = Cli_MergeFileInto(merged, libFiles[si], userNames, userNameCount);
stdAdded = stdAdded + added;
si = si + 1;
}
}
Print("Stdlib declarations added: ");
PrintInt(stdAdded);
PrintLine("");
}
// Merge dependency declarations (deps shadow stdlib, user shadows deps)
if man.depCount > 0 {
let depsDir: String = bux_path_join(projectDir, "deps");
var di: int = 0;
while di < man.depCount {
var depName: String = "";
if di == 0 { depName = man.depName0; }
else if di == 1 { depName = man.depName1; }
else if di == 2 { depName = man.depName2; }
else if di == 3 { depName = man.depName3; }
else if di == 4 { depName = man.depName4; }
else if di == 5 { depName = man.depName5; }
else if di == 6 { depName = man.depName6; }
else if di == 7 { depName = man.depName7; }
let depSrcDir: String = bux_path_join(bux_path_join(depsDir, depName), "src");
if DirExists(depSrcDir) {
var depFileCount: int = 0;
let depFiles: *String = bux_list_dir(depSrcDir, ".bux", &depFileCount);
if depFileCount > 0 {
Print("Merging dependency: ");
PrintLine(depName);
var dfi: int = 0;
while dfi < depFileCount {
Print(" Merging ");
PrintLine(depFiles[dfi]);
let added: int = Cli_MergeFileInto(merged, depFiles[dfi], userNames, userNameCount);
Print(" -> ");
PrintInt(added as int64);
PrintLine(" decls added");
dfi = dfi + 1;
}
}
}
di = di + 1;
}
}
// Copy user declarations into merged (user shadows stdlib)
Cli_CopyModuleDecls(merged, userMerged);
Print("Merged ");
PrintInt(merged.itemCount);
PrintLine(" declarations");
// Semantic analysis
PrintLine("Running sema...");
let sema: *Sema = Sema_Analyze(merged);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(" col ");
PrintInt(sema.diags[i].column as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// HIR lowering
PrintLine("Lowering to HIR...");
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
// C code generation
PrintLine("Generating C code...");
let cCode: String = CBackend_Generate(hirMod);
// Create build directory
let buildDir: String = bux_path_join(projectDir, "build");
discard bux_mkdir_if_needed(buildDir);
// Write C output
let cFile: String = bux_path_join(buildDir, "main.c");
if !WriteFile(cFile, cCode) {
PrintLine("Error: cannot write main.c");
return 1;
}
Print("C code written to ");
PrintLine(cFile);
// Find runtime.c and io.c (look in rt/ directory)
var rtPath: String = bux_path_join(projectDir, "rt/runtime.c");
var ioPath: String = bux_path_join(projectDir, "rt/io.c");
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../rt/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../rt/io.c");
}
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../../rt/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../../rt/io.c");
}
// Compile with cc
PrintLine("Compiling C...");
let outBin: String = bux_path_join(buildDir, outName);
var ccBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&ccBuf, outBin);
StringBuilder_Append(&ccBuf, " ");
StringBuilder_Append(&ccBuf, cFile);
StringBuilder_Append(&ccBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&ccBuf, rtPath);
StringBuilder_Append(&ccBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&ccBuf, ioPath);
StringBuilder_Append(&ccBuf, " ");
}
StringBuilder_Append(&ccBuf, "-lm");
StringBuilder_Append(&ccBuf, " -lcrypto");
let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print("Build successful: ");
PrintLine(outBin);
return 0;
}
// ---------------------------------------------------------------------------
// Run command — build project and execute
// ---------------------------------------------------------------------------
func Cli_RunProject(projectDir: String) -> int {
let rc: int = Cli_BuildProject(projectDir);
if rc != 0 {
return rc;
}
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
Print("Running: ");
PrintLine(outBin);
return bux_system(outBin);
}
// ---------------------------------------------------------------------------
// Main entry — dispatch based on args
// ---------------------------------------------------------------------------
func Cli_Run(args: *String, argCount: int) -> int {
if argCount < 2 {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, test, run, project, help, version");
return 0;
}
let cmd: String = args[1];
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
return 0;
}
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, test, run, project, help, version");
PrintLine("Pipeline modules:");
PrintLine(" Lexer ✅ 695 lines");
PrintLine(" Parser ✅ 1004 lines");
PrintLine(" Sema ✅ 393 lines");
PrintLine(" HirLower ✅ 307 lines");
PrintLine(" CBackend ✅ 585 lines");
PrintLine(" Total: 3830 lines of Bux");
return 0;
}
if String_Eq(cmd, "new") {
if argCount < 3 {
PrintLine("Usage: buxc new <name>");
return 1;
}
return Cli_New(args[2]);
}
if String_Eq(cmd, "init") {
return Cli_Init();
}
if String_Eq(cmd, "add") {
if argCount < 4 {
PrintLine("Usage: buxc add <name> <url>");
return 1;
}
return Cli_Add(args[2], args[3]);
}
if String_Eq(cmd, "remove") {
if argCount < 3 {
PrintLine("Usage: buxc remove <name>");
return 1;
}
return Cli_Remove(args[2]);
}
if String_Eq(cmd, "fetch") {
return Cli_Fetch();
}
if String_Eq(cmd, "fmt") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_Fmt(dir);
}
if String_Eq(cmd, "check") {
if argCount < 3 {
PrintLine("Usage: buxc check <file.bux>");
return 1;
}
return Cli_Check(args[2]);
}
if String_Eq(cmd, "build") {
let src: String = "src/Main.bux";
let out: String = "build/main";
if argCount >= 3 { src = args[2]; }
if argCount >= 4 { out = args[3]; }
return Cli_Build(src, out);
}
if String_Eq(cmd, "project") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_BuildProject(dir);
}
if String_Eq(cmd, "test") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_Test(dir);
}
if String_Eq(cmd, "run") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_RunProject(dir);
}
Print("Unknown command: ");
PrintLine(cmd);
return 1;
}
}