commit 37ae8b01d1a040a58bceabe4ff9efdabc82ecc1a (tree)
parent 40b7c198485e9627b90d678a5b8f94ea3d38a6c8
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Tue, 10 Feb 2026 19:23:30 +0000
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>
Diffstat:
3 files changed, 116 insertions(+), 8 deletions(-)
diff --git a/build.zig b/build.zig
@@ -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);
diff --git a/parser.c b/parser.c
@@ -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
diff --git a/parser_test.zig b/parser_test.zig
@@ -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