parser: port test "errdefer with payload"

Implement in parser.c:
- defer and errdefer statements with expectBlockExprStatement
- parseAssignExpr for assignment expressions (expr op= expr)
- expectBlockExprStatement: block or assign expr + semicolon
- assignOpTag: map all assignment operator tokens to AST tags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-10 14:44:00 +00:00
parent cd5ebdb904
commit 2279ac85a0
2 changed files with 148 additions and 30 deletions

165
parser.c
View File

@@ -29,6 +29,7 @@ static AstTokenIndex expectToken(Parser*, TokenizerTag);
static AstNodeIndex parseFnProto(Parser*);
static Members parseContainerMembers(Parser*);
static AstNodeIndex parseInitList(Parser*, AstNodeIndex, AstTokenIndex);
static AstNodeIndex expectBlockExprStatement(Parser*);
typedef struct {
enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag;
@@ -1129,13 +1130,6 @@ static AstNodeIndex parseLoopStatement(Parser* p) {
return 0; // tcc
}
static AstNodeIndex parseAssignExpr(Parser* p) {
(void)p;
fprintf(stderr, "parseAssignExpr not implemented\n");
exit(1);
return 0; // tcc
}
static AstNodeIndex parseVarDeclProto(Parser* p) {
AstTokenIndex mut_token;
if ((mut_token = eatToken(p, TOKEN_KEYWORD_CONST)) == null_token)
@@ -1714,6 +1708,89 @@ static AstNodeIndex parsePrefixExpr(Parser* p) {
});
}
static AstNodeTag assignOpTag(TokenizerTag tok) {
switch (tok) {
case TOKEN_EQUAL:
return AST_NODE_ASSIGN;
case TOKEN_PLUS_EQUAL:
return AST_NODE_ASSIGN_ADD;
case TOKEN_MINUS_EQUAL:
return AST_NODE_ASSIGN_SUB;
case TOKEN_ASTERISK_EQUAL:
return AST_NODE_ASSIGN_MUL;
case TOKEN_SLASH_EQUAL:
return AST_NODE_ASSIGN_DIV;
case TOKEN_PERCENT_EQUAL:
return AST_NODE_ASSIGN_MOD;
case TOKEN_AMPERSAND_EQUAL:
return AST_NODE_ASSIGN_BIT_AND;
case TOKEN_PIPE_EQUAL:
return AST_NODE_ASSIGN_BIT_OR;
case TOKEN_CARET_EQUAL:
return AST_NODE_ASSIGN_BIT_XOR;
case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL:
return AST_NODE_ASSIGN_SHL;
case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL:
return AST_NODE_ASSIGN_SHR;
case TOKEN_PLUS_PERCENT_EQUAL:
return AST_NODE_ASSIGN_ADD_WRAP;
case TOKEN_MINUS_PERCENT_EQUAL:
return AST_NODE_ASSIGN_SUB_WRAP;
case TOKEN_ASTERISK_PERCENT_EQUAL:
return AST_NODE_ASSIGN_MUL_WRAP;
case TOKEN_PLUS_PIPE_EQUAL:
return AST_NODE_ASSIGN_ADD_SAT;
case TOKEN_MINUS_PIPE_EQUAL:
return AST_NODE_ASSIGN_SUB_SAT;
case TOKEN_ASTERISK_PIPE_EQUAL:
return AST_NODE_ASSIGN_MUL_SAT;
case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL:
return AST_NODE_ASSIGN_SHL_SAT;
default:
return AST_NODE_ROOT; // not an assignment op
}
}
static AstNodeIndex parseAssignExpr(Parser* p) {
const AstNodeIndex expr = parseExpr(p);
if (expr == 0)
return null_node;
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
if (assign_tag == AST_NODE_ROOT)
return expr;
const AstTokenIndex op_token = nextToken(p);
const AstNodeIndex rhs = expectExpr(p);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = assign_tag,
.main_token = op_token,
.data = { .lhs = expr, .rhs = rhs },
});
}
static AstNodeIndex expectBlockExprStatement(Parser* p) {
// Try block first (labeled or unlabeled)
if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
return parseBlock(p);
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
&& p->token_tags[p->tok_i + 1] == TOKEN_COLON
&& p->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) {
p->tok_i += 2;
return parseBlock(p);
}
// Assign expr + semicolon
const AstNodeIndex expr = parseAssignExpr(p);
if (expr != 0) {
expectSemicolon(p);
return expr;
}
fprintf(stderr, "expectBlockExprStatement: expected block or expr\n");
exit(1);
return 0; // tcc
}
static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
@@ -1742,35 +1819,37 @@ static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
case TOKEN_R_BRACE:
// Expression that doesn't need semicolon (block-terminated)
return lhs;
case TOKEN_EQUAL: {
// Check if lhs is a var decl that needs initialization
const AstNodeTag lhs_tag = p->nodes.tags[lhs];
if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
|| lhs_tag == AST_NODE_ALIGNED_VAR_DECL
|| lhs_tag == AST_NODE_LOCAL_VAR_DECL
|| lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
p->tok_i++;
p->nodes.datas[lhs].rhs = expectExpr(p);
expectSemicolon(p);
return lhs;
default: {
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
if (assign_tag == AST_NODE_ROOT) {
fprintf(stderr,
"expectVarDeclExprStatement: unexpected token %s\n",
tokenizerGetTagString(p->token_tags[p->tok_i]));
exit(1);
}
// Regular assignment expression
const AstTokenIndex eq_token = nextToken(p);
if (assign_tag == AST_NODE_ASSIGN) {
// Check if lhs is a var decl that needs initialization
const AstNodeTag lhs_tag = p->nodes.tags[lhs];
if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
|| lhs_tag == AST_NODE_ALIGNED_VAR_DECL
|| lhs_tag == AST_NODE_LOCAL_VAR_DECL
|| lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
p->tok_i++;
p->nodes.datas[lhs].rhs = expectExpr(p);
expectSemicolon(p);
return lhs;
}
}
const AstTokenIndex op_token = nextToken(p);
const AstNodeIndex rhs = expectExpr(p);
expectSemicolon(p);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASSIGN,
.main_token = eq_token,
.tag = assign_tag,
.main_token = op_token,
.data = { .lhs = lhs, .rhs = rhs },
});
}
default:
fprintf(stderr,
"expectVarDeclExprStatement: assignment not implemented "
"for token %s\n",
tokenizerGetTagString(p->token_tags[p->tok_i]));
exit(1);
}
}
@@ -1804,10 +1883,36 @@ 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 = 0,
.rhs = expectBlockExprStatement(p),
},
});
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);
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ERRDEFER,
.main_token = errdefer_token,
.data = {
.lhs = payload,
.rhs = expectBlockExprStatement(p),
},
});
}
case TOKEN_KEYWORD_NOSUSPEND:
case TOKEN_KEYWORD_SUSPEND:
case TOKEN_KEYWORD_DEFER:
case TOKEN_KEYWORD_ERRDEFER:
case TOKEN_KEYWORD_ENUM:
case TOKEN_KEYWORD_STRUCT:
case TOKEN_KEYWORD_UNION:;

View File

@@ -1819,3 +1819,16 @@ test "zig fmt: top-level bare asterisk+asterisk+identifier" {
\\
);
}
test "zig fmt: errdefer with payload" {
try testCanonical(
\\pub fn main() anyerror!void {
\\ errdefer |a| x += 1;
\\ errdefer |a| {}
\\ errdefer |a| {
\\ x += 1;
\\ }
\\}
\\
);
}