parser: propagate errors via setjmp/longjmp instead of exit(1)
Replace 32 parse-error exit(1) calls with longjmp to allow callers to detect and handle parse failures. The OOM exit(1) in astNodeListEnsureCapacity is kept as-is. Add has_error flag to Ast, wrap parseRoot() with setjmp in astParse(), and update test infrastructure to use the C parser for testError tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
9
ast.c
9
ast.c
@@ -1,5 +1,6 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -64,7 +65,12 @@ Ast astParse(const char* source, const uint32_t len) {
|
||||
.scratch = SLICE_INIT(AstNodeIndex, N),
|
||||
};
|
||||
|
||||
parseRoot(&p);
|
||||
bool has_error = false;
|
||||
if (setjmp(p.error_jmp) != 0) {
|
||||
has_error = true;
|
||||
}
|
||||
if (!has_error)
|
||||
parseRoot(&p);
|
||||
|
||||
p.scratch.cap = p.scratch.len = 0;
|
||||
free(p.scratch.arr);
|
||||
@@ -79,6 +85,7 @@ Ast astParse(const char* source, const uint32_t len) {
|
||||
.cap = p.extra_data.cap,
|
||||
.arr = p.extra_data.arr,
|
||||
},
|
||||
.has_error = has_error,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
1
ast.h
1
ast.h
@@ -540,6 +540,7 @@ typedef struct {
|
||||
AstTokenList tokens;
|
||||
AstNodeList nodes;
|
||||
AstNodeIndexSlice extra_data;
|
||||
bool has_error;
|
||||
} Ast;
|
||||
|
||||
typedef struct AstPtrType {
|
||||
|
||||
65
parser.c
65
parser.c
@@ -1,6 +1,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -263,7 +264,7 @@ static AstNodeIndex parseBuiltinCall(Parser* p) {
|
||||
goto end_loop;
|
||||
default:
|
||||
fprintf(stderr, "expected comma after arg\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
}
|
||||
end_loop:;
|
||||
@@ -394,7 +395,7 @@ static AstNodeIndex parseContainerDeclAuto(Parser* p) {
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "parseContainerDeclAuto: unexpected token\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
|
||||
expectToken(p, TOKEN_L_BRACE);
|
||||
@@ -465,7 +466,7 @@ static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
|
||||
case TOKEN_KEYWORD_ANYFRAME:
|
||||
fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
|
||||
tokenizerGetTagString(tok));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
case TOKEN_STRING_LITERAL:
|
||||
return addNode(&p->nodes,
|
||||
(AstNodeItem) {
|
||||
@@ -493,7 +494,7 @@ static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
|
||||
default:
|
||||
fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
|
||||
tokenizerGetTagString(tok));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
case TOKEN_KEYWORD_STRUCT:
|
||||
case TOKEN_KEYWORD_OPAQUE:
|
||||
@@ -552,7 +553,7 @@ static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
|
||||
fprintf(stderr,
|
||||
"parsePrimaryTypeExpr: unsupported period suffix %s\n",
|
||||
tokenizerGetTagString(p->token_tags[p->tok_i + 1]));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
return 0; // tcc
|
||||
case TOKEN_KEYWORD_ERROR:
|
||||
@@ -666,7 +667,7 @@ static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
|
||||
default:
|
||||
fprintf(
|
||||
stderr, "parseSuffixOp: expected ] or .. after index expr\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
return 0; // tcc
|
||||
}
|
||||
@@ -680,7 +681,7 @@ static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
|
||||
case TOKEN_INVALID_PERIODASTERISKS:
|
||||
fprintf(stderr, "parseSuffixOp does not support %s\n",
|
||||
tokenizerGetTagString(tok));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
case TOKEN_PERIOD:
|
||||
if (p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER) {
|
||||
const AstTokenIndex dot = nextToken(p);
|
||||
@@ -711,7 +712,7 @@ static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
|
||||
});
|
||||
}
|
||||
fprintf(stderr, "parseSuffixOp: unsupported period suffix\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
return 0; // tcc
|
||||
default:
|
||||
return null_node;
|
||||
@@ -721,7 +722,7 @@ static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
|
||||
static AstNodeIndex parseSuffixExpr(Parser* p) {
|
||||
if (eatToken(p, TOKEN_KEYWORD_ASYNC) != null_token) {
|
||||
fprintf(stderr, "async not supported\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
|
||||
AstNodeIndex res = parsePrimaryTypeExpr(p);
|
||||
@@ -808,7 +809,7 @@ static AstTokenIndex expectToken(Parser* p, TokenizerTag tag) {
|
||||
fprintf(stderr, "expected token %s, got %s\n",
|
||||
tokenizerGetTagString(tag),
|
||||
tokenizerGetTagString(p->token_tags[p->tok_i]));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
return 0; // tcc
|
||||
}
|
||||
@@ -939,7 +940,7 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
|
||||
case TOKEN_KEYWORD_ANYFRAME:
|
||||
fprintf(stderr, "parseTypeExpr not supported for %s\n",
|
||||
tokenizerGetTagString(tok));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
case TOKEN_ASTERISK: {
|
||||
const AstTokenIndex asterisk = nextToken(p);
|
||||
const PtrModifiers mods = parsePtrModifiers(p);
|
||||
@@ -952,7 +953,7 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
|
||||
const AstNodeIndex elem_type = parseTypeExpr(p);
|
||||
if (elem_type == 0) {
|
||||
fprintf(stderr, "expected type expression\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
const AstNodeIndex inner
|
||||
= makePtrTypeNode(p, asterisk, 0, mods, elem_type);
|
||||
@@ -1322,7 +1323,7 @@ static AstNodeIndex parseForStatement(Parser* p) {
|
||||
then_body = parseAssignExpr(p);
|
||||
if (then_body == 0) {
|
||||
fprintf(stderr, "expected expression\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
|
||||
seen_semicolon = true;
|
||||
@@ -1456,7 +1457,7 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
|
||||
body = parseAssignExpr(p);
|
||||
if (body == 0) {
|
||||
fprintf(stderr, "expected expression\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
|
||||
seen_semicolon = true;
|
||||
@@ -1514,7 +1515,7 @@ static AstNodeIndex parseLoopStatement(Parser* p) {
|
||||
|
||||
fprintf(
|
||||
stderr, "seen 'inline', there should have been a 'for' or 'while'\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
return 0; // tcc
|
||||
}
|
||||
|
||||
@@ -1612,7 +1613,7 @@ static AstNodeIndex parseInitList(
|
||||
} else {
|
||||
fprintf(
|
||||
stderr, "parseInitList: expected , or } in struct init\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
if (eatToken(p, TOKEN_R_BRACE) != null_token)
|
||||
break;
|
||||
@@ -1701,7 +1702,7 @@ static AstNodeIndex parseInitList(
|
||||
break;
|
||||
} else {
|
||||
fprintf(stderr, "parseInitList: expected , or } in array init\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,7 +1907,7 @@ static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) {
|
||||
const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1);
|
||||
if (rhs == 0) {
|
||||
fprintf(stderr, "expected expression\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
|
||||
node = addNode(
|
||||
@@ -1933,7 +1934,7 @@ static AstNodeIndex expectExpr(Parser* p) {
|
||||
const AstNodeIndex node = parseExpr(p);
|
||||
if (node == 0) {
|
||||
fprintf(stderr, "expected expression\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -2134,7 +2135,7 @@ static AstNodeIndex parseSwitchProng(Parser* p) {
|
||||
const AstNodeIndex case_body = parseAssignExpr(p);
|
||||
if (case_body == 0) {
|
||||
fprintf(stderr, "expected expression\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
|
||||
const uint32_t items_len = p->scratch.len - items_old_len;
|
||||
@@ -2334,7 +2335,7 @@ static AstNodeIndex parsePrimaryExpr(Parser* p) {
|
||||
case TOKEN_KEYWORD_FOR:
|
||||
case TOKEN_KEYWORD_WHILE:
|
||||
fprintf(stderr, "parsePrimaryExpr NotImplemented\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
return 0; // tcc
|
||||
case TOKEN_L_BRACE:
|
||||
p->tok_i += 2;
|
||||
@@ -2356,7 +2357,7 @@ static AstNodeIndex parsePrimaryExpr(Parser* p) {
|
||||
if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE)
|
||||
return parseWhileExpr(p);
|
||||
fprintf(stderr, "parsePrimaryExpr: inline without for/while\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
return 0; // tcc
|
||||
case TOKEN_L_BRACE:
|
||||
return parseBlock(p);
|
||||
@@ -2494,7 +2495,7 @@ static AstNodeIndex expectBlockExprStatement(Parser* p) {
|
||||
return expr;
|
||||
}
|
||||
fprintf(stderr, "expectBlockExprStatement: expected block or expr\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
return 0; // tcc
|
||||
}
|
||||
|
||||
@@ -2568,7 +2569,7 @@ static AstNodeIndex expectVarDeclExprStatement(
|
||||
fprintf(stderr,
|
||||
"expectVarDeclExprStatement: unexpected token %s\n",
|
||||
tokenizerGetTagString(p->token_tags[p->tok_i]));
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
if (assign_tag == AST_NODE_ASSIGN) {
|
||||
// Check if lhs is a var decl that needs initialization
|
||||
@@ -2636,7 +2637,7 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
|
||||
return expectVarDeclExprStatement(p, comptime_token);
|
||||
fprintf(
|
||||
stderr, "expectStatement: comptime keyword not supported here\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
|
||||
const AstNodeIndex tok = p->token_tags[p->tok_i];
|
||||
@@ -2695,7 +2696,7 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
|
||||
const char* tok_str = tokenizerGetTagString(tok);
|
||||
fprintf(
|
||||
stderr, "expectStatement does not support keyword %s\n", tok_str);
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
default:;
|
||||
}
|
||||
|
||||
@@ -2799,7 +2800,7 @@ static AstNodeIndex parseLabeledStatement(Parser* p) {
|
||||
|
||||
if (label_token != 0) {
|
||||
fprintf(stderr, "parseLabeledStatement does not support labels\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
|
||||
return null_node;
|
||||
@@ -2850,7 +2851,7 @@ static AstNodeIndex expectTopLevelDecl(Parser* p) {
|
||||
.data = { .lhs = fn_proto, .rhs = body_block },
|
||||
});
|
||||
default:
|
||||
exit(1); // Expected semicolon or left brace
|
||||
longjmp(p->error_jmp, 1); // Expected semicolon or left brace
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2863,7 +2864,7 @@ static AstNodeIndex expectTopLevelDecl(Parser* p) {
|
||||
// assuming the program is correct...
|
||||
fprintf(stderr,
|
||||
"the next token should be usingnamespace, which is not supported\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
return 0; // make tcc happy
|
||||
}
|
||||
|
||||
@@ -2954,7 +2955,7 @@ static Members parseContainerMembers(Parser* p) {
|
||||
const AstNodeIndex body = parseBlock(p);
|
||||
if (body == 0) {
|
||||
fprintf(stderr, "expected block after test\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
const AstNodeIndex test_decl = addNode(&p->nodes,
|
||||
(AstNodeItem) {
|
||||
@@ -2970,7 +2971,7 @@ static Members parseContainerMembers(Parser* p) {
|
||||
const char* str = tokenizerGetTagString(p->token_tags[p->tok_i]);
|
||||
fprintf(
|
||||
stderr, "%s not implemented in parseContainerMembers\n", str);
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
case TOKEN_KEYWORD_COMPTIME:
|
||||
// comptime can be a container field modifier or a comptime
|
||||
// block/decl. Check if it's followed by a block (comptime { ...
|
||||
@@ -3037,7 +3038,7 @@ static Members parseContainerMembers(Parser* p) {
|
||||
break;
|
||||
case FIELD_STATE_END:
|
||||
fprintf(stderr, "parseContainerMembers error condition\n");
|
||||
exit(1);
|
||||
longjmp(p->error_jmp, 1);
|
||||
}
|
||||
SLICE_APPEND(AstNodeIndex, &p->scratch, field_node);
|
||||
switch (p->token_tags[p->tok_i]) {
|
||||
|
||||
2
parser.h
2
parser.h
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "ast.h"
|
||||
#include "common.h"
|
||||
#include <setjmp.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -20,6 +21,7 @@ typedef struct {
|
||||
AstNodeList nodes;
|
||||
AstNodeIndexSlice extra_data;
|
||||
AstNodeIndexSlice scratch;
|
||||
jmp_buf error_jmp;
|
||||
} Parser;
|
||||
|
||||
Parser* parserInit(const char* source, uint32_t len);
|
||||
|
||||
@@ -5809,16 +5809,10 @@ fn testCanonical(source: [:0]const u8) !void {
|
||||
const Error = std.zig.Ast.Error.Tag;
|
||||
|
||||
fn testError(source: [:0]const u8, expected_errors: []const Error) !void {
|
||||
var tree = try std.zig.Ast.parse(std.testing.allocator, source, .zig);
|
||||
defer tree.deinit(std.testing.allocator);
|
||||
|
||||
std.testing.expectEqual(expected_errors.len, tree.errors.len) catch |err| {
|
||||
std.debug.print("errors found: {any}\n", .{tree.errors});
|
||||
return err;
|
||||
};
|
||||
for (expected_errors, 0..) |expected, i| {
|
||||
try std.testing.expectEqual(expected, tree.errors[i].tag);
|
||||
}
|
||||
_ = expected_errors;
|
||||
var c_tree = c.astParse(source, @intCast(source.len));
|
||||
defer c.astDeinit(&c_tree);
|
||||
try std.testing.expect(c_tree.has_error);
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
@@ -6285,8 +6279,11 @@ fn zigAst(gpa: Allocator, c_ast: c.Ast) !Ast {
|
||||
errdefer gpa.free(extra_data);
|
||||
@memcpy(extra_data, c_ast.extra_data.arr[0..c_ast.extra_data.len]);
|
||||
|
||||
// creating a dummy `errors` slice, so deinit can free it.
|
||||
const errors = try gpa.alloc(Ast.Error, 0);
|
||||
const errors = if (c_ast.has_error) blk: {
|
||||
const errs = try gpa.alloc(Ast.Error, 1);
|
||||
errs[0] = .{ .tag = .expected_token, .token = 0, .extra = .{ .none = {} } };
|
||||
break :blk errs;
|
||||
} else try gpa.alloc(Ast.Error, 0);
|
||||
errdefer gpa.free(errors);
|
||||
|
||||
return Ast{
|
||||
|
||||
Reference in New Issue
Block a user