Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions pkg/lib/purus-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 16 additions & 2 deletions src/cmd/main/main.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down
5 changes: 4 additions & 1 deletion src/codegen/codegen_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

///|
Expand Down
18 changes: 16 additions & 2 deletions src/parser/parser.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] }
}

///|
Expand Down Expand Up @@ -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)))
}
}
Expand Down
25 changes: 23 additions & 2 deletions src/parser/parser_wbtest.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

///|
Expand Down
Loading