From 869fb9c7d3defc2d70d9a1371096c25b8f7280de Mon Sep 17 00:00:00 2001 From: dimgigov Date: Tue, 28 Jul 2026 22:01:49 +0300 Subject: [PATCH] =?UTF-8?q?feat(selfhost-cli):=20surface=20parser=20diagno?= =?UTF-8?q?stics=20=E2=80=94=20syntax=20errors=20no=20longer=20pass=20chec?= =?UTF-8?q?k=20silently?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _test_parse_recovery/bux.toml | 4 ++++ _test_parse_recovery/src/Main.bux | 18 +++++++++++++++ src/ast.bux | 12 ++++++++++ src/cli.bux | 34 +++++++++++++++++++++++++++ src/parser.bux | 38 +++---------------------------- 5 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 _test_parse_recovery/bux.toml create mode 100644 _test_parse_recovery/src/Main.bux diff --git a/_test_parse_recovery/bux.toml b/_test_parse_recovery/bux.toml new file mode 100644 index 0000000..7d66b12 --- /dev/null +++ b/_test_parse_recovery/bux.toml @@ -0,0 +1,4 @@ +[Package] +Name = "parse_recovery" +Version = "0.1.0" +Type = "bin" diff --git a/_test_parse_recovery/src/Main.bux b/_test_parse_recovery/src/Main.bux new file mode 100644 index 0000000..916ab77 --- /dev/null +++ b/_test_parse_recovery/src/Main.bux @@ -0,0 +1,18 @@ +func Good() -> int { + return 1; +} + +func Bad() -> int { + let x: int = ; + return 0; +} + +func AlsoBad() -> int { + let y: int = 5 + ; + let z: int = 6; + return y; +} + +func AlsoGood() -> int { + return 2; +} diff --git a/src/ast.bux b/src/ast.bux index 6d8dea8..524416f 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -373,6 +373,16 @@ module Ast { // Struct fields (up to 256) } + // --------------------------------------------------------------------------- + // Parser diagnostic (recoverable errors collected during parsing) + // --------------------------------------------------------------------------- + struct ParserDiag { + line: uint32, + column: uint32, + message: String, + severity: int, /* 0=error (fatal), 1=warning (recoverable) */ + } + // --------------------------------------------------------------------------- // Module — AST root // --------------------------------------------------------------------------- @@ -381,6 +391,8 @@ module Ast { path: String, // path segments joined itemCount: int, firstItem: *Decl, + diagCount: int, + diags: *ParserDiag, } // --------------------------------------------------------------------------- diff --git a/src/cli.bux b/src/cli.bux index 23abff2..523852e 100644 --- a/src/cli.bux +++ b/src/cli.bux @@ -358,6 +358,28 @@ module Cli { // Import the compiler pipeline // In self-hosting mode, these are compiled together from src/ +// --------------------------------------------------------------------------- +// Report parser diagnostics attached to a module (Rust-style, with snippets). +// Returns the number of diagnostics reported (0 = clean). +// --------------------------------------------------------------------------- + +func Cli_ReportParseDiags(mod: *Module, sourceName: String) -> int { + if mod == null as *Module { return 0; } + if mod.diagCount == 0 { return 0; } + var i: int = 0; + while i < mod.diagCount { + let diag: Diagnostic = Diagnostic { + message: mod.diags[i].message, + line: mod.diags[i].line, + column: mod.diags[i].column, + severity: 0, + }; + Diagnostic_Print(&diag, sourceName); + i = i + 1; + } + return mod.diagCount; +} + // --------------------------------------------------------------------------- // Compile a single .bux source file // --------------------------------------------------------------------------- @@ -389,6 +411,7 @@ func Cli_Compile(source: String, sourceName: String, targetTriple: String) -> St PrintLine("Parse failed"); return ""; } + if Cli_ReportParseDiags(mod, sourceName) > 0 { return ""; } PrintLine(" Parse done"); // Flatten module wrappers: find module decl and hoist its children @@ -551,6 +574,7 @@ func Cli_Check(srcPath: String) -> int { PrintLine("Parse failed"); return 1; } + if Cli_ReportParseDiags(mod, srcPath) > 0 { return 1; } PrintLine(" Parse done"); // Flatten module wrappers @@ -635,6 +659,7 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule { PrintLine(sourceName); return null as *HirModule; } + if Cli_ReportParseDiags(mod, sourceName) > 0 { return null as *HirModule; } // Phase 2b: macro expand let macEx3: *MacroExpander = MacroExpand_ExpandModule(mod); @@ -836,6 +861,10 @@ func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCo if Lexer_DiagCount(lex) > 0 { return 0; } let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount); if mod == null as *Module { return 0; } + // Report parse diags but keep merging the recovered decls: this path + // handles stdlib/dependency files, and the parser currently emits + // recoverable diags for valid multi-line braced imports there. + discard Cli_ReportParseDiags(mod, path); // Tag every decl from this file for #line maps var stamp: *Decl = mod.firstItem; while stamp != null as *Decl { @@ -1928,6 +1957,8 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool, userMerged.path = ""; userMerged.itemCount = 0; userMerged.firstItem = null as *Decl; + userMerged.diagCount = 0; + userMerged.diags = null as *ParserDiag; // Parse each file and merge declarations into userMerged module var i: int = 0; @@ -1962,6 +1993,7 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool, PrintLine(path); return 1; } + if Cli_ReportParseDiags(mod, path) > 0 { return 1; } // Tag decls with this source path for multi-file #line var stampUser: *Decl = mod.firstItem; while stampUser != null as *Decl { @@ -2031,6 +2063,8 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool, merged.path = ""; merged.itemCount = 0; merged.firstItem = null as *Decl; + merged.diagCount = 0; + merged.diags = null as *ParserDiag; // Find and merge ALL stdlib declarations let stdlibDir: String = Cli_FindStdlibDir(projectDir); diff --git a/src/parser.bux b/src/parser.bux index 84a0ad4..b5158c6 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -30,13 +30,6 @@ module Parser { macroTemplateMode: bool, // allows $(…)* in macro! bodies } - struct ParserDiag { - line: uint32, - column: uint32, - message: String, - severity: int, /* 0=error (fatal), 1=warning (recoverable) */ - } - // --------------------------------------------------------------------------- // Token helpers // --------------------------------------------------------------------------- @@ -2827,34 +2820,9 @@ module Parser { } } - /* Print fatal parser diagnostics (severity == 0) or if nothing valid was parsed */ - if p.diagCount > 0 && mod.itemCount == 0 { - var di: int = 0; - while di < p.diagCount { - let d: ParserDiag = p.diags[di]; - Print("error: "); - PrintLine(d.message); - Print(" --> :"); - PrintInt(d.line as int64); - Print(":"); - PrintInt(d.column as int64); - PrintLine(""); - Print(" |"); - PrintLine(""); - Print(" "); - PrintInt(d.line as int64); - Print(" | "); - PrintLine(""); - Print(" | "); - var sp: uint32 = 0; - while sp < d.column - 1 && sp < 120 { - Print(" "); - sp = sp + 1; - } - PrintLine("^"); - di = di + 1; - } - } + // Attach parser diagnostics to the module; the CLI reports them + mod.diagCount = p.diagCount; + mod.diags = p.diags; return mod; }