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>
This commit is contained in:
2026-02-10 13:13:45 +00:00
parent 9f77f5a234
commit bf632b9d6b
2 changed files with 132 additions and 5 deletions

View File

@@ -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:;

View File

@@ -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();
\\ }
\\}
\\
);
}