commit bf632b9d6b2db4b141c94a7707a1d0943b727bc1 (tree)
parent 9f77f5a2343718fa29eb939c9e8f55a26a05d1d5
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Tue, 10 Feb 2026 13:13:45 +0000
parser: add if expression and statement tests
Port tests from upstream parser_test.zig:
- "if statement"
- "respect line breaks in if-else"
- "if nested"
- "remove empty lines at start/end of block"
Implement in parser.c:
- parseIfExpr: if/else expression parsing with payloads
- parsePtrPayload, parsePayload: |value| and |*value| handling
- Handle block-terminated expressions without semicolons in
expectVarDeclExprStatement
Fix zigData in parser_test.zig:
- if, while, while_cont use node_and_extra (not node_and_node)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | parser.c | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | parser_test.zig | | | 80 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
2 files changed, 132 insertions(+), 5 deletions(-)
diff --git a/parser.c b/parser.c
@@ -1515,14 +1515,65 @@ static AstNodeIndex expectExpr(Parser* p) {
return node;
}
+static void parsePtrPayload(Parser* p) {
+ if (eatToken(p, TOKEN_PIPE) == null_token)
+ return;
+ eatToken(p, TOKEN_ASTERISK);
+ expectToken(p, TOKEN_IDENTIFIER);
+ expectToken(p, TOKEN_PIPE);
+}
+
+static void parsePayload(Parser* p) {
+ if (eatToken(p, TOKEN_PIPE) == null_token)
+ return;
+ expectToken(p, TOKEN_IDENTIFIER);
+ expectToken(p, TOKEN_PIPE);
+}
+
+static AstNodeIndex parseIfExpr(Parser* p) {
+ const AstTokenIndex if_token = eatToken(p, TOKEN_KEYWORD_IF);
+ if (if_token == null_token)
+ return null_node;
+
+ expectToken(p, TOKEN_L_PAREN);
+ const AstNodeIndex condition = expectExpr(p);
+ expectToken(p, TOKEN_R_PAREN);
+ parsePtrPayload(p);
+
+ const AstNodeIndex then_expr = expectExpr(p);
+
+ if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_IF_SIMPLE,
+ .main_token = if_token,
+ .data = { .lhs = condition, .rhs = then_expr },
+ });
+ }
+
+ parsePayload(p);
+ const AstNodeIndex else_expr = expectExpr(p);
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_IF,
+ .main_token = if_token,
+ .data = {
+ .lhs = condition,
+ .rhs = addExtra(p,
+ (AstNodeIndex[]) { then_expr, else_expr }, 2),
+ },
+ });
+}
+
static AstNodeIndex parsePrimaryExpr(Parser* p) {
const char* tok = tokenizerGetTagString(p->token_tags[p->tok_i]);
switch (p->token_tags[p->tok_i]) {
case TOKEN_KEYWORD_ASM:
- case TOKEN_KEYWORD_IF:
fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
exit(1);
break;
+ case TOKEN_KEYWORD_IF:
+ return parseIfExpr(p);
case TOKEN_KEYWORD_BREAK:
return addNode(
&p->nodes,
@@ -1670,6 +1721,9 @@ static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
case TOKEN_SEMICOLON:
p->tok_i++;
return lhs;
+ 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];
@@ -1736,7 +1790,6 @@ static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
case TOKEN_KEYWORD_SUSPEND:
case TOKEN_KEYWORD_DEFER:
case TOKEN_KEYWORD_ERRDEFER:
- case TOKEN_KEYWORD_IF:
case TOKEN_KEYWORD_ENUM:
case TOKEN_KEYWORD_STRUCT:
case TOKEN_KEYWORD_UNION:;
diff --git a/parser_test.zig b/parser_test.zig
@@ -412,13 +412,15 @@ fn zigData(tag: Ast.Node.Tag, lhs: u32, rhs: u32) Ast.Node.Data {
=> .{ .node_and_node = .{ toIndex(lhs), toIndex(rhs) } },
.while_simple,
- .while_cont,
- .@"while",
.for_simple,
.if_simple,
- .@"if",
=> .{ .node_and_node = .{ toIndex(lhs), toIndex(rhs) } },
+ .while_cont,
+ .@"while",
+ .@"if",
+ => .{ .node_and_extra = .{ toIndex(lhs), toExtraIndex(rhs) } },
+
.for_range,
=> .{ .node_and_opt_node = .{ toIndex(lhs), toOptIndex(rhs) } },
@@ -1594,3 +1596,75 @@ test "zig fmt: function call with multiline argument" {
\\
);
}
+
+test "zig fmt: if statement" {
+ try testCanonical(
+ \\test "" {
+ \\ if (optional()) |some|
+ \\ bar = some.foo();
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: respect line breaks in if-else" {
+ try testCanonical(
+ \\comptime {
+ \\ return if (cond) a else b;
+ \\ return if (cond)
+ \\ a
+ \\ else
+ \\ b;
+ \\ return if (cond)
+ \\ a
+ \\ else if (cond)
+ \\ b
+ \\ else
+ \\ c;
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: if nested" {
+ try testCanonical(
+ \\pub fn foo() void {
+ \\ return if ((aInt & bInt) >= 0)
+ \\ if (aInt < bInt)
+ \\ GE_LESS
+ \\ else if (aInt == bInt)
+ \\ GE_EQUAL
+ \\ else
+ \\ GE_GREATER
+ \\ // comment
+ \\ else if (aInt > bInt)
+ \\ GE_LESS
+ \\ else if (aInt == bInt)
+ \\ GE_EQUAL
+ \\ else
+ \\ GE_GREATER;
+ \\ // comment
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: remove empty lines at start/end of block" {
+ try testTransform(
+ \\test {
+ \\
+ \\ if (foo) {
+ \\ foo();
+ \\ }
+ \\
+ \\}
+ \\
+ ,
+ \\test {
+ \\ if (foo) {
+ \\ foo();
+ \\ }
+ \\}
+ \\
+ );
+}