commit fe86388d1e8fc47bf7376377d7b088909ab0c37b (tree)
parent 2dc5993a2908f175c18f5b241cd01162ac0aa758
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Tue, 10 Feb 2026 18:47:50 +0000
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>
Diffstat:
| M | parser.c | | | 107 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | parser_test.zig | | | 25 | +++++++++++++++++++++++++ |
2 files changed, 131 insertions(+), 1 deletion(-)
diff --git a/parser.c b/parser.c
@@ -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;
diff --git a/parser_test.zig b/parser_test.zig
@@ -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 {