From 759bdcde244dd647ddb810b56f48ab47d56ea6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Tue, 17 Feb 2026 12:17:18 +0000 Subject: [PATCH] parser: add warn, assign destructure, fix statement bodies; enable CodeGen.zig - Add warn() for non-fatal parse errors (like Zig's Parse.warn) - Implement finishAssignDestructureExpr for destructuring assignments - Use parseBlockExpr instead of parseBlock in for/while statement bodies - Use parseSingleAssignExpr in switch prongs - Enable x86_64/CodeGen.zig corpus entry Co-Authored-By: Claude Opus 4.6 --- stage0/ast.c | 3 ++- stage0/parser.c | 53 +++++++++++++++++++++++++++++++++++------- stage0/parser.h | 5 ++++ stage0/stages_test.zig | 2 +- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/stage0/ast.c b/stage0/ast.c index e9a1d04eb3..dd89dda6f3 100644 --- a/stage0/ast.c +++ b/stage0/ast.c @@ -66,6 +66,7 @@ Ast astParse(const char* source, const uint32_t len) { }, .extra_data = SLICE_INIT(AstNodeIndex, N), .scratch = SLICE_INIT(AstNodeIndex, N), + .has_warn = false, .err_buf = err_buf, }; @@ -98,7 +99,7 @@ Ast astParse(const char* source, const uint32_t len) { .cap = p.extra_data.cap, .arr = p.extra_data.arr, }, - .has_error = has_error, + .has_error = has_error || p.has_warn, .err_msg = err_msg, }; } diff --git a/stage0/parser.c b/stage0/parser.c index 2c47bec424..f2dcad2a74 100644 --- a/stage0/parser.c +++ b/stage0/parser.c @@ -76,6 +76,7 @@ static AstTokenIndex expectToken(Parser*, TokenizerTag); static AstNodeIndex expectTopLevelDecl(Parser*); static AstNodeIndex expectVarDeclExprStatement(Parser*, AstTokenIndex); static void findNextContainerMember(Parser*); +static AstNodeIndex finishAssignDestructureExpr(Parser*, AstNodeIndex); static AstNodeIndex finishAssignExpr(Parser*, AstNodeIndex); static uint32_t forPrefix(Parser*); static AstSubRange listToSpan(Parser*, const AstNodeIndex*, uint32_t); @@ -946,7 +947,7 @@ static AstNodeIndex expectIfStatement(Parser* p) { } if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (else_required) - fail(p, "expected_semi_or_else"); + warn(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IF_SIMPLE, @@ -1019,7 +1020,7 @@ static AstNodeIndex parseForStatement(Parser* p) { bool else_required = false; bool seen_semicolon = false; AstNodeIndex then_body; - const AstNodeIndex block = parseBlock(p); + const AstNodeIndex block = parseBlockExpr(p); if (block != 0) { then_body = block; } else { @@ -1043,7 +1044,7 @@ static AstNodeIndex parseForStatement(Parser* p) { has_else = true; } else if (inputs == 1) { if (else_required) - fail(p, "expected_semi_or_else"); + warn(p); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { @@ -1056,7 +1057,7 @@ static AstNodeIndex parseForStatement(Parser* p) { }); } else { if (else_required) - fail(p, "expected_semi_or_else"); + warn(p); SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); } @@ -1148,7 +1149,7 @@ static AstNodeIndex parseWhileStatement(Parser* p) { // Statement body: block, or assign expr bool else_required = false; AstNodeIndex body; - const AstNodeIndex block = parseBlock(p); + const AstNodeIndex block = parseBlockExpr(p); if (block != 0) { body = block; } else { @@ -1181,7 +1182,7 @@ static AstNodeIndex parseWhileStatement(Parser* p) { if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (else_required) - fail(p, "expected_semi_or_else"); + warn(p); if (cont_expr != 0) { return addNode(&p->nodes, (AstNodeItem) { @@ -1268,6 +1269,8 @@ static AstNodeIndex parseSingleAssignExpr(Parser* p) { } static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) { + if (p->token_tags[p->tok_i] == TOKEN_COMMA) + return finishAssignDestructureExpr(p, lhs); const AstNodeTag assign_tag = assignOpNode(p->token_tags[p->tok_i]); if (assign_tag == AST_NODE_ROOT) return lhs; @@ -1282,6 +1285,40 @@ static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) { }); } +static AstNodeIndex finishAssignDestructureExpr( + Parser* p, AstNodeIndex first_lhs) { + const uint32_t scratch_top = p->scratch.len; + + SLICE_APPEND(AstNodeIndex, &p->scratch, first_lhs); + + while (eatToken(p, TOKEN_COMMA) != null_token) { + const AstNodeIndex expr = expectExpr(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, expr); + } + + const AstTokenIndex equal_token = expectToken(p, TOKEN_EQUAL); + + const AstNodeIndex rhs = expectExpr(p); + + const uint32_t lhs_count = p->scratch.len - scratch_top; + assert(lhs_count > 1); + + const AstNodeIndex extra_start = p->extra_data.len; + SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, lhs_count + 1); + p->extra_data.arr[p->extra_data.len++] = lhs_count; + memcpy(p->extra_data.arr + p->extra_data.len, &p->scratch.arr[scratch_top], + lhs_count * sizeof(AstNodeIndex)); + p->extra_data.len += lhs_count; + p->scratch.len = scratch_top; + + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ASSIGN_DESTRUCTURE, + .main_token = equal_token, + .data = { .lhs = extra_start, .rhs = rhs }, + }); +} + static AstNodeTag assignOpNode(TokenizerTag tok) { switch (tok) { case TOKEN_EQUAL: @@ -2963,7 +3000,7 @@ static AstNodeIndex parseSwitchProng(Parser* p) { // switch_case_one, the items are in data[0] and the body // (expectSingleAssignExpr) is in data[1]. Since items don't go // through extra_data here, ordering doesn't matter. - const AstNodeIndex case_body = parseAssignExpr(p); + const AstNodeIndex case_body = parseSingleAssignExpr(p); if (case_body == 0) { fail(p, "expected expression"); } @@ -2990,7 +3027,7 @@ static AstNodeIndex parseSwitchProng(Parser* p) { = listToSpan(p, &p->scratch.arr[items_old_len], items_len); const AstNodeIndex extra_idx = addExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2); - const AstNodeIndex case_body = parseAssignExpr(p); + const AstNodeIndex case_body = parseSingleAssignExpr(p); if (case_body == 0) { fail(p, "expected expression"); } diff --git a/stage0/parser.h b/stage0/parser.h index 448194d8c9..202405f0b9 100644 --- a/stage0/parser.h +++ b/stage0/parser.h @@ -22,6 +22,7 @@ typedef struct { AstNodeList nodes; AstNodeIndexSlice extra_data; AstNodeIndexSlice scratch; + bool has_warn; jmp_buf error_jmp; char* err_buf; } Parser; @@ -37,6 +38,10 @@ _Noreturn static inline void fail(Parser* p, const char* msg) { longjmp(p->error_jmp, 1); } +// warn records a non-fatal parse error (like Zig's Parse.warn). +// Unlike fail(), parsing continues. +static inline void warn(Parser* p) { p->has_warn = true; } + Parser* parserInit(const char* source, uint32_t len); void parserDeinit(Parser* parser); void parseRoot(Parser* parser); diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig index 65a34f40e4..7299808846 100644 --- a/stage0/stages_test.zig +++ b/stage0/stages_test.zig @@ -993,7 +993,7 @@ const corpus_files = .{ "../src/arch/wasm/Mir.zig", "../src/arch/x86_64/abi.zig", "../src/arch/x86_64/bits.zig", - //"../src/arch/x86_64/CodeGen.zig", + "../src/arch/x86_64/CodeGen.zig", "../src/arch/x86_64/Disassembler.zig", "../src/arch/x86_64/Emit.zig", "../src/arch/x86_64/encoder.zig",