feat: restructure repo, borrow checker, expanded stdlib
- Reorganize repository to Rust-style layout: compiler/bootstrap/ compiler/selfhost/ compiler/tests/ library/std/ library/runtime/ tests/ tools/ - Add buxs/ Windows-compatible project root - Add borrow checker tests and implement: - Alias analysis (double mutable borrow detection) - Use-after-move detection for own T - Expand standard library: - Std::Os: Args, Env, Cwd, Chdir - Std::Time: NowMs, NowUs, SleepMs - Std::Process: Run, Output - Std::Io: PrintInt64 (fixes 32-bit truncation bug) - Add examples: os_time.bux, process.bux - Fix PrintInt to use int64_t in C runtime
This commit is contained in:
@@ -0,0 +1,761 @@
|
||||
// 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_path_join(a: String, b: String) -> String;
|
||||
extern func bux_mkdir_if_needed(path: String) -> int;
|
||||
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
|
||||
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
|
||||
extern func bux_system(cmd: String) -> int;
|
||||
|
||||
func ReadFile(path: String) -> String {
|
||||
return bux_read_file(path);
|
||||
}
|
||||
|
||||
func WriteFile(path: String, content: String) -> bool {
|
||||
return bux_write_file(path, content);
|
||||
}
|
||||
|
||||
func FileExists(path: String) -> bool {
|
||||
return bux_file_exists(path) != 0;
|
||||
}
|
||||
|
||||
func DirExists(path: String) -> bool {
|
||||
return bux_dir_exists(path) != 0;
|
||||
}
|
||||
|
||||
// Import the compiler pipeline
|
||||
// In self-hosting mode, these are compiled together from compiler/selfhost/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compile a single .bux source file
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Compile(source: String, sourceName: String) -> String {
|
||||
// Phase 1: Lex
|
||||
PrintLine(" Lexing...");
|
||||
let lex: *Lexer = Lexer_Tokenize(source);
|
||||
PrintLine(" Lex done");
|
||||
if Lexer_DiagCount(lex) > 0 {
|
||||
PrintLine("Lex errors:");
|
||||
var i: int = 0;
|
||||
while i < Lexer_DiagCount(lex) {
|
||||
Print(" ");
|
||||
PrintLine(lex.diags[i].message);
|
||||
i = i + 1;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Phase 2: Parse
|
||||
PrintLine(" Parsing...");
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
if mod == null as *Module {
|
||||
PrintLine("Parse failed");
|
||||
return "";
|
||||
}
|
||||
PrintLine(" Parse done");
|
||||
|
||||
// Flatten module wrappers: find module decl and hoist its children
|
||||
var decl: *Decl = mod.firstItem;
|
||||
while decl != null as *Decl {
|
||||
if decl.kind == dkModule && decl.childDecl1 != null as *Decl {
|
||||
mod.firstItem = decl.childDecl1;
|
||||
break;
|
||||
}
|
||||
decl = decl.childDecl2;
|
||||
}
|
||||
|
||||
// Phase 3: Semantic analysis
|
||||
PrintLine(" Sema...");
|
||||
let sema: *Sema = Sema_Analyze(mod);
|
||||
if Sema_HasError(sema) {
|
||||
PrintLine("Sema errors:");
|
||||
var i: int = 0;
|
||||
while i < Sema_DiagCount(sema) {
|
||||
Print(" ");
|
||||
PrintLine(sema.diags[i].message);
|
||||
i = i + 1;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
PrintLine(" Sema done");
|
||||
|
||||
// Phase 4: HIR lowering
|
||||
PrintLine(" HIR lowering...");
|
||||
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
|
||||
if hirMod == null as *HirModule {
|
||||
PrintLine("HIR lowering failed");
|
||||
return "";
|
||||
}
|
||||
Print("HIR funcCount=");
|
||||
PrintInt(hirMod.funcCount);
|
||||
|
||||
// Phase 5: C code generation
|
||||
let cCode: String = CBackend_Generate(hirMod);
|
||||
|
||||
// Cleanup
|
||||
Lexer_Free(lex);
|
||||
Sema_Free(sema);
|
||||
|
||||
return cCode;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Build command
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Build(srcPath: String, outPath: String) -> int {
|
||||
let source: String = ReadFile(srcPath);
|
||||
if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
|
||||
Print("Error: cannot read source file: ");
|
||||
PrintLine(srcPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Print("Compiling ");
|
||||
PrintLine(srcPath);
|
||||
|
||||
let cCode: String = Cli_Compile(source, srcPath);
|
||||
|
||||
if String_Eq(cCode, "") {
|
||||
PrintLine("Compilation failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write C output
|
||||
let cFile: String = String_Concat(outPath, ".c");
|
||||
let ok: bool = WriteFile(cFile, cCode);
|
||||
if !ok {
|
||||
PrintLine("Error: cannot write output file");
|
||||
return 1;
|
||||
}
|
||||
Print(" → C written to ");
|
||||
PrintLine(cFile);
|
||||
|
||||
// Find runtime.c and io.c for linking
|
||||
var rtPath: String = "library/runtime/runtime.c";
|
||||
var ioPath: String = "library/runtime/io.c";
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = "../library/runtime/runtime.c";
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = "../library/runtime/io.c";
|
||||
}
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c";
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c";
|
||||
}
|
||||
|
||||
// Compile with cc
|
||||
PrintLine("Compiling C...");
|
||||
var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
|
||||
StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o ");
|
||||
StringBuilder_Append(&cmdBuf, outPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
StringBuilder_Append(&cmdBuf, cFile);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
if FileExists(rtPath) {
|
||||
StringBuilder_Append(&cmdBuf, rtPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
}
|
||||
if FileExists(ioPath) {
|
||||
StringBuilder_Append(&cmdBuf, ioPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
}
|
||||
StringBuilder_Append(&cmdBuf, "-lm");
|
||||
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
|
||||
if ccRc != 0 {
|
||||
PrintLine("Error: C compilation failed");
|
||||
return 1;
|
||||
}
|
||||
Print(" → Binary: ");
|
||||
PrintLine(outPath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Check command (compile only, no output)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Check(srcPath: String) -> int {
|
||||
Print("Check: ");
|
||||
PrintLine(srcPath);
|
||||
let source: String = ReadFile(srcPath);
|
||||
if source == null as String || String_Eq(source, "") {
|
||||
PrintLine("Error: cannot read source file");
|
||||
return 1;
|
||||
}
|
||||
PrintLine(" File read OK");
|
||||
|
||||
// Phase 1: Lex
|
||||
PrintLine(" Lexing...");
|
||||
let lex: *Lexer = Lexer_Tokenize(source);
|
||||
PrintLine(" Lex done");
|
||||
if Lexer_DiagCount(lex) > 0 {
|
||||
PrintLine("Lex errors found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Phase 2: Parse
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
if mod == null as *Module {
|
||||
PrintLine("Parse failed");
|
||||
return 1;
|
||||
}
|
||||
PrintLine(" Parse done");
|
||||
|
||||
// Flatten module wrappers
|
||||
var decl2: *Decl = mod.firstItem;
|
||||
while decl2 != null as *Decl {
|
||||
if decl2.kind == dkModule && decl2.childDecl1 != null as *Decl {
|
||||
mod.firstItem = decl2.childDecl1;
|
||||
break;
|
||||
}
|
||||
decl2 = decl2.childDecl2;
|
||||
}
|
||||
|
||||
// Phase 3: Sema
|
||||
let sema: *Sema = Sema_Analyze(mod);
|
||||
if Sema_HasError(sema) {
|
||||
PrintLine("Sema errors found");
|
||||
var i: int = 0;
|
||||
while i < Sema_DiagCount(sema) {
|
||||
Print(" line ");
|
||||
PrintInt(sema.diags[i].line as int64);
|
||||
Print(": ");
|
||||
PrintLine(sema.diags[i].message);
|
||||
i = i + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Phase 4: HIR lowering
|
||||
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
|
||||
|
||||
var dc: *Decl = mod.firstItem;
|
||||
while dc != null as *Decl {
|
||||
if dc.kind == dkFunc {
|
||||
Print("check func ");
|
||||
PrintLine(dc.strValue);
|
||||
}
|
||||
dc = dc.childDecl2;
|
||||
}
|
||||
PrintLine("Check passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Project build — compile all .bux files in src/ directory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
|
||||
// Phase 1: Lex
|
||||
PrintLine(" Lexing...");
|
||||
let lex: *Lexer = Lexer_Tokenize(source);
|
||||
PrintLine(" Lex done");
|
||||
if Lexer_DiagCount(lex) > 0 {
|
||||
Print("Lex errors in ");
|
||||
PrintLine(sourceName);
|
||||
return null as *HirModule;
|
||||
}
|
||||
|
||||
// Phase 2: Parse
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
if mod == null as *Module {
|
||||
Print("Parse failed for ");
|
||||
PrintLine(sourceName);
|
||||
return null as *HirModule;
|
||||
}
|
||||
|
||||
// Phase 3: Semantic analysis
|
||||
let sema: *Sema = Sema_Analyze(mod);
|
||||
if Sema_HasError(sema) {
|
||||
Print("Sema errors in ");
|
||||
PrintLine(sourceName);
|
||||
return null as *HirModule;
|
||||
}
|
||||
|
||||
// Phase 4: HIR lowering
|
||||
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
|
||||
return hirMod;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stdlib discovery and merging
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_FindStdlibDir(projectDir: String) -> String {
|
||||
var path: String = bux_path_join(projectDir, "stdlib");
|
||||
if DirExists(path) { return path; }
|
||||
path = bux_path_join(projectDir, "../stdlib");
|
||||
if DirExists(path) { return path; }
|
||||
path = "/home/ziko/z-git/bux/bux/stdlib";
|
||||
if DirExists(path) { return path; }
|
||||
return "";
|
||||
}
|
||||
|
||||
func Cli_DeclName(decl: *Decl) -> String {
|
||||
if decl == null as *Decl { return ""; }
|
||||
return decl.strValue;
|
||||
}
|
||||
|
||||
func Cli_NameInList(name: String, names: *String, count: int) -> bool {
|
||||
if String_Eq(name, "") { return false; }
|
||||
var i: int = 0;
|
||||
while i < count {
|
||||
if String_Eq(names[i], name) {
|
||||
return true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int {
|
||||
if mod == null as *Module { return 0; }
|
||||
var count: int = 0;
|
||||
var decl: *Decl = mod.firstItem;
|
||||
while decl != null as *Decl && count < maxCount {
|
||||
let next: *Decl = decl.childDecl2;
|
||||
if decl.kind == dkModule {
|
||||
var inner: *Decl = decl.childDecl1;
|
||||
while inner != null as *Decl && count < maxCount {
|
||||
let iname: String = Cli_DeclName(inner);
|
||||
if !String_Eq(iname, "") {
|
||||
names[count] = iname;
|
||||
count = count + 1;
|
||||
}
|
||||
inner = inner.childDecl2;
|
||||
}
|
||||
} else {
|
||||
let dname: String = Cli_DeclName(decl);
|
||||
if !String_Eq(dname, "") {
|
||||
names[count] = dname;
|
||||
count = count + 1;
|
||||
}
|
||||
}
|
||||
decl = next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Helper: convert single char int to String
|
||||
func String_FromChar(c: int) -> String {
|
||||
var buf: *char8 = bux_alloc(2) as *char8;
|
||||
buf[0] = c as char8;
|
||||
buf[1] = 0 as char8;
|
||||
return buf as String;
|
||||
}
|
||||
|
||||
// Collect stdlib module paths imported by user code.
|
||||
// Returns number of unique stdlib files to merge (paths written into outPaths).
|
||||
func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int {
|
||||
if mod == null as *Module { return 0; }
|
||||
var count: int = 0;
|
||||
var decl: *Decl = mod.firstItem;
|
||||
while decl != null as *Decl && count < maxCount {
|
||||
if decl.kind == dkUse {
|
||||
let path: String = decl.usePath;
|
||||
// Only handle Std::* imports
|
||||
if String_StartsWith(path, "Std::") {
|
||||
// Extract module name after Std:: (e.g., "Std::Io" -> "Io")
|
||||
var j: int = 5; // skip "Std::"
|
||||
var modName: String = "";
|
||||
while j < 100 {
|
||||
let c: int = path[j] as int;
|
||||
if c == 0 || c == 58 { break; } // null or ':'
|
||||
modName = String_Concat(modName, String_FromChar(c));
|
||||
j = j + 1;
|
||||
}
|
||||
if !String_Eq(modName, "") {
|
||||
let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux"));
|
||||
// Check for duplicates
|
||||
var dup: bool = false;
|
||||
var di: int = 0;
|
||||
while di < count {
|
||||
if String_Eq(outPaths[di], filePath) { dup = true; }
|
||||
di = di + 1;
|
||||
}
|
||||
if !dup {
|
||||
outPaths[count] = filePath;
|
||||
count = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decl = decl.childDecl2;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
|
||||
if !FileExists(path) {
|
||||
Print("Error: stdlib file not found: ");
|
||||
PrintLine(path);
|
||||
return 0;
|
||||
}
|
||||
let source: String = ReadFile(path);
|
||||
if source == null as String || String_Eq(source, "") { return 0; }
|
||||
let lex: *Lexer = Lexer_Tokenize(source);
|
||||
if Lexer_DiagCount(lex) > 0 { return 0; }
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
if mod == null as *Module { return 0; }
|
||||
|
||||
var added: int = 0;
|
||||
var decl: *Decl = mod.firstItem;
|
||||
while decl != null as *Decl {
|
||||
let next: *Decl = decl.childDecl2;
|
||||
decl.childDecl2 = null as *Decl;
|
||||
if decl.kind == dkModule {
|
||||
var inner: *Decl = decl.childDecl1;
|
||||
while inner != null as *Decl {
|
||||
let innerNext: *Decl = inner.childDecl2;
|
||||
let iname: String = Cli_DeclName(inner);
|
||||
if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) {
|
||||
// Skip shadowed stdlib decl
|
||||
} else {
|
||||
inner.childDecl2 = target.firstItem;
|
||||
target.firstItem = 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 = target.firstItem;
|
||||
target.firstItem = 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; }
|
||||
var decl: *Decl = source.firstItem;
|
||||
var limit: int = 0;
|
||||
while decl != null as *Decl && limit < 10000 {
|
||||
let next: *Decl = decl.childDecl2;
|
||||
decl.childDecl2 = target.firstItem;
|
||||
target.firstItem = decl;
|
||||
target.itemCount = target.itemCount + 1;
|
||||
decl = next;
|
||||
limit = limit + 1;
|
||||
}
|
||||
source.firstItem = null as *Decl;
|
||||
source.itemCount = 0;
|
||||
}
|
||||
|
||||
func Cli_BuildProject(projectDir: String) -> int {
|
||||
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
|
||||
var outName: String = man.name;
|
||||
if String_Eq(outName, "") {
|
||||
outName = "bux_out";
|
||||
}
|
||||
let srcDir: String = bux_path_join(projectDir, "src");
|
||||
if !DirExists(srcDir) {
|
||||
Print("Error: no src/ directory in ");
|
||||
PrintLine(projectDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Print("Scanning ");
|
||||
PrintLine(srcDir);
|
||||
|
||||
// List all .bux files in src/
|
||||
var fileCount: int = 0;
|
||||
let files: *String = bux_list_dir(srcDir, ".bux", &fileCount);
|
||||
if fileCount == 0 {
|
||||
PrintLine("Error: no .bux files found in src/");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Print("Found ");
|
||||
PrintInt(fileCount);
|
||||
PrintLine(" source files");
|
||||
|
||||
// Create user merged module (collect user decls first)
|
||||
let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module;
|
||||
userMerged.name = "main";
|
||||
userMerged.path = "";
|
||||
userMerged.itemCount = 0;
|
||||
userMerged.firstItem = null as *Decl;
|
||||
|
||||
// Parse each file and merge declarations into userMerged module
|
||||
var i: int = 0;
|
||||
while i < fileCount {
|
||||
let path: String = files[i];
|
||||
Print(" Parsing ");
|
||||
PrintLine(path);
|
||||
let source: String = ReadFile(path);
|
||||
if String_Eq(source, "") {
|
||||
Print(" Error: cannot read ");
|
||||
PrintLine(path);
|
||||
return 1;
|
||||
}
|
||||
let lex: *Lexer = Lexer_Tokenize(source);
|
||||
if Lexer_DiagCount(lex) > 0 {
|
||||
Print(" Lex errors in ");
|
||||
PrintLine(path);
|
||||
return 1;
|
||||
}
|
||||
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
||||
if mod == null as *Module {
|
||||
Print(" Parse failed for ");
|
||||
PrintLine(path);
|
||||
return 1;
|
||||
}
|
||||
// Merge declarations from this module into userMerged
|
||||
var decl: *Decl = mod.firstItem;
|
||||
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;
|
||||
while inner != null as *Decl {
|
||||
let innerNext: *Decl = inner.childDecl2;
|
||||
inner.childDecl2 = userMerged.firstItem;
|
||||
userMerged.firstItem = inner;
|
||||
userMerged.itemCount = userMerged.itemCount + 1;
|
||||
inner = innerNext;
|
||||
}
|
||||
} else {
|
||||
decl.childDecl2 = userMerged.firstItem;
|
||||
userMerged.firstItem = decl;
|
||||
userMerged.itemCount = userMerged.itemCount + 1;
|
||||
}
|
||||
decl = next;
|
||||
}
|
||||
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 stdlib declarations (only imported modules)
|
||||
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
|
||||
if !String_Eq(stdlibDir, "") {
|
||||
Print("Stdlib found: ");
|
||||
PrintLine(stdlibDir);
|
||||
// Collect imported stdlib modules from user code
|
||||
let maxImports: int = 64;
|
||||
let importPaths: *String = bux_alloc(maxImports * 8) as *String;
|
||||
let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir);
|
||||
Print("Imported stdlib modules: ");
|
||||
PrintInt(importCount);
|
||||
PrintLine("");
|
||||
if importCount > 0 {
|
||||
var si: int = 0;
|
||||
var stdAdded: int = 0;
|
||||
while si < importCount {
|
||||
Print(" Merging ");
|
||||
PrintLine(importPaths[si]);
|
||||
let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount);
|
||||
stdAdded = stdAdded + added;
|
||||
si = si + 1;
|
||||
}
|
||||
Print("Stdlib declarations added: ");
|
||||
PrintInt(stdAdded);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy user declarations into merged (user shadows stdlib)
|
||||
Cli_CopyModuleDecls(merged, userMerged);
|
||||
|
||||
Print("Merged ");
|
||||
PrintInt(merged.itemCount);
|
||||
PrintLine(" declarations");
|
||||
|
||||
// Semantic analysis
|
||||
PrintLine("Running sema...");
|
||||
let sema: *Sema = Sema_Analyze(merged);
|
||||
if Sema_HasError(sema) {
|
||||
PrintLine("Sema errors found");
|
||||
var i: int = 0;
|
||||
while i < Sema_DiagCount(sema) {
|
||||
Print(" line ");
|
||||
PrintInt(sema.diags[i].line as int64);
|
||||
Print(": ");
|
||||
PrintLine(sema.diags[i].message);
|
||||
i = i + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// HIR lowering
|
||||
PrintLine("Lowering to HIR...");
|
||||
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
|
||||
|
||||
// C code generation
|
||||
PrintLine("Generating C code...");
|
||||
let cCode: String = CBackend_Generate(hirMod);
|
||||
|
||||
// Create build directory
|
||||
let buildDir: String = bux_path_join(projectDir, "build");
|
||||
discard bux_mkdir_if_needed(buildDir);
|
||||
|
||||
// Write C output
|
||||
let cFile: String = bux_path_join(buildDir, "main.c");
|
||||
if !WriteFile(cFile, cCode) {
|
||||
PrintLine("Error: cannot write main.c");
|
||||
return 1;
|
||||
}
|
||||
Print("C code written to ");
|
||||
PrintLine(cFile);
|
||||
|
||||
// Find runtime.c and io.c (use stdlibDir if available)
|
||||
var rtPath: String = bux_path_join(stdlibDir, "runtime.c");
|
||||
var ioPath: String = bux_path_join(stdlibDir, "io.c");
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = bux_path_join(projectDir, "library/runtime/runtime.c");
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = bux_path_join(projectDir, "library/runtime/io.c");
|
||||
}
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c");
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = bux_path_join(projectDir, "../library/runtime/io.c");
|
||||
}
|
||||
|
||||
// Compile with cc
|
||||
PrintLine("Compiling C...");
|
||||
let outBin: String = bux_path_join(buildDir, outName);
|
||||
var ccBuf: StringBuilder = StringBuilder_NewCap(512);
|
||||
StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o ");
|
||||
StringBuilder_Append(&ccBuf, outBin);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
StringBuilder_Append(&ccBuf, cFile);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
if FileExists(rtPath) {
|
||||
StringBuilder_Append(&ccBuf, rtPath);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
}
|
||||
if FileExists(ioPath) {
|
||||
StringBuilder_Append(&ccBuf, ioPath);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
}
|
||||
StringBuilder_Append(&ccBuf, "-lm");
|
||||
let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
|
||||
if ccRc != 0 {
|
||||
PrintLine("Error: C compilation failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Print("Build successful: ");
|
||||
PrintLine(outBin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run command — build project and execute
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_RunProject(projectDir: String) -> int {
|
||||
let rc: int = Cli_BuildProject(projectDir);
|
||||
if rc != 0 {
|
||||
return rc;
|
||||
}
|
||||
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
|
||||
var outName: String = man.name;
|
||||
if String_Eq(outName, "") {
|
||||
outName = "bux_out";
|
||||
}
|
||||
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
|
||||
Print("Running: ");
|
||||
PrintLine(outBin);
|
||||
return bux_system(outBin);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main entry — dispatch based on args
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Run(args: *String, argCount: int) -> int {
|
||||
if argCount < 2 {
|
||||
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
||||
PrintLine("Usage: buxc <command> [args]");
|
||||
PrintLine("Commands: build, check, run, project, help, version");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let cmd: String = args[1];
|
||||
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
|
||||
PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
|
||||
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
||||
PrintLine("Usage: buxc <command> [args]");
|
||||
PrintLine("Commands: build, check, run, project, help, version");
|
||||
PrintLine("Pipeline modules:");
|
||||
PrintLine(" Lexer ✅ 695 lines");
|
||||
PrintLine(" Parser ✅ 1004 lines");
|
||||
PrintLine(" Sema ✅ 393 lines");
|
||||
PrintLine(" HirLower ✅ 307 lines");
|
||||
PrintLine(" CBackend ✅ 585 lines");
|
||||
PrintLine(" Total: 3830 lines of Bux");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "check") {
|
||||
if argCount < 3 {
|
||||
PrintLine("Usage: buxc check <file.bux>");
|
||||
return 1;
|
||||
}
|
||||
return Cli_Check(args[2]);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "build") {
|
||||
let src: String = "src/Main.bux";
|
||||
let out: String = "build/main";
|
||||
if argCount >= 3 { src = args[2]; }
|
||||
if argCount >= 4 { out = args[3]; }
|
||||
return Cli_Build(src, out);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "project") {
|
||||
let dir: String = ".";
|
||||
if argCount >= 3 { dir = args[2]; }
|
||||
return Cli_BuildProject(dir);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "run") {
|
||||
let dir: String = ".";
|
||||
if argCount >= 3 { dir = args[2]; }
|
||||
return Cli_RunProject(dir);
|
||||
}
|
||||
|
||||
Print("Unknown command: ");
|
||||
PrintLine(cmd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user