Files
bux-lang/src/cli.bux
T
dimgigov ec5984762b
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
feat: try/unwrap payload types, LSP format, macro paste, freestanding runtime
- Type `?`/`!` as Result/Option Ok payload (not always int); fix unwrap C types
- LSP 0.18 document formatting (bux fmt) + VS Code format-on-save
- Macro `:type` generics (Array_New<$t>) and operators-only tt paste
- Ship runtime_freestanding.c + BUX_RUNTIME=freestanding + smokes/examples
2026-07-28 16:56:35 +03:00

2457 lines
86 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;
extern func bux_process_output(cmd: String) -> String;
extern func bux_getenv(name: String) -> String;
extern func bux_setenv(name: String, value: String) -> int;
extern func bux_cc_ld_stable() -> String;
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;
}
// ---------------------------------------------------------------------------
// Link / runtime helpers (session 76 — selfhost parity with bootstrap 75)
// ---------------------------------------------------------------------------
func Cli_EnvTruthy(name: String) -> bool {
let v: String = bux_getenv(name);
if v == null as String { return false; }
if String_Eq(v, "1") { return true; }
if String_Eq(v, "true") { return true; }
if String_Eq(v, "yes") { return true; }
if String_Eq(v, "on") { return true; }
return false;
}
func Cli_RuntimeRel(isStatic: bool, targetTriple: String) -> String {
let env: String = bux_getenv("BUX_RUNTIME");
if env != null as String {
if String_Eq(env, "win") || String_Eq(env, "windows") {
return "rt/runtime_win.c";
}
if String_Eq(env, "minimal") || String_Eq(env, "thin") ||
String_Eq(env, "embed") || String_Eq(env, "embedded") {
return "rt/runtime_minimal.c";
}
if String_Eq(env, "freestanding") || String_Eq(env, "bare") ||
String_Eq(env, "nolibc") {
return "rt/runtime_freestanding.c";
}
if String_Eq(env, "full") || String_Eq(env, "posix") {
return "rt/runtime.c";
}
}
if isStatic {
return "rt/runtime_minimal.c";
}
if targetTriple != null as String && !String_Eq(targetTriple, "") {
return "rt/runtime_minimal.c";
}
return "rt/runtime.c";
}
func Cli_IsThinRuntimePath(rtPath: String) -> bool {
if String_Contains(rtPath, "runtime_minimal.c") { return true; }
if String_Contains(rtPath, "runtime_freestanding.c") { return true; }
if String_Contains(rtPath, "runtime_win.c") { return true; }
return false;
}
func Cli_FindRtFile(projectDir: String, rel: String) -> String {
// Try projectDir/rel, then parent, then grandparent (selfhost build dirs).
var p: String = bux_path_join(projectDir, rel);
if FileExists(p) { return p; }
p = bux_path_join(projectDir, String_Concat("../", rel));
if FileExists(p) { return p; }
p = bux_path_join(projectDir, String_Concat("../../", rel));
if FileExists(p) { return p; }
// Next to stdlib: $BUX_STDLIB/../rt/...
let stdlib: String = bux_getenv("BUX_STDLIB");
if stdlib != null as String && !String_Eq(stdlib, "") {
p = bux_path_join(bux_path_parent(stdlib), rel);
if FileExists(p) { return p; }
}
// Also try finding lib/ next to project to infer repo root
let libBeside: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(libBeside, "") {
p = bux_path_join(bux_path_parent(libBeside), rel);
if FileExists(p) { return p; }
}
// cwd-relative (single-file builds)
if FileExists(rel) { return rel; }
p = String_Concat("../", rel);
if FileExists(p) { return p; }
p = String_Concat("../../", rel);
if FileExists(p) { return p; }
return "";
}
func Cli_PickCc(targetTriple: String) -> String {
let envCc: String = bux_getenv("BUX_CC");
if envCc != null as String && !String_Eq(envCc, "") {
return envCc;
}
if targetTriple != null as String && !String_Eq(targetTriple, "") {
let tripleGcc: String = String_Concat(targetTriple, "-gcc");
let probe: String = String_Concat("command -v ", tripleGcc);
probe = String_Concat(probe, " >/dev/null 2>&1");
if bux_system(probe) == 0 {
return tripleGcc;
}
let probeClang: String = "command -v clang >/dev/null 2>&1";
if bux_system(probeClang) == 0 {
return "clang";
}
return tripleGcc;
}
return "cc";
}
func Cli_CcNeedsTargetFlag(ccBin: String) -> bool {
// clang needs -target; *-gcc is already a cross binary.
if String_Eq(ccBin, "clang") { return true; }
if String_Contains(ccBin, "clang-") { return true; }
if String_Contains(ccBin, "/clang") { return true; }
return false;
}
func Cli_LinkProgram(cFile: String, outBin: String, projectDir: String,
targetTriple: String, isRelease: bool, isStatic: bool) -> int {
let relRt: String = Cli_RuntimeRel(isStatic, targetTriple);
let rtPath: String = Cli_FindRtFile(projectDir, relRt);
let ioPath: String = Cli_FindRtFile(projectDir, "rt/io.c");
if String_Eq(rtPath, "") {
Print("Error: runtime not found: ");
PrintLine(relRt);
return 1;
}
if String_Eq(ioPath, "") {
PrintLine("Error: rt/io.c not found");
return 1;
}
let thin: bool = Cli_IsThinRuntimePath(rtPath);
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 isStatic || Cli_EnvTruthy("BUX_STATIC") {
optFlags = String_Concat(optFlags, " -static");
}
let ccBin: String = Cli_PickCc(targetTriple);
var cmdBuf: StringBuilder = StringBuilder_NewCap(768);
StringBuilder_Append(&cmdBuf, ccBin);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, optFlags);
if targetTriple != null as String && !String_Eq(targetTriple, "") {
if Cli_CcNeedsTargetFlag(ccBin) {
StringBuilder_Append(&cmdBuf, " -target ");
StringBuilder_Append(&cmdBuf, targetTriple);
}
}
if thin {
StringBuilder_Append(&cmdBuf, " -ffunction-sections -fdata-sections");
} else {
StringBuilder_Append(&cmdBuf, " -pthread");
StringBuilder_Append(&cmdBuf, bux_cc_ld_stable());
}
StringBuilder_Append(&cmdBuf, " -o ");
StringBuilder_Append(&cmdBuf, outBin);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, cFile);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, rtPath);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, ioPath);
if thin {
StringBuilder_Append(&cmdBuf, " -Wl,--gc-sections -lm");
} else {
StringBuilder_Append(&cmdBuf, " -lm -lssl -lcrypto");
}
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
return 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: <message>
* --> <path>:<line>:<col>
* |
* 42 | <source_line>
* | <spaces>^
* = help: <hint>
*/
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 2b: declarative macro! / quote! expansion
PrintLine(" Macro expand...");
let macEx: *MacroExpander = MacroExpand_ExpandModule(mod);
if MacroExpand_DiagCount(macEx) > 0 {
var mi: int = 0;
while mi < MacroExpand_DiagCount(macEx) {
let md: MacroDiag = MacroExpand_GetDiag(macEx, mi);
let diag: Diagnostic = Diagnostic {
message: md.message,
line: md.line,
column: md.column,
severity: 0,
};
Diagnostic_Print(&diag, sourceName);
mi = mi + 1;
}
return "";
}
// 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, isStatic: 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, isStatic);
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);
PrintLine("Compiling C...");
let linkRc: int = Cli_LinkProgram(cFile, outPath, ".", targetTriple, isRelease, isStatic);
if linkRc != 0 {
return linkRc;
}
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 2b: macro expand
let macEx2: *MacroExpander = MacroExpand_ExpandModule(mod);
if MacroExpand_DiagCount(macEx2) > 0 {
var mi2: int = 0;
while mi2 < MacroExpand_DiagCount(macEx2) {
let md2: MacroDiag = MacroExpand_GetDiag(macEx2, mi2);
let diag: Diagnostic = Diagnostic {
message: md2.message,
line: md2.line,
column: md2.column,
severity: 0,
};
Diagnostic_Print(&diag, srcPath);
mi2 = mi2 + 1;
}
return 1;
}
// 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 2b: macro expand
let macEx3: *MacroExpander = MacroExpand_ExpandModule(mod);
if MacroExpand_DiagCount(macEx3) > 0 {
Print("Macro errors in ");
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
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Registry — search / add by name (session 81)
// ---------------------------------------------------------------------------
func Cli_Search(query: String) -> int {
let reg: Registry = Reg_FindIndex();
if String_Eq(reg.path, "") {
if !String_Eq(reg.sourceUrl, "") {
Print("Error: failed to fetch registry from ");
PrintLine(reg.sourceUrl);
PrintLine("hint: curl/wget + network, or BUX_REGISTRY=local file");
} else {
PrintLine("Error: no package registry (set BUX_REGISTRY or config/registry.toml)");
}
return 1;
}
if !String_Eq(reg.sourceUrl, "") {
Print("Registry: ");
PrintLine(reg.sourceUrl);
Print(" (cached: ");
Print(reg.path);
PrintLine(")");
} else {
Print("Registry: ");
PrintLine(reg.path);
}
let hits: int = Reg_Search(reg, query);
if hits == 0 {
PrintLine("No packages matched.");
return 1;
}
return 0;
}
// ---------------------------------------------------------------------------
// 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;
}
/// Resolve package from registry index and add as path/git dep.
func Cli_AddFromRegistry(pkgName: String, versionReq: String) -> int {
let reg: Registry = Reg_FindIndex();
if String_Eq(reg.path, "") {
if !String_Eq(reg.sourceUrl, "") {
Print("Error: failed to fetch registry from ");
PrintLine(reg.sourceUrl);
} else {
PrintLine("Error: no package registry (set BUX_REGISTRY)");
}
return 1;
}
let pkg: RegistryPackage = Reg_Lookup(reg, pkgName, versionReq);
if String_Eq(pkg.name, "") {
Print("Error: package '");
Print(pkgName);
PrintLine("' not found in registry");
PrintLine("hint: buxc search | buxc add name <url>");
return 1;
}
var url: String = pkg.source;
if !String_Eq(pkg.resolvedPath, "") {
url = pkg.resolvedPath;
}
if !String_Eq(reg.sourceUrl, "") {
Print("Resolved '");
Print(pkgName);
Print("' ");
Print(pkg.version);
Print(" from registry ");
PrintLine(reg.sourceUrl);
} else {
Print("Resolved '");
Print(pkgName);
Print("' ");
Print(pkg.version);
Print(" from registry ");
PrintLine(reg.path);
}
return Cli_Add(pkgName, url);
}
// ---------------------------------------------------------------------------
// 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;
}
// ---------------------------------------------------------------------------
// Package checksum (session 80 — selfhost install --locked parity)
// ---------------------------------------------------------------------------
func Cli_PackageChecksum(dir: String) -> String {
// Same idea as bootstrap: sha1 of sorted *.bux paths+contents via shell.
if String_Eq(dir, "") || !DirExists(dir) {
return "";
}
let cmd: String = String_Concat(
"find \"",
String_Concat(dir, "\" -name '*.bux' -type f 2>/dev/null | LC_ALL=C sort | xargs cat 2>/dev/null | sha1sum | awk '{print $1}'")
);
let out: String = bux_process_output(cmd);
if out == null as String { return ""; }
// trim trailing newline
var s: String = String_Trim(out);
return s;
}
func Cli_ShellQuote(s: String) -> String {
return String_Concat("\"", String_Concat(s, "\""));
}
func Cli_IsAbsPath(p: String) -> bool {
if String_Eq(p, "") { return false; }
if p[0] == 47 as char8 { return true; } // /
return false;
}
func Cli_DepResolvedPath(projectDir: String, depName: String, depUrl: String) -> String {
// Path dep: absolute or relative; git dep: deps/<name>
if Cli_IsAbsPath(depUrl) {
return depUrl;
}
if String_StartsWith(depUrl, "http://") || String_StartsWith(depUrl, "https://") ||
String_EndsWith(depUrl, ".git") {
return bux_path_join(bux_path_join(projectDir, "deps"), depName);
}
// relative path
return bux_path_join(projectDir, depUrl);
}
func Cli_InstallLocked(projectDir: String) -> int {
let lockPath: String = bux_path_join(projectDir, "bux.lock");
if !FileExists(lockPath) {
PrintLine("Error: install --locked: bux.lock missing (run install first)");
return 1;
}
let content: String = ReadFile(lockPath);
if content == null as String || String_Eq(content, "") {
PrintLine("Error: install --locked: empty lock");
return 1;
}
// Parse [[Package]] blocks: Name, Version, Source, Checksum
var name: String = "";
var version: String = "";
var source: String = "";
var checksum: String = "";
var verified: int = 0;
let nlines: uint = String_SplitCount(content, "\n");
var li: uint = 0;
while li <= nlines {
var line: String = "";
if li < nlines {
line = String_Trim(String_SplitPart(content, "\n", li));
}
let isEnd: bool = li == nlines;
let isNew: bool = String_StartsWith(line, "[[Package]]") || String_StartsWith(line, "[[package]]");
if (isNew || isEnd) && !String_Eq(name, "") {
// verify entry
var path: String = source;
if !Cli_IsAbsPath(path) && !String_StartsWith(path, "http") {
path = bux_path_join(projectDir, source);
}
if String_StartsWith(source, "http://") || String_StartsWith(source, "https://") ||
String_EndsWith(source, ".git") {
path = bux_path_join(bux_path_join(projectDir, "deps"), name);
}
if !DirExists(path) {
Print("Error: install --locked: missing ");
Print(name);
Print(" at ");
PrintLine(path);
return 1;
}
if !String_Eq(checksum, "") {
let got: String = Cli_PackageChecksum(path);
if !String_Eq(got, checksum) {
Print("Error: install --locked: checksum mismatch for ");
PrintLine(name);
Print(" lock: "); PrintLine(checksum);
Print(" got: "); PrintLine(got);
return 1;
}
}
Print("locked ok: ");
Print(name);
Print(" ");
PrintLine(version);
verified = verified + 1;
name = "";
version = "";
source = "";
checksum = "";
}
if isEnd { break; }
if isNew {
li = li + 1;
continue;
}
if String_StartsWith(line, "Name") || String_StartsWith(line, "name") {
// Name = "x"
let parts: uint = String_SplitCount(line, "\"");
if parts >= 2 {
name = String_SplitPart(line, "\"", 1);
}
} else if String_StartsWith(line, "Version") || String_StartsWith(line, "version") {
let parts: uint = String_SplitCount(line, "\"");
if parts >= 2 {
version = String_SplitPart(line, "\"", 1);
}
} else if String_StartsWith(line, "Source") || String_StartsWith(line, "source") {
let parts: uint = String_SplitCount(line, "\"");
if parts >= 2 {
source = String_SplitPart(line, "\"", 1);
}
} else if String_StartsWith(line, "Checksum") || String_StartsWith(line, "checksum") {
let parts: uint = String_SplitCount(line, "\"");
if parts >= 2 {
checksum = String_SplitPart(line, "\"", 1);
}
}
li = li + 1;
}
Print("install --locked: ");
PrintInt(verified as int64);
PrintLine(" package(s) verified");
return 0;
}
func Cli_Install(projectDir: String, lockedOnly: bool) -> int {
if lockedOnly {
return Cli_InstallLocked(projectDir);
}
let tomlPath: String = bux_path_join(projectDir, "bux.toml");
if !FileExists(tomlPath) {
PrintLine("Error: no bux.toml found");
return 1;
}
let man: Manifest = Manifest_Load(tomlPath);
if man.depCount == 0 {
// empty lock
discard WriteFile(bux_path_join(projectDir, "bux.lock"), "");
PrintLine("install: no dependencies (empty lock)");
return 0;
}
var sb: StringBuilder = StringBuilder_NewCap(1024);
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; }
// Fetch git deps into deps/<name> when missing
if String_StartsWith(depUrl, "http://") || String_StartsWith(depUrl, "https://") ||
String_EndsWith(depUrl, ".git") {
let depsDir: String = bux_path_join(projectDir, "deps");
discard bux_mkdir_if_needed(depsDir);
let depPath: String = bux_path_join(depsDir, depName);
if !DirExists(depPath) {
Print("Fetching "); Print(depName); Print(" from "); PrintLine(depUrl);
let cmd: String = String_Concat("git clone --quiet \"", String_Concat(depUrl, String_Concat("\" \"", String_Concat(depPath, "\""))));
if bux_system(cmd) != 0 {
Print("Error: failed to fetch "); PrintLine(depName);
return 1;
}
}
}
let path: String = Cli_DepResolvedPath(projectDir, depName, depUrl);
if !DirExists(path) {
Print("Error: dependency path missing: ");
PrintLine(path);
return 1;
}
var ver: String = "0.0.0";
let depToml: String = bux_path_join(path, "bux.toml");
if FileExists(depToml) {
let dm: Manifest = Manifest_Load(depToml);
if !String_Eq(dm.version, "") {
ver = dm.version;
}
}
let csum: String = Cli_PackageChecksum(path);
StringBuilder_Append(&sb, "[[Package]]\n");
StringBuilder_Append(&sb, "Name = \"");
StringBuilder_Append(&sb, depName);
StringBuilder_Append(&sb, "\"\n");
StringBuilder_Append(&sb, "Version = \"");
StringBuilder_Append(&sb, ver);
StringBuilder_Append(&sb, "\"\n");
StringBuilder_Append(&sb, "Source = \"");
StringBuilder_Append(&sb, path);
StringBuilder_Append(&sb, "\"\n");
if !String_Eq(csum, "") {
StringBuilder_Append(&sb, "Checksum = \"");
StringBuilder_Append(&sb, csum);
StringBuilder_Append(&sb, "\"\n");
}
StringBuilder_Append(&sb, "\n");
Print("Resolved ");
Print(depName);
Print(" ");
Print(ver);
Print(" → ");
PrintLine(path);
i = i + 1;
}
let lockPath: String = bux_path_join(projectDir, "bux.lock");
if !WriteFile(lockPath, StringBuilder_Build(&sb)) {
PrintLine("Error: cannot write bux.lock");
return 1;
}
Print("Generated ");
PrintLine(lockPath);
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, 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, 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, isStatic: 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 (path or deps/<name>; shadow stdlib)
if man.depCount > 0 {
var di: int = 0;
while di < man.depCount {
var depName: String = "";
var depUrl: String = "";
if di == 0 { depName = man.depName0; depUrl = man.depUrl0; }
else if di == 1 { depName = man.depName1; depUrl = man.depUrl1; }
else if di == 2 { depName = man.depName2; depUrl = man.depUrl2; }
else if di == 3 { depName = man.depName3; depUrl = man.depUrl3; }
else if di == 4 { depName = man.depName4; depUrl = man.depUrl4; }
else if di == 5 { depName = man.depName5; depUrl = man.depUrl5; }
else if di == 6 { depName = man.depName6; depUrl = man.depUrl6; }
else if di == 7 { depName = man.depName7; depUrl = man.depUrl7; }
// Prefer resolved path (absolute / relative / deps/<name>)
let depRoot: String = Cli_DepResolvedPath(projectDir, depName, depUrl);
var depSrcDir: String = bux_path_join(depRoot, "src");
if !DirExists(depSrcDir) {
// package root may be the src tree itself
if DirExists(depRoot) {
depSrcDir = depRoot;
}
}
if DirExists(depSrcDir) {
var depFileCount: int = 0;
let depFiles: *String = bux_list_dir(depSrcDir, ".bux", &depFileCount);
if depFileCount > 0 {
Print("Merging dependency: ");
PrintLine(depName);
Print(" from ");
PrintLine(depSrcDir);
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;
}
}
} else {
Print("WARN: dependency sources not found for ");
PrintLine(depName);
}
di = di + 1;
}
}
// Copy user declarations into merged (user shadows stdlib)
Cli_CopyModuleDecls(merged, userMerged);
Print("Merged ");
PrintInt(merged.itemCount);
PrintLine(" declarations");
// Declarative macro! / quote! expansion (before type-check)
PrintLine("Expanding macros...");
let macEx: *MacroExpander = MacroExpand_ExpandModule(merged);
if MacroExpand_DiagCount(macEx) > 0 {
var mi: int = 0;
while mi < MacroExpand_DiagCount(macEx) {
let md: MacroDiag = MacroExpand_GetDiag(macEx, mi);
let diag: Diagnostic = Diagnostic {
message: md.message,
line: md.line,
column: md.column,
severity: 0,
};
Diagnostic_Print(&diag, "<macro>");
mi = mi + 1;
}
return 1;
}
// 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 = "<merged>";
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);
PrintLine("Compiling C...");
let outBin: String = bux_path_join(buildDir, outName);
let linkRc2: int = Cli_LinkProgram(cFile, outBin, projectDir, targetTriple, isRelease, isStatic);
if linkRc2 != 0 {
return linkRc2;
}
Print("Build successful: ");
PrintLine(outBin);
return 0;
}
// ---------------------------------------------------------------------------
// Run command — build project and execute
// ---------------------------------------------------------------------------
func Cli_RunProject(projectDir: String, targetTriple: String, isRelease: bool, isStatic: bool) -> int {
let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease, isStatic);
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 / --release / --static before processing command */
var targetTriple: String = "";
var isRelease: bool = false;
var isStatic: bool = Cli_EnvTruthy("BUX_STATIC");
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;
var j: int = ai;
while j + 1 < argCount {
args[j] = args[j + 1];
j = j + 1;
}
argCount = argCount - 1;
ai = ai - 1;
} else if String_Eq(args[ai], "--static") {
isStatic = true;
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 v1.0.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
PrintLine(" search [query] Search package registry");
PrintLine(" install [--locked] Write/verify bux.lock (checksums)");
PrintLine(" add <name> [ver|url] Registry resolve or explicit source");
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(" --static Fully-static link (minimal runtime)");
PrintLine(" --target <triple> Cross-compile (e.g. aarch64-linux-gnu)");
PrintLine(" BUX_CFLAGS / BUX_CC / BUX_RUNTIME / BUX_STATIC / BUX_REGISTRY");
return 0;
}
let cmd: String = args[1];
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
PrintLine("Bux 1.0.0 (self-hosting)");
return 0;
}
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
PrintLine("Bux Self-Hosting Compiler v1.0.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
PrintLine(" search [query] Search package registry");
PrintLine(" install [--locked] Write/verify bux.lock (checksums)");
PrintLine(" add <name> [ver|url] Registry resolve or explicit source");
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(" --static Fully-static link (minimal runtime)");
PrintLine(" --target <triple> Cross-compile (e.g. aarch64-linux-gnu)");
PrintLine(" BUX_CFLAGS / BUX_CC / BUX_RUNTIME / BUX_STATIC / BUX_REGISTRY");
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 <name>");
return 1;
}
return Cli_New(args[2]);
}
if String_Eq(cmd, "init") {
return Cli_Init();
}
if String_Eq(cmd, "search") {
var query: String = "";
if argCount >= 3 {
query = args[2];
}
return Cli_Search(query);
}
if String_Eq(cmd, "add") {
if argCount < 3 {
PrintLine("Usage: buxc add <name> [version|url]");
PrintLine(" buxc add greet # resolve from registry");
PrintLine(" buxc add greet 0.1.1 # registry version");
PrintLine(" buxc add greet /path/or/git-url");
return 1;
}
if argCount >= 4 {
let a3: String = args[3];
if !String_Contains(a3, "/") && !String_StartsWith(a3, "http") &&
!String_EndsWith(a3, ".git") {
return Cli_AddFromRegistry(args[2], a3);
}
return Cli_Add(args[2], a3);
}
return Cli_AddFromRegistry(args[2], "*");
}
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, "install") {
var lockedOnly: bool = false;
var dir: String = ".";
var ii: int = 2;
while ii < argCount {
if String_Eq(args[ii], "--locked") {
lockedOnly = true;
} else if !String_StartsWith(args[ii], "-") {
dir = args[ii];
}
ii = ii + 1;
}
return Cli_Install(dir, lockedOnly);
}
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 <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, targetTriple, isRelease, isStatic);
}
if String_Eq(cmd, "project") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_BuildProject(dir, targetTriple, isRelease, isStatic);
}
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, isStatic);
}
Print("Unknown command: ");
PrintLine(cmd);
return 1;
}
}