diff --git a/pkg/lib/purus-core.js b/pkg/lib/purus-core.js index bb48cfa..87e1157 100644 --- a/pkg/lib/purus-core.js +++ b/pkg/lib/purus-core.js @@ -39,11 +39,17 @@ function compile(source, options = {}) { args.push("--type", "commonjs"); } args.push(tmpFile); - let result = execFileSync(process.execPath, [COMPILER_JS, ...args], { - encoding: "utf8", - stdio: ["pipe", "pipe", "pipe"], - timeout: 30000, - }); + let result; + try { + result = execFileSync(process.execPath, [COMPILER_JS, ...args], { + encoding: "utf8", + stdio: ["pipe", "pipe", "pipe"], + timeout: 30000, + }); + } catch (err) { + const detail = `${err.stdout || ""}${err.stderr || ""}`.trim(); + throw new Error(detail || `Compile error: ${err.message}`); + } result = stdlib.postProcess(result); if (header) { return `// Generated by Purus ${VERSION}\n${result}`; @@ -80,7 +86,8 @@ function check(source) { }); return true; } catch (err) { - throw new Error(`Syntax error: ${err.stderr || err.message}`); + const detail = `${err.stdout || ""}${err.stderr || ""}`.trim(); + throw new Error(`Syntax error: ${detail || err.message}`); } finally { try { fs.unlinkSync(tmpFile); diff --git a/src/cmd/main/main.mbt b/src/cmd/main/main.mbt index 88e2b99..2bcb491 100644 --- a/src/cmd/main/main.mbt +++ b/src/cmd/main/main.mbt @@ -69,7 +69,14 @@ fn cmd_build(args : Array[String]) -> Unit { filtered.push(tokens[fi]) fi += 1 } - let stmts = @parser.Parser::new(filtered).parse() + let parser = @parser.Parser::new(filtered) + let stmts = parser.parse() + if parser.errors.length() > 0 { + for msg in parser.errors { + println("Error: " + msg) + } + @sys.exit(1) + } let header = if no_header { "" } else { @@ -140,7 +147,14 @@ fn cmd_check(file : String) -> Unit { } } let tokens = @lexer.Lexer::new(src).tokenize() - let _stmts = @parser.Parser::new(tokens).parse() + let parser = @parser.Parser::new(tokens) + let _stmts = parser.parse() + if parser.errors.length() > 0 { + for msg in parser.errors { + println("Error: " + msg) + } + @sys.exit(1) + } println("OK: " + file) } diff --git a/src/codegen/codegen_test.mbt b/src/codegen/codegen_test.mbt index fc233ac..3f272a0 100644 --- a/src/codegen/codegen_test.mbt +++ b/src/codegen/codegen_test.mbt @@ -266,8 +266,11 @@ test "codegen: hyphenated ident becomes underscore" { ///| test "codegen: bare ident assignment is discarded" { + // Bare identifier assignment is a parse error (reported via Parser::errors + // and rejected by the CLI). The recovery AST still emits the identifier as + // an expression so codegen never sees the dropped assignment. let js = compile("x be 42") - assert_eq(js, "x;\n") // bare ident assignment is not supported; emits identifier as expression + assert_eq(js, "x;\n") } ///| diff --git a/src/parser/parser.mbt b/src/parser/parser.mbt index 5c2e246..051cf5c 100644 --- a/src/parser/parser.mbt +++ b/src/parser/parser.mbt @@ -4,11 +4,12 @@ pub(all) struct Parser { tokens : Array[@lexer.Token] mut pos : Int + errors : Array[String] } ///| pub fn Parser::new(tokens : Array[@lexer.Token]) -> Parser { - { tokens, pos: 0 } + { tokens, pos: 0, errors: [] } } ///| @@ -223,7 +224,20 @@ fn Parser::parse_stmt(self : Parser) -> Stmt? { // Bare identifier assignment (x be val) is not supported. // Use const/let/var for declarations. Property and index access assignments remain valid. match expr { - Ident(_) => Some(ExprStmt(expr)) // discard; bare variable assignment is removed + Ident(name) => { + self.errors.push( + "bare identifier assignment ('" + + name + + " be ...') is not supported. Declare with 'const " + + name + + " be ...' or 'let " + + name + + " be ...', or use compound assignment such as '" + + name + + " add be 1'", + ) + Some(ExprStmt(expr)) + } _ => Some(self.maybe_postfix(Assign(expr, value))) } } diff --git a/src/parser/parser_wbtest.mbt b/src/parser/parser_wbtest.mbt index 1951e83..8a1c510 100644 --- a/src/parser/parser_wbtest.mbt +++ b/src/parser/parser_wbtest.mbt @@ -45,13 +45,34 @@ test "parser: var declaration with string" { } ///| -test "parser: bare ident assignment is discarded" { - let stmts = parse("x be 10") +test "parser: bare ident assignment is a parse error" { + let parser = Parser::new(tokenize("x be 10")) + let stmts = parser.parse() assert_eq(stmts.length(), 1) match stmts[0] { ExprStmt(Ident(name)) => assert_eq(name, "x") _ => fail("expected ExprStmt(Ident) - bare assignment is not supported") } + assert_eq(parser.errors.length(), 1) + assert_true(parser.errors[0].contains("bare identifier assignment")) + assert_true(parser.errors[0].contains("'x be ...'")) +} + +///| +test "parser: bare reassignment after let is a parse error" { + let parser = Parser::new(tokenize("let y be 10\ny be 20")) + let _stmts = parser.parse() + assert_eq(parser.errors.length(), 1) + assert_true(parser.errors[0].contains("'y be ...'")) +} + +///| +test "parser: property and compound assignments report no errors" { + let parser = Parser::new( + tokenize("obj.field be 10\narr[\\0] be 1\nlet z be 0\nz add be 1"), + ) + let _stmts = parser.parse() + assert_eq(parser.errors.length(), 0) } ///|