// 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; extern func bux_getenv(name: String) -> String; extern func bux_setenv(name: String, value: String) -> int; extern func bux_strlen(s: String) -> uint; extern func bux_str_slice(s: String, start: uint, len: uint) -> String; 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; } // --------------------------------------------------------------------------- // Diagnostic formatting (Rust-style errors with snippets) // --------------------------------------------------------------------------- struct Diagnostic { message: String; line: uint32; column: uint32; severity: int; } /* Read a single line from a file (1-based). Returns "" on error or EOF. */ func Diagnostic_GetLine(path: String, lineNum: uint32) -> String { let content: String = bux_read_file(path); if String_Eq(content, "") { return ""; } /* bux_str_split_part uses 0-based index */ return bux_str_split_part(content, "\n", lineNum - 1); } /* Simple substring check for help hints */ func Diagnostic_MsgContains(msg: String, needle: String) -> bool { return bux_str_contains(msg, needle) != 0; } /* Actionable help for common error messages */ func Diagnostic_Hint(msg: String) -> String { if Diagnostic_MsgContains(msg, "cannot assign") { return "ensure the right-hand side type matches the left-hand side"; } if Diagnostic_MsgContains(msg, "undeclared identifier") { return "check the spelling, or import the symbol from the right module"; } if Diagnostic_MsgContains(msg, "too few arguments") { return "compare the call with the function's parameter list"; } if Diagnostic_MsgContains(msg, "too many arguments") { return "compare the call with the function's parameter list"; } if Diagnostic_MsgContains(msg, "use of moved value") { return "the value was moved; clone it or restructure ownership"; } if Diagnostic_MsgContains(msg, "expected expression") { return "the previous statement may be incomplete (missing value or ';')"; } if Diagnostic_MsgContains(msg, "duplicate symbol") { return "rename one of the definitions or remove the duplicate"; } return ""; } /* Print a diagnostic in Rust-style format: * error: * --> :: * | * 42 | * | ^ * = help: */ func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) { /* Severity prefix */ if diag.severity == 0 { Print("error: "); } else if diag.severity == 1 { Print("warning: "); } else { Print("note: "); } PrintLine(diag.message); /* Location header */ Print(" --> "); Print(sourcePath); Print(":"); PrintInt(diag.line as int64); Print(":"); PrintInt(diag.column as int64); PrintLine(""); /* Source snippet */ let lineText: String = Diagnostic_GetLine(sourcePath, diag.line); if !String_Eq(lineText, "") { let lineNumStr: String = String_FromInt(diag.line as int64); Print(" |"); PrintLine(""); Print(" "); Print(lineNumStr); Print(" | "); PrintLine(lineText); /* Underline (multi-char for identifiers/string tokens) */ Print(" | "); var i: uint32 = 0; while i < diag.column - 1 && i < 120 { Print(" "); i = i + 1; } /* Estimate token length from the source line */ var ulen: uint = 1; let col0: uint = diag.column - 1; let lineLen: uint = String_Len(lineText); if col0 < lineLen { let first: String = String_Chars(lineText, col0); if String_Eq(first, "\"") || String_Eq(first, "`") || String_Eq(first, "'") { var j: uint = col0 + 1; while j < lineLen { let cj: String = String_Chars(lineText, j); if String_Eq(cj, first) { ulen = j - col0 + 1; break; } j = j + 1; } } else { var j: uint = col0; while j < lineLen { let cj: String = String_Chars(lineText, j); if String_Contains("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", cj) { j = j + 1; } else { break; } } if j > col0 { ulen = j - col0; } } } var k: uint = 0; while k < ulen { Print("^"); k = k + 1; } PrintLine(""); } /* Helpful hint when we recognize the error */ let hint: String = Diagnostic_Hint(diag.message); if !String_Eq(hint, "") { Print(" = help: "); PrintLine(hint); } } // 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, targetTriple: String) -> String { // Phase 1: Lex PrintLine(" Lexing..."); let lex: *Lexer = Lexer_Tokenize(source); PrintLine(" Lex done"); if Lexer_DiagCount(lex) > 0 { var i: int = 0; while i < Lexer_DiagCount(lex) { let diag: Diagnostic = Diagnostic { message: lex.diags[i].message, line: lex.diags[i].line, column: lex.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, sourceName); 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) { var i: int = 0; while i < Sema_DiagCount(sema) { let diag: Diagnostic = Diagnostic { message: sema.diags[i].message, line: sema.diags[i].line, column: sema.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, sourceName); 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, targetTriple: String, isRelease: bool) -> 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(".", targetTriple, isRelease); 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, targetTriple); 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 or clang for cross-compilation // Default: -O0 -g (debug, matches bootstrap). --release: -O2 -DNDEBUG. PrintLine("Compiling C..."); var cmdBuf: StringBuilder = StringBuilder_NewCap(512); var optFlags: String = "-O0 -g"; if isRelease { optFlags = "-O2 -DNDEBUG"; } let extraCf: String = bux_getenv("BUX_CFLAGS"); if extraCf != null as String && !String_Eq(extraCf, "") { optFlags = String_Concat(optFlags, " "); optFlags = String_Concat(optFlags, extraCf); } if !String_Eq(targetTriple, "") { StringBuilder_Append(&cmdBuf, "clang "); StringBuilder_Append(&cmdBuf, optFlags); StringBuilder_Append(&cmdBuf, " -pthread -Wl,--build-id=none -target "); StringBuilder_Append(&cmdBuf, targetTriple); StringBuilder_Append(&cmdBuf, " "); } else { StringBuilder_Append(&cmdBuf, "cc "); StringBuilder_Append(&cmdBuf, optFlags); StringBuilder_Append(&cmdBuf, " -pthread -Wl,--build-id=none "); } StringBuilder_Append(&cmdBuf, "-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 { var i: int = 0; while i < Lexer_DiagCount(lex) { let diag: Diagnostic = Diagnostic { message: lex.diags[i].message, line: lex.diags[i].line, column: lex.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, srcPath); i = i + 1; } 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) { var i: int = 0; while i < Sema_DiagCount(sema) { let diag: Diagnostic = Diagnostic { message: sema.diags[i].message, line: sema.diags[i].line, column: sema.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, srcPath); 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 { // Allow tests and temp packages to inherit the parent project's stdlib. let envPath: String = bux_getenv("BUX_STDLIB"); if !String_Eq(envPath, "") && DirExists(envPath) && FileExists(bux_path_join(envPath, "Fs.bux")) { return envPath; } var path: String = bux_path_join(projectDir, "lib"); if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; } path = bux_path_join(projectDir, "../lib"); if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; } path = bux_path_join(projectDir, "../../lib"); if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { 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; } /* Count lines in a string (split by newline) */ func Cli_CountLines(content: String) -> uint { return bux_str_split_count(content, "\n"); } // 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; } /// Stamp sourceFile on a decl, nested items, and body Expr/Stmt/Block trees. /// Prepares multi-file #line and future macro / cross-file grafts (empty slots only). func Cli_StampSourceFile(decl: *Decl, path: String) { if decl == null as *Decl { return; } decl.sourceFile = path; // Function / method body if decl.refBody != null as *Block { Ast_StampBlockFile(decl.refBody, path); } // Const / type alias init exprs if present if decl.constValue != null as *Expr { Ast_StampExprFile(decl.constValue, path); } if decl.kind == dkModule { var inner: *Decl = decl.childDecl1; while inner != null as *Decl { Cli_StampSourceFile(inner, path); inner = inner.childDecl2; } } // Impl methods if decl.kind == dkImpl { var m: *Decl = decl.childDecl1; while m != null as *Decl { Cli_StampSourceFile(m, path); m = m.childDecl2; } } // Default param values var pi: int = 0; while pi < decl.paramCount { var p: *Param = null as *Param; if pi == 0 { p = &decl.param0; } else if pi == 1 { p = &decl.param1; } else if pi == 2 { p = &decl.param2; } else if pi == 3 { p = &decl.param3; } else if pi == 4 { p = &decl.param4; } else if pi == 5 { p = &decl.param5; } else if pi == 6 { p = &decl.param6; } else if pi == 7 { p = &decl.param7; } else if pi == 8 { p = &decl.param8; } if p != null as *Param && p.defaultExpr != null as *Expr { Ast_StampExprFile(p.defaultExpr, path); } pi = pi + 1; } } 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; } // Tag every decl from this file for #line maps var stamp: *Decl = mod.firstItem; while stamp != null as *Decl { Cli_StampSourceFile(stamp, path); stamp = stamp.childDecl2; } 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; } // --------------------------------------------------------------------------- // Doc command — Markdown from /// comments (D.4) // --------------------------------------------------------------------------- func Cli_DocIsDeclStart(line: String) -> bool { if String_StartsWith(line, "func ") { return true; } if String_StartsWith(line, "pub func ") { return true; } if String_StartsWith(line, "extern func ") { return true; } if String_StartsWith(line, "const func ") { return true; } if String_StartsWith(line, "async func ") { return true; } if String_StartsWith(line, "struct ") { return true; } if String_StartsWith(line, "pub struct ") { return true; } if String_StartsWith(line, "enum ") { return true; } if String_StartsWith(line, "interface ") { return true; } if String_StartsWith(line, "module ") { return true; } if String_StartsWith(line, "type ") { return true; } return false; } func Cli_DocExtractName(line: String) -> String { // Skip leading keywords var s: String = line; if String_StartsWith(s, "pub ") { s = bux_str_slice(s, 4, bux_strlen(s) - 4); } if String_StartsWith(s, "extern ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); } if String_StartsWith(s, "const ") { s = bux_str_slice(s, 6, bux_strlen(s) - 6); } if String_StartsWith(s, "async ") { s = bux_str_slice(s, 6, bux_strlen(s) - 6); } if String_StartsWith(s, "func ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); } else if String_StartsWith(s, "struct ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); } else if String_StartsWith(s, "enum ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); } else if String_StartsWith(s, "interface ") { s = bux_str_slice(s, 10, bux_strlen(s) - 10); } else if String_StartsWith(s, "module ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); } else if String_StartsWith(s, "type ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); } // Take until space, <, (, {, :, ; var i: uint = 0; let n: uint = bux_strlen(s); while i < n { let ch: String = bux_str_slice(s, i, 1); if String_Eq(ch, " ") || String_Eq(ch, "<") || String_Eq(ch, "(") || String_Eq(ch, "{") || String_Eq(ch, ":") || String_Eq(ch, ";") { break; } i = i + 1; } if i == 0 { return s; } return bux_str_slice(s, 0, i); } func Cli_DocProcessFile(path: String, outSb: *StringBuilder) -> int { let source: String = ReadFile(path); if source == null as String || String_Eq(source, "") { return 0; } var itemCount: int = 0; var pending: String = ""; var hasPending: bool = false; let lineCount: uint = bux_str_split_count(source, "\n"); // Drop trailing empty split artifact var nLines: uint = lineCount; if nLines > 0 { let last: String = bux_str_split_part(source, "\n", nLines - 1); if String_Eq(last, "") { nLines = nLines - 1; } } var li: uint = 0; var wroteHeader: bool = false; while li < nLines { let raw: String = bux_str_split_part(source, "\n", li); let line: String = String_Trim(raw); if String_StartsWith(line, "///") { var body: String = bux_str_slice(line, 3, bux_strlen(line) - 3); if String_StartsWith(body, " ") { body = bux_str_slice(body, 1, bux_strlen(body) - 1); } if hasPending { pending = String_Concat(pending, String_Concat("\n", body)); } else { pending = body; hasPending = true; } li = li + 1; continue; } if String_Eq(line, "") || String_StartsWith(line, "@[") { li = li + 1; continue; } if hasPending && Cli_DocIsDeclStart(line) { if !wroteHeader { StringBuilder_Append(outSb, "## `"); StringBuilder_Append(outSb, Cli_FileNameFromPath(path)); StringBuilder_Append(outSb, "`\n\n"); StringBuilder_Append(outSb, "_Source: `"); StringBuilder_Append(outSb, path); StringBuilder_Append(outSb, "`_\n\n"); wroteHeader = true; } let name: String = Cli_DocExtractName(line); StringBuilder_Append(outSb, "### `"); StringBuilder_Append(outSb, name); StringBuilder_Append(outSb, "`\n\n"); StringBuilder_Append(outSb, "```bux\n"); StringBuilder_Append(outSb, line); StringBuilder_Append(outSb, "\n```\n\n"); StringBuilder_Append(outSb, pending); StringBuilder_Append(outSb, "\n\n"); itemCount = itemCount + 1; hasPending = false; pending = ""; li = li + 1; continue; } if String_StartsWith(line, "//") { li = li + 1; continue; } // Other code clears pending hasPending = false; pending = ""; li = li + 1; } return itemCount; } func Cli_Doc(dir: String, outPath: String) -> int { var sb: StringBuilder = StringBuilder_NewCap(16384); StringBuilder_Append(&sb, "# API Reference\n\n"); StringBuilder_Append(&sb, "Generated by `bux doc` from `///` documentation comments.\n\n"); var total: int = 0; if FileExists(dir) { total = total + Cli_DocProcessFile(dir, &sb); } else if DirExists(dir) { var fileCount: int = 0; let files: *String = bux_list_dir(dir, ".bux", &fileCount); var i: int = 0; while i < fileCount { total = total + Cli_DocProcessFile(files[i], &sb); i = i + 1; } } else { Print("Error: path not found: "); PrintLine(dir); StringBuilder_Free(&sb); return 1; } let md: String = StringBuilder_Build(&sb); StringBuilder_Free(&sb); if String_Eq(outPath, "") { Print(md); } else { if !WriteFile(outPath, md) { Print("Error: cannot write "); PrintLine(outPath); return 1; } Print("Wrote "); PrintInt(total as int64); Print(" documented items → "); PrintLine(outPath); } if total == 0 { PrintLine("warning: no /// documented declarations found"); } return 0; } // --------------------------------------------------------------------------- // Fmt command — format source files (write or --check for CI) // --------------------------------------------------------------------------- func Cli_Fmt(dir: String, checkOnly: bool) -> int { // If dir is a file, format/check just that file if FileExists(dir) { if checkOnly { if Fmt_CheckFile(dir) == 0 { Print(" ok "); PrintLine(dir); return 0; } Print(" would reformat "); PrintLine(dir); return 1; } Print("Formatting "); PrintLine(dir); if Fmt_FormatFile(dir) { PrintLine(" OK"); return 0; } return 1; } // Otherwise format/check all .bux files in directory if checkOnly { Print("Checking format in "); } else { 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; var changeCount: int = 0; while i < fileCount { if checkOnly { if Fmt_CheckFile(files[i]) == 0 { okCount = okCount + 1; } else { Print(" would reformat "); PrintLine(files[i]); changeCount = changeCount + 1; } } else { Print(" "); PrintLine(files[i]); if Fmt_FormatFile(files[i]) { okCount = okCount + 1; } } i = i + 1; } if checkOnly { Print("fmt --check: "); PrintInt(changeCount as int64); Print(" would reformat, "); PrintInt(okCount as int64); PrintLine(" ok"); if changeCount > 0 { return 1; } return 0; } Print("Formatted "); PrintInt(okCount as int64); Print("/"); PrintInt(fileCount as int64); PrintLine(" files"); return 0; } func Cli_FileNameFromPath(path: String) -> String { let len: int = bux_strlen(path) as int; var i: int = len - 1; while i >= 0 { let ch: String = bux_str_slice(path, i as uint, 1); if String_Eq(ch, "/") { return bux_str_slice(path, (i + 1) as uint, (len - i - 1) as uint); } i = i - 1; } return path; } func Cli_StripExtension(name: String) -> String { let len: int = bux_strlen(name) as int; var i: int = len - 1; while i >= 0 { let ch: String = bux_str_slice(name, i as uint, 1); if String_Eq(ch, ".") { return bux_str_slice(name, 0, i as uint); } i = i - 1; } return name; } func Cli_Test(projectDir: String, filter: String) -> int { Print("Testing project: "); PrintLine(projectDir); if !String_Eq(filter, "") { Print("Filter: "); PrintLine(filter); } // Without --filter, build and run the project's own Main first. // With --filter, only run matching tests/*.bux files. if String_Eq(filter, "") { let mainRc: int = Cli_BuildProject(projectDir, "", false); if mainRc != 0 { PrintLine("Main test build failed"); return mainRc; } let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml")); var mainName: String = man.name; if String_Eq(mainName, "") { mainName = "bux_out"; } let mainBin: String = bux_path_join(bux_path_join(projectDir, "build"), mainName); if !FileExists(mainBin) { Print("Error: test binary not found: "); PrintLine(mainBin); return 1; } let mainResult: int = bux_system(mainBin); if mainResult != 0 { Print("Main tests failed (exit code "); PrintInt(mainResult as int64); PrintLine(")"); return mainResult; } PrintLine("Main tests passed"); } // Propagate the project's stdlib to temp test packages. let stdlibDir: String = Cli_FindStdlibDir(projectDir); if !String_Eq(stdlibDir, "") { discard bux_setenv("BUX_STDLIB", stdlibDir); } // Run individual test files from tests/ directory let testsDir: String = bux_path_join(projectDir, "tests"); if !DirExists(testsDir) { PrintLine("No tests/ directory found"); return 0; } var testCount: int = 0; let testFiles: *String = bux_list_dir(testsDir, ".bux", &testCount); if testCount == 0 { PrintLine("No .bux test files found in tests/"); return 0; } PrintLine("┌──────────────────────────────┬────────┐"); PrintLine("│ Test │ Status │"); PrintLine("├──────────────────────────────┼────────┤"); var passed: int = 0; var failed: int = 0; var skipped: int = 0; var i: int = 0; while i < testCount { let testPath: String = testFiles[i]; let fileName: String = Cli_FileNameFromPath(testPath); let testName: String = Cli_StripExtension(fileName); // --filter: only run tests whose name contains the filter substring if !String_Eq(filter, "") { if !String_Contains(testName, filter) { skipped = skipped + 1; i = i + 1; continue; } } Print("│ "); Print(testName); // Pad status column roughly (name may be long) Print(" ... "); // Create temp package for this test file let tmpDir: String = bux_path_join(bux_path_join(projectDir, "build"), "_test_tmp"); let tmpSrc: String = bux_path_join(tmpDir, "src"); discard bux_mkdir_if_needed(tmpDir); discard bux_mkdir_if_needed(tmpSrc); let source: String = ReadFile(testPath); if String_Eq(source, "") { PrintLine("FAIL │"); failed = failed + 1; i = i + 1; continue; } let tmpMain: String = bux_path_join(tmpSrc, "Main.bux"); if !WriteFile(tmpMain, source) { PrintLine("FAIL │"); failed = failed + 1; i = i + 1; continue; } let tmpToml: String = bux_path_join(tmpDir, "bux.toml"); var tomlContent: String = "[Package]\nName = \"_test_tmp\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n"; if !WriteFile(tmpToml, tomlContent) { PrintLine("FAIL │"); failed = failed + 1; i = i + 1; continue; } // Copy runtime shims so the temp package links even when nested deep. if !String_Eq(stdlibDir, "") { let rtDir: String = bux_path_join(bux_path_parent(stdlibDir), "rt"); let tmpRtDir: String = bux_path_join(tmpDir, "rt"); discard bux_mkdir_if_needed(tmpRtDir); let rtSrc: String = bux_path_join(rtDir, "runtime.c"); let rtDst: String = bux_path_join(tmpRtDir, "runtime.c"); if FileExists(rtSrc) && !FileExists(rtDst) { discard WriteFile(rtDst, ReadFile(rtSrc)); } let ioSrc: String = bux_path_join(rtDir, "io.c"); let ioDst: String = bux_path_join(tmpRtDir, "io.c"); if FileExists(ioSrc) && !FileExists(ioDst) { discard WriteFile(ioDst, ReadFile(ioSrc)); } } let buildRc: int = Cli_BuildProject(tmpDir, "", false); if buildRc != 0 { PrintLine("FAIL │"); failed = failed + 1; i = i + 1; continue; } let testBin: String = bux_path_join(bux_path_join(tmpDir, "build"), "_test_tmp"); if !FileExists(testBin) { PrintLine("FAIL │"); failed = failed + 1; i = i + 1; continue; } let runRc: int = bux_system(testBin); if runRc == 0 { PrintLine("PASS │"); passed = passed + 1; } else { Print("FAIL │"); PrintLine(""); failed = failed + 1; } i = i + 1; } PrintLine("└──────────────────────────────┴────────┘"); Print("Results: "); PrintInt(passed as int64); Print(" passed, "); PrintInt(failed as int64); Print(" failed"); if skipped > 0 { Print(", "); PrintInt(skipped as int64); Print(" skipped"); } PrintLine(""); if !String_Eq(filter, "") && passed == 0 && failed == 0 { Print("No tests matching filter '"); Print(filter); PrintLine("'"); return 1; } if failed > 0 { return 1; } return 0; } func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> 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 { var li: int = 0; while li < Lexer_DiagCount(lex) { let diag: Diagnostic = Diagnostic { message: lex.diags[li].message, line: lex.diags[li].line, column: lex.diags[li].column, severity: 0, }; Diagnostic_Print(&diag, path); li = li + 1; } return 1; } let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount); if mod == null as *Module { Print(" Parse failed for "); PrintLine(path); return 1; } // Tag decls with this source path for multi-file #line var stampUser: *Decl = mod.firstItem; while stampUser != null as *Decl { Cli_StampSourceFile(stampUser, path); stampUser = stampUser.childDecl2; } // 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) { var i: int = 0; while i < Sema_DiagCount(sema) { let diag: Diagnostic = Diagnostic { message: sema.diags[i].message, line: sema.diags[i].line, column: sema.diags[i].column, severity: 0, }; /* Try to guess which source file has this line */ let errLine: uint32 = sema.diags[i].line; let mainSrc: String = bux_path_join(projectDir, "src/Main.bux"); let mainContent: String = bux_read_file(mainSrc); var sourcePath: String = ""; if !String_Eq(mainContent, "") { let mainLines: uint = Cli_CountLines(mainContent); if errLine <= mainLines { sourcePath = mainSrc; } } Diagnostic_Print(&diag, sourcePath); 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 — default debug (-O0 -g); --release → -O2 -DNDEBUG PrintLine("Compiling C..."); let outBin: String = bux_path_join(buildDir, outName); var ccBuf: StringBuilder = StringBuilder_NewCap(512); var optFlags2: String = "-O0 -g"; if isRelease { optFlags2 = "-O2 -DNDEBUG"; } let extraCf2: String = bux_getenv("BUX_CFLAGS"); if extraCf2 != null as String && !String_Eq(extraCf2, "") { optFlags2 = String_Concat(optFlags2, " "); optFlags2 = String_Concat(optFlags2, extraCf2); } if !String_Eq(targetTriple, "") { StringBuilder_Append(&ccBuf, "clang "); StringBuilder_Append(&ccBuf, optFlags2); StringBuilder_Append(&ccBuf, " -pthread -Wl,--build-id=none -target "); StringBuilder_Append(&ccBuf, targetTriple); StringBuilder_Append(&ccBuf, " "); } else { StringBuilder_Append(&ccBuf, "cc "); StringBuilder_Append(&ccBuf, optFlags2); StringBuilder_Append(&ccBuf, " -pthread -Wl,--build-id=none "); } StringBuilder_Append(&ccBuf, "-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, targetTriple: String, isRelease: bool) -> int { let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease); 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 { /* Scan for --target and --release flags before processing command */ var targetTriple: String = ""; var isRelease: bool = false; var ai: int = 1; while ai < argCount { if String_Eq(args[ai], "--target") && ai + 1 < argCount { targetTriple = args[ai + 1]; /* Remove --target and its value from args by shifting */ var j: int = ai; while j + 2 < argCount { args[j] = args[j + 2]; j = j + 1; } argCount = argCount - 2; ai = ai - 1; } else if String_Eq(args[ai], "--release") { isRelease = true; /* Remove --release from args by shifting */ var j: int = ai; while j + 1 < argCount { args[j] = args[j + 1]; j = j + 1; } argCount = argCount - 1; ai = ai - 1; } ai = ai + 1; } if argCount < 2 { PrintLine("Bux Self-Hosting Compiler v0.2.0"); PrintLine("Usage: buxc [args]"); PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version"); PrintLine(" test --filter Only run tests/*.bux whose name contains "); PrintLine(" fmt --check [path] Exit 1 if any file would be reformatted (CI)"); PrintLine(" doc --out file.md [path] API docs from /// comments"); PrintLine(" --release Optimize (-O2 -DNDEBUG); default is -O0 -g"); PrintLine(" BUX_CFLAGS Extra flags appended to cc"); 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 [args]"); PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version"); PrintLine(" test --filter Only run tests/*.bux whose name contains "); PrintLine(" fmt --check [path] Exit 1 if any file would be reformatted (CI)"); PrintLine(" doc --out file.md [path] API docs from /// comments"); PrintLine(" --release Optimize (-O2 -DNDEBUG); default is -O0 -g"); PrintLine(" BUX_CFLAGS Extra flags appended to cc"); PrintLine("Pipeline modules:"); PrintLine(" Lexer ✅"); PrintLine(" Parser ✅"); PrintLine(" Sema ✅"); PrintLine(" HirLower ✅"); PrintLine(" CBackend ✅"); return 0; } if String_Eq(cmd, "new") { if argCount < 3 { PrintLine("Usage: buxc new "); 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 "); return 1; } return Cli_Add(args[2], args[3]); } if String_Eq(cmd, "remove") { if argCount < 3 { PrintLine("Usage: buxc remove "); return 1; } return Cli_Remove(args[2]); } if String_Eq(cmd, "fetch") { return Cli_Fetch(); } if String_Eq(cmd, "fmt") { var dir: String = "."; var checkOnly: bool = false; var fi: int = 2; while fi < argCount { if String_Eq(args[fi], "--check") { checkOnly = true; } else { dir = args[fi]; } fi = fi + 1; } return Cli_Fmt(dir, checkOnly); } if String_Eq(cmd, "doc") { var dir: String = "lib"; var outPath: String = ""; var di: int = 2; while di < argCount { if String_Eq(args[di], "--out") && di + 1 < argCount { outPath = args[di + 1]; di = di + 1; } else if String_StartsWith(args[di], "--out=") { outPath = bux_str_slice(args[di], 6, bux_strlen(args[di]) - 6); } else if !String_StartsWith(args[di], "-") { dir = args[di]; } di = di + 1; } return Cli_Doc(dir, outPath); } if String_Eq(cmd, "check") { if argCount < 3 { PrintLine("Usage: buxc check "); 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, targetTriple, isRelease); } if String_Eq(cmd, "project") { let dir: String = "."; if argCount >= 3 { dir = args[2]; } return Cli_BuildProject(dir, targetTriple, isRelease); } if String_Eq(cmd, "test") { var dir: String = "."; var filter: String = ""; var ti: int = 2; while ti < argCount { if String_Eq(args[ti], "--filter") && ti + 1 < argCount { filter = args[ti + 1]; ti = ti + 1; } else if String_StartsWith(args[ti], "--filter=") { // --filter=name form filter = bux_str_slice(args[ti], 9, bux_strlen(args[ti]) - 9); } else if !String_StartsWith(args[ti], "-") { dir = args[ti]; } ti = ti + 1; } return Cli_Test(dir, filter); } if String_Eq(cmd, "run") { let dir: String = "."; if argCount >= 3 { dir = args[2]; } return Cli_RunProject(dir, targetTriple, isRelease); } Print("Unknown command: "); PrintLine(cmd); return 1; } }