parser: refactor to match upstream Parse.zig structure

Extract shared helpers and fix error handling to align with upstream:

- Replace 7 assert() calls that crash on valid input with fprintf+exit
- Extract parsePtrModifiers() and makePtrTypeNode() to deduplicate
  pointer modifier parsing from 4 inline copies into 1 shared function
- Extract parseBlockExpr() and parseWhileContinueExpr() helpers
- Move comptime wrapping into expectVarDeclExprStatement() via
  comptime_token parameter
- Extract finishAssignExpr(), parseSwitchItem(), parseSwitchProng()

Net effect: 3233 → 3106 lines. All 298+ parser tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-11 09:56:13 +00:00
parent 7193385f94
commit bda3329eee

519
parser.c
View File

@@ -838,76 +838,75 @@ static AstNodeIndex parseErrorUnionExpr(Parser* p) {
});
}
// parsePtrModifiersAndType parses pointer modifiers (allowzero, align,
// addrspace, const, volatile, sentinel) and the child type for a pointer
// started at main_token.
static AstNodeIndex parsePtrModifiersAndType(
Parser* p, AstTokenIndex main_token) {
AstNodeIndex sentinel = 0;
AstNodeIndex align_expr = 0;
AstNodeIndex bit_range_start = 0;
AstNodeIndex bit_range_end = 0;
AstNodeIndex addrspace_expr = 0;
typedef struct {
AstNodeIndex align_node;
AstNodeIndex addrspace_node;
AstNodeIndex bit_range_start;
AstNodeIndex bit_range_end;
} PtrModifiers;
// sentinel: *:0
if (eatToken(p, TOKEN_COLON) != null_token)
sentinel = expectExpr(p);
static PtrModifiers parsePtrModifiers(Parser* p) {
PtrModifiers mods = {};
// allowzero, const, volatile (before align)
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
while (true) {
switch (p->token_tags[p->tok_i]) {
case TOKEN_KEYWORD_CONST:
case TOKEN_KEYWORD_VOLATILE:
case TOKEN_KEYWORD_ALLOWZERO:
p->tok_i++;
continue;
case TOKEN_KEYWORD_ALIGN:
p->tok_i++;
// align(expr) or align(expr:expr:expr)
if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
expectToken(p, TOKEN_L_PAREN);
align_expr = expectExpr(p);
mods.align_node = expectExpr(p);
if (eatToken(p, TOKEN_COLON) != null_token) {
bit_range_start = expectExpr(p);
mods.bit_range_start = expectExpr(p);
expectToken(p, TOKEN_COLON);
bit_range_end = expectExpr(p);
mods.bit_range_end = expectExpr(p);
}
expectToken(p, TOKEN_R_PAREN);
continue;
case TOKEN_KEYWORD_ADDRSPACE:
p->tok_i++;
expectToken(p, TOKEN_L_PAREN);
mods.addrspace_node = expectExpr(p);
expectToken(p, TOKEN_R_PAREN);
continue;
default:
return mods;
}
}
}
// addrspace
addrspace_expr = parseAddrSpace(p);
// const, volatile, allowzero (after align/addrspace)
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
const AstNodeIndex child_type = parseTypeExpr(p);
if (bit_range_start != 0) {
static AstNodeIndex makePtrTypeNode(Parser* p, AstTokenIndex main_token,
AstNodeIndex sentinel, PtrModifiers mods, AstNodeIndex elem_type) {
if (mods.bit_range_start != 0) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
.main_token = main_token,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(sentinel), align_expr,
OPT(addrspace_expr), bit_range_start,
bit_range_end },
(AstNodeIndex[]) { OPT(sentinel), mods.align_node,
OPT(mods.addrspace_node), mods.bit_range_start,
mods.bit_range_end },
5),
.rhs = child_type,
.rhs = elem_type,
},
});
}
if (addrspace_expr != 0 || (sentinel != 0 && align_expr != 0)) {
if (mods.addrspace_node != 0 || (sentinel != 0 && mods.align_node != 0)) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE,
.main_token = main_token,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(sentinel), OPT(align_expr),
OPT(addrspace_expr) },
(AstNodeIndex[]) { OPT(sentinel),
OPT(mods.align_node),
OPT(mods.addrspace_node) },
3),
.rhs = child_type,
.rhs = elem_type,
},
});
}
@@ -916,14 +915,14 @@ static AstNodeIndex parsePtrModifiersAndType(
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_SENTINEL,
.main_token = main_token,
.data = { .lhs = sentinel, .rhs = child_type },
.data = { .lhs = sentinel, .rhs = elem_type },
});
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_ALIGNED,
.main_token = main_token,
.data = { .lhs = align_expr, .rhs = child_type },
.data = { .lhs = mods.align_node, .rhs = elem_type },
});
}
@@ -943,77 +942,20 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
exit(1);
case TOKEN_ASTERISK: {
const AstTokenIndex asterisk = nextToken(p);
return parsePtrModifiersAndType(p, asterisk);
const PtrModifiers mods = parsePtrModifiers(p);
const AstNodeIndex elem_type = parseTypeExpr(p);
return makePtrTypeNode(p, asterisk, 0, mods, elem_type);
}
case TOKEN_ASTERISK_ASTERISK: {
// ** is two nested pointer types sharing the same token.
// Inner pointer gets modifiers, outer wraps it with none.
// (Matches upstream Parse.zig asterisk_asterisk case)
const AstTokenIndex asterisk = nextToken(p);
// Parse inner pointer modifiers (no sentinel for **)
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
AstNodeIndex align_expr = 0;
AstNodeIndex bit_range_start = 0;
AstNodeIndex bit_range_end = 0;
if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
expectToken(p, TOKEN_L_PAREN);
align_expr = expectExpr(p);
if (eatToken(p, TOKEN_COLON) != null_token) {
bit_range_start = expectExpr(p);
expectToken(p, TOKEN_COLON);
bit_range_end = expectExpr(p);
}
expectToken(p, TOKEN_R_PAREN);
}
const AstNodeIndex addrspace_expr = parseAddrSpace(p);
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
const PtrModifiers mods = parsePtrModifiers(p);
const AstNodeIndex elem_type = parseTypeExpr(p);
assert(elem_type != 0);
AstNodeIndex inner;
if (bit_range_start != 0) {
inner = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
.main_token = asterisk,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(0), align_expr,
OPT(addrspace_expr), bit_range_start,
bit_range_end },
5),
.rhs = elem_type,
},
});
} else if (addrspace_expr != 0) {
inner = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE,
.main_token = asterisk,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(0), OPT(align_expr),
addrspace_expr },
3),
.rhs = elem_type,
},
});
} else {
inner = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_ALIGNED,
.main_token = asterisk,
.data = { .lhs = align_expr, .rhs = elem_type },
});
if (elem_type == 0) {
fprintf(stderr, "expected type expression\n");
exit(1);
}
const AstNodeIndex inner
= makePtrTypeNode(p, asterisk, 0, mods, elem_type);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_ALIGNED,
@@ -1029,7 +971,6 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
AstNodeIndex sentinel = 0;
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) {
// Check for 'c' modifier: [*c]
// The 'c' is a regular identifier token
const char c = p->source[p->token_starts[p->tok_i]];
if (c == 'c'
&& p->token_starts[p->tok_i + 1]
@@ -1041,133 +982,19 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
sentinel = expectExpr(p);
}
expectToken(p, TOKEN_R_BRACKET);
// Reuse shared pointer modifier + type parsing
// If we captured a sentinel from [*:s], temporarily store it
// and let parsePtrModifiersAndType handle the rest.
// But parsePtrModifiersAndType expects sentinel after main
// token via `:`. Since we already consumed it, we need to
// handle this inline.
// allowzero, const, volatile (before align)
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
AstNodeIndex align_expr = 0;
AstNodeIndex bit_range_start = 0;
AstNodeIndex bit_range_end = 0;
if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
expectToken(p, TOKEN_L_PAREN);
align_expr = expectExpr(p);
if (eatToken(p, TOKEN_COLON) != null_token) {
bit_range_start = expectExpr(p);
expectToken(p, TOKEN_COLON);
bit_range_end = expectExpr(p);
}
expectToken(p, TOKEN_R_PAREN);
}
const AstNodeIndex addrspace_expr = parseAddrSpace(p);
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
const PtrModifiers mods = parsePtrModifiers(p);
const AstNodeIndex elem_type = parseTypeExpr(p);
if (bit_range_start != 0) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_BIT_RANGE,
.main_token = lbracket,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(sentinel),
align_expr, OPT(addrspace_expr),
bit_range_start, bit_range_end },
5),
.rhs = elem_type,
},
});
}
if (addrspace_expr != 0 || (sentinel != 0 && align_expr != 0)) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE,
.main_token = lbracket,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(sentinel),
OPT(align_expr),
OPT(addrspace_expr) },
3),
.rhs = elem_type,
},
});
}
if (sentinel != 0) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_SENTINEL,
.main_token = lbracket,
.data = { .lhs = sentinel, .rhs = elem_type },
});
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_ALIGNED,
.main_token = lbracket,
.data = { .lhs = align_expr, .rhs = elem_type },
});
return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type);
}
const AstNodeIndex len_expr = parseExpr(p);
const AstNodeIndex sentinel
= eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : 0;
expectToken(p, TOKEN_R_BRACKET);
if (len_expr == 0) {
// Slice type: []T or [:s]T — reuse shared modifier parsing
// allowzero, const, volatile
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
const AstNodeIndex align_expr = parseByteAlign(p);
const AstNodeIndex addrspace_expr = parseAddrSpace(p);
while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
|| p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
p->tok_i++;
// Slice type: []T or [:s]T
const PtrModifiers mods = parsePtrModifiers(p);
const AstNodeIndex elem_type = parseTypeExpr(p);
if (addrspace_expr != 0 || (sentinel != 0 && align_expr != 0)) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE,
.main_token = lbracket,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) { OPT(sentinel),
OPT(align_expr),
OPT(addrspace_expr) },
3),
.rhs = elem_type,
},
});
}
if (sentinel != 0) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_SENTINEL,
.main_token = lbracket,
.data = { .lhs = sentinel, .rhs = elem_type },
});
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_PTR_TYPE_ALIGNED,
.main_token = lbracket,
.data = { .lhs = align_expr, .rhs = elem_type },
});
return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type);
}
// Array type: [N]T or [N:s]T
const AstNodeIndex elem_type = parseTypeExpr(p);
@@ -1493,7 +1320,10 @@ static AstNodeIndex parseForStatement(Parser* p) {
then_body = block;
} else {
then_body = parseAssignExpr(p);
assert(then_body != 0);
if (then_body == 0) {
fprintf(stderr, "expected expression\n");
exit(1);
}
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
seen_semicolon = true;
}
@@ -1545,6 +1375,15 @@ static AstNodeIndex parseForStatement(Parser* p) {
});
}
static AstNodeIndex parseWhileContinueExpr(Parser* p) {
if (eatToken(p, TOKEN_COLON) == null_token)
return null_node;
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex expr = parseAssignExpr(p);
expectToken(p, TOKEN_R_PAREN);
return expr;
}
static AstNodeIndex parseWhileExpr(Parser* p) {
const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
if (while_token == null_token)
@@ -1555,13 +1394,7 @@ static AstNodeIndex parseWhileExpr(Parser* p) {
expectToken(p, TOKEN_R_PAREN);
parsePtrPayload(p);
// Continue expression: : (expr)
AstNodeIndex cont_expr = 0;
if (eatToken(p, TOKEN_COLON) != null_token) {
expectToken(p, TOKEN_L_PAREN);
cont_expr = parseAssignExpr(p);
expectToken(p, TOKEN_R_PAREN);
}
const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
const AstNodeIndex body = expectExpr(p);
@@ -1611,12 +1444,7 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
expectToken(p, TOKEN_R_PAREN);
parsePtrPayload(p);
AstNodeIndex cont_expr = 0;
if (eatToken(p, TOKEN_COLON) != null_token) {
expectToken(p, TOKEN_L_PAREN);
cont_expr = parseAssignExpr(p);
expectToken(p, TOKEN_R_PAREN);
}
const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
// Statement body: block, or assign expr
AstNodeIndex body;
@@ -1626,7 +1454,10 @@ static AstNodeIndex parseWhileStatement(Parser* p) {
body = block;
} else {
body = parseAssignExpr(p);
assert(body != 0);
if (body == 0) {
fprintf(stderr, "expected expression\n");
exit(1);
}
if (eatToken(p, TOKEN_SEMICOLON) != null_token)
seen_semicolon = true;
}
@@ -2073,7 +1904,10 @@ static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) {
if (tok_tag == TOKEN_KEYWORD_CATCH)
parsePayload(p);
const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1);
assert(rhs != 0);
if (rhs == 0) {
fprintf(stderr, "expected expression\n");
exit(1);
}
node = addNode(
&p->nodes,
@@ -2097,7 +1931,10 @@ static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); }
static AstNodeIndex expectExpr(Parser* p) {
const AstNodeIndex node = parseExpr(p);
assert(node != 0);
if (node == 0) {
fprintf(stderr, "expected expression\n");
exit(1);
}
return node;
}
@@ -2258,56 +2095,47 @@ static AstNodeIndex parseAsmExpr(Parser* p) {
});
}
static AstNodeIndex parseSwitchExpr(Parser* p) {
const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH);
if (switch_token == null_token)
static AstNodeIndex parseSwitchItem(Parser* p) {
const AstNodeIndex expr = parseExpr(p);
if (expr == 0)
return null_node;
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex operand = expectExpr(p);
expectToken(p, TOKEN_R_PAREN);
expectToken(p, TOKEN_L_BRACE);
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
while (true) {
if (eatToken(p, TOKEN_R_BRACE) != null_token)
break;
eatDocComments(p);
// Parse switch case items
const uint32_t items_old_len = p->scratch.len;
while (true) {
if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_ELSE) {
p->tok_i++;
break;
}
if (p->token_tags[p->tok_i] == TOKEN_EQUAL_ANGLE_BRACKET_RIGHT)
break;
const AstNodeIndex item = expectExpr(p);
if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) {
const AstTokenIndex range_tok = nextToken(p);
const AstNodeIndex range_end = expectExpr(p);
SLICE_APPEND(AstNodeIndex, &p->scratch,
addNode(&p->nodes,
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_SWITCH_RANGE,
.main_token = range_tok,
.data = { .lhs = item, .rhs = range_end },
}));
} else {
SLICE_APPEND(AstNodeIndex, &p->scratch, item);
.data = { .lhs = expr, .rhs = range_end },
});
}
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
p->tok_i++;
return expr;
}
static AstNodeIndex parseSwitchProng(Parser* p) {
const uint32_t items_old_len = p->scratch.len;
if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
while (true) {
const AstNodeIndex item = parseSwitchItem(p);
if (item == 0)
break;
SLICE_APPEND(AstNodeIndex, &p->scratch, item);
if (eatToken(p, TOKEN_COMMA) == null_token)
break;
}
if (p->scratch.len == items_old_len)
return null_node;
}
const AstTokenIndex arrow
= expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT);
parsePtrPayload(p);
const AstNodeIndex case_body = parseAssignExpr(p);
assert(case_body != 0);
if (case_body == 0) {
fprintf(stderr, "expected expression\n");
exit(1);
}
const uint32_t items_len = p->scratch.len - items_old_len;
AstNodeIndex case_node;
@@ -2319,9 +2147,8 @@ static AstNodeIndex parseSwitchExpr(Parser* p) {
.tag = AST_NODE_SWITCH_CASE_ONE,
.main_token = arrow,
.data = {
.lhs = items_len >= 1
? p->scratch.arr[items_old_len]
: 0,
.lhs
= items_len >= 1 ? p->scratch.arr[items_old_len] : 0,
.rhs = case_body,
},
});
@@ -2342,10 +2169,31 @@ static AstNodeIndex parseSwitchExpr(Parser* p) {
} break;
}
// Restore scratch to before items but keep case_node count
p->scratch.len = items_old_len;
SLICE_APPEND(AstNodeIndex, &p->scratch, case_node);
return case_node;
}
static AstNodeIndex parseSwitchExpr(Parser* p) {
const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH);
if (switch_token == null_token)
return null_node;
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex operand = expectExpr(p);
expectToken(p, TOKEN_R_PAREN);
expectToken(p, TOKEN_L_BRACE);
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
while (true) {
if (eatToken(p, TOKEN_R_BRACE) != null_token)
break;
eatDocComments(p);
const AstNodeIndex case_node = parseSwitchProng(p);
if (case_node == 0)
break;
SLICE_APPEND(AstNodeIndex, &p->scratch, case_node);
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
p->tok_i++;
}
@@ -2601,14 +2449,10 @@ static AstNodeTag assignOpTag(TokenizerTag tok) {
}
}
static AstNodeIndex parseAssignExpr(Parser* p) {
const AstNodeIndex expr = parseExpr(p);
if (expr == 0)
return null_node;
static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) {
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
if (assign_tag == AST_NODE_ROOT)
return expr;
return lhs;
const AstTokenIndex op_token = nextToken(p);
const AstNodeIndex rhs = expectExpr(p);
@@ -2616,12 +2460,18 @@ static AstNodeIndex parseAssignExpr(Parser* p) {
(AstNodeItem) {
.tag = assign_tag,
.main_token = op_token,
.data = { .lhs = expr, .rhs = rhs },
.data = { .lhs = lhs, .rhs = rhs },
});
}
static AstNodeIndex expectBlockExprStatement(Parser* p) {
// Try block first (labeled or unlabeled)
static AstNodeIndex parseAssignExpr(Parser* p) {
const AstNodeIndex expr = parseExpr(p);
if (expr == 0)
return null_node;
return finishAssignExpr(p, expr);
}
static AstNodeIndex parseBlockExpr(Parser* p) {
if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
return parseBlock(p);
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
@@ -2630,6 +2480,13 @@ static AstNodeIndex expectBlockExprStatement(Parser* p) {
p->tok_i += 2;
return parseBlock(p);
}
return null_node;
}
static AstNodeIndex expectBlockExprStatement(Parser* p) {
const AstNodeIndex block_expr = parseBlockExpr(p);
if (block_expr != 0)
return block_expr;
// Assign expr + semicolon
const AstNodeIndex expr = parseAssignExpr(p);
if (expr != 0) {
@@ -2641,7 +2498,8 @@ static AstNodeIndex expectBlockExprStatement(Parser* p) {
return 0; // tcc
}
static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
static AstNodeIndex expectVarDeclExprStatement(
Parser* p, AstTokenIndex comptime_token) {
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
@@ -2665,15 +2523,46 @@ static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
switch (p->token_tags[p->tok_i]) {
case TOKEN_SEMICOLON:
p->tok_i++;
if (comptime_token != null_token) {
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) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = lhs, .rhs = 0 },
});
}
}
return lhs;
case TOKEN_R_BRACE:
// Expression that doesn't need semicolon (block-terminated)
if (comptime_token != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = lhs, .rhs = 0 },
});
}
return lhs;
default: {
// Check if expression ended with a block (previous token is })
// and thus doesn't need a semicolon
if (p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE)
if (p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE) {
if (comptime_token != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = lhs, .rhs = 0 },
});
}
return lhs;
}
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
if (assign_tag == AST_NODE_ROOT) {
fprintf(stderr,
@@ -2743,27 +2632,8 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
});
}
// comptime var decl or expression
if (allow_defer_var) {
// Pass through to expectVarDeclExprStatement.
// For var decls, the comptime prefix is detected from token
// positions by the renderer (no wrapping needed).
// For expressions, the result is wrapped in a comptime node.
const AstNodeIndex inner = expectVarDeclExprStatement(p);
const AstNodeTag inner_tag = p->nodes.tags[inner];
if (inner_tag == AST_NODE_SIMPLE_VAR_DECL
|| inner_tag == AST_NODE_ALIGNED_VAR_DECL
|| inner_tag == AST_NODE_LOCAL_VAR_DECL
|| inner_tag == AST_NODE_GLOBAL_VAR_DECL
|| inner_tag == AST_NODE_ASSIGN_DESTRUCTURE) {
return inner;
}
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = inner, .rhs = 0 },
});
}
if (allow_defer_var)
return expectVarDeclExprStatement(p, comptime_token);
fprintf(
stderr, "expectStatement: comptime keyword not supported here\n");
exit(1);
@@ -2834,7 +2704,7 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
return labeled_statement;
if (allow_defer_var) {
return expectVarDeclExprStatement(p);
return expectVarDeclExprStatement(p, null_token);
} else {
const AstNodeIndex assign_expr = parseAssignExpr(p);
expectSemicolon(p);
@@ -3082,7 +2952,10 @@ static Members parseContainerMembers(Parser* p) {
? nextToken(p)
: null_token;
const AstNodeIndex body = parseBlock(p);
assert(body != 0);
if (body == 0) {
fprintf(stderr, "expected block after test\n");
exit(1);
}
const AstNodeIndex test_decl = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_TEST_DECL,