zig0

my attempts at zig bootstrapping in C
Log | Files | Refs | README | LICENSE

commit b229e001ff650b117f3103502ff4bb4765d226da (tree)
parent e471e4564a27564ff4eb6db4a3d53070252a7341
Author: Motiejus Jakštys <motiejus.jakstys@chronosphere.io>
Date:   Tue, 10 Feb 2026 12:58:44 +0000

parser: add struct/array init literal tests

Port tests from upstream parser_test.zig:
- "anon struct literal 0/1/2/3 element" (with and without comma)
- "struct literal 0/1 element"

Implement in parser.c:
- parseFieldInit: parse .field = expr field initializers
- parseInitList: full struct/array init list parsing
- parseCurlySuffixExpr: use parseInitList for X{...} syntax
- parsePrimaryTypeExpr: handle .{...} anonymous init and .ident
  enum literals
- Empty X{} produces struct_init_one (matching upstream behavior)

Fix zigData in parser_test.zig:
- array_init_one uses node_and_node (not node_and_opt_node)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Mparser.c | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mparser_test.zig | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 318 insertions(+), 4 deletions(-)

diff --git a/parser.c b/parser.c @@ -28,6 +28,7 @@ static AstNodeIndex expectSemicolon(Parser*); static AstTokenIndex expectToken(Parser*, TokenizerTag); static AstNodeIndex parseFnProto(Parser*); static Members parseContainerMembers(Parser*); +static AstNodeIndex parseInitList(Parser*, AstNodeIndex, AstTokenIndex); typedef struct { enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag; @@ -512,6 +513,29 @@ static AstNodeIndex parsePrimaryTypeExpr(Parser* p) { case TOKEN_KEYWORD_FOR: case TOKEN_KEYWORD_WHILE: case TOKEN_PERIOD: + switch (p->token_tags[p->tok_i + 1]) { + case TOKEN_IDENTIFIER: { + const AstTokenIndex dot = nextToken(p); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_ENUM_LITERAL, + .main_token = nextToken(p), + .data = { .lhs = dot, .rhs = 0 }, + }); + } + case TOKEN_L_BRACE: { + // Anonymous struct/array init: .{ ... } + const AstTokenIndex lbrace = p->tok_i + 1; + p->tok_i = lbrace + 1; + return parseInitList(p, null_node, lbrace); + } + default: + fprintf(stderr, + "parsePrimaryTypeExpr: unsupported period suffix %s\n", + tokenizerGetTagString(p->token_tags[p->tok_i + 1])); + exit(1); + } + return 0; // tcc case TOKEN_KEYWORD_ERROR: fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n", tokenizerGetTagString(tok)); @@ -987,6 +1011,204 @@ static AstTokenIndex parseBreakLabel(Parser* p) { return expectToken(p, TOKEN_IDENTIFIER); } +// parseFieldInit tries to parse .field_name = expr; returns 0 if not a +// field init +static AstNodeIndex parseFieldInit(Parser* p) { + if (p->token_tags[p->tok_i] == TOKEN_PERIOD + && p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER + && p->token_tags[p->tok_i + 2] == TOKEN_EQUAL) { + p->tok_i += 3; + return expectExpr(p); + } + return null_node; +} + +// parseInitList parses the contents of { ... } for struct/array init. +// lhs is the type expression (0 for anonymous .{...}). +// lbrace is the lbrace token index. +static AstNodeIndex parseInitList( + Parser* p, AstNodeIndex lhs, AstTokenIndex lbrace) { + CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch))) + = initCleanupScratch(p); + + const AstNodeIndex field_init = parseFieldInit(p); + if (field_init != 0) { + // Struct init + SLICE_APPEND(AstNodeIndex, &p->scratch, field_init); + while (true) { + if (p->token_tags[p->tok_i] == TOKEN_COMMA) + p->tok_i++; + else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) { + p->tok_i++; + break; + } else { + fprintf( + stderr, "parseInitList: expected , or } in struct init\n"); + exit(1); + } + if (eatToken(p, TOKEN_R_BRACE) != null_token) + break; + const AstNodeIndex next = parseFieldInit(p); + assert(next != 0); + SLICE_APPEND(AstNodeIndex, &p->scratch, next); + } + const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; + const uint32_t inits_len = p->scratch.len - scratch_top.old_len; + if (lhs == 0) { + // Anonymous struct init: .{...} + switch (inits_len) { + case 0: + case 1: + case 2: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma + ? AST_NODE_STRUCT_INIT_DOT_TWO_COMMA + : AST_NODE_STRUCT_INIT_DOT_TWO, + .main_token = lbrace, + .data = { + .lhs = inits_len >= 1 + ? p->scratch.arr[scratch_top.old_len] + : 0, + .rhs = inits_len >= 2 + ? p->scratch.arr[scratch_top.old_len + 1] + : 0, + }, + }); + default:; + const AstSubRange span = listToSpan( + p, &p->scratch.arr[scratch_top.old_len], inits_len); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_STRUCT_INIT_DOT_COMMA + : AST_NODE_STRUCT_INIT_DOT, + .main_token = lbrace, + .data = { .lhs = span.start, .rhs = span.end }, + }); + } + } + // Named struct init: X{...} + switch (inits_len) { + case 0: + case 1: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_STRUCT_INIT_ONE_COMMA + : AST_NODE_STRUCT_INIT_ONE, + .main_token = lbrace, + .data = { + .lhs = lhs, + .rhs = inits_len >= 1 + ? p->scratch.arr[scratch_top.old_len] + : 0, + }, + }); + default:; + const AstSubRange span = listToSpan( + p, &p->scratch.arr[scratch_top.old_len], inits_len); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_STRUCT_INIT_COMMA + : AST_NODE_STRUCT_INIT, + .main_token = lbrace, + .data = { + .lhs = lhs, + .rhs = addExtra(p, + (AstNodeIndex[]) { span.start, span.end }, 2), + }, + }); + } + } + + // Array init or empty init + while (true) { + if (eatToken(p, TOKEN_R_BRACE) != null_token) + break; + const AstNodeIndex elem = expectExpr(p); + SLICE_APPEND(AstNodeIndex, &p->scratch, elem); + if (p->token_tags[p->tok_i] == TOKEN_COMMA) + p->tok_i++; + else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) { + p->tok_i++; + break; + } else { + fprintf(stderr, "parseInitList: expected , or } in array init\n"); + exit(1); + } + } + + const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; + const uint32_t elems_len = p->scratch.len - scratch_top.old_len; + if (lhs == 0) { + // Anonymous array init: .{a, b, ...} + switch (elems_len) { + case 0: + case 1: + case 2: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_ARRAY_INIT_DOT_TWO_COMMA + : AST_NODE_ARRAY_INIT_DOT_TWO, + .main_token = lbrace, + .data = { + .lhs = elems_len >= 1 + ? p->scratch.arr[scratch_top.old_len] + : 0, + .rhs = elems_len >= 2 + ? p->scratch.arr[scratch_top.old_len + 1] + : 0, + }, + }); + default:; + const AstSubRange span = listToSpan( + p, &p->scratch.arr[scratch_top.old_len], elems_len); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_ARRAY_INIT_DOT_COMMA + : AST_NODE_ARRAY_INIT_DOT, + .main_token = lbrace, + .data = { .lhs = span.start, .rhs = span.end }, + }); + } + } + // Named init: X{a, b, ...} + switch (elems_len) { + case 0: + // Empty init X{} — treat as struct init + return addNode(&p->nodes, + (AstNodeItem) { + .tag = AST_NODE_STRUCT_INIT_ONE, + .main_token = lbrace, + .data = { .lhs = lhs, .rhs = 0 }, + }); + case 1: + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_ARRAY_INIT_ONE_COMMA + : AST_NODE_ARRAY_INIT_ONE, + .main_token = lbrace, + .data = { + .lhs = lhs, + .rhs = p->scratch.arr[scratch_top.old_len], + }, + }); + default:; + const AstSubRange span + = listToSpan(p, &p->scratch.arr[scratch_top.old_len], elems_len); + return addNode(&p->nodes, + (AstNodeItem) { + .tag = comma ? AST_NODE_ARRAY_INIT_COMMA + : AST_NODE_ARRAY_INIT, + .main_token = lbrace, + .data = { + .lhs = lhs, + .rhs = addExtra(p, + (AstNodeIndex[]) { span.start, span.end }, 2), + }, + }); + } +} + static AstNodeIndex parseCurlySuffixExpr(Parser* p) { const AstNodeIndex lhs = parseTypeExpr(p); if (lhs == 0) @@ -996,9 +1218,7 @@ static AstNodeIndex parseCurlySuffixExpr(Parser* p) { if (lbrace == null_token) return lhs; - fprintf(stderr, "parseCurlySuffixExpr is not implemented\n"); - exit(1); - return 0; // tcc + return parseInitList(p, lhs, lbrace); } typedef struct { diff --git a/parser_test.zig b/parser_test.zig @@ -310,9 +310,13 @@ fn zigData(tag: Ast.Node.Tag, lhs: u32, rhs: u32) Ast.Node.Data { .struct_init_one_comma, .container_field_init, .aligned_var_decl, + => .{ .node_and_opt_node = .{ toIndex(lhs), toOptIndex(rhs) } }, + + // .node_and_node (array_init_one uses node_and_node, not + // node_and_opt_node) .array_init_one, .array_init_one_comma, - => .{ .node_and_opt_node = .{ toIndex(lhs), toOptIndex(rhs) } }, + => .{ .node_and_node = .{ toIndex(lhs), toIndex(rhs) } }, // .opt_node_and_node .ptr_type_aligned, @@ -932,3 +936,93 @@ test "zig fmt: array types last token" { \\ ); } + +test "zig fmt: anon struct literal 0 element" { + try testCanonical( + \\test { + \\ const x = .{}; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 1 element" { + try testCanonical( + \\test { + \\ const x = .{ .a = b }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 1 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ .a = b, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 2 element" { + try testCanonical( + \\test { + \\ const x = .{ .a = b, .c = d }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 2 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ .a = b, + \\ .c = d, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 3 element" { + try testCanonical( + \\test { + \\ const x = .{ .a = b, .c = d, .e = f }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 3 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ .a = b, + \\ .c = d, + \\ .e = f, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: struct literal 0 element" { + try testCanonical( + \\test { + \\ const x = X{}; + \\} + \\ + ); +} + +test "zig fmt: struct literal 1 element" { + try testCanonical( + \\test { + \\ const x = X{ .a = b }; + \\} + \\ + ); +}