parser: implement for loops, port for/while loop test

Implement in parser.c:
- forPrefix: parse for input expressions and capture variables
- parseForExpr: for_simple and for AST nodes with optional else
- Handle for and while in parsePrimaryTypeExpr for top-level usage

Remove stale cppcheck knownConditionTrueFalse suppression.

Port test "top-level for/while loop".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-10 19:23:30 +00:00
parent 40b7c19848
commit 37ae8b01d1
3 changed files with 116 additions and 8 deletions

View File

@@ -88,7 +88,6 @@ pub fn build(b: *std.Build) !void {
"--suppress=checkersReport",
"--suppress=unusedFunction", // TODO remove after plumbing is done
"--suppress=unusedStructMember", // TODO remove after plumbing is done
"--suppress=knownConditionTrueFalse", // TODO remove after plumbing is done
});
for (all_c_files) |cfile| cppcheck.addFileArg(b.path(cfile));
cppcheck.expectExitCode(0);

112
parser.c
View File

@@ -38,6 +38,7 @@ static AstNodeIndex parseAssignExpr(Parser*);
static void parsePtrPayload(Parser*);
static void parsePayload(Parser*);
static AstNodeIndex parseSwitchExpr(Parser*);
static AstNodeIndex parseForExpr(Parser*);
typedef struct {
enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag;
@@ -525,9 +526,11 @@ static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
.main_token = nextToken(p),
.data = {},
});
case TOKEN_KEYWORD_INLINE:
case TOKEN_KEYWORD_FOR:
return parseForExpr(p);
case TOKEN_KEYWORD_WHILE:
return parseWhileExpr(p);
case TOKEN_KEYWORD_INLINE:
case TOKEN_PERIOD:
switch (p->token_tags[p->tok_i + 1]) {
case TOKEN_IDENTIFIER: {
@@ -1309,16 +1312,110 @@ static AstTokenIndex parseBlockLabel(Parser* p) {
return null_node;
}
static AstNodeIndex parseForStatement(Parser* p) {
const AstNodeIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
// forPrefix parses the for prefix: (expr, expr, ...) |captures|.
// Returns the number of input expressions. The inputs are appended
// to the scratch buffer.
static uint32_t forPrefix(Parser* p) {
const uint32_t start = p->scratch.len;
expectToken(p, TOKEN_L_PAREN);
while (true) {
AstNodeIndex input = expectExpr(p);
if (eatToken(p, TOKEN_ELLIPSIS2) != null_token) {
const AstTokenIndex ellipsis = p->tok_i - 1;
const AstNodeIndex end = parseExpr(p);
input = addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_FOR_RANGE,
.main_token = ellipsis,
.data = { .lhs = input, .rhs = end },
});
}
SLICE_APPEND(AstNodeIndex, &p->scratch, input);
if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
p->tok_i++;
continue;
}
expectToken(p, TOKEN_R_PAREN);
break;
}
const uint32_t inputs = p->scratch.len - start;
// Parse payload |a, *b, c|
if (eatToken(p, TOKEN_PIPE) != null_token) {
while (true) {
eatToken(p, TOKEN_ASTERISK);
expectToken(p, TOKEN_IDENTIFIER);
if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
p->tok_i++;
continue;
}
expectToken(p, TOKEN_PIPE);
break;
}
}
return inputs;
}
static AstNodeIndex parseForExpr(Parser* p) {
const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
if (for_token == null_token)
return null_node;
(void)for_token;
fprintf(stderr, "parseForStatement cannot parse for statements\n");
return 0; // tcc
const uint32_t scratch_top = p->scratch.len;
const uint32_t inputs = forPrefix(p);
const AstNodeIndex then_expr = expectExpr(p);
if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) {
parsePayload(p);
SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr);
const AstNodeIndex else_expr = expectExpr(p);
SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr);
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_expr },
});
}
SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr);
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 parseForStatement(Parser* p) { return parseForExpr(p); }
static AstNodeIndex parseWhileExpr(Parser* p) {
const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
if (while_token == null_token)
@@ -2050,8 +2147,9 @@ static AstNodeIndex parsePrimaryExpr(Parser* p) {
}
case TOKEN_KEYWORD_WHILE:
return parseWhileExpr(p);
case TOKEN_KEYWORD_INLINE:
case TOKEN_KEYWORD_FOR:
return parseForExpr(p);
case TOKEN_KEYWORD_INLINE:
fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
exit(1);
return 0; // tcc

View File

@@ -624,6 +624,17 @@ test "zig fmt: respect line breaks before functions" {
);
}
test "zig fmt: top-level for/while loop" {
try testCanonical(
\\for (foo) |_| foo
\\
);
try testCanonical(
\\while (foo) |_| foo
\\
);
}
test "zig fmt: simple top level comptime block" {
try testCanonical(
\\// line comment