parser: implement switch, port switch comment tests

Implement parseSwitchExpr in parser.c:
- switch(expr) { cases... } with case items, ranges, else
- switch_case_one and switch_case node types
- Proper scratch management for nested case items

Port tests:
- "switch comment before prong"
- "switch comment after prong"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-10 18:47:50 +00:00
parent 2dc5993a29
commit fe86388d1e
2 changed files with 131 additions and 1 deletions

107
parser.c
View File

@@ -37,6 +37,7 @@ static AstNodeIndex parseWhileExpr(Parser*);
static AstNodeIndex parseAssignExpr(Parser*);
static void parsePtrPayload(Parser*);
static void parsePayload(Parser*);
static AstNodeIndex parseSwitchExpr(Parser*);
typedef struct {
enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag;
@@ -474,10 +475,11 @@ static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
case TOKEN_KEYWORD_FN:
return parseFnProto(p);
case TOKEN_KEYWORD_IF:
case TOKEN_KEYWORD_SWITCH:
fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
tokenizerGetTagString(tok));
exit(1);
case TOKEN_KEYWORD_SWITCH:
return parseSwitchExpr(p);
case TOKEN_KEYWORD_EXTERN:
case TOKEN_KEYWORD_PACKED:
// extern/packed can precede struct/union/enum
@@ -1775,6 +1777,109 @@ static AstNodeIndex expectExpr(Parser* p) {
return 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);
// 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,
(AstNodeItem) {
.tag = AST_NODE_SWITCH_RANGE,
.main_token = range_tok,
.data = { .lhs = item, .rhs = range_end },
}));
} else {
SLICE_APPEND(AstNodeIndex, &p->scratch, item);
}
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
p->tok_i++;
}
const AstTokenIndex arrow
= expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT);
parsePtrPayload(p);
const AstNodeIndex case_body = expectExpr(p);
const uint32_t items_len = p->scratch.len - items_old_len;
AstNodeIndex case_node;
switch (items_len) {
case 0:
case 1:
case_node = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_SWITCH_CASE_ONE,
.main_token = arrow,
.data = {
.lhs = items_len >= 1
? p->scratch.arr[items_old_len]
: 0,
.rhs = case_body,
},
});
break;
default: {
const AstSubRange span
= listToSpan(p, &p->scratch.arr[items_old_len], items_len);
case_node = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_SWITCH_CASE,
.main_token = arrow,
.data = { .lhs = span.start, .rhs = case_body },
});
} break;
}
// Restore scratch to before items but keep case_node count
p->scratch.len = items_old_len;
SLICE_APPEND(AstNodeIndex, &p->scratch, case_node);
if (p->token_tags[p->tok_i] == TOKEN_COMMA)
p->tok_i++;
}
const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
const uint32_t cases_len = p->scratch.len - scratch_top.old_len;
const AstSubRange span
= listToSpan(p, &p->scratch.arr[scratch_top.old_len], cases_len);
return addNode(&p->nodes,
(AstNodeItem) {
.tag = comma ? AST_NODE_SWITCH_COMMA : AST_NODE_SWITCH,
.main_token = switch_token,
.data = {
.lhs = operand,
.rhs = addExtra(p,
(AstNodeIndex[]) { span.start, span.end }, 2),
},
});
}
static void parsePtrPayload(Parser* p) {
if (eatToken(p, TOKEN_PIPE) == null_token)
return;

View File

@@ -2137,6 +2137,31 @@ test "zig fmt: function call with multiline argument" {
);
}
test "zig fmt: switch comment before prong" {
try testCanonical(
\\comptime {
\\ switch (a) {
\\ // hi
\\ 0 => {},
\\ }
\\}
\\
);
}
test "zig fmt: switch comment after prong" {
try testCanonical(
\\comptime {
\\ switch (a) {
\\ 0,
\\ // hi
\\ => {},
\\ }
\\}
\\
);
}
test "zig fmt: if-else with comment before else" {
try testCanonical(
\\comptime {