21f8b2e85a
Self-hosted compiler (buxc2):
- Add async/await/spawn tokens, parsing, HIR, lowering, C emission
- Fix pointer type emission (*T -> T*) in C backend
- Fix sizeof(Type) parsing with parentheses
- Fix import ::{...} infinite loop guard
- Fix int64 emission by avoiding else-if chain workaround
- Add debug-free cli and hir_lower
Bootstrap compiler (Nim):
- Fix critical else-if lowering bug in hir_lower.nim:
when both elseIfs and else block exist, elseIfs were dropped
causing all else-if chains to collapse to if+else only
Runtime:
- Fix bux_async_await memory corruption (don't free in run)
- Add bux_remove_from_ready for safe cleanup
- Fix forward declaration and deduplicate bux_now_ms
Docs & examples:
- Update async.bux example with proper int64 params
- Update README, LanguageRef, Stdlib, PLAN for async features
- Mark Phase 7.10 bootstrap loop as completed
All 27 examples pass. buxc2 check/build work on all examples.
365 lines
11 KiB
Plaintext
365 lines
11 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_path_join(a: String, b: String) -> String;
|
|
extern func bux_mkdir_if_needed(path: String) -> int;
|
|
extern func bux_run_cc(c_file: String, out_bin: String, runtime_c: String, io_c: String, math_lib: String) -> int;
|
|
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *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;
|
|
}
|
|
|
|
// Import the compiler pipeline
|
|
// In self-hosting mode, these are compiled together from src_bux/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Compile a single .bux source file
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Cli_Compile(source: String, sourceName: String) -> String {
|
|
// Phase 1: Lex
|
|
let lex: *Lexer = Lexer_Tokenize(source);
|
|
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
|
|
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
|
|
if mod == null as *Module {
|
|
PrintLine("Parse failed");
|
|
return "";
|
|
}
|
|
|
|
// Phase 3: Semantic analysis
|
|
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 "";
|
|
}
|
|
|
|
// Phase 4: 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);
|
|
PrintLine("");
|
|
|
|
// 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 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 ok: bool = WriteFile(outPath, cCode);
|
|
if ok {
|
|
Print(" → C code written to ");
|
|
PrintLine(outPath);
|
|
return 0;
|
|
} else {
|
|
PrintLine("Error: cannot write output file");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Check command (compile only, no output)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Cli_Check(srcPath: String) -> int {
|
|
let source: String = ReadFile(srcPath);
|
|
if String_Eq(source, "") {
|
|
PrintLine("Error: cannot read source file");
|
|
return 1;
|
|
}
|
|
|
|
let cCode: String = Cli_Compile(source, srcPath);
|
|
if String_Eq(cCode, "") {
|
|
return 1;
|
|
}
|
|
|
|
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
|
|
let lex: *Lexer = Lexer_Tokenize(source);
|
|
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;
|
|
}
|
|
|
|
func Cli_BuildProject(projectDir: String) -> int {
|
|
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 merged module
|
|
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
|
|
merged.name = "main";
|
|
merged.path = "";
|
|
merged.itemCount = 0;
|
|
merged.firstItem = null as *Decl;
|
|
|
|
// Parse each file and merge declarations into merged 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 merged
|
|
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 = merged.firstItem;
|
|
merged.firstItem = inner;
|
|
merged.itemCount = merged.itemCount + 1;
|
|
inner = innerNext;
|
|
}
|
|
} else {
|
|
decl.childDecl2 = merged.firstItem;
|
|
merged.firstItem = decl;
|
|
merged.itemCount = merged.itemCount + 1;
|
|
}
|
|
decl = next;
|
|
}
|
|
i = i + 1;
|
|
}
|
|
|
|
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");
|
|
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 stdlib files
|
|
let runtimeC: String = bux_path_join(bux_path_join(projectDir, ".."), "stdlib/runtime.c");
|
|
let ioC: String = bux_path_join(bux_path_join(projectDir, ".."), "stdlib/io.c");
|
|
|
|
// Invoke C compiler
|
|
PrintLine("Invoking C compiler...");
|
|
let outBin: String = bux_path_join(buildDir, "buxc3");
|
|
let rc: int = bux_run_cc(cFile, outBin, runtimeC, ioC, "-lm");
|
|
if rc != 0 {
|
|
PrintLine("Error: C compilation failed");
|
|
return 1;
|
|
}
|
|
|
|
Print("Build successful: ");
|
|
PrintLine(outBin);
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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, version");
|
|
PrintLine("");
|
|
PrintLine("Pipeline modules:");
|
|
PrintLine(" Lexer ✅ 695 lines");
|
|
PrintLine(" Parser ✅ 1004 lines");
|
|
PrintLine(" Sema ✅ 393 lines");
|
|
PrintLine(" HirLower ✅ 307 lines");
|
|
PrintLine(" CBackend ✅ 264 lines");
|
|
PrintLine(" Total: 3830 lines of Bux");
|
|
return 0;
|
|
}
|
|
|
|
let cmd: String = args[1];
|
|
if String_Eq(cmd, "version") {
|
|
PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
|
|
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.c";
|
|
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);
|
|
}
|
|
|
|
Print("Unknown command: ");
|
|
PrintLine(cmd);
|
|
return 1;
|
|
}
|
|
}
|