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:
57
parser.c
57
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:;
|
||||
|
||||
@@ -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();
|
||||
\\ }
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user