parser: align structural logic with upstream Parse.zig

Fix 11 divergences where parser.c differed from Parse.zig in logic or
structure, not justified by C vs Zig language differences:

- parseContainerMembers: set trailing=false after test decl, add
  field_state tracking (A1, A2)
- expectStatement: guard defer/errdefer behind allow_defer_var (A3)
- expectVarDeclExprStatement: wrap assignment in comptime node when
  comptime_token is set (A4)
- parseBlock: guard semicolon check with statements_len != 0 (A5)
- parseLabeledStatement: add parseSwitchExpr call (A6)
- parseWhileStatement: restructure with else_required and early
  returns to match upstream control flow (A7)
- parseForStatement: restructure with else_required/has_else and
  early returns to match upstream control flow (A8)
- parseFnProto: fail when return_type_expr is missing (A9)
- expectTopLevelDecl: track is_extern, reject extern fn body (A10)
- parsePrefixExpr: remove TOKEN_KEYWORD_AWAIT case (A11)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 18:10:26 +00:00
parent 67706f86f3
commit 9d15552f1c

179
parser.c
View File

@@ -251,8 +251,12 @@ static Members parseContainerMembers(Parser* p) {
if (doc_comment != null_token)
fail(p, "test_doc_comment");
const AstNodeIndex test_decl = expectTestDecl(p);
if (field_state.tag == FIELD_STATE_SEEN) {
field_state.tag = FIELD_STATE_END;
field_state.payload.end = test_decl;
}
SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl);
trailing = p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE;
trailing = false;
break;
}
case TOKEN_KEYWORD_USINGNAMESPACE:;
@@ -464,10 +468,12 @@ static AstNodeIndex expectTestDecl(Parser* p) {
static AstNodeIndex expectTopLevelDecl(Parser* p) {
AstTokenIndex extern_export_inline_token = nextToken(p);
bool is_extern = false;
switch (p->token_tags[extern_export_inline_token]) {
case TOKEN_KEYWORD_EXTERN:
eatToken(p, TOKEN_STRING_LITERAL);
is_extern = true;
break;
case TOKEN_KEYWORD_EXPORT:
case TOKEN_KEYWORD_INLINE:
@@ -484,6 +490,9 @@ static AstNodeIndex expectTopLevelDecl(Parser* p) {
p->tok_i++;
return fn_proto;
case TOKEN_L_BRACE:;
if (is_extern) {
fail(p, "extern_fn_body");
}
AstNodeIndex fn_decl_index = reserveNode(p, AST_NODE_FN_DECL);
AstNodeIndex body_block = parseBlock(p);
return setNode(p, fn_decl_index,
@@ -525,6 +534,9 @@ static AstNodeIndex parseFnProto(Parser* p) {
eatToken(p, TOKEN_BANG);
const AstNodeIndex return_type_expr = parseTypeExpr(p);
if (return_type_expr == 0) {
fail(p, "expected_return_type");
}
if (align_expr == 0 && section_expr == 0 && callconv_expr == 0
&& addrspace_expr == 0) {
@@ -747,33 +759,37 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
const AstNodeIndex tok = p->token_tags[p->tok_i];
switch (tok) {
case TOKEN_KEYWORD_DEFER:
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_DEFER,
.main_token = nextToken(p),
.data = {
.lhs = expectBlockExprStatement(p),
.rhs = 0,
},
});
case TOKEN_KEYWORD_ERRDEFER: {
const AstTokenIndex errdefer_token = nextToken(p);
AstTokenIndex payload = null_token;
if (p->token_tags[p->tok_i] == TOKEN_PIPE) {
p->tok_i++;
payload = expectToken(p, TOKEN_IDENTIFIER);
expectToken(p, TOKEN_PIPE);
if (allow_defer_var)
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_DEFER,
.main_token = nextToken(p),
.data = {
.lhs = expectBlockExprStatement(p),
.rhs = 0,
},
});
break;
case TOKEN_KEYWORD_ERRDEFER:
if (allow_defer_var) {
const AstTokenIndex errdefer_token = nextToken(p);
AstTokenIndex payload = null_token;
if (p->token_tags[p->tok_i] == TOKEN_PIPE) {
p->tok_i++;
payload = expectToken(p, TOKEN_IDENTIFIER);
expectToken(p, TOKEN_PIPE);
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ERRDEFER,
.main_token = errdefer_token,
.data = {
.lhs = payload,
.rhs = expectBlockExprStatement(p),
},
});
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ERRDEFER,
.main_token = errdefer_token,
.data = {
.lhs = payload,
.rhs = expectBlockExprStatement(p),
},
});
}
break;
case TOKEN_KEYWORD_NOSUSPEND:
return addNode(&p->nodes,
(AstNodeItem) {
@@ -884,12 +900,21 @@ static AstNodeIndex expectVarDeclExprStatement(
return lhs;
}
// Simple assignment: x = val;
return addNode(&p->nodes,
const AstNodeIndex assign = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASSIGN,
.main_token = equal_token,
.data = { .lhs = lhs, .rhs = rhs },
});
if (comptime_token != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = assign, .rhs = 0 },
});
}
return assign;
}
// Destructure: a, b, c = rhs
@@ -970,8 +995,12 @@ static AstNodeIndex parseLabeledStatement(Parser* p) {
if (loop_stmt != 0)
return loop_stmt;
const AstNodeIndex switch_expr = parseSwitchExpr(p);
if (switch_expr != 0)
return switch_expr;
if (label_token != 0) {
fail(p, "parseLabeledStatement does not support labels");
fail(p, "expected_labelable");
}
return null_node;
@@ -1004,56 +1033,50 @@ static AstNodeIndex parseForStatement(Parser* p) {
const uint32_t inputs = forPrefix(p);
// Statement body: block or assign expr
AstNodeIndex then_body;
bool else_required = false;
bool seen_semicolon = false;
AstNodeIndex then_body;
const AstNodeIndex block = parseBlock(p);
if (block != 0) {
then_body = block;
} else {
then_body = parseAssignExpr(p);
if (then_body == 0) {
fail(p, "expected expression");
fail(p, "expected_block_or_assignment");
}
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
if (eatToken(p, TOKEN_SEMICOLON) != null_token) {
seen_semicolon = true;
} else {
else_required = true;
}
}
bool has_else = false;
if (!seen_semicolon && eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) {
parsePayload(p);
SLICE_APPEND(AstNodeIndex, &p->scratch, then_body);
const AstNodeIndex else_body = expectBlockExprStatement(p);
const AstNodeIndex else_body = expectStatement(p, false);
SLICE_APPEND(AstNodeIndex, &p->scratch, else_body);
const uint32_t total = p->scratch.len - scratch_top;
const AstSubRange span
= listToSpan(p, &p->scratch.arr[scratch_top], total);
p->scratch.len = scratch_top;
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_FOR,
.main_token = for_token,
.data = {
.lhs = span.start,
.rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31),
},
});
}
if (!seen_semicolon && block == 0) {
fail(p, "expected_semi_or_else");
}
if (inputs == 1) {
const AstNodeIndex input = p->scratch.arr[scratch_top];
has_else = true;
} else if (inputs == 1) {
if (else_required)
fail(p, "expected_semi_or_else");
p->scratch.len = scratch_top;
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_FOR_SIMPLE,
.main_token = for_token,
.data = { .lhs = input, .rhs = then_body },
.data = {
.lhs = p->scratch.arr[scratch_top],
.rhs = then_body,
},
});
} else {
if (else_required)
fail(p, "expected_semi_or_else");
SLICE_APPEND(AstNodeIndex, &p->scratch, then_body);
}
SLICE_APPEND(AstNodeIndex, &p->scratch, then_body);
const uint32_t total = p->scratch.len - scratch_top;
const AstSubRange span
= listToSpan(p, &p->scratch.arr[scratch_top], total);
@@ -1064,7 +1087,8 @@ static AstNodeIndex parseForStatement(Parser* p) {
.main_token = for_token,
.data = {
.lhs = span.start,
.rhs = (uint32_t)inputs & 0x7FFFFFFF,
.rhs = ((uint32_t)inputs & 0x7FFFFFFF)
| (has_else ? (1u << 31) : 0),
},
});
}
@@ -1139,24 +1163,42 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
// Statement body: block, or assign expr
bool else_required = false;
AstNodeIndex body;
bool seen_semicolon = false;
const AstNodeIndex block = parseBlock(p);
if (block != 0) {
body = block;
} else {
body = parseAssignExpr(p);
if (body == 0) {
fail(p, "expected expression");
fail(p, "expected_block_or_assignment");
}
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
seen_semicolon = true;
if (eatToken(p, TOKEN_SEMICOLON) != null_token) {
if (cont_expr != 0) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_WHILE_CONT,
.main_token = while_token,
.data = {
.lhs = condition,
.rhs = addExtra(p,
(AstNodeIndex[]) { cont_expr, body }, 2),
},
});
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_WHILE_SIMPLE,
.main_token = while_token,
.data = { .lhs = condition, .rhs = body },
});
}
else_required = true;
}
if (seen_semicolon || eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
if (!seen_semicolon && block == 0) {
if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
if (else_required)
fail(p, "expected_semi_or_else");
}
if (cont_expr != 0) {
return addNode(&p->nodes,
(AstNodeItem) {
@@ -1178,7 +1220,7 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
}
parsePayload(p);
const AstNodeIndex else_body = expectBlockExprStatement(p);
const AstNodeIndex else_body = expectStatement(p, false);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_WHILE,
@@ -1520,9 +1562,6 @@ static AstNodeIndex parsePrefixExpr(Parser* p) {
case TOKEN_KEYWORD_TRY:
tag = AST_NODE_TRY;
break;
case TOKEN_KEYWORD_AWAIT:
tag = AST_NODE_AWAIT;
break;
default:
return parsePrimaryExpr(p);
}
@@ -1951,9 +1990,9 @@ static AstNodeIndex parseBlock(Parser* p) {
SLICE_APPEND(AstNodeIndex, &p->scratch, statement);
}
expectToken(p, TOKEN_R_BRACE);
const bool semicolon = (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON);
const uint32_t statements_len = p->scratch.len - scratch_top.old_len;
const bool semicolon = statements_len != 0
&& (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON);
switch (statements_len) {
case 0:
return addNode(