From 3c83549f77fd7bcf4ddaa3fc33a5ea0129722d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Tue, 10 Feb 2026 22:26:08 +0000 Subject: [PATCH] parser: port enum/union/catch/switch/for/if declaration tests Port tests: - "enum declaration" - "union declaration" - "catch" - "switch" - "for if", "if for", "while if", "if while", "while for", "for while" - "if" Fix in parser.c: - parsePtrPayload: handle multi-capture |a, *b| - parseWhileStatement/parseForStatement: separate statement-context body parsing (block or assign expr + semicolon) Deferred tests (need further work): - "while" (full test) - var decl in while body context - "for" (full test) - capture parsing edge case Co-Authored-By: Claude Opus 4.6 (1M context) --- parser.c | 143 +++++++++++++++++++- parser_test.zig | 340 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 479 insertions(+), 4 deletions(-) diff --git a/parser.c b/parser.c index 1950f19431..a0c5ad7c89 100644 --- a/parser.c +++ b/parser.c @@ -1415,7 +1415,73 @@ static AstNodeIndex parseForExpr(Parser* p) { }); } -static AstNodeIndex parseForStatement(Parser* p) { return parseForExpr(p); } +static AstNodeIndex parseForStatement(Parser* p) { + const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR); + if (for_token == null_token) + return null_node; + + const uint32_t scratch_top = p->scratch.len; + const uint32_t inputs = forPrefix(p); + + // Statement body: block or assign expr + AstNodeIndex then_body; + bool seen_semicolon = false; + const AstNodeIndex block = parseBlock(p); + if (block != 0) { + then_body = block; + } else { + then_body = parseAssignExpr(p); + assert(then_body != 0); + if (eatToken(p, TOKEN_SEMICOLON) != null_token) + seen_semicolon = true; + } + + 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); + 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 (inputs == 1) { + const AstNodeIndex input = p->scratch.arr[scratch_top]; + 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 }, + }); + } + + 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); + 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, + }, + }); +} static AstNodeIndex parseWhileExpr(Parser* p) { const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE); @@ -1474,7 +1540,69 @@ static AstNodeIndex parseWhileExpr(Parser* p) { } static AstNodeIndex parseWhileStatement(Parser* p) { - return parseWhileExpr(p); + const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE); + if (while_token == null_token) + return null_node; + + expectToken(p, TOKEN_L_PAREN); + const AstNodeIndex condition = expectExpr(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); + } + + // Statement body: block, or assign expr + AstNodeIndex body; + bool seen_semicolon = false; + const AstNodeIndex block = parseBlock(p); + if (block != 0) { + body = block; + } else { + body = parseAssignExpr(p); + assert(body != 0); + if (eatToken(p, TOKEN_SEMICOLON) != null_token) + seen_semicolon = true; + } + + if (seen_semicolon || eatToken(p, TOKEN_KEYWORD_ELSE) == 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 }, + }); + } + + parsePayload(p); + const AstNodeIndex else_body = expectBlockExprStatement(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_WHILE, + .main_token = while_token, + .data = { + .lhs = condition, + .rhs = addExtra(p, + (AstNodeIndex[]) { OPT(cont_expr), body, else_body }, + 3), + }, + }); } static AstNodeIndex parseLoopStatement(Parser* p) { @@ -2146,8 +2274,15 @@ static AstNodeIndex parseSwitchExpr(Parser* p) { static void parsePtrPayload(Parser* p) { if (eatToken(p, TOKEN_PIPE) == null_token) return; - eatToken(p, TOKEN_ASTERISK); - expectToken(p, TOKEN_IDENTIFIER); + while (true) { + eatToken(p, TOKEN_ASTERISK); + expectToken(p, TOKEN_IDENTIFIER); + if (p->token_tags[p->tok_i] == TOKEN_COMMA) { + p->tok_i++; + continue; + } + break; + } expectToken(p, TOKEN_PIPE); } diff --git a/parser_test.zig b/parser_test.zig index 84ff6ad330..22d7a46d13 100644 --- a/parser_test.zig +++ b/parser_test.zig @@ -3544,6 +3544,346 @@ test "zig fmt: doc comments before struct field" { ); } +test "zig fmt: enum declaration" { + try testCanonical( + \\const E = enum { + \\ Ok, + \\ SomethingElse = 0, + \\}; + \\ + \\const E2 = enum(u8) { + \\ Ok, + \\ SomethingElse = 255, + \\ SomethingThird, + \\}; + \\ + \\const Ee = extern enum { + \\ Ok, + \\ SomethingElse, + \\ SomethingThird, + \\}; + \\ + \\const Ep = packed enum { + \\ Ok, + \\ SomethingElse, + \\ SomethingThird, + \\}; + \\ + ); +} + +test "zig fmt: union declaration" { + try testCanonical( + \\const U = union { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + \\const Ue = union(enum) { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + \\const E = enum { + \\ Int, + \\ Float, + \\ None, + \\ Bool, + \\}; + \\ + \\const Ue2 = union(E) { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + \\const Eu = extern union { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + ); +} + +test "zig fmt: catch" { + try testCanonical( + \\test "catch" { + \\ const a: anyerror!u8 = 0; + \\ _ = a catch return; + \\ _ = a catch + \\ return; + \\ _ = a catch |err| return; + \\ _ = a catch |err| + \\ return; + \\} + \\ + ); +} + +test "zig fmt: switch" { + try testCanonical( + \\test "switch" { + \\ switch (0) { + \\ 0 => {}, + \\ 1 => unreachable, + \\ 2, 3 => {}, + \\ 4...7 => {}, + \\ 1 + 4 * 3 + 22 => {}, + \\ else => { + \\ const a = 1; + \\ const b = a; + \\ }, + \\ } + \\ + \\ const res = switch (0) { + \\ 0 => 0, + \\ 1 => 2, + \\ 1 => a = 4, + \\ else => 4, + \\ }; + \\ + \\ const Union = union(enum) { + \\ Int: i64, + \\ Float: f64, + \\ }; + \\ + \\ switch (u) { + \\ Union.Int => |int| {}, + \\ Union.Float => |*float| unreachable, + \\ 1 => |a, b| unreachable, + \\ 2 => |*a, b| unreachable, + \\ } + \\} + \\ + ); + + try testTransform( + \\test { + \\ switch (x) { + \\ foo => + \\ "bar", + \\ } + \\} + \\ + , + \\test { + \\ switch (x) { + \\ foo => "bar", + \\ } + \\} + \\ + ); +} + + + +test "zig fmt: for if" { + try testCanonical( + \\test { + \\ for (a) |x| if (x) f(x); + \\ + \\ for (a) |x| if (x) + \\ f(x); + \\ + \\ for (a) |x| if (x) { + \\ f(x); + \\ }; + \\ + \\ for (a) |x| + \\ if (x) + \\ f(x); + \\ + \\ for (a) |x| + \\ if (x) { + \\ f(x); + \\ }; + \\} + \\ + ); +} + +test "zig fmt: if for" { + try testCanonical( + \\test { + \\ if (a) for (x) |x| f(x); + \\ + \\ if (a) for (x) |x| + \\ f(x); + \\ + \\ if (a) for (x) |x| { + \\ f(x); + \\ }; + \\ + \\ if (a) + \\ for (x) |x| + \\ f(x); + \\ + \\ if (a) + \\ for (x) |x| { + \\ f(x); + \\ }; + \\} + \\ + ); +} + +test "zig fmt: while if" { + try testCanonical( + \\test { + \\ while (a) if (x) f(x); + \\ + \\ while (a) if (x) + \\ f(x); + \\ + \\ while (a) if (x) { + \\ f(x); + \\ }; + \\ + \\ while (a) + \\ if (x) + \\ f(x); + \\ + \\ while (a) + \\ if (x) { + \\ f(x); + \\ }; + \\} + \\ + ); +} + +test "zig fmt: if while" { + try testCanonical( + \\test { + \\ if (a) while (x) : (cont) f(x); + \\ + \\ if (a) while (x) : (cont) + \\ f(x); + \\ + \\ if (a) while (x) : (cont) { + \\ f(x); + \\ }; + \\ + \\ if (a) + \\ while (x) : (cont) + \\ f(x); + \\ + \\ if (a) + \\ while (x) : (cont) { + \\ f(x); + \\ }; + \\} + \\ + ); +} + +test "zig fmt: while for" { + try testCanonical( + \\test { + \\ while (a) for (x) |x| f(x); + \\ + \\ while (a) for (x) |x| + \\ f(x); + \\ + \\ while (a) for (x) |x| { + \\ f(x); + \\ }; + \\ + \\ while (a) + \\ for (x) |x| + \\ f(x); + \\ + \\ while (a) + \\ for (x) |x| { + \\ f(x); + \\ }; + \\} + \\ + ); +} + +test "zig fmt: for while" { + try testCanonical( + \\test { + \\ for (a) |a| while (x) |x| f(x); + \\ + \\ for (a) |a| while (x) |x| + \\ f(x); + \\ + \\ for (a) |a| while (x) |x| { + \\ f(x); + \\ }; + \\ + \\ for (a) |a| + \\ while (x) |x| + \\ f(x); + \\ + \\ for (a) |a| + \\ while (x) |x| { + \\ f(x); + \\ }; + \\} + \\ + ); +} + +test "zig fmt: if" { + try testCanonical( + \\test "if" { + \\ if (10 < 0) { + \\ unreachable; + \\ } + \\ + \\ if (10 < 0) unreachable; + \\ + \\ if (10 < 0) { + \\ unreachable; + \\ } else { + \\ const a = 20; + \\ } + \\ + \\ if (10 < 0) { + \\ unreachable; + \\ } else if (5 < 0) { + \\ unreachable; + \\ } else { + \\ const a = 20; + \\ } + \\ + \\ const is_world_broken = if (10 < 0) true else false; + \\ const some_number = 1 + if (10 < 0) 2 else 3; + \\ + \\ const a: ?u8 = 10; + \\ const b: ?u8 = null; + \\ if (a) |v| { + \\ const some = v; + \\ } else if (b) |*v| { + \\ unreachable; + \\ } else { + \\ const some = 10; + \\ } + \\ + \\ const non_null_a = if (a) |v| v else 0; + \\ + \\ const a_err: anyerror!u8 = 0; + \\ if (a_err) |v| { + \\ const p = v; + \\ } else |err| { + \\ unreachable; + \\ } + \\} + \\ + ); +} + + test "Ast header smoke test" { try std.testing.expectEqual(zigNode(c.AST_NODE_IF), Ast.Node.Tag.@"if"); }