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:
@@ -6,6 +6,12 @@ extern func PrintLine(s: String);
|
|||||||
extern func Print(s: String);
|
extern func Print(s: String);
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_write_file(path: String, content: String) -> bool;
|
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 {
|
func ReadFile(path: String) -> String {
|
||||||
return bux_read_file(path);
|
return bux_read_file(path);
|
||||||
@@ -15,6 +21,14 @@ func WriteFile(path: String, content: String) -> bool {
|
|||||||
return bux_write_file(path, content);
|
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
|
// Import the compiler pipeline
|
||||||
// In self-hosting mode, these are compiled together from src_bux/
|
// In self-hosting mode, these are compiled together from src_bux/
|
||||||
|
|
||||||
@@ -127,6 +141,171 @@ func Cli_Check(srcPath: String) -> int {
|
|||||||
return 0;
|
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
|
// Main entry — dispatch based on args
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -169,6 +348,12 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
return Cli_Build(src, out);
|
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: ");
|
Print("Unknown command: ");
|
||||||
PrintLine(cmd);
|
PrintLine(cmd);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func lexIsAtEnd(lex: *Lexer) -> bool {
|
|||||||
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
|
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
|
||||||
let i: int = lex.pos + ahead;
|
let i: int = lex.pos + ahead;
|
||||||
if i < lex.sourceLen {
|
if i < lex.sourceLen {
|
||||||
return lex.source[i] as uint32;
|
return (lex.source[i] as uint32) & 255;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -447,8 +447,15 @@ func parserParseStmt(p: *Parser) -> *Stmt {
|
|||||||
var elseBlock: *Block = null as *Block;
|
var elseBlock: *Block = null as *Block;
|
||||||
if parserMatch(p, tkElse) {
|
if parserMatch(p, tkElse) {
|
||||||
if parserCheck(p, tkIf) {
|
if parserCheck(p, tkIf) {
|
||||||
// else if → recursively parse an if stmt inside else
|
// else if → parse the if statement, wrap in a synthetic block
|
||||||
elseBlock = parserParseBlock(p);
|
let innerIf: *Stmt = parserParseStmt(p);
|
||||||
|
elseBlock = bux_alloc(sizeof(Block)) as *Block;
|
||||||
|
elseBlock.line = innerIf.line;
|
||||||
|
elseBlock.column = innerIf.column;
|
||||||
|
elseBlock.stmtCount = 1;
|
||||||
|
elseBlock.firstStmt = innerIf;
|
||||||
|
elseBlock.lastStmt = innerIf;
|
||||||
|
innerIf.nextStmt = null as *Stmt;
|
||||||
} else {
|
} else {
|
||||||
elseBlock = parserParseBlock(p);
|
elseBlock = parserParseBlock(p);
|
||||||
}
|
}
|
||||||
@@ -739,10 +746,17 @@ func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|||||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
if d.fieldCount >= 8 { break; }
|
if d.fieldCount >= 8 { break; }
|
||||||
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
||||||
|
if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
|
||||||
|
let beforePos: int = p.pos;
|
||||||
let fName: LexToken = parserExpect(p, tkIdent, "expected field name");
|
let fName: LexToken = parserExpect(p, tkIdent, "expected field name");
|
||||||
discard parserExpect(p, tkColon, "expected ':' in struct field");
|
discard parserExpect(p, tkColon, "expected ':' in struct field");
|
||||||
let fType: *TypeExpr = parserParseType(p);
|
let fType: *TypeExpr = parserParseType(p);
|
||||||
parserMatch(p, tkSemicolon);
|
parserMatch(p, tkSemicolon);
|
||||||
|
// Infinite-loop safeguard
|
||||||
|
if p.pos == beforePos {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if d.fieldCount == 0 {
|
if d.fieldCount == 0 {
|
||||||
d.field0.name = fName.text;
|
d.field0.name = fName.text;
|
||||||
@@ -939,10 +953,46 @@ func parserParseDecl(p: *Parser) -> *Decl {
|
|||||||
if kind == tkModule {
|
if kind == tkModule {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
let name: LexToken = parserExpect(p, tkIdent, "expected module name");
|
let name: LexToken = parserExpect(p, tkIdent, "expected module name");
|
||||||
parserMatch(p, tkSemicolon);
|
// Parse optional path segments (::Name)
|
||||||
|
while parserCheck(p, tkColonColon) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
discard parserExpect(p, tkIdent, "expected module path segment");
|
||||||
|
}
|
||||||
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
||||||
d.kind = dkModule;
|
d.kind = dkModule;
|
||||||
d.strValue = name.text;
|
d.strValue = name.text;
|
||||||
|
// Parse module body with braces
|
||||||
|
if parserCheck(p, tkLBrace) {
|
||||||
|
discard parserAdvance(p); // consume {
|
||||||
|
var items: *Decl = null as *Decl;
|
||||||
|
var lastItem: *Decl = null as *Decl;
|
||||||
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
|
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let beforePos: int = p.pos;
|
||||||
|
let item: *Decl = parserParseDecl(p);
|
||||||
|
if item != null as *Decl {
|
||||||
|
item.childDecl2 = null as *Decl;
|
||||||
|
if items == null as *Decl {
|
||||||
|
items = item;
|
||||||
|
lastItem = item;
|
||||||
|
} else {
|
||||||
|
lastItem.childDecl2 = item;
|
||||||
|
lastItem = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Infinite-loop safeguard: if no progress, skip token
|
||||||
|
if p.pos == beforePos {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parserMatch(p, tkRBrace); // consume }
|
||||||
|
d.childDecl1 = items; // first item of module
|
||||||
|
} else {
|
||||||
|
parserMatch(p, tkSemicolon);
|
||||||
|
}
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,4 +1063,39 @@ func Parser_Free(p: *Parser) {
|
|||||||
bux_free(p.diags as *void);
|
bux_free(p.diags as *void);
|
||||||
bux_free(p as *void);
|
bux_free(p as *void);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Module merging — merge multiple parsed modules into one
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Add a single declaration to the merged module (push front to linked list)
|
||||||
|
func parserMergeAddDecl(mod: *Module, decl: *Decl) {
|
||||||
|
if decl == null as *Decl { return; }
|
||||||
|
// Flatten module declarations: extract inner items
|
||||||
|
if decl.kind == dkModule {
|
||||||
|
var inner: *Decl = decl.childDecl1;
|
||||||
|
while inner != null as *Decl {
|
||||||
|
let next: *Decl = inner.childDecl2;
|
||||||
|
inner.childDecl2 = mod.firstItem;
|
||||||
|
mod.firstItem = inner;
|
||||||
|
mod.itemCount = mod.itemCount + 1;
|
||||||
|
inner = next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decl.childDecl2 = mod.firstItem;
|
||||||
|
mod.firstItem = decl;
|
||||||
|
mod.itemCount = mod.itemCount + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parser_MergeModules(modules: *void, count: int) -> *Module {
|
||||||
|
// This function is kept for future use but not called by Cli_BuildProject
|
||||||
|
// which does inline merging for simplicity
|
||||||
|
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
|
||||||
|
merged.name = "main";
|
||||||
|
merged.path = "";
|
||||||
|
merged.itemCount = 0;
|
||||||
|
merged.firstItem = null as *Decl;
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+185
@@ -6,6 +6,12 @@ extern func PrintLine(s: String);
|
|||||||
extern func Print(s: String);
|
extern func Print(s: String);
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_write_file(path: String, content: String) -> bool;
|
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 {
|
func ReadFile(path: String) -> String {
|
||||||
return bux_read_file(path);
|
return bux_read_file(path);
|
||||||
@@ -15,6 +21,14 @@ func WriteFile(path: String, content: String) -> bool {
|
|||||||
return bux_write_file(path, content);
|
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
|
// Import the compiler pipeline
|
||||||
// In self-hosting mode, these are compiled together from src_bux/
|
// In self-hosting mode, these are compiled together from src_bux/
|
||||||
|
|
||||||
@@ -127,6 +141,171 @@ func Cli_Check(srcPath: String) -> int {
|
|||||||
return 0;
|
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
|
// Main entry — dispatch based on args
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -169,6 +348,12 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
return Cli_Build(src, out);
|
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: ");
|
Print("Unknown command: ");
|
||||||
PrintLine(cmd);
|
PrintLine(cmd);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ func lexIsAtEnd(lex: *Lexer) -> bool {
|
|||||||
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
|
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
|
||||||
let i: int = lex.pos + ahead;
|
let i: int = lex.pos + ahead;
|
||||||
if i < lex.sourceLen {
|
if i < lex.sourceLen {
|
||||||
return lex.source[i] as uint32;
|
return (lex.source[i] as uint32) & 255;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-3
@@ -447,8 +447,15 @@ func parserParseStmt(p: *Parser) -> *Stmt {
|
|||||||
var elseBlock: *Block = null as *Block;
|
var elseBlock: *Block = null as *Block;
|
||||||
if parserMatch(p, tkElse) {
|
if parserMatch(p, tkElse) {
|
||||||
if parserCheck(p, tkIf) {
|
if parserCheck(p, tkIf) {
|
||||||
// else if → recursively parse an if stmt inside else
|
// else if → parse the if statement, wrap in a synthetic block
|
||||||
elseBlock = parserParseBlock(p);
|
let innerIf: *Stmt = parserParseStmt(p);
|
||||||
|
elseBlock = bux_alloc(sizeof(Block)) as *Block;
|
||||||
|
elseBlock.line = innerIf.line;
|
||||||
|
elseBlock.column = innerIf.column;
|
||||||
|
elseBlock.stmtCount = 1;
|
||||||
|
elseBlock.firstStmt = innerIf;
|
||||||
|
elseBlock.lastStmt = innerIf;
|
||||||
|
innerIf.nextStmt = null as *Stmt;
|
||||||
} else {
|
} else {
|
||||||
elseBlock = parserParseBlock(p);
|
elseBlock = parserParseBlock(p);
|
||||||
}
|
}
|
||||||
@@ -739,10 +746,17 @@ func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|||||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
if d.fieldCount >= 8 { break; }
|
if d.fieldCount >= 8 { break; }
|
||||||
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
||||||
|
if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
|
||||||
|
let beforePos: int = p.pos;
|
||||||
let fName: LexToken = parserExpect(p, tkIdent, "expected field name");
|
let fName: LexToken = parserExpect(p, tkIdent, "expected field name");
|
||||||
discard parserExpect(p, tkColon, "expected ':' in struct field");
|
discard parserExpect(p, tkColon, "expected ':' in struct field");
|
||||||
let fType: *TypeExpr = parserParseType(p);
|
let fType: *TypeExpr = parserParseType(p);
|
||||||
parserMatch(p, tkSemicolon);
|
parserMatch(p, tkSemicolon);
|
||||||
|
// Infinite-loop safeguard
|
||||||
|
if p.pos == beforePos {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if d.fieldCount == 0 {
|
if d.fieldCount == 0 {
|
||||||
d.field0.name = fName.text;
|
d.field0.name = fName.text;
|
||||||
@@ -939,10 +953,46 @@ func parserParseDecl(p: *Parser) -> *Decl {
|
|||||||
if kind == tkModule {
|
if kind == tkModule {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
let name: LexToken = parserExpect(p, tkIdent, "expected module name");
|
let name: LexToken = parserExpect(p, tkIdent, "expected module name");
|
||||||
parserMatch(p, tkSemicolon);
|
// Parse optional path segments (::Name)
|
||||||
|
while parserCheck(p, tkColonColon) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
discard parserExpect(p, tkIdent, "expected module path segment");
|
||||||
|
}
|
||||||
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
||||||
d.kind = dkModule;
|
d.kind = dkModule;
|
||||||
d.strValue = name.text;
|
d.strValue = name.text;
|
||||||
|
// Parse module body with braces
|
||||||
|
if parserCheck(p, tkLBrace) {
|
||||||
|
discard parserAdvance(p); // consume {
|
||||||
|
var items: *Decl = null as *Decl;
|
||||||
|
var lastItem: *Decl = null as *Decl;
|
||||||
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
|
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let beforePos: int = p.pos;
|
||||||
|
let item: *Decl = parserParseDecl(p);
|
||||||
|
if item != null as *Decl {
|
||||||
|
item.childDecl2 = null as *Decl;
|
||||||
|
if items == null as *Decl {
|
||||||
|
items = item;
|
||||||
|
lastItem = item;
|
||||||
|
} else {
|
||||||
|
lastItem.childDecl2 = item;
|
||||||
|
lastItem = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Infinite-loop safeguard: if no progress, skip token
|
||||||
|
if p.pos == beforePos {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parserMatch(p, tkRBrace); // consume }
|
||||||
|
d.childDecl1 = items; // first item of module
|
||||||
|
} else {
|
||||||
|
parserMatch(p, tkSemicolon);
|
||||||
|
}
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,4 +1063,39 @@ func Parser_Free(p: *Parser) {
|
|||||||
bux_free(p.diags as *void);
|
bux_free(p.diags as *void);
|
||||||
bux_free(p as *void);
|
bux_free(p as *void);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Module merging — merge multiple parsed modules into one
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Add a single declaration to the merged module (push front to linked list)
|
||||||
|
func parserMergeAddDecl(mod: *Module, decl: *Decl) {
|
||||||
|
if decl == null as *Decl { return; }
|
||||||
|
// Flatten module declarations: extract inner items
|
||||||
|
if decl.kind == dkModule {
|
||||||
|
var inner: *Decl = decl.childDecl1;
|
||||||
|
while inner != null as *Decl {
|
||||||
|
let next: *Decl = inner.childDecl2;
|
||||||
|
inner.childDecl2 = mod.firstItem;
|
||||||
|
mod.firstItem = inner;
|
||||||
|
mod.itemCount = mod.itemCount + 1;
|
||||||
|
inner = next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decl.childDecl2 = mod.firstItem;
|
||||||
|
mod.firstItem = decl;
|
||||||
|
mod.itemCount = mod.itemCount + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parser_MergeModules(modules: *void, count: int) -> *Module {
|
||||||
|
// This function is kept for future use but not called by Cli_BuildProject
|
||||||
|
// which does inline merging for simplicity
|
||||||
|
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
|
||||||
|
merged.name = "main";
|
||||||
|
merged.path = "";
|
||||||
|
merged.itemCount = 0;
|
||||||
|
merged.firstItem = null as *Decl;
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -569,3 +569,82 @@ unsigned int bux_hash_string(const char* s) {
|
|||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Directory listing and build tools (for self-hosting compiler)
|
||||||
|
* --------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
/* bux_list_dir: returns array of file paths in dir matching ext suffix.
|
||||||
|
* Result is malloc'd array of malloc'd strings. Caller must free.
|
||||||
|
* Sets *out_count to number of files found. */
|
||||||
|
char** bux_list_dir(const char* dir, const char* ext, int* out_count) {
|
||||||
|
DIR* d = opendir(dir);
|
||||||
|
if (!d) { *out_count = 0; return NULL; }
|
||||||
|
|
||||||
|
/* First pass: count matching files */
|
||||||
|
int count = 0;
|
||||||
|
size_t ext_len = strlen(ext);
|
||||||
|
struct dirent* entry;
|
||||||
|
while ((entry = readdir(d)) != NULL) {
|
||||||
|
size_t name_len = strlen(entry->d_name);
|
||||||
|
if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate result array */
|
||||||
|
char** result = (char**)malloc(count * sizeof(char*));
|
||||||
|
if (!result) { closedir(d); *out_count = 0; return NULL; }
|
||||||
|
|
||||||
|
/* Second pass: collect paths */
|
||||||
|
rewinddir(d);
|
||||||
|
int idx = 0;
|
||||||
|
size_t dir_len = strlen(dir);
|
||||||
|
while ((entry = readdir(d)) != NULL && idx < count) {
|
||||||
|
size_t name_len = strlen(entry->d_name);
|
||||||
|
if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) {
|
||||||
|
/* Build full path: dir/name */
|
||||||
|
size_t path_len = dir_len + 1 + name_len + 1;
|
||||||
|
char* path = (char*)malloc(path_len);
|
||||||
|
if (path) {
|
||||||
|
snprintf(path, path_len, "%s/%s", dir, entry->d_name);
|
||||||
|
result[idx++] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
*out_count = idx;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bux_mkdir_if_needed: create directory if it doesn't exist */
|
||||||
|
int bux_mkdir_if_needed(const char* path) {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return 0;
|
||||||
|
return mkdir(path, 0755);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bux_run_cc: invoke C compiler to compile c_file into out_bin,
|
||||||
|
* linking runtime.c and io.c. Returns exit code. */
|
||||||
|
int bux_run_cc(const char* c_file, const char* out_bin,
|
||||||
|
const char* runtime_c, const char* io_c,
|
||||||
|
const char* math_lib) {
|
||||||
|
char cmd[4096];
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"cc %s %s %s -o %s %s 2>&1",
|
||||||
|
c_file,
|
||||||
|
runtime_c ? runtime_c : "",
|
||||||
|
io_c ? io_c : "",
|
||||||
|
out_bin,
|
||||||
|
math_lib ? "-lm" : "");
|
||||||
|
return system(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bux_dir_exists: check if directory exists */
|
||||||
|
int bux_dir_exists(const char* path) {
|
||||||
|
struct stat st;
|
||||||
|
return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user