From 37ae8b01d1a040a58bceabe4ff9efdabc82ecc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Tue, 10 Feb 2026 19:23:30 +0000 Subject: [PATCH] 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) --- build.zig | 1 - parser.c | 112 +++++++++++++++++++++++++++++++++++++++++++++--- parser_test.zig | 11 +++++ 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/build.zig b/build.zig index b66f665e06..8b43b13fbb 100644 --- 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 index 292823909c..ddca1de313 100644 --- 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 index 29af78a31f..b86947e0c0 100644 --- 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