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) <noreply@anthropic.com>
This commit is contained in:
143
parser.c
143
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);
|
||||
}
|
||||
|
||||
|
||||
340
parser_test.zig
340
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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user