Phase 7.10: multi-file project build, module braces, else-if fix, UTF-8 lexer
Runtime (stdlib/runtime.c):
- bux_list_dir() — directory listing with extension filter
- bux_run_cc() — invoke C compiler on generated C code
- bux_mkdir_if_needed() — create build directory
- bux_dir_exists() — check if directory exists
- Removed duplicate bux_path_join
Parser (src_bux/parser.bux):
- Module parsing with braces: module X { ... }
- else-if parsing: wraps inner if in synthetic Block
- Infinite-loop safeguards in module body and struct body parsing
Lexer (src_bux/lexer.bux):
- lexPeek: mask with & 255 to handle UTF-8 signed char bytes
CLI (src_bux/cli.bux):
- Cli_BuildProject(): multi-file build from src/ directory
- Lists .bux files, parses each, merges declarations inline
- Runs sema + HIR + C codegen on merged module
- Invokes C compiler to produce binary
- New extern declarations: bux_file_exists, bux_dir_exists, bux_path_join,
bux_mkdir_if_needed, bux_run_cc, bux_list_dir
- FileExists(), DirExists() wrapper functions
- "project" command dispatch
Known issue: else-if chain not fully emitted by buxc2 C backend
(lexSkipWhitespace missing comment handling in generated C).
All 18 examples pass, all unit tests pass.
This commit is contained in:
+185
@@ -6,6 +6,12 @@ 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);
|
||||
@@ -15,6 +21,14 @@ 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/
|
||||
|
||||
@@ -127,6 +141,171 @@ func Cli_Check(srcPath: String) -> int {
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -169,6 +348,12 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user