authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-03 14:20:49-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-02-03 14:20:49-05:00
log60935decd318498529a016eeb1379d943a7e830d
tree666ab5ab7608ac30307b2ecaf3f2c4ed0720ba92
parent4c7f8286d53bf3df22b1a4596cfdd38d3736240e
parent81c27c74bc8ccc8087b75c5d4eb1b350ad907cd0
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14523 from ziglang/zon

introduce Zig Object Notation and use it for the build manifest file (build.zig.zon)

15 files changed, 4561 insertions(+), 4098 deletions(-)

CMakeLists.txt+1-1
...@@ -513,7 +513,7 @@ set(ZIG_STAGE2_SOURCES...@@ -513,7 +513,7 @@ set(ZIG_STAGE2_SOURCES
513 "${CMAKE_SOURCE_DIR}/lib/std/zig/Ast.zig"513 "${CMAKE_SOURCE_DIR}/lib/std/zig/Ast.zig"
514 "${CMAKE_SOURCE_DIR}/lib/std/zig/CrossTarget.zig"514 "${CMAKE_SOURCE_DIR}/lib/std/zig/CrossTarget.zig"
515 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"515 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"
516 "${CMAKE_SOURCE_DIR}/lib/std/zig/parse.zig"516 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
517 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"517 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
518 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"518 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
519 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"519 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
lib/std/Build.zig+2-2
...@@ -1496,8 +1496,8 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {...@@ -1496,8 +1496,8 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
1496 }1496 }
1497 }1497 }
14981498
1499 const full_path = b.pathFromRoot("build.zig.ini");1499 const full_path = b.pathFromRoot("build.zig.zon");
1500 std.debug.print("no dependency named '{s}' in '{s}'\n", .{ name, full_path });1500 std.debug.print("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file.\n", .{ name, full_path });
1501 std.process.exit(1);1501 std.process.exit(1);
1502}1502}
15031503
lib/std/Build/OptionsStep.zig+1-1
...@@ -367,5 +367,5 @@ test "OptionsStep" {...@@ -367,5 +367,5 @@ test "OptionsStep" {
367 \\367 \\
368 , options.contents.items);368 , options.contents.items);
369369
370 _ = try std.zig.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0));370 _ = try std.zig.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0), .zig);
371}371}
lib/std/array_hash_map.zig+2-1
...@@ -1145,7 +1145,8 @@ pub fn ArrayHashMapUnmanaged(...@@ -1145,7 +1145,8 @@ pub fn ArrayHashMapUnmanaged(
1145 }1145 }
11461146
1147 /// Create a copy of the hash map which can be modified separately.1147 /// Create a copy of the hash map which can be modified separately.
1148 /// The copy uses the same context and allocator as this instance.1148 /// The copy uses the same context as this instance, but is allocated
1149 /// with the provided allocator.
1149 pub fn clone(self: Self, allocator: Allocator) !Self {1150 pub fn clone(self: Self, allocator: Allocator) !Self {
1150 if (@sizeOf(ByIndexContext) != 0)1151 if (@sizeOf(ByIndexContext) != 0)
1151 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call cloneContext instead.");1152 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call cloneContext instead.");
lib/std/zig.zig-1
...@@ -8,7 +8,6 @@ pub const Tokenizer = tokenizer.Tokenizer;...@@ -8,7 +8,6 @@ pub const Tokenizer = tokenizer.Tokenizer;
8pub const fmtId = fmt.fmtId;8pub const fmtId = fmt.fmtId;
9pub const fmtEscapes = fmt.fmtEscapes;9pub const fmtEscapes = fmt.fmtEscapes;
10pub const isValidId = fmt.isValidId;10pub const isValidId = fmt.isValidId;
11pub const parse = @import("zig/parse.zig").parse;
12pub const string_literal = @import("zig/string_literal.zig");11pub const string_literal = @import("zig/string_literal.zig");
13pub const number_literal = @import("zig/number_literal.zig");12pub const number_literal = @import("zig/number_literal.zig");
14pub const primitives = @import("zig/primitives.zig");13pub const primitives = @import("zig/primitives.zig");
lib/std/zig/Ast.zig+73-9
...@@ -1,4 +1,8 @@...@@ -1,4 +1,8 @@
1//! Abstract Syntax Tree for Zig source code.1//! Abstract Syntax Tree for Zig source code.
2//! For Zig syntax, the root node is at nodes[0] and contains the list of
3//! sub-nodes.
4//! For Zon syntax, the root node is at nodes[0] and contains lhs as the node
5//! index of the main expression.
26
3/// Reference to externally-owned data.7/// Reference to externally-owned data.
4source: [:0]const u8,8source: [:0]const u8,
...@@ -11,13 +15,6 @@ extra_data: []Node.Index,...@@ -11,13 +15,6 @@ extra_data: []Node.Index,
1115
12errors: []const Error,16errors: []const Error,
1317
14const std = @import("../std.zig");
15const assert = std.debug.assert;
16const testing = std.testing;
17const mem = std.mem;
18const Token = std.zig.Token;
19const Ast = @This();
20
21pub const TokenIndex = u32;18pub const TokenIndex = u32;
22pub const ByteOffset = u32;19pub const ByteOffset = u32;
2320
...@@ -34,7 +31,7 @@ pub const Location = struct {...@@ -34,7 +31,7 @@ pub const Location = struct {
34 line_end: usize,31 line_end: usize,
35};32};
3633
37pub fn deinit(tree: *Ast, gpa: mem.Allocator) void {34pub fn deinit(tree: *Ast, gpa: Allocator) void {
38 tree.tokens.deinit(gpa);35 tree.tokens.deinit(gpa);
39 tree.nodes.deinit(gpa);36 tree.nodes.deinit(gpa);
40 gpa.free(tree.extra_data);37 gpa.free(tree.extra_data);
...@@ -48,11 +45,69 @@ pub const RenderError = error{...@@ -48,11 +45,69 @@ pub const RenderError = error{
48 OutOfMemory,45 OutOfMemory,
49};46};
5047
48pub const Mode = enum { zig, zon };
49
50/// Result should be freed with tree.deinit() when there are
51/// no more references to any of the tokens or nodes.
52pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!Ast {
53 var tokens = Ast.TokenList{};
54 defer tokens.deinit(gpa);
55
56 // Empirically, the zig std lib has an 8:1 ratio of source bytes to token count.
57 const estimated_token_count = source.len / 8;
58 try tokens.ensureTotalCapacity(gpa, estimated_token_count);
59
60 var tokenizer = std.zig.Tokenizer.init(source);
61 while (true) {
62 const token = tokenizer.next();
63 try tokens.append(gpa, .{
64 .tag = token.tag,
65 .start = @intCast(u32, token.loc.start),
66 });
67 if (token.tag == .eof) break;
68 }
69
70 var parser: Parse = .{
71 .source = source,
72 .gpa = gpa,
73 .token_tags = tokens.items(.tag),
74 .token_starts = tokens.items(.start),
75 .errors = .{},
76 .nodes = .{},
77 .extra_data = .{},
78 .scratch = .{},
79 .tok_i = 0,
80 };
81 defer parser.errors.deinit(gpa);
82 defer parser.nodes.deinit(gpa);
83 defer parser.extra_data.deinit(gpa);
84 defer parser.scratch.deinit(gpa);
85
86 // Empirically, Zig source code has a 2:1 ratio of tokens to AST nodes.
87 // Make sure at least 1 so we can use appendAssumeCapacity on the root node below.
88 const estimated_node_count = (tokens.len + 2) / 2;
89 try parser.nodes.ensureTotalCapacity(gpa, estimated_node_count);
90
91 switch (mode) {
92 .zig => try parser.parseRoot(),
93 .zon => try parser.parseZon(),
94 }
95
96 // TODO experiment with compacting the MultiArrayList slices here
97 return Ast{
98 .source = source,
99 .tokens = tokens.toOwnedSlice(),
100 .nodes = parser.nodes.toOwnedSlice(),
101 .extra_data = try parser.extra_data.toOwnedSlice(gpa),
102 .errors = try parser.errors.toOwnedSlice(gpa),
103 };
104}
105
51/// `gpa` is used for allocating the resulting formatted source code, as well as106/// `gpa` is used for allocating the resulting formatted source code, as well as
52/// for allocating extra stack memory if needed, because this function utilizes recursion.107/// for allocating extra stack memory if needed, because this function utilizes recursion.
53/// Note: that's not actually true yet, see https://github.com/ziglang/zig/issues/1006.108/// Note: that's not actually true yet, see https://github.com/ziglang/zig/issues/1006.
54/// Caller owns the returned slice of bytes, allocated with `gpa`.109/// Caller owns the returned slice of bytes, allocated with `gpa`.
55pub fn render(tree: Ast, gpa: mem.Allocator) RenderError![]u8 {110pub fn render(tree: Ast, gpa: Allocator) RenderError![]u8 {
56 var buffer = std.ArrayList(u8).init(gpa);111 var buffer = std.ArrayList(u8).init(gpa);
57 defer buffer.deinit();112 defer buffer.deinit();
58113
...@@ -3347,3 +3402,12 @@ pub const Node = struct {...@@ -3347,3 +3402,12 @@ pub const Node = struct {
3347 rparen: TokenIndex,3402 rparen: TokenIndex,
3348 };3403 };
3349};3404};
3405
3406const std = @import("../std.zig");
3407const assert = std.debug.assert;
3408const testing = std.testing;
3409const mem = std.mem;
3410const Token = std.zig.Token;
3411const Ast = @This();
3412const Allocator = std.mem.Allocator;
3413const Parse = @import("Parse.zig");
lib/std/zig/Parse.zig created+3825
...@@ -0,0 +1,3825 @@
1//! Represents in-progress parsing, will be converted to an Ast after completion.
2
3pub const Error = error{ParseError} || Allocator.Error;
4
5gpa: Allocator,
6source: []const u8,
7token_tags: []const Token.Tag,
8token_starts: []const Ast.ByteOffset,
9tok_i: TokenIndex,
10errors: std.ArrayListUnmanaged(AstError),
11nodes: Ast.NodeList,
12extra_data: std.ArrayListUnmanaged(Node.Index),
13scratch: std.ArrayListUnmanaged(Node.Index),
14
15const SmallSpan = union(enum) {
16 zero_or_one: Node.Index,
17 multi: Node.SubRange,
18};
19
20const Members = struct {
21 len: usize,
22 lhs: Node.Index,
23 rhs: Node.Index,
24 trailing: bool,
25
26 fn toSpan(self: Members, p: *Parse) !Node.SubRange {
27 if (self.len <= 2) {
28 const nodes = [2]Node.Index{ self.lhs, self.rhs };
29 return p.listToSpan(nodes[0..self.len]);
30 } else {
31 return Node.SubRange{ .start = self.lhs, .end = self.rhs };
32 }
33 }
34};
35
36fn listToSpan(p: *Parse, list: []const Node.Index) !Node.SubRange {
37 try p.extra_data.appendSlice(p.gpa, list);
38 return Node.SubRange{
39 .start = @intCast(Node.Index, p.extra_data.items.len - list.len),
40 .end = @intCast(Node.Index, p.extra_data.items.len),
41 };
42}
43
44fn addNode(p: *Parse, elem: Ast.NodeList.Elem) Allocator.Error!Node.Index {
45 const result = @intCast(Node.Index, p.nodes.len);
46 try p.nodes.append(p.gpa, elem);
47 return result;
48}
49
50fn setNode(p: *Parse, i: usize, elem: Ast.NodeList.Elem) Node.Index {
51 p.nodes.set(i, elem);
52 return @intCast(Node.Index, i);
53}
54
55fn reserveNode(p: *Parse, tag: Ast.Node.Tag) !usize {
56 try p.nodes.resize(p.gpa, p.nodes.len + 1);
57 p.nodes.items(.tag)[p.nodes.len - 1] = tag;
58 return p.nodes.len - 1;
59}
60
61fn unreserveNode(p: *Parse, node_index: usize) void {
62 if (p.nodes.len == node_index) {
63 p.nodes.resize(p.gpa, p.nodes.len - 1) catch unreachable;
64 } else {
65 // There is zombie node left in the tree, let's make it as inoffensive as possible
66 // (sadly there's no no-op node)
67 p.nodes.items(.tag)[node_index] = .unreachable_literal;
68 p.nodes.items(.main_token)[node_index] = p.tok_i;
69 }
70}
71
72fn addExtra(p: *Parse, extra: anytype) Allocator.Error!Node.Index {
73 const fields = std.meta.fields(@TypeOf(extra));
74 try p.extra_data.ensureUnusedCapacity(p.gpa, fields.len);
75 const result = @intCast(u32, p.extra_data.items.len);
76 inline for (fields) |field| {
77 comptime assert(field.type == Node.Index);
78 p.extra_data.appendAssumeCapacity(@field(extra, field.name));
79 }
80 return result;
81}
82
83fn warnExpected(p: *Parse, expected_token: Token.Tag) error{OutOfMemory}!void {
84 @setCold(true);
85 try p.warnMsg(.{
86 .tag = .expected_token,
87 .token = p.tok_i,
88 .extra = .{ .expected_tag = expected_token },
89 });
90}
91
92fn warn(p: *Parse, error_tag: AstError.Tag) error{OutOfMemory}!void {
93 @setCold(true);
94 try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i });
95}
96
97fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void {
98 @setCold(true);
99 switch (msg.tag) {
100 .expected_semi_after_decl,
101 .expected_semi_after_stmt,
102 .expected_comma_after_field,
103 .expected_comma_after_arg,
104 .expected_comma_after_param,
105 .expected_comma_after_initializer,
106 .expected_comma_after_switch_prong,
107 .expected_semi_or_else,
108 .expected_semi_or_lbrace,
109 .expected_token,
110 .expected_block,
111 .expected_block_or_assignment,
112 .expected_block_or_expr,
113 .expected_block_or_field,
114 .expected_expr,
115 .expected_expr_or_assignment,
116 .expected_fn,
117 .expected_inlinable,
118 .expected_labelable,
119 .expected_param_list,
120 .expected_prefix_expr,
121 .expected_primary_type_expr,
122 .expected_pub_item,
123 .expected_return_type,
124 .expected_suffix_op,
125 .expected_type_expr,
126 .expected_var_decl,
127 .expected_var_decl_or_fn,
128 .expected_loop_payload,
129 .expected_container,
130 => if (msg.token != 0 and !p.tokensOnSameLine(msg.token - 1, msg.token)) {
131 var copy = msg;
132 copy.token_is_prev = true;
133 copy.token -= 1;
134 return p.errors.append(p.gpa, copy);
135 },
136 else => {},
137 }
138 try p.errors.append(p.gpa, msg);
139}
140
141fn fail(p: *Parse, tag: Ast.Error.Tag) error{ ParseError, OutOfMemory } {
142 @setCold(true);
143 return p.failMsg(.{ .tag = tag, .token = p.tok_i });
144}
145
146fn failExpected(p: *Parse, expected_token: Token.Tag) error{ ParseError, OutOfMemory } {
147 @setCold(true);
148 return p.failMsg(.{
149 .tag = .expected_token,
150 .token = p.tok_i,
151 .extra = .{ .expected_tag = expected_token },
152 });
153}
154
155fn failMsg(p: *Parse, msg: Ast.Error) error{ ParseError, OutOfMemory } {
156 @setCold(true);
157 try p.warnMsg(msg);
158 return error.ParseError;
159}
160
161/// Root <- skip container_doc_comment? ContainerMembers eof
162pub fn parseRoot(p: *Parse) !void {
163 // Root node must be index 0.
164 p.nodes.appendAssumeCapacity(.{
165 .tag = .root,
166 .main_token = 0,
167 .data = undefined,
168 });
169 const root_members = try p.parseContainerMembers();
170 const root_decls = try root_members.toSpan(p);
171 if (p.token_tags[p.tok_i] != .eof) {
172 try p.warnExpected(.eof);
173 }
174 p.nodes.items(.data)[0] = .{
175 .lhs = root_decls.start,
176 .rhs = root_decls.end,
177 };
178}
179
180/// Parse in ZON mode. Subset of the language.
181/// TODO: set a flag in Parse struct, and honor that flag
182/// by emitting compilation errors when non-zon nodes are encountered.
183pub fn parseZon(p: *Parse) !void {
184 // We must use index 0 so that 0 can be used as null elsewhere.
185 p.nodes.appendAssumeCapacity(.{
186 .tag = .root,
187 .main_token = 0,
188 .data = undefined,
189 });
190 const node_index = p.expectExpr() catch |err| switch (err) {
191 error.ParseError => {
192 assert(p.errors.items.len > 0);
193 return;
194 },
195 else => |e| return e,
196 };
197 if (p.token_tags[p.tok_i] != .eof) {
198 try p.warnExpected(.eof);
199 }
200 p.nodes.items(.data)[0] = .{
201 .lhs = node_index,
202 .rhs = undefined,
203 };
204}
205
206/// ContainerMembers <- ContainerDeclarations (ContainerField COMMA)* (ContainerField / ContainerDeclarations)
207///
208/// ContainerDeclarations
209/// <- TestDecl ContainerDeclarations
210/// / ComptimeDecl ContainerDeclarations
211/// / doc_comment? KEYWORD_pub? Decl ContainerDeclarations
212/// /
213///
214/// ComptimeDecl <- KEYWORD_comptime Block
215fn parseContainerMembers(p: *Parse) !Members {
216 const scratch_top = p.scratch.items.len;
217 defer p.scratch.shrinkRetainingCapacity(scratch_top);
218
219 var field_state: union(enum) {
220 /// No fields have been seen.
221 none,
222 /// Currently parsing fields.
223 seen,
224 /// Saw fields and then a declaration after them.
225 /// Payload is first token of previous declaration.
226 end: Node.Index,
227 /// There was a declaration between fields, don't report more errors.
228 err,
229 } = .none;
230
231 var last_field: TokenIndex = undefined;
232
233 // Skip container doc comments.
234 while (p.eatToken(.container_doc_comment)) |_| {}
235
236 var trailing = false;
237 while (true) {
238 const doc_comment = try p.eatDocComments();
239
240 switch (p.token_tags[p.tok_i]) {
241 .keyword_test => {
242 if (doc_comment) |some| {
243 try p.warnMsg(.{ .tag = .test_doc_comment, .token = some });
244 }
245 const test_decl_node = try p.expectTestDeclRecoverable();
246 if (test_decl_node != 0) {
247 if (field_state == .seen) {
248 field_state = .{ .end = test_decl_node };
249 }
250 try p.scratch.append(p.gpa, test_decl_node);
251 }
252 trailing = false;
253 },
254 .keyword_comptime => switch (p.token_tags[p.tok_i + 1]) {
255 .l_brace => {
256 if (doc_comment) |some| {
257 try p.warnMsg(.{ .tag = .comptime_doc_comment, .token = some });
258 }
259 const comptime_token = p.nextToken();
260 const block = p.parseBlock() catch |err| switch (err) {
261 error.OutOfMemory => return error.OutOfMemory,
262 error.ParseError => blk: {
263 p.findNextContainerMember();
264 break :blk null_node;
265 },
266 };
267 if (block != 0) {
268 const comptime_node = try p.addNode(.{
269 .tag = .@"comptime",
270 .main_token = comptime_token,
271 .data = .{
272 .lhs = block,
273 .rhs = undefined,
274 },
275 });
276 if (field_state == .seen) {
277 field_state = .{ .end = comptime_node };
278 }
279 try p.scratch.append(p.gpa, comptime_node);
280 }
281 trailing = false;
282 },
283 else => {
284 const identifier = p.tok_i;
285 defer last_field = identifier;
286 const container_field = p.expectContainerField() catch |err| switch (err) {
287 error.OutOfMemory => return error.OutOfMemory,
288 error.ParseError => {
289 p.findNextContainerMember();
290 continue;
291 },
292 };
293 switch (field_state) {
294 .none => field_state = .seen,
295 .err, .seen => {},
296 .end => |node| {
297 try p.warnMsg(.{
298 .tag = .decl_between_fields,
299 .token = p.nodes.items(.main_token)[node],
300 });
301 try p.warnMsg(.{
302 .tag = .previous_field,
303 .is_note = true,
304 .token = last_field,
305 });
306 try p.warnMsg(.{
307 .tag = .next_field,
308 .is_note = true,
309 .token = identifier,
310 });
311 // Continue parsing; error will be reported later.
312 field_state = .err;
313 },
314 }
315 try p.scratch.append(p.gpa, container_field);
316 switch (p.token_tags[p.tok_i]) {
317 .comma => {
318 p.tok_i += 1;
319 trailing = true;
320 continue;
321 },
322 .r_brace, .eof => {
323 trailing = false;
324 break;
325 },
326 else => {},
327 }
328 // There is not allowed to be a decl after a field with no comma.
329 // Report error but recover parser.
330 try p.warn(.expected_comma_after_field);
331 p.findNextContainerMember();
332 },
333 },
334 .keyword_pub => {
335 p.tok_i += 1;
336 const top_level_decl = try p.expectTopLevelDeclRecoverable();
337 if (top_level_decl != 0) {
338 if (field_state == .seen) {
339 field_state = .{ .end = top_level_decl };
340 }
341 try p.scratch.append(p.gpa, top_level_decl);
342 }
343 trailing = p.token_tags[p.tok_i - 1] == .semicolon;
344 },
345 .keyword_usingnamespace => {
346 const node = try p.expectUsingNamespaceRecoverable();
347 if (node != 0) {
348 if (field_state == .seen) {
349 field_state = .{ .end = node };
350 }
351 try p.scratch.append(p.gpa, node);
352 }
353 trailing = p.token_tags[p.tok_i - 1] == .semicolon;
354 },
355 .keyword_const,
356 .keyword_var,
357 .keyword_threadlocal,
358 .keyword_export,
359 .keyword_extern,
360 .keyword_inline,
361 .keyword_noinline,
362 .keyword_fn,
363 => {
364 const top_level_decl = try p.expectTopLevelDeclRecoverable();
365 if (top_level_decl != 0) {
366 if (field_state == .seen) {
367 field_state = .{ .end = top_level_decl };
368 }
369 try p.scratch.append(p.gpa, top_level_decl);
370 }
371 trailing = p.token_tags[p.tok_i - 1] == .semicolon;
372 },
373 .eof, .r_brace => {
374 if (doc_comment) |tok| {
375 try p.warnMsg(.{
376 .tag = .unattached_doc_comment,
377 .token = tok,
378 });
379 }
380 break;
381 },
382 else => {
383 const c_container = p.parseCStyleContainer() catch |err| switch (err) {
384 error.OutOfMemory => return error.OutOfMemory,
385 error.ParseError => false,
386 };
387 if (c_container) continue;
388
389 const identifier = p.tok_i;
390 defer last_field = identifier;
391 const container_field = p.expectContainerField() catch |err| switch (err) {
392 error.OutOfMemory => return error.OutOfMemory,
393 error.ParseError => {
394 p.findNextContainerMember();
395 continue;
396 },
397 };
398 switch (field_state) {
399 .none => field_state = .seen,
400 .err, .seen => {},
401 .end => |node| {
402 try p.warnMsg(.{
403 .tag = .decl_between_fields,
404 .token = p.nodes.items(.main_token)[node],
405 });
406 try p.warnMsg(.{
407 .tag = .previous_field,
408 .is_note = true,
409 .token = last_field,
410 });
411 try p.warnMsg(.{
412 .tag = .next_field,
413 .is_note = true,
414 .token = identifier,
415 });
416 // Continue parsing; error will be reported later.
417 field_state = .err;
418 },
419 }
420 try p.scratch.append(p.gpa, container_field);
421 switch (p.token_tags[p.tok_i]) {
422 .comma => {
423 p.tok_i += 1;
424 trailing = true;
425 continue;
426 },
427 .r_brace, .eof => {
428 trailing = false;
429 break;
430 },
431 else => {},
432 }
433 // There is not allowed to be a decl after a field with no comma.
434 // Report error but recover parser.
435 try p.warn(.expected_comma_after_field);
436 if (p.token_tags[p.tok_i] == .semicolon and p.token_tags[identifier] == .identifier) {
437 try p.warnMsg(.{
438 .tag = .var_const_decl,
439 .is_note = true,
440 .token = identifier,
441 });
442 }
443 p.findNextContainerMember();
444 continue;
445 },
446 }
447 }
448
449 const items = p.scratch.items[scratch_top..];
450 switch (items.len) {
451 0 => return Members{
452 .len = 0,
453 .lhs = 0,
454 .rhs = 0,
455 .trailing = trailing,
456 },
457 1 => return Members{
458 .len = 1,
459 .lhs = items[0],
460 .rhs = 0,
461 .trailing = trailing,
462 },
463 2 => return Members{
464 .len = 2,
465 .lhs = items[0],
466 .rhs = items[1],
467 .trailing = trailing,
468 },
469 else => {
470 const span = try p.listToSpan(items);
471 return Members{
472 .len = items.len,
473 .lhs = span.start,
474 .rhs = span.end,
475 .trailing = trailing,
476 };
477 },
478 }
479}
480
481/// Attempts to find next container member by searching for certain tokens
482fn findNextContainerMember(p: *Parse) void {
483 var level: u32 = 0;
484 while (true) {
485 const tok = p.nextToken();
486 switch (p.token_tags[tok]) {
487 // Any of these can start a new top level declaration.
488 .keyword_test,
489 .keyword_comptime,
490 .keyword_pub,
491 .keyword_export,
492 .keyword_extern,
493 .keyword_inline,
494 .keyword_noinline,
495 .keyword_usingnamespace,
496 .keyword_threadlocal,
497 .keyword_const,
498 .keyword_var,
499 .keyword_fn,
500 => {
501 if (level == 0) {
502 p.tok_i -= 1;
503 return;
504 }
505 },
506 .identifier => {
507 if (p.token_tags[tok + 1] == .comma and level == 0) {
508 p.tok_i -= 1;
509 return;
510 }
511 },
512 .comma, .semicolon => {
513 // this decl was likely meant to end here
514 if (level == 0) {
515 return;
516 }
517 },
518 .l_paren, .l_bracket, .l_brace => level += 1,
519 .r_paren, .r_bracket => {
520 if (level != 0) level -= 1;
521 },
522 .r_brace => {
523 if (level == 0) {
524 // end of container, exit
525 p.tok_i -= 1;
526 return;
527 }
528 level -= 1;
529 },
530 .eof => {
531 p.tok_i -= 1;
532 return;
533 },
534 else => {},
535 }
536 }
537}
538
539/// Attempts to find the next statement by searching for a semicolon
540fn findNextStmt(p: *Parse) void {
541 var level: u32 = 0;
542 while (true) {
543 const tok = p.nextToken();
544 switch (p.token_tags[tok]) {
545 .l_brace => level += 1,
546 .r_brace => {
547 if (level == 0) {
548 p.tok_i -= 1;
549 return;
550 }
551 level -= 1;
552 },
553 .semicolon => {
554 if (level == 0) {
555 return;
556 }
557 },
558 .eof => {
559 p.tok_i -= 1;
560 return;
561 },
562 else => {},
563 }
564 }
565}
566
567/// TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
568fn expectTestDecl(p: *Parse) !Node.Index {
569 const test_token = p.assertToken(.keyword_test);
570 const name_token = switch (p.token_tags[p.nextToken()]) {
571 .string_literal, .identifier => p.tok_i - 1,
572 else => blk: {
573 p.tok_i -= 1;
574 break :blk null;
575 },
576 };
577 const block_node = try p.parseBlock();
578 if (block_node == 0) return p.fail(.expected_block);
579 return p.addNode(.{
580 .tag = .test_decl,
581 .main_token = test_token,
582 .data = .{
583 .lhs = name_token orelse 0,
584 .rhs = block_node,
585 },
586 });
587}
588
589fn expectTestDeclRecoverable(p: *Parse) error{OutOfMemory}!Node.Index {
590 return p.expectTestDecl() catch |err| switch (err) {
591 error.OutOfMemory => return error.OutOfMemory,
592 error.ParseError => {
593 p.findNextContainerMember();
594 return null_node;
595 },
596 };
597}
598
599/// Decl
600/// <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block)
601/// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
602/// / KEYWORD_usingnamespace Expr SEMICOLON
603fn expectTopLevelDecl(p: *Parse) !Node.Index {
604 const extern_export_inline_token = p.nextToken();
605 var is_extern: bool = false;
606 var expect_fn: bool = false;
607 var expect_var_or_fn: bool = false;
608 switch (p.token_tags[extern_export_inline_token]) {
609 .keyword_extern => {
610 _ = p.eatToken(.string_literal);
611 is_extern = true;
612 expect_var_or_fn = true;
613 },
614 .keyword_export => expect_var_or_fn = true,
615 .keyword_inline, .keyword_noinline => expect_fn = true,
616 else => p.tok_i -= 1,
617 }
618 const fn_proto = try p.parseFnProto();
619 if (fn_proto != 0) {
620 switch (p.token_tags[p.tok_i]) {
621 .semicolon => {
622 p.tok_i += 1;
623 return fn_proto;
624 },
625 .l_brace => {
626 if (is_extern) {
627 try p.warnMsg(.{ .tag = .extern_fn_body, .token = extern_export_inline_token });
628 return null_node;
629 }
630 const fn_decl_index = try p.reserveNode(.fn_decl);
631 errdefer p.unreserveNode(fn_decl_index);
632
633 const body_block = try p.parseBlock();
634 assert(body_block != 0);
635 return p.setNode(fn_decl_index, .{
636 .tag = .fn_decl,
637 .main_token = p.nodes.items(.main_token)[fn_proto],
638 .data = .{
639 .lhs = fn_proto,
640 .rhs = body_block,
641 },
642 });
643 },
644 else => {
645 // Since parseBlock only return error.ParseError on
646 // a missing '}' we can assume this function was
647 // supposed to end here.
648 try p.warn(.expected_semi_or_lbrace);
649 return null_node;
650 },
651 }
652 }
653 if (expect_fn) {
654 try p.warn(.expected_fn);
655 return error.ParseError;
656 }
657
658 const thread_local_token = p.eatToken(.keyword_threadlocal);
659 const var_decl = try p.parseVarDecl();
660 if (var_decl != 0) {
661 try p.expectSemicolon(.expected_semi_after_decl, false);
662 return var_decl;
663 }
664 if (thread_local_token != null) {
665 return p.fail(.expected_var_decl);
666 }
667 if (expect_var_or_fn) {
668 return p.fail(.expected_var_decl_or_fn);
669 }
670 if (p.token_tags[p.tok_i] != .keyword_usingnamespace) {
671 return p.fail(.expected_pub_item);
672 }
673 return p.expectUsingNamespace();
674}
675
676fn expectTopLevelDeclRecoverable(p: *Parse) error{OutOfMemory}!Node.Index {
677 return p.expectTopLevelDecl() catch |err| switch (err) {
678 error.OutOfMemory => return error.OutOfMemory,
679 error.ParseError => {
680 p.findNextContainerMember();
681 return null_node;
682 },
683 };
684}
685
686fn expectUsingNamespace(p: *Parse) !Node.Index {
687 const usingnamespace_token = p.assertToken(.keyword_usingnamespace);
688 const expr = try p.expectExpr();
689 try p.expectSemicolon(.expected_semi_after_decl, false);
690 return p.addNode(.{
691 .tag = .@"usingnamespace",
692 .main_token = usingnamespace_token,
693 .data = .{
694 .lhs = expr,
695 .rhs = undefined,
696 },
697 });
698}
699
700fn expectUsingNamespaceRecoverable(p: *Parse) error{OutOfMemory}!Node.Index {
701 return p.expectUsingNamespace() catch |err| switch (err) {
702 error.OutOfMemory => return error.OutOfMemory,
703 error.ParseError => {
704 p.findNextContainerMember();
705 return null_node;
706 },
707 };
708}
709
710/// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
711fn parseFnProto(p: *Parse) !Node.Index {
712 const fn_token = p.eatToken(.keyword_fn) orelse return null_node;
713
714 // We want the fn proto node to be before its children in the array.
715 const fn_proto_index = try p.reserveNode(.fn_proto);
716 errdefer p.unreserveNode(fn_proto_index);
717
718 _ = p.eatToken(.identifier);
719 const params = try p.parseParamDeclList();
720 const align_expr = try p.parseByteAlign();
721 const addrspace_expr = try p.parseAddrSpace();
722 const section_expr = try p.parseLinkSection();
723 const callconv_expr = try p.parseCallconv();
724 _ = p.eatToken(.bang);
725
726 const return_type_expr = try p.parseTypeExpr();
727 if (return_type_expr == 0) {
728 // most likely the user forgot to specify the return type.
729 // Mark return type as invalid and try to continue.
730 try p.warn(.expected_return_type);
731 }
732
733 if (align_expr == 0 and section_expr == 0 and callconv_expr == 0 and addrspace_expr == 0) {
734 switch (params) {
735 .zero_or_one => |param| return p.setNode(fn_proto_index, .{
736 .tag = .fn_proto_simple,
737 .main_token = fn_token,
738 .data = .{
739 .lhs = param,
740 .rhs = return_type_expr,
741 },
742 }),
743 .multi => |span| {
744 return p.setNode(fn_proto_index, .{
745 .tag = .fn_proto_multi,
746 .main_token = fn_token,
747 .data = .{
748 .lhs = try p.addExtra(Node.SubRange{
749 .start = span.start,
750 .end = span.end,
751 }),
752 .rhs = return_type_expr,
753 },
754 });
755 },
756 }
757 }
758 switch (params) {
759 .zero_or_one => |param| return p.setNode(fn_proto_index, .{
760 .tag = .fn_proto_one,
761 .main_token = fn_token,
762 .data = .{
763 .lhs = try p.addExtra(Node.FnProtoOne{
764 .param = param,
765 .align_expr = align_expr,
766 .addrspace_expr = addrspace_expr,
767 .section_expr = section_expr,
768 .callconv_expr = callconv_expr,
769 }),
770 .rhs = return_type_expr,
771 },
772 }),
773 .multi => |span| {
774 return p.setNode(fn_proto_index, .{
775 .tag = .fn_proto,
776 .main_token = fn_token,
777 .data = .{
778 .lhs = try p.addExtra(Node.FnProto{
779 .params_start = span.start,
780 .params_end = span.end,
781 .align_expr = align_expr,
782 .addrspace_expr = addrspace_expr,
783 .section_expr = section_expr,
784 .callconv_expr = callconv_expr,
785 }),
786 .rhs = return_type_expr,
787 },
788 });
789 },
790 }
791}
792
793/// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? (EQUAL Expr)? SEMICOLON
794fn parseVarDecl(p: *Parse) !Node.Index {
795 const mut_token = p.eatToken(.keyword_const) orelse
796 p.eatToken(.keyword_var) orelse
797 return null_node;
798
799 _ = try p.expectToken(.identifier);
800 const type_node: Node.Index = if (p.eatToken(.colon) == null) 0 else try p.expectTypeExpr();
801 const align_node = try p.parseByteAlign();
802 const addrspace_node = try p.parseAddrSpace();
803 const section_node = try p.parseLinkSection();
804 const init_node: Node.Index = switch (p.token_tags[p.tok_i]) {
805 .equal_equal => blk: {
806 try p.warn(.wrong_equal_var_decl);
807 p.tok_i += 1;
808 break :blk try p.expectExpr();
809 },
810 .equal => blk: {
811 p.tok_i += 1;
812 break :blk try p.expectExpr();
813 },
814 else => 0,
815 };
816 if (section_node == 0 and addrspace_node == 0) {
817 if (align_node == 0) {
818 return p.addNode(.{
819 .tag = .simple_var_decl,
820 .main_token = mut_token,
821 .data = .{
822 .lhs = type_node,
823 .rhs = init_node,
824 },
825 });
826 } else if (type_node == 0) {
827 return p.addNode(.{
828 .tag = .aligned_var_decl,
829 .main_token = mut_token,
830 .data = .{
831 .lhs = align_node,
832 .rhs = init_node,
833 },
834 });
835 } else {
836 return p.addNode(.{
837 .tag = .local_var_decl,
838 .main_token = mut_token,
839 .data = .{
840 .lhs = try p.addExtra(Node.LocalVarDecl{
841 .type_node = type_node,
842 .align_node = align_node,
843 }),
844 .rhs = init_node,
845 },
846 });
847 }
848 } else {
849 return p.addNode(.{
850 .tag = .global_var_decl,
851 .main_token = mut_token,
852 .data = .{
853 .lhs = try p.addExtra(Node.GlobalVarDecl{
854 .type_node = type_node,
855 .align_node = align_node,
856 .addrspace_node = addrspace_node,
857 .section_node = section_node,
858 }),
859 .rhs = init_node,
860 },
861 });
862 }
863}
864
865/// ContainerField
866/// <- doc_comment? KEYWORD_comptime? IDENTIFIER (COLON TypeExpr)? ByteAlign? (EQUAL Expr)?
867/// / doc_comment? KEYWORD_comptime? (IDENTIFIER COLON)? !KEYWORD_fn TypeExpr ByteAlign? (EQUAL Expr)?
868fn expectContainerField(p: *Parse) !Node.Index {
869 var main_token = p.tok_i;
870 _ = p.eatToken(.keyword_comptime);
871 const tuple_like = p.token_tags[p.tok_i] != .identifier or p.token_tags[p.tok_i + 1] != .colon;
872 if (!tuple_like) {
873 main_token = p.assertToken(.identifier);
874 }
875
876 var align_expr: Node.Index = 0;
877 var type_expr: Node.Index = 0;
878 if (p.eatToken(.colon) != null or tuple_like) {
879 type_expr = try p.expectTypeExpr();
880 align_expr = try p.parseByteAlign();
881 }
882
883 const value_expr: Node.Index = if (p.eatToken(.equal) == null) 0 else try p.expectExpr();
884
885 if (align_expr == 0) {
886 return p.addNode(.{
887 .tag = .container_field_init,
888 .main_token = main_token,
889 .data = .{
890 .lhs = type_expr,
891 .rhs = value_expr,
892 },
893 });
894 } else if (value_expr == 0) {
895 return p.addNode(.{
896 .tag = .container_field_align,
897 .main_token = main_token,
898 .data = .{
899 .lhs = type_expr,
900 .rhs = align_expr,
901 },
902 });
903 } else {
904 return p.addNode(.{
905 .tag = .container_field,
906 .main_token = main_token,
907 .data = .{
908 .lhs = type_expr,
909 .rhs = try p.addExtra(Node.ContainerField{
910 .value_expr = value_expr,
911 .align_expr = align_expr,
912 }),
913 },
914 });
915 }
916}
917
918/// Statement
919/// <- KEYWORD_comptime? VarDecl
920/// / KEYWORD_comptime BlockExprStatement
921/// / KEYWORD_nosuspend BlockExprStatement
922/// / KEYWORD_suspend BlockExprStatement
923/// / KEYWORD_defer BlockExprStatement
924/// / KEYWORD_errdefer Payload? BlockExprStatement
925/// / IfStatement
926/// / LabeledStatement
927/// / SwitchExpr
928/// / AssignExpr SEMICOLON
929fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
930 const comptime_token = p.eatToken(.keyword_comptime);
931
932 if (allow_defer_var) {
933 const var_decl = try p.parseVarDecl();
934 if (var_decl != 0) {
935 try p.expectSemicolon(.expected_semi_after_decl, true);
936 return var_decl;
937 }
938 }
939
940 if (comptime_token) |token| {
941 return p.addNode(.{
942 .tag = .@"comptime",
943 .main_token = token,
944 .data = .{
945 .lhs = try p.expectBlockExprStatement(),
946 .rhs = undefined,
947 },
948 });
949 }
950
951 switch (p.token_tags[p.tok_i]) {
952 .keyword_nosuspend => {
953 return p.addNode(.{
954 .tag = .@"nosuspend",
955 .main_token = p.nextToken(),
956 .data = .{
957 .lhs = try p.expectBlockExprStatement(),
958 .rhs = undefined,
959 },
960 });
961 },
962 .keyword_suspend => {
963 const token = p.nextToken();
964 const block_expr = try p.expectBlockExprStatement();
965 return p.addNode(.{
966 .tag = .@"suspend",
967 .main_token = token,
968 .data = .{
969 .lhs = block_expr,
970 .rhs = undefined,
971 },
972 });
973 },
974 .keyword_defer => if (allow_defer_var) return p.addNode(.{
975 .tag = .@"defer",
976 .main_token = p.nextToken(),
977 .data = .{
978 .lhs = undefined,
979 .rhs = try p.expectBlockExprStatement(),
980 },
981 }),
982 .keyword_errdefer => if (allow_defer_var) return p.addNode(.{
983 .tag = .@"errdefer",
984 .main_token = p.nextToken(),
985 .data = .{
986 .lhs = try p.parsePayload(),
987 .rhs = try p.expectBlockExprStatement(),
988 },
989 }),
990 .keyword_switch => return p.expectSwitchExpr(),
991 .keyword_if => return p.expectIfStatement(),
992 .keyword_enum, .keyword_struct, .keyword_union => {
993 const identifier = p.tok_i + 1;
994 if (try p.parseCStyleContainer()) {
995 // Return something so that `expectStatement` is happy.
996 return p.addNode(.{
997 .tag = .identifier,
998 .main_token = identifier,
999 .data = .{
1000 .lhs = undefined,
1001 .rhs = undefined,
1002 },
1003 });
1004 }
1005 },
1006 else => {},
1007 }
1008
1009 const labeled_statement = try p.parseLabeledStatement();
1010 if (labeled_statement != 0) return labeled_statement;
1011
1012 const assign_expr = try p.parseAssignExpr();
1013 if (assign_expr != 0) {
1014 try p.expectSemicolon(.expected_semi_after_stmt, true);
1015 return assign_expr;
1016 }
1017
1018 return null_node;
1019}
1020
1021fn expectStatement(p: *Parse, allow_defer_var: bool) !Node.Index {
1022 const statement = try p.parseStatement(allow_defer_var);
1023 if (statement == 0) {
1024 return p.fail(.expected_statement);
1025 }
1026 return statement;
1027}
1028
1029/// If a parse error occurs, reports an error, but then finds the next statement
1030/// and returns that one instead. If a parse error occurs but there is no following
1031/// statement, returns 0.
1032fn expectStatementRecoverable(p: *Parse) Error!Node.Index {
1033 while (true) {
1034 return p.expectStatement(true) catch |err| switch (err) {
1035 error.OutOfMemory => return error.OutOfMemory,
1036 error.ParseError => {
1037 p.findNextStmt(); // Try to skip to the next statement.
1038 switch (p.token_tags[p.tok_i]) {
1039 .r_brace => return null_node,
1040 .eof => return error.ParseError,
1041 else => continue,
1042 }
1043 },
1044 };
1045 }
1046}
1047
1048/// IfStatement
1049/// <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
1050/// / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
1051fn expectIfStatement(p: *Parse) !Node.Index {
1052 const if_token = p.assertToken(.keyword_if);
1053 _ = try p.expectToken(.l_paren);
1054 const condition = try p.expectExpr();
1055 _ = try p.expectToken(.r_paren);
1056 _ = try p.parsePtrPayload();
1057
1058 // TODO propose to change the syntax so that semicolons are always required
1059 // inside if statements, even if there is an `else`.
1060 var else_required = false;
1061 const then_expr = blk: {
1062 const block_expr = try p.parseBlockExpr();
1063 if (block_expr != 0) break :blk block_expr;
1064 const assign_expr = try p.parseAssignExpr();
1065 if (assign_expr == 0) {
1066 return p.fail(.expected_block_or_assignment);
1067 }
1068 if (p.eatToken(.semicolon)) |_| {
1069 return p.addNode(.{
1070 .tag = .if_simple,
1071 .main_token = if_token,
1072 .data = .{
1073 .lhs = condition,
1074 .rhs = assign_expr,
1075 },
1076 });
1077 }
1078 else_required = true;
1079 break :blk assign_expr;
1080 };
1081 _ = p.eatToken(.keyword_else) orelse {
1082 if (else_required) {
1083 try p.warn(.expected_semi_or_else);
1084 }
1085 return p.addNode(.{
1086 .tag = .if_simple,
1087 .main_token = if_token,
1088 .data = .{
1089 .lhs = condition,
1090 .rhs = then_expr,
1091 },
1092 });
1093 };
1094 _ = try p.parsePayload();
1095 const else_expr = try p.expectStatement(false);
1096 return p.addNode(.{
1097 .tag = .@"if",
1098 .main_token = if_token,
1099 .data = .{
1100 .lhs = condition,
1101 .rhs = try p.addExtra(Node.If{
1102 .then_expr = then_expr,
1103 .else_expr = else_expr,
1104 }),
1105 },
1106 });
1107}
1108
1109/// LabeledStatement <- BlockLabel? (Block / LoopStatement)
1110fn parseLabeledStatement(p: *Parse) !Node.Index {
1111 const label_token = p.parseBlockLabel();
1112 const block = try p.parseBlock();
1113 if (block != 0) return block;
1114
1115 const loop_stmt = try p.parseLoopStatement();
1116 if (loop_stmt != 0) return loop_stmt;
1117
1118 if (label_token != 0) {
1119 const after_colon = p.tok_i;
1120 const node = try p.parseTypeExpr();
1121 if (node != 0) {
1122 const a = try p.parseByteAlign();
1123 const b = try p.parseAddrSpace();
1124 const c = try p.parseLinkSection();
1125 const d = if (p.eatToken(.equal) == null) 0 else try p.expectExpr();
1126 if (a != 0 or b != 0 or c != 0 or d != 0) {
1127 return p.failMsg(.{ .tag = .expected_var_const, .token = label_token });
1128 }
1129 }
1130 return p.failMsg(.{ .tag = .expected_labelable, .token = after_colon });
1131 }
1132
1133 return null_node;
1134}
1135
1136/// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
1137fn parseLoopStatement(p: *Parse) !Node.Index {
1138 const inline_token = p.eatToken(.keyword_inline);
1139
1140 const for_statement = try p.parseForStatement();
1141 if (for_statement != 0) return for_statement;
1142
1143 const while_statement = try p.parseWhileStatement();
1144 if (while_statement != 0) return while_statement;
1145
1146 if (inline_token == null) return null_node;
1147
1148 // If we've seen "inline", there should have been a "for" or "while"
1149 return p.fail(.expected_inlinable);
1150}
1151
1152/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
1153///
1154/// ForStatement
1155/// <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
1156/// / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
1157fn parseForStatement(p: *Parse) !Node.Index {
1158 const for_token = p.eatToken(.keyword_for) orelse return null_node;
1159 _ = try p.expectToken(.l_paren);
1160 const array_expr = try p.expectExpr();
1161 _ = try p.expectToken(.r_paren);
1162 const found_payload = try p.parsePtrIndexPayload();
1163 if (found_payload == 0) try p.warn(.expected_loop_payload);
1164
1165 // TODO propose to change the syntax so that semicolons are always required
1166 // inside while statements, even if there is an `else`.
1167 var else_required = false;
1168 const then_expr = blk: {
1169 const block_expr = try p.parseBlockExpr();
1170 if (block_expr != 0) break :blk block_expr;
1171 const assign_expr = try p.parseAssignExpr();
1172 if (assign_expr == 0) {
1173 return p.fail(.expected_block_or_assignment);
1174 }
1175 if (p.eatToken(.semicolon)) |_| {
1176 return p.addNode(.{
1177 .tag = .for_simple,
1178 .main_token = for_token,
1179 .data = .{
1180 .lhs = array_expr,
1181 .rhs = assign_expr,
1182 },
1183 });
1184 }
1185 else_required = true;
1186 break :blk assign_expr;
1187 };
1188 _ = p.eatToken(.keyword_else) orelse {
1189 if (else_required) {
1190 try p.warn(.expected_semi_or_else);
1191 }
1192 return p.addNode(.{
1193 .tag = .for_simple,
1194 .main_token = for_token,
1195 .data = .{
1196 .lhs = array_expr,
1197 .rhs = then_expr,
1198 },
1199 });
1200 };
1201 return p.addNode(.{
1202 .tag = .@"for",
1203 .main_token = for_token,
1204 .data = .{
1205 .lhs = array_expr,
1206 .rhs = try p.addExtra(Node.If{
1207 .then_expr = then_expr,
1208 .else_expr = try p.expectStatement(false),
1209 }),
1210 },
1211 });
1212}
1213
1214/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
1215///
1216/// WhileStatement
1217/// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
1218/// / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
1219fn parseWhileStatement(p: *Parse) !Node.Index {
1220 const while_token = p.eatToken(.keyword_while) orelse return null_node;
1221 _ = try p.expectToken(.l_paren);
1222 const condition = try p.expectExpr();
1223 _ = try p.expectToken(.r_paren);
1224 _ = try p.parsePtrPayload();
1225 const cont_expr = try p.parseWhileContinueExpr();
1226
1227 // TODO propose to change the syntax so that semicolons are always required
1228 // inside while statements, even if there is an `else`.
1229 var else_required = false;
1230 const then_expr = blk: {
1231 const block_expr = try p.parseBlockExpr();
1232 if (block_expr != 0) break :blk block_expr;
1233 const assign_expr = try p.parseAssignExpr();
1234 if (assign_expr == 0) {
1235 return p.fail(.expected_block_or_assignment);
1236 }
1237 if (p.eatToken(.semicolon)) |_| {
1238 if (cont_expr == 0) {
1239 return p.addNode(.{
1240 .tag = .while_simple,
1241 .main_token = while_token,
1242 .data = .{
1243 .lhs = condition,
1244 .rhs = assign_expr,
1245 },
1246 });
1247 } else {
1248 return p.addNode(.{
1249 .tag = .while_cont,
1250 .main_token = while_token,
1251 .data = .{
1252 .lhs = condition,
1253 .rhs = try p.addExtra(Node.WhileCont{
1254 .cont_expr = cont_expr,
1255 .then_expr = assign_expr,
1256 }),
1257 },
1258 });
1259 }
1260 }
1261 else_required = true;
1262 break :blk assign_expr;
1263 };
1264 _ = p.eatToken(.keyword_else) orelse {
1265 if (else_required) {
1266 try p.warn(.expected_semi_or_else);
1267 }
1268 if (cont_expr == 0) {
1269 return p.addNode(.{
1270 .tag = .while_simple,
1271 .main_token = while_token,
1272 .data = .{
1273 .lhs = condition,
1274 .rhs = then_expr,
1275 },
1276 });
1277 } else {
1278 return p.addNode(.{
1279 .tag = .while_cont,
1280 .main_token = while_token,
1281 .data = .{
1282 .lhs = condition,
1283 .rhs = try p.addExtra(Node.WhileCont{
1284 .cont_expr = cont_expr,
1285 .then_expr = then_expr,
1286 }),
1287 },
1288 });
1289 }
1290 };
1291 _ = try p.parsePayload();
1292 const else_expr = try p.expectStatement(false);
1293 return p.addNode(.{
1294 .tag = .@"while",
1295 .main_token = while_token,
1296 .data = .{
1297 .lhs = condition,
1298 .rhs = try p.addExtra(Node.While{
1299 .cont_expr = cont_expr,
1300 .then_expr = then_expr,
1301 .else_expr = else_expr,
1302 }),
1303 },
1304 });
1305}
1306
1307/// BlockExprStatement
1308/// <- BlockExpr
1309/// / AssignExpr SEMICOLON
1310fn parseBlockExprStatement(p: *Parse) !Node.Index {
1311 const block_expr = try p.parseBlockExpr();
1312 if (block_expr != 0) {
1313 return block_expr;
1314 }
1315 const assign_expr = try p.parseAssignExpr();
1316 if (assign_expr != 0) {
1317 try p.expectSemicolon(.expected_semi_after_stmt, true);
1318 return assign_expr;
1319 }
1320 return null_node;
1321}
1322
1323fn expectBlockExprStatement(p: *Parse) !Node.Index {
1324 const node = try p.parseBlockExprStatement();
1325 if (node == 0) {
1326 return p.fail(.expected_block_or_expr);
1327 }
1328 return node;
1329}
1330
1331/// BlockExpr <- BlockLabel? Block
1332fn parseBlockExpr(p: *Parse) Error!Node.Index {
1333 switch (p.token_tags[p.tok_i]) {
1334 .identifier => {
1335 if (p.token_tags[p.tok_i + 1] == .colon and
1336 p.token_tags[p.tok_i + 2] == .l_brace)
1337 {
1338 p.tok_i += 2;
1339 return p.parseBlock();
1340 } else {
1341 return null_node;
1342 }
1343 },
1344 .l_brace => return p.parseBlock(),
1345 else => return null_node,
1346 }
1347}
1348
1349/// AssignExpr <- Expr (AssignOp Expr)?
1350///
1351/// AssignOp
1352/// <- ASTERISKEQUAL
1353/// / ASTERISKPIPEEQUAL
1354/// / SLASHEQUAL
1355/// / PERCENTEQUAL
1356/// / PLUSEQUAL
1357/// / PLUSPIPEEQUAL
1358/// / MINUSEQUAL
1359/// / MINUSPIPEEQUAL
1360/// / LARROW2EQUAL
1361/// / LARROW2PIPEEQUAL
1362/// / RARROW2EQUAL
1363/// / AMPERSANDEQUAL
1364/// / CARETEQUAL
1365/// / PIPEEQUAL
1366/// / ASTERISKPERCENTEQUAL
1367/// / PLUSPERCENTEQUAL
1368/// / MINUSPERCENTEQUAL
1369/// / EQUAL
1370fn parseAssignExpr(p: *Parse) !Node.Index {
1371 const expr = try p.parseExpr();
1372 if (expr == 0) return null_node;
1373
1374 const tag: Node.Tag = switch (p.token_tags[p.tok_i]) {
1375 .asterisk_equal => .assign_mul,
1376 .slash_equal => .assign_div,
1377 .percent_equal => .assign_mod,
1378 .plus_equal => .assign_add,
1379 .minus_equal => .assign_sub,
1380 .angle_bracket_angle_bracket_left_equal => .assign_shl,
1381 .angle_bracket_angle_bracket_left_pipe_equal => .assign_shl_sat,
1382 .angle_bracket_angle_bracket_right_equal => .assign_shr,
1383 .ampersand_equal => .assign_bit_and,
1384 .caret_equal => .assign_bit_xor,
1385 .pipe_equal => .assign_bit_or,
1386 .asterisk_percent_equal => .assign_mul_wrap,
1387 .plus_percent_equal => .assign_add_wrap,
1388 .minus_percent_equal => .assign_sub_wrap,
1389 .asterisk_pipe_equal => .assign_mul_sat,
1390 .plus_pipe_equal => .assign_add_sat,
1391 .minus_pipe_equal => .assign_sub_sat,
1392 .equal => .assign,
1393 else => return expr,
1394 };
1395 return p.addNode(.{
1396 .tag = tag,
1397 .main_token = p.nextToken(),
1398 .data = .{
1399 .lhs = expr,
1400 .rhs = try p.expectExpr(),
1401 },
1402 });
1403}
1404
1405fn expectAssignExpr(p: *Parse) !Node.Index {
1406 const expr = try p.parseAssignExpr();
1407 if (expr == 0) {
1408 return p.fail(.expected_expr_or_assignment);
1409 }
1410 return expr;
1411}
1412
1413fn parseExpr(p: *Parse) Error!Node.Index {
1414 return p.parseExprPrecedence(0);
1415}
1416
1417fn expectExpr(p: *Parse) Error!Node.Index {
1418 const node = try p.parseExpr();
1419 if (node == 0) {
1420 return p.fail(.expected_expr);
1421 } else {
1422 return node;
1423 }
1424}
1425
1426const Assoc = enum {
1427 left,
1428 none,
1429};
1430
1431const OperInfo = struct {
1432 prec: i8,
1433 tag: Node.Tag,
1434 assoc: Assoc = Assoc.left,
1435};
1436
1437// A table of binary operator information. Higher precedence numbers are
1438// stickier. All operators at the same precedence level should have the same
1439// associativity.
1440const operTable = std.enums.directEnumArrayDefault(Token.Tag, OperInfo, .{ .prec = -1, .tag = Node.Tag.root }, 0, .{
1441 .keyword_or = .{ .prec = 10, .tag = .bool_or },
1442
1443 .keyword_and = .{ .prec = 20, .tag = .bool_and },
1444
1445 .equal_equal = .{ .prec = 30, .tag = .equal_equal, .assoc = Assoc.none },
1446 .bang_equal = .{ .prec = 30, .tag = .bang_equal, .assoc = Assoc.none },
1447 .angle_bracket_left = .{ .prec = 30, .tag = .less_than, .assoc = Assoc.none },
1448 .angle_bracket_right = .{ .prec = 30, .tag = .greater_than, .assoc = Assoc.none },
1449 .angle_bracket_left_equal = .{ .prec = 30, .tag = .less_or_equal, .assoc = Assoc.none },
1450 .angle_bracket_right_equal = .{ .prec = 30, .tag = .greater_or_equal, .assoc = Assoc.none },
1451
1452 .ampersand = .{ .prec = 40, .tag = .bit_and },
1453 .caret = .{ .prec = 40, .tag = .bit_xor },
1454 .pipe = .{ .prec = 40, .tag = .bit_or },
1455 .keyword_orelse = .{ .prec = 40, .tag = .@"orelse" },
1456 .keyword_catch = .{ .prec = 40, .tag = .@"catch" },
1457
1458 .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .shl },
1459 .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .shl_sat },
1460 .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .shr },
1461
1462 .plus = .{ .prec = 60, .tag = .add },
1463 .minus = .{ .prec = 60, .tag = .sub },
1464 .plus_plus = .{ .prec = 60, .tag = .array_cat },
1465 .plus_percent = .{ .prec = 60, .tag = .add_wrap },
1466 .minus_percent = .{ .prec = 60, .tag = .sub_wrap },
1467 .plus_pipe = .{ .prec = 60, .tag = .add_sat },
1468 .minus_pipe = .{ .prec = 60, .tag = .sub_sat },
1469
1470 .pipe_pipe = .{ .prec = 70, .tag = .merge_error_sets },
1471 .asterisk = .{ .prec = 70, .tag = .mul },
1472 .slash = .{ .prec = 70, .tag = .div },
1473 .percent = .{ .prec = 70, .tag = .mod },
1474 .asterisk_asterisk = .{ .prec = 70, .tag = .array_mult },
1475 .asterisk_percent = .{ .prec = 70, .tag = .mul_wrap },
1476 .asterisk_pipe = .{ .prec = 70, .tag = .mul_sat },
1477});
1478
1479fn parseExprPrecedence(p: *Parse, min_prec: i32) Error!Node.Index {
1480 assert(min_prec >= 0);
1481 var node = try p.parsePrefixExpr();
1482 if (node == 0) {
1483 return null_node;
1484 }
1485
1486 var banned_prec: i8 = -1;
1487
1488 while (true) {
1489 const tok_tag = p.token_tags[p.tok_i];
1490 const info = operTable[@intCast(usize, @enumToInt(tok_tag))];
1491 if (info.prec < min_prec) {
1492 break;
1493 }
1494 if (info.prec == banned_prec) {
1495 return p.fail(.chained_comparison_operators);
1496 }
1497
1498 const oper_token = p.nextToken();
1499 // Special-case handling for "catch"
1500 if (tok_tag == .keyword_catch) {
1501 _ = try p.parsePayload();
1502 }
1503 const rhs = try p.parseExprPrecedence(info.prec + 1);
1504 if (rhs == 0) {
1505 try p.warn(.expected_expr);
1506 return node;
1507 }
1508
1509 {
1510 const tok_len = tok_tag.lexeme().?.len;
1511 const char_before = p.source[p.token_starts[oper_token] - 1];
1512 const char_after = p.source[p.token_starts[oper_token] + tok_len];
1513 if (tok_tag == .ampersand and char_after == '&') {
1514 // without types we don't know if '&&' was intended as 'bitwise_and address_of', or a c-style logical_and
1515 // The best the parser can do is recommend changing it to 'and' or ' & &'
1516 try p.warnMsg(.{ .tag = .invalid_ampersand_ampersand, .token = oper_token });
1517 } else if (std.ascii.isWhitespace(char_before) != std.ascii.isWhitespace(char_after)) {
1518 try p.warnMsg(.{ .tag = .mismatched_binary_op_whitespace, .token = oper_token });
1519 }
1520 }
1521
1522 node = try p.addNode(.{
1523 .tag = info.tag,
1524 .main_token = oper_token,
1525 .data = .{
1526 .lhs = node,
1527 .rhs = rhs,
1528 },
1529 });
1530
1531 if (info.assoc == Assoc.none) {
1532 banned_prec = info.prec;
1533 }
1534 }
1535
1536 return node;
1537}
1538
1539/// PrefixExpr <- PrefixOp* PrimaryExpr
1540///
1541/// PrefixOp
1542/// <- EXCLAMATIONMARK
1543/// / MINUS
1544/// / TILDE
1545/// / MINUSPERCENT
1546/// / AMPERSAND
1547/// / KEYWORD_try
1548/// / KEYWORD_await
1549fn parsePrefixExpr(p: *Parse) Error!Node.Index {
1550 const tag: Node.Tag = switch (p.token_tags[p.tok_i]) {
1551 .bang => .bool_not,
1552 .minus => .negation,
1553 .tilde => .bit_not,
1554 .minus_percent => .negation_wrap,
1555 .ampersand => .address_of,
1556 .keyword_try => .@"try",
1557 .keyword_await => .@"await",
1558 else => return p.parsePrimaryExpr(),
1559 };
1560 return p.addNode(.{
1561 .tag = tag,
1562 .main_token = p.nextToken(),
1563 .data = .{
1564 .lhs = try p.expectPrefixExpr(),
1565 .rhs = undefined,
1566 },
1567 });
1568}
1569
1570fn expectPrefixExpr(p: *Parse) Error!Node.Index {
1571 const node = try p.parsePrefixExpr();
1572 if (node == 0) {
1573 return p.fail(.expected_prefix_expr);
1574 }
1575 return node;
1576}
1577
1578/// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
1579///
1580/// PrefixTypeOp
1581/// <- QUESTIONMARK
1582/// / KEYWORD_anyframe MINUSRARROW
1583/// / SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
1584/// / PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
1585/// / ArrayTypeStart
1586///
1587/// SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
1588///
1589/// PtrTypeStart
1590/// <- ASTERISK
1591/// / ASTERISK2
1592/// / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
1593///
1594/// ArrayTypeStart <- LBRACKET Expr (COLON Expr)? RBRACKET
1595fn parseTypeExpr(p: *Parse) Error!Node.Index {
1596 switch (p.token_tags[p.tok_i]) {
1597 .question_mark => return p.addNode(.{
1598 .tag = .optional_type,
1599 .main_token = p.nextToken(),
1600 .data = .{
1601 .lhs = try p.expectTypeExpr(),
1602 .rhs = undefined,
1603 },
1604 }),
1605 .keyword_anyframe => switch (p.token_tags[p.tok_i + 1]) {
1606 .arrow => return p.addNode(.{
1607 .tag = .anyframe_type,
1608 .main_token = p.nextToken(),
1609 .data = .{
1610 .lhs = p.nextToken(),
1611 .rhs = try p.expectTypeExpr(),
1612 },
1613 }),
1614 else => return p.parseErrorUnionExpr(),
1615 },
1616 .asterisk => {
1617 const asterisk = p.nextToken();
1618 const mods = try p.parsePtrModifiers();
1619 const elem_type = try p.expectTypeExpr();
1620 if (mods.bit_range_start != 0) {
1621 return p.addNode(.{
1622 .tag = .ptr_type_bit_range,
1623 .main_token = asterisk,
1624 .data = .{
1625 .lhs = try p.addExtra(Node.PtrTypeBitRange{
1626 .sentinel = 0,
1627 .align_node = mods.align_node,
1628 .addrspace_node = mods.addrspace_node,
1629 .bit_range_start = mods.bit_range_start,
1630 .bit_range_end = mods.bit_range_end,
1631 }),
1632 .rhs = elem_type,
1633 },
1634 });
1635 } else if (mods.addrspace_node != 0) {
1636 return p.addNode(.{
1637 .tag = .ptr_type,
1638 .main_token = asterisk,
1639 .data = .{
1640 .lhs = try p.addExtra(Node.PtrType{
1641 .sentinel = 0,
1642 .align_node = mods.align_node,
1643 .addrspace_node = mods.addrspace_node,
1644 }),
1645 .rhs = elem_type,
1646 },
1647 });
1648 } else {
1649 return p.addNode(.{
1650 .tag = .ptr_type_aligned,
1651 .main_token = asterisk,
1652 .data = .{
1653 .lhs = mods.align_node,
1654 .rhs = elem_type,
1655 },
1656 });
1657 }
1658 },
1659 .asterisk_asterisk => {
1660 const asterisk = p.nextToken();
1661 const mods = try p.parsePtrModifiers();
1662 const elem_type = try p.expectTypeExpr();
1663 const inner: Node.Index = inner: {
1664 if (mods.bit_range_start != 0) {
1665 break :inner try p.addNode(.{
1666 .tag = .ptr_type_bit_range,
1667 .main_token = asterisk,
1668 .data = .{
1669 .lhs = try p.addExtra(Node.PtrTypeBitRange{
1670 .sentinel = 0,
1671 .align_node = mods.align_node,
1672 .addrspace_node = mods.addrspace_node,
1673 .bit_range_start = mods.bit_range_start,
1674 .bit_range_end = mods.bit_range_end,
1675 }),
1676 .rhs = elem_type,
1677 },
1678 });
1679 } else if (mods.addrspace_node != 0) {
1680 break :inner try p.addNode(.{
1681 .tag = .ptr_type,
1682 .main_token = asterisk,
1683 .data = .{
1684 .lhs = try p.addExtra(Node.PtrType{
1685 .sentinel = 0,
1686 .align_node = mods.align_node,
1687 .addrspace_node = mods.addrspace_node,
1688 }),
1689 .rhs = elem_type,
1690 },
1691 });
1692 } else {
1693 break :inner try p.addNode(.{
1694 .tag = .ptr_type_aligned,
1695 .main_token = asterisk,
1696 .data = .{
1697 .lhs = mods.align_node,
1698 .rhs = elem_type,
1699 },
1700 });
1701 }
1702 };
1703 return p.addNode(.{
1704 .tag = .ptr_type_aligned,
1705 .main_token = asterisk,
1706 .data = .{
1707 .lhs = 0,
1708 .rhs = inner,
1709 },
1710 });
1711 },
1712 .l_bracket => switch (p.token_tags[p.tok_i + 1]) {
1713 .asterisk => {
1714 _ = p.nextToken();
1715 const asterisk = p.nextToken();
1716 var sentinel: Node.Index = 0;
1717 if (p.eatToken(.identifier)) |ident| {
1718 const ident_slice = p.source[p.token_starts[ident]..p.token_starts[ident + 1]];
1719 if (!std.mem.eql(u8, std.mem.trimRight(u8, ident_slice, &std.ascii.whitespace), "c")) {
1720 p.tok_i -= 1;
1721 }
1722 } else if (p.eatToken(.colon)) |_| {
1723 sentinel = try p.expectExpr();
1724 }
1725 _ = try p.expectToken(.r_bracket);
1726 const mods = try p.parsePtrModifiers();
1727 const elem_type = try p.expectTypeExpr();
1728 if (mods.bit_range_start == 0) {
1729 if (sentinel == 0 and mods.addrspace_node == 0) {
1730 return p.addNode(.{
1731 .tag = .ptr_type_aligned,
1732 .main_token = asterisk,
1733 .data = .{
1734 .lhs = mods.align_node,
1735 .rhs = elem_type,
1736 },
1737 });
1738 } else if (mods.align_node == 0 and mods.addrspace_node == 0) {
1739 return p.addNode(.{
1740 .tag = .ptr_type_sentinel,
1741 .main_token = asterisk,
1742 .data = .{
1743 .lhs = sentinel,
1744 .rhs = elem_type,
1745 },
1746 });
1747 } else {
1748 return p.addNode(.{
1749 .tag = .ptr_type,
1750 .main_token = asterisk,
1751 .data = .{
1752 .lhs = try p.addExtra(Node.PtrType{
1753 .sentinel = sentinel,
1754 .align_node = mods.align_node,
1755 .addrspace_node = mods.addrspace_node,
1756 }),
1757 .rhs = elem_type,
1758 },
1759 });
1760 }
1761 } else {
1762 return p.addNode(.{
1763 .tag = .ptr_type_bit_range,
1764 .main_token = asterisk,
1765 .data = .{
1766 .lhs = try p.addExtra(Node.PtrTypeBitRange{
1767 .sentinel = sentinel,
1768 .align_node = mods.align_node,
1769 .addrspace_node = mods.addrspace_node,
1770 .bit_range_start = mods.bit_range_start,
1771 .bit_range_end = mods.bit_range_end,
1772 }),
1773 .rhs = elem_type,
1774 },
1775 });
1776 }
1777 },
1778 else => {
1779 const lbracket = p.nextToken();
1780 const len_expr = try p.parseExpr();
1781 const sentinel: Node.Index = if (p.eatToken(.colon)) |_|
1782 try p.expectExpr()
1783 else
1784 0;
1785 _ = try p.expectToken(.r_bracket);
1786 if (len_expr == 0) {
1787 const mods = try p.parsePtrModifiers();
1788 const elem_type = try p.expectTypeExpr();
1789 if (mods.bit_range_start != 0) {
1790 try p.warnMsg(.{
1791 .tag = .invalid_bit_range,
1792 .token = p.nodes.items(.main_token)[mods.bit_range_start],
1793 });
1794 }
1795 if (sentinel == 0 and mods.addrspace_node == 0) {
1796 return p.addNode(.{
1797 .tag = .ptr_type_aligned,
1798 .main_token = lbracket,
1799 .data = .{
1800 .lhs = mods.align_node,
1801 .rhs = elem_type,
1802 },
1803 });
1804 } else if (mods.align_node == 0 and mods.addrspace_node == 0) {
1805 return p.addNode(.{
1806 .tag = .ptr_type_sentinel,
1807 .main_token = lbracket,
1808 .data = .{
1809 .lhs = sentinel,
1810 .rhs = elem_type,
1811 },
1812 });
1813 } else {
1814 return p.addNode(.{
1815 .tag = .ptr_type,
1816 .main_token = lbracket,
1817 .data = .{
1818 .lhs = try p.addExtra(Node.PtrType{
1819 .sentinel = sentinel,
1820 .align_node = mods.align_node,
1821 .addrspace_node = mods.addrspace_node,
1822 }),
1823 .rhs = elem_type,
1824 },
1825 });
1826 }
1827 } else {
1828 switch (p.token_tags[p.tok_i]) {
1829 .keyword_align,
1830 .keyword_const,
1831 .keyword_volatile,
1832 .keyword_allowzero,
1833 .keyword_addrspace,
1834 => return p.fail(.ptr_mod_on_array_child_type),
1835 else => {},
1836 }
1837 const elem_type = try p.expectTypeExpr();
1838 if (sentinel == 0) {
1839 return p.addNode(.{
1840 .tag = .array_type,
1841 .main_token = lbracket,
1842 .data = .{
1843 .lhs = len_expr,
1844 .rhs = elem_type,
1845 },
1846 });
1847 } else {
1848 return p.addNode(.{
1849 .tag = .array_type_sentinel,
1850 .main_token = lbracket,
1851 .data = .{
1852 .lhs = len_expr,
1853 .rhs = try p.addExtra(.{
1854 .elem_type = elem_type,
1855 .sentinel = sentinel,
1856 }),
1857 },
1858 });
1859 }
1860 }
1861 },
1862 },
1863 else => return p.parseErrorUnionExpr(),
1864 }
1865}
1866
1867fn expectTypeExpr(p: *Parse) Error!Node.Index {
1868 const node = try p.parseTypeExpr();
1869 if (node == 0) {
1870 return p.fail(.expected_type_expr);
1871 }
1872 return node;
1873}
1874
1875/// PrimaryExpr
1876/// <- AsmExpr
1877/// / IfExpr
1878/// / KEYWORD_break BreakLabel? Expr?
1879/// / KEYWORD_comptime Expr
1880/// / KEYWORD_nosuspend Expr
1881/// / KEYWORD_continue BreakLabel?
1882/// / KEYWORD_resume Expr
1883/// / KEYWORD_return Expr?
1884/// / BlockLabel? LoopExpr
1885/// / Block
1886/// / CurlySuffixExpr
1887fn parsePrimaryExpr(p: *Parse) !Node.Index {
1888 switch (p.token_tags[p.tok_i]) {
1889 .keyword_asm => return p.expectAsmExpr(),
1890 .keyword_if => return p.parseIfExpr(),
1891 .keyword_break => {
1892 p.tok_i += 1;
1893 return p.addNode(.{
1894 .tag = .@"break",
1895 .main_token = p.tok_i - 1,
1896 .data = .{
1897 .lhs = try p.parseBreakLabel(),
1898 .rhs = try p.parseExpr(),
1899 },
1900 });
1901 },
1902 .keyword_continue => {
1903 p.tok_i += 1;
1904 return p.addNode(.{
1905 .tag = .@"continue",
1906 .main_token = p.tok_i - 1,
1907 .data = .{
1908 .lhs = try p.parseBreakLabel(),
1909 .rhs = undefined,
1910 },
1911 });
1912 },
1913 .keyword_comptime => {
1914 p.tok_i += 1;
1915 return p.addNode(.{
1916 .tag = .@"comptime",
1917 .main_token = p.tok_i - 1,
1918 .data = .{
1919 .lhs = try p.expectExpr(),
1920 .rhs = undefined,
1921 },
1922 });
1923 },
1924 .keyword_nosuspend => {
1925 p.tok_i += 1;
1926 return p.addNode(.{
1927 .tag = .@"nosuspend",
1928 .main_token = p.tok_i - 1,
1929 .data = .{
1930 .lhs = try p.expectExpr(),
1931 .rhs = undefined,
1932 },
1933 });
1934 },
1935 .keyword_resume => {
1936 p.tok_i += 1;
1937 return p.addNode(.{
1938 .tag = .@"resume",
1939 .main_token = p.tok_i - 1,
1940 .data = .{
1941 .lhs = try p.expectExpr(),
1942 .rhs = undefined,
1943 },
1944 });
1945 },
1946 .keyword_return => {
1947 p.tok_i += 1;
1948 return p.addNode(.{
1949 .tag = .@"return",
1950 .main_token = p.tok_i - 1,
1951 .data = .{
1952 .lhs = try p.parseExpr(),
1953 .rhs = undefined,
1954 },
1955 });
1956 },
1957 .identifier => {
1958 if (p.token_tags[p.tok_i + 1] == .colon) {
1959 switch (p.token_tags[p.tok_i + 2]) {
1960 .keyword_inline => {
1961 p.tok_i += 3;
1962 switch (p.token_tags[p.tok_i]) {
1963 .keyword_for => return p.parseForExpr(),
1964 .keyword_while => return p.parseWhileExpr(),
1965 else => return p.fail(.expected_inlinable),
1966 }
1967 },
1968 .keyword_for => {
1969 p.tok_i += 2;
1970 return p.parseForExpr();
1971 },
1972 .keyword_while => {
1973 p.tok_i += 2;
1974 return p.parseWhileExpr();
1975 },
1976 .l_brace => {
1977 p.tok_i += 2;
1978 return p.parseBlock();
1979 },
1980 else => return p.parseCurlySuffixExpr(),
1981 }
1982 } else {
1983 return p.parseCurlySuffixExpr();
1984 }
1985 },
1986 .keyword_inline => {
1987 p.tok_i += 1;
1988 switch (p.token_tags[p.tok_i]) {
1989 .keyword_for => return p.parseForExpr(),
1990 .keyword_while => return p.parseWhileExpr(),
1991 else => return p.fail(.expected_inlinable),
1992 }
1993 },
1994 .keyword_for => return p.parseForExpr(),
1995 .keyword_while => return p.parseWhileExpr(),
1996 .l_brace => return p.parseBlock(),
1997 else => return p.parseCurlySuffixExpr(),
1998 }
1999}
2000
2001/// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
2002fn parseIfExpr(p: *Parse) !Node.Index {
2003 return p.parseIf(expectExpr);
2004}
2005
2006/// Block <- LBRACE Statement* RBRACE
2007fn parseBlock(p: *Parse) !Node.Index {
2008 const lbrace = p.eatToken(.l_brace) orelse return null_node;
2009 const scratch_top = p.scratch.items.len;
2010 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2011 while (true) {
2012 if (p.token_tags[p.tok_i] == .r_brace) break;
2013 const statement = try p.expectStatementRecoverable();
2014 if (statement == 0) break;
2015 try p.scratch.append(p.gpa, statement);
2016 }
2017 _ = try p.expectToken(.r_brace);
2018 const semicolon = (p.token_tags[p.tok_i - 2] == .semicolon);
2019 const statements = p.scratch.items[scratch_top..];
2020 switch (statements.len) {
2021 0 => return p.addNode(.{
2022 .tag = .block_two,
2023 .main_token = lbrace,
2024 .data = .{
2025 .lhs = 0,
2026 .rhs = 0,
2027 },
2028 }),
2029 1 => return p.addNode(.{
2030 .tag = if (semicolon) .block_two_semicolon else .block_two,
2031 .main_token = lbrace,
2032 .data = .{
2033 .lhs = statements[0],
2034 .rhs = 0,
2035 },
2036 }),
2037 2 => return p.addNode(.{
2038 .tag = if (semicolon) .block_two_semicolon else .block_two,
2039 .main_token = lbrace,
2040 .data = .{
2041 .lhs = statements[0],
2042 .rhs = statements[1],
2043 },
2044 }),
2045 else => {
2046 const span = try p.listToSpan(statements);
2047 return p.addNode(.{
2048 .tag = if (semicolon) .block_semicolon else .block,
2049 .main_token = lbrace,
2050 .data = .{
2051 .lhs = span.start,
2052 .rhs = span.end,
2053 },
2054 });
2055 },
2056 }
2057}
2058
2059/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
2060///
2061/// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
2062fn parseForExpr(p: *Parse) !Node.Index {
2063 const for_token = p.eatToken(.keyword_for) orelse return null_node;
2064 _ = try p.expectToken(.l_paren);
2065 const array_expr = try p.expectExpr();
2066 _ = try p.expectToken(.r_paren);
2067 const found_payload = try p.parsePtrIndexPayload();
2068 if (found_payload == 0) try p.warn(.expected_loop_payload);
2069
2070 const then_expr = try p.expectExpr();
2071 _ = p.eatToken(.keyword_else) orelse {
2072 return p.addNode(.{
2073 .tag = .for_simple,
2074 .main_token = for_token,
2075 .data = .{
2076 .lhs = array_expr,
2077 .rhs = then_expr,
2078 },
2079 });
2080 };
2081 const else_expr = try p.expectExpr();
2082 return p.addNode(.{
2083 .tag = .@"for",
2084 .main_token = for_token,
2085 .data = .{
2086 .lhs = array_expr,
2087 .rhs = try p.addExtra(Node.If{
2088 .then_expr = then_expr,
2089 .else_expr = else_expr,
2090 }),
2091 },
2092 });
2093}
2094
2095/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
2096///
2097/// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
2098fn parseWhileExpr(p: *Parse) !Node.Index {
2099 const while_token = p.eatToken(.keyword_while) orelse return null_node;
2100 _ = try p.expectToken(.l_paren);
2101 const condition = try p.expectExpr();
2102 _ = try p.expectToken(.r_paren);
2103 _ = try p.parsePtrPayload();
2104 const cont_expr = try p.parseWhileContinueExpr();
2105
2106 const then_expr = try p.expectExpr();
2107 _ = p.eatToken(.keyword_else) orelse {
2108 if (cont_expr == 0) {
2109 return p.addNode(.{
2110 .tag = .while_simple,
2111 .main_token = while_token,
2112 .data = .{
2113 .lhs = condition,
2114 .rhs = then_expr,
2115 },
2116 });
2117 } else {
2118 return p.addNode(.{
2119 .tag = .while_cont,
2120 .main_token = while_token,
2121 .data = .{
2122 .lhs = condition,
2123 .rhs = try p.addExtra(Node.WhileCont{
2124 .cont_expr = cont_expr,
2125 .then_expr = then_expr,
2126 }),
2127 },
2128 });
2129 }
2130 };
2131 _ = try p.parsePayload();
2132 const else_expr = try p.expectExpr();
2133 return p.addNode(.{
2134 .tag = .@"while",
2135 .main_token = while_token,
2136 .data = .{
2137 .lhs = condition,
2138 .rhs = try p.addExtra(Node.While{
2139 .cont_expr = cont_expr,
2140 .then_expr = then_expr,
2141 .else_expr = else_expr,
2142 }),
2143 },
2144 });
2145}
2146
2147/// CurlySuffixExpr <- TypeExpr InitList?
2148///
2149/// InitList
2150/// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
2151/// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
2152/// / LBRACE RBRACE
2153fn parseCurlySuffixExpr(p: *Parse) !Node.Index {
2154 const lhs = try p.parseTypeExpr();
2155 if (lhs == 0) return null_node;
2156 const lbrace = p.eatToken(.l_brace) orelse return lhs;
2157
2158 // If there are 0 or 1 items, we can use ArrayInitOne/StructInitOne;
2159 // otherwise we use the full ArrayInit/StructInit.
2160
2161 const scratch_top = p.scratch.items.len;
2162 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2163 const field_init = try p.parseFieldInit();
2164 if (field_init != 0) {
2165 try p.scratch.append(p.gpa, field_init);
2166 while (true) {
2167 switch (p.token_tags[p.tok_i]) {
2168 .comma => p.tok_i += 1,
2169 .r_brace => {
2170 p.tok_i += 1;
2171 break;
2172 },
2173 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2174 // Likely just a missing comma; give error but continue parsing.
2175 else => try p.warn(.expected_comma_after_initializer),
2176 }
2177 if (p.eatToken(.r_brace)) |_| break;
2178 const next = try p.expectFieldInit();
2179 try p.scratch.append(p.gpa, next);
2180 }
2181 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2182 const inits = p.scratch.items[scratch_top..];
2183 switch (inits.len) {
2184 0 => unreachable,
2185 1 => return p.addNode(.{
2186 .tag = if (comma) .struct_init_one_comma else .struct_init_one,
2187 .main_token = lbrace,
2188 .data = .{
2189 .lhs = lhs,
2190 .rhs = inits[0],
2191 },
2192 }),
2193 else => return p.addNode(.{
2194 .tag = if (comma) .struct_init_comma else .struct_init,
2195 .main_token = lbrace,
2196 .data = .{
2197 .lhs = lhs,
2198 .rhs = try p.addExtra(try p.listToSpan(inits)),
2199 },
2200 }),
2201 }
2202 }
2203
2204 while (true) {
2205 if (p.eatToken(.r_brace)) |_| break;
2206 const elem_init = try p.expectExpr();
2207 try p.scratch.append(p.gpa, elem_init);
2208 switch (p.token_tags[p.tok_i]) {
2209 .comma => p.tok_i += 1,
2210 .r_brace => {
2211 p.tok_i += 1;
2212 break;
2213 },
2214 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2215 // Likely just a missing comma; give error but continue parsing.
2216 else => try p.warn(.expected_comma_after_initializer),
2217 }
2218 }
2219 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2220 const inits = p.scratch.items[scratch_top..];
2221 switch (inits.len) {
2222 0 => return p.addNode(.{
2223 .tag = .struct_init_one,
2224 .main_token = lbrace,
2225 .data = .{
2226 .lhs = lhs,
2227 .rhs = 0,
2228 },
2229 }),
2230 1 => return p.addNode(.{
2231 .tag = if (comma) .array_init_one_comma else .array_init_one,
2232 .main_token = lbrace,
2233 .data = .{
2234 .lhs = lhs,
2235 .rhs = inits[0],
2236 },
2237 }),
2238 else => return p.addNode(.{
2239 .tag = if (comma) .array_init_comma else .array_init,
2240 .main_token = lbrace,
2241 .data = .{
2242 .lhs = lhs,
2243 .rhs = try p.addExtra(try p.listToSpan(inits)),
2244 },
2245 }),
2246 }
2247}
2248
2249/// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
2250fn parseErrorUnionExpr(p: *Parse) !Node.Index {
2251 const suffix_expr = try p.parseSuffixExpr();
2252 if (suffix_expr == 0) return null_node;
2253 const bang = p.eatToken(.bang) orelse return suffix_expr;
2254 return p.addNode(.{
2255 .tag = .error_union,
2256 .main_token = bang,
2257 .data = .{
2258 .lhs = suffix_expr,
2259 .rhs = try p.expectTypeExpr(),
2260 },
2261 });
2262}
2263
2264/// SuffixExpr
2265/// <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
2266/// / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
2267///
2268/// FnCallArguments <- LPAREN ExprList RPAREN
2269///
2270/// ExprList <- (Expr COMMA)* Expr?
2271fn parseSuffixExpr(p: *Parse) !Node.Index {
2272 if (p.eatToken(.keyword_async)) |_| {
2273 var res = try p.expectPrimaryTypeExpr();
2274 while (true) {
2275 const node = try p.parseSuffixOp(res);
2276 if (node == 0) break;
2277 res = node;
2278 }
2279 const lparen = p.eatToken(.l_paren) orelse {
2280 try p.warn(.expected_param_list);
2281 return res;
2282 };
2283 const scratch_top = p.scratch.items.len;
2284 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2285 while (true) {
2286 if (p.eatToken(.r_paren)) |_| break;
2287 const param = try p.expectExpr();
2288 try p.scratch.append(p.gpa, param);
2289 switch (p.token_tags[p.tok_i]) {
2290 .comma => p.tok_i += 1,
2291 .r_paren => {
2292 p.tok_i += 1;
2293 break;
2294 },
2295 .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren),
2296 // Likely just a missing comma; give error but continue parsing.
2297 else => try p.warn(.expected_comma_after_arg),
2298 }
2299 }
2300 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2301 const params = p.scratch.items[scratch_top..];
2302 switch (params.len) {
2303 0 => return p.addNode(.{
2304 .tag = if (comma) .async_call_one_comma else .async_call_one,
2305 .main_token = lparen,
2306 .data = .{
2307 .lhs = res,
2308 .rhs = 0,
2309 },
2310 }),
2311 1 => return p.addNode(.{
2312 .tag = if (comma) .async_call_one_comma else .async_call_one,
2313 .main_token = lparen,
2314 .data = .{
2315 .lhs = res,
2316 .rhs = params[0],
2317 },
2318 }),
2319 else => return p.addNode(.{
2320 .tag = if (comma) .async_call_comma else .async_call,
2321 .main_token = lparen,
2322 .data = .{
2323 .lhs = res,
2324 .rhs = try p.addExtra(try p.listToSpan(params)),
2325 },
2326 }),
2327 }
2328 }
2329
2330 var res = try p.parsePrimaryTypeExpr();
2331 if (res == 0) return res;
2332 while (true) {
2333 const suffix_op = try p.parseSuffixOp(res);
2334 if (suffix_op != 0) {
2335 res = suffix_op;
2336 continue;
2337 }
2338 const lparen = p.eatToken(.l_paren) orelse return res;
2339 const scratch_top = p.scratch.items.len;
2340 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2341 while (true) {
2342 if (p.eatToken(.r_paren)) |_| break;
2343 const param = try p.expectExpr();
2344 try p.scratch.append(p.gpa, param);
2345 switch (p.token_tags[p.tok_i]) {
2346 .comma => p.tok_i += 1,
2347 .r_paren => {
2348 p.tok_i += 1;
2349 break;
2350 },
2351 .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren),
2352 // Likely just a missing comma; give error but continue parsing.
2353 else => try p.warn(.expected_comma_after_arg),
2354 }
2355 }
2356 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2357 const params = p.scratch.items[scratch_top..];
2358 res = switch (params.len) {
2359 0 => try p.addNode(.{
2360 .tag = if (comma) .call_one_comma else .call_one,
2361 .main_token = lparen,
2362 .data = .{
2363 .lhs = res,
2364 .rhs = 0,
2365 },
2366 }),
2367 1 => try p.addNode(.{
2368 .tag = if (comma) .call_one_comma else .call_one,
2369 .main_token = lparen,
2370 .data = .{
2371 .lhs = res,
2372 .rhs = params[0],
2373 },
2374 }),
2375 else => try p.addNode(.{
2376 .tag = if (comma) .call_comma else .call,
2377 .main_token = lparen,
2378 .data = .{
2379 .lhs = res,
2380 .rhs = try p.addExtra(try p.listToSpan(params)),
2381 },
2382 }),
2383 };
2384 }
2385}
2386
2387/// PrimaryTypeExpr
2388/// <- BUILTINIDENTIFIER FnCallArguments
2389/// / CHAR_LITERAL
2390/// / ContainerDecl
2391/// / DOT IDENTIFIER
2392/// / DOT InitList
2393/// / ErrorSetDecl
2394/// / FLOAT
2395/// / FnProto
2396/// / GroupedExpr
2397/// / LabeledTypeExpr
2398/// / IDENTIFIER
2399/// / IfTypeExpr
2400/// / INTEGER
2401/// / KEYWORD_comptime TypeExpr
2402/// / KEYWORD_error DOT IDENTIFIER
2403/// / KEYWORD_anyframe
2404/// / KEYWORD_unreachable
2405/// / STRINGLITERAL
2406/// / SwitchExpr
2407///
2408/// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
2409///
2410/// ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE
2411///
2412/// InitList
2413/// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
2414/// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
2415/// / LBRACE RBRACE
2416///
2417/// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
2418///
2419/// GroupedExpr <- LPAREN Expr RPAREN
2420///
2421/// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
2422///
2423/// LabeledTypeExpr
2424/// <- BlockLabel Block
2425/// / BlockLabel? LoopTypeExpr
2426///
2427/// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
2428fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {
2429 switch (p.token_tags[p.tok_i]) {
2430 .char_literal => return p.addNode(.{
2431 .tag = .char_literal,
2432 .main_token = p.nextToken(),
2433 .data = .{
2434 .lhs = undefined,
2435 .rhs = undefined,
2436 },
2437 }),
2438 .number_literal => return p.addNode(.{
2439 .tag = .number_literal,
2440 .main_token = p.nextToken(),
2441 .data = .{
2442 .lhs = undefined,
2443 .rhs = undefined,
2444 },
2445 }),
2446 .keyword_unreachable => return p.addNode(.{
2447 .tag = .unreachable_literal,
2448 .main_token = p.nextToken(),
2449 .data = .{
2450 .lhs = undefined,
2451 .rhs = undefined,
2452 },
2453 }),
2454 .keyword_anyframe => return p.addNode(.{
2455 .tag = .anyframe_literal,
2456 .main_token = p.nextToken(),
2457 .data = .{
2458 .lhs = undefined,
2459 .rhs = undefined,
2460 },
2461 }),
2462 .string_literal => {
2463 const main_token = p.nextToken();
2464 return p.addNode(.{
2465 .tag = .string_literal,
2466 .main_token = main_token,
2467 .data = .{
2468 .lhs = undefined,
2469 .rhs = undefined,
2470 },
2471 });
2472 },
2473
2474 .builtin => return p.parseBuiltinCall(),
2475 .keyword_fn => return p.parseFnProto(),
2476 .keyword_if => return p.parseIf(expectTypeExpr),
2477 .keyword_switch => return p.expectSwitchExpr(),
2478
2479 .keyword_extern,
2480 .keyword_packed,
2481 => {
2482 p.tok_i += 1;
2483 return p.parseContainerDeclAuto();
2484 },
2485
2486 .keyword_struct,
2487 .keyword_opaque,
2488 .keyword_enum,
2489 .keyword_union,
2490 => return p.parseContainerDeclAuto(),
2491
2492 .keyword_comptime => return p.addNode(.{
2493 .tag = .@"comptime",
2494 .main_token = p.nextToken(),
2495 .data = .{
2496 .lhs = try p.expectTypeExpr(),
2497 .rhs = undefined,
2498 },
2499 }),
2500 .multiline_string_literal_line => {
2501 const first_line = p.nextToken();
2502 while (p.token_tags[p.tok_i] == .multiline_string_literal_line) {
2503 p.tok_i += 1;
2504 }
2505 return p.addNode(.{
2506 .tag = .multiline_string_literal,
2507 .main_token = first_line,
2508 .data = .{
2509 .lhs = first_line,
2510 .rhs = p.tok_i - 1,
2511 },
2512 });
2513 },
2514 .identifier => switch (p.token_tags[p.tok_i + 1]) {
2515 .colon => switch (p.token_tags[p.tok_i + 2]) {
2516 .keyword_inline => {
2517 p.tok_i += 3;
2518 switch (p.token_tags[p.tok_i]) {
2519 .keyword_for => return p.parseForTypeExpr(),
2520 .keyword_while => return p.parseWhileTypeExpr(),
2521 else => return p.fail(.expected_inlinable),
2522 }
2523 },
2524 .keyword_for => {
2525 p.tok_i += 2;
2526 return p.parseForTypeExpr();
2527 },
2528 .keyword_while => {
2529 p.tok_i += 2;
2530 return p.parseWhileTypeExpr();
2531 },
2532 .l_brace => {
2533 p.tok_i += 2;
2534 return p.parseBlock();
2535 },
2536 else => return p.addNode(.{
2537 .tag = .identifier,
2538 .main_token = p.nextToken(),
2539 .data = .{
2540 .lhs = undefined,
2541 .rhs = undefined,
2542 },
2543 }),
2544 },
2545 else => return p.addNode(.{
2546 .tag = .identifier,
2547 .main_token = p.nextToken(),
2548 .data = .{
2549 .lhs = undefined,
2550 .rhs = undefined,
2551 },
2552 }),
2553 },
2554 .keyword_inline => {
2555 p.tok_i += 1;
2556 switch (p.token_tags[p.tok_i]) {
2557 .keyword_for => return p.parseForTypeExpr(),
2558 .keyword_while => return p.parseWhileTypeExpr(),
2559 else => return p.fail(.expected_inlinable),
2560 }
2561 },
2562 .keyword_for => return p.parseForTypeExpr(),
2563 .keyword_while => return p.parseWhileTypeExpr(),
2564 .period => switch (p.token_tags[p.tok_i + 1]) {
2565 .identifier => return p.addNode(.{
2566 .tag = .enum_literal,
2567 .data = .{
2568 .lhs = p.nextToken(), // dot
2569 .rhs = undefined,
2570 },
2571 .main_token = p.nextToken(), // identifier
2572 }),
2573 .l_brace => {
2574 const lbrace = p.tok_i + 1;
2575 p.tok_i = lbrace + 1;
2576
2577 // If there are 0, 1, or 2 items, we can use ArrayInitDotTwo/StructInitDotTwo;
2578 // otherwise we use the full ArrayInitDot/StructInitDot.
2579
2580 const scratch_top = p.scratch.items.len;
2581 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2582 const field_init = try p.parseFieldInit();
2583 if (field_init != 0) {
2584 try p.scratch.append(p.gpa, field_init);
2585 while (true) {
2586 switch (p.token_tags[p.tok_i]) {
2587 .comma => p.tok_i += 1,
2588 .r_brace => {
2589 p.tok_i += 1;
2590 break;
2591 },
2592 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2593 // Likely just a missing comma; give error but continue parsing.
2594 else => try p.warn(.expected_comma_after_initializer),
2595 }
2596 if (p.eatToken(.r_brace)) |_| break;
2597 const next = try p.expectFieldInit();
2598 try p.scratch.append(p.gpa, next);
2599 }
2600 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2601 const inits = p.scratch.items[scratch_top..];
2602 switch (inits.len) {
2603 0 => unreachable,
2604 1 => return p.addNode(.{
2605 .tag = if (comma) .struct_init_dot_two_comma else .struct_init_dot_two,
2606 .main_token = lbrace,
2607 .data = .{
2608 .lhs = inits[0],
2609 .rhs = 0,
2610 },
2611 }),
2612 2 => return p.addNode(.{
2613 .tag = if (comma) .struct_init_dot_two_comma else .struct_init_dot_two,
2614 .main_token = lbrace,
2615 .data = .{
2616 .lhs = inits[0],
2617 .rhs = inits[1],
2618 },
2619 }),
2620 else => {
2621 const span = try p.listToSpan(inits);
2622 return p.addNode(.{
2623 .tag = if (comma) .struct_init_dot_comma else .struct_init_dot,
2624 .main_token = lbrace,
2625 .data = .{
2626 .lhs = span.start,
2627 .rhs = span.end,
2628 },
2629 });
2630 },
2631 }
2632 }
2633
2634 while (true) {
2635 if (p.eatToken(.r_brace)) |_| break;
2636 const elem_init = try p.expectExpr();
2637 try p.scratch.append(p.gpa, elem_init);
2638 switch (p.token_tags[p.tok_i]) {
2639 .comma => p.tok_i += 1,
2640 .r_brace => {
2641 p.tok_i += 1;
2642 break;
2643 },
2644 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2645 // Likely just a missing comma; give error but continue parsing.
2646 else => try p.warn(.expected_comma_after_initializer),
2647 }
2648 }
2649 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2650 const inits = p.scratch.items[scratch_top..];
2651 switch (inits.len) {
2652 0 => return p.addNode(.{
2653 .tag = .struct_init_dot_two,
2654 .main_token = lbrace,
2655 .data = .{
2656 .lhs = 0,
2657 .rhs = 0,
2658 },
2659 }),
2660 1 => return p.addNode(.{
2661 .tag = if (comma) .array_init_dot_two_comma else .array_init_dot_two,
2662 .main_token = lbrace,
2663 .data = .{
2664 .lhs = inits[0],
2665 .rhs = 0,
2666 },
2667 }),
2668 2 => return p.addNode(.{
2669 .tag = if (comma) .array_init_dot_two_comma else .array_init_dot_two,
2670 .main_token = lbrace,
2671 .data = .{
2672 .lhs = inits[0],
2673 .rhs = inits[1],
2674 },
2675 }),
2676 else => {
2677 const span = try p.listToSpan(inits);
2678 return p.addNode(.{
2679 .tag = if (comma) .array_init_dot_comma else .array_init_dot,
2680 .main_token = lbrace,
2681 .data = .{
2682 .lhs = span.start,
2683 .rhs = span.end,
2684 },
2685 });
2686 },
2687 }
2688 },
2689 else => return null_node,
2690 },
2691 .keyword_error => switch (p.token_tags[p.tok_i + 1]) {
2692 .l_brace => {
2693 const error_token = p.tok_i;
2694 p.tok_i += 2;
2695 while (true) {
2696 if (p.eatToken(.r_brace)) |_| break;
2697 _ = try p.eatDocComments();
2698 _ = try p.expectToken(.identifier);
2699 switch (p.token_tags[p.tok_i]) {
2700 .comma => p.tok_i += 1,
2701 .r_brace => {
2702 p.tok_i += 1;
2703 break;
2704 },
2705 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2706 // Likely just a missing comma; give error but continue parsing.
2707 else => try p.warn(.expected_comma_after_field),
2708 }
2709 }
2710 return p.addNode(.{
2711 .tag = .error_set_decl,
2712 .main_token = error_token,
2713 .data = .{
2714 .lhs = undefined,
2715 .rhs = p.tok_i - 1, // rbrace
2716 },
2717 });
2718 },
2719 else => {
2720 const main_token = p.nextToken();
2721 const period = p.eatToken(.period);
2722 if (period == null) try p.warnExpected(.period);
2723 const identifier = p.eatToken(.identifier);
2724 if (identifier == null) try p.warnExpected(.identifier);
2725 return p.addNode(.{
2726 .tag = .error_value,
2727 .main_token = main_token,
2728 .data = .{
2729 .lhs = period orelse 0,
2730 .rhs = identifier orelse 0,
2731 },
2732 });
2733 },
2734 },
2735 .l_paren => return p.addNode(.{
2736 .tag = .grouped_expression,
2737 .main_token = p.nextToken(),
2738 .data = .{
2739 .lhs = try p.expectExpr(),
2740 .rhs = try p.expectToken(.r_paren),
2741 },
2742 }),
2743 else => return null_node,
2744 }
2745}
2746
2747fn expectPrimaryTypeExpr(p: *Parse) !Node.Index {
2748 const node = try p.parsePrimaryTypeExpr();
2749 if (node == 0) {
2750 return p.fail(.expected_primary_type_expr);
2751 }
2752 return node;
2753}
2754
2755/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
2756///
2757/// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
2758fn parseForTypeExpr(p: *Parse) !Node.Index {
2759 const for_token = p.eatToken(.keyword_for) orelse return null_node;
2760 _ = try p.expectToken(.l_paren);
2761 const array_expr = try p.expectExpr();
2762 _ = try p.expectToken(.r_paren);
2763 const found_payload = try p.parsePtrIndexPayload();
2764 if (found_payload == 0) try p.warn(.expected_loop_payload);
2765
2766 const then_expr = try p.expectTypeExpr();
2767 _ = p.eatToken(.keyword_else) orelse {
2768 return p.addNode(.{
2769 .tag = .for_simple,
2770 .main_token = for_token,
2771 .data = .{
2772 .lhs = array_expr,
2773 .rhs = then_expr,
2774 },
2775 });
2776 };
2777 const else_expr = try p.expectTypeExpr();
2778 return p.addNode(.{
2779 .tag = .@"for",
2780 .main_token = for_token,
2781 .data = .{
2782 .lhs = array_expr,
2783 .rhs = try p.addExtra(Node.If{
2784 .then_expr = then_expr,
2785 .else_expr = else_expr,
2786 }),
2787 },
2788 });
2789}
2790
2791/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
2792///
2793/// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
2794fn parseWhileTypeExpr(p: *Parse) !Node.Index {
2795 const while_token = p.eatToken(.keyword_while) orelse return null_node;
2796 _ = try p.expectToken(.l_paren);
2797 const condition = try p.expectExpr();
2798 _ = try p.expectToken(.r_paren);
2799 _ = try p.parsePtrPayload();
2800 const cont_expr = try p.parseWhileContinueExpr();
2801
2802 const then_expr = try p.expectTypeExpr();
2803 _ = p.eatToken(.keyword_else) orelse {
2804 if (cont_expr == 0) {
2805 return p.addNode(.{
2806 .tag = .while_simple,
2807 .main_token = while_token,
2808 .data = .{
2809 .lhs = condition,
2810 .rhs = then_expr,
2811 },
2812 });
2813 } else {
2814 return p.addNode(.{
2815 .tag = .while_cont,
2816 .main_token = while_token,
2817 .data = .{
2818 .lhs = condition,
2819 .rhs = try p.addExtra(Node.WhileCont{
2820 .cont_expr = cont_expr,
2821 .then_expr = then_expr,
2822 }),
2823 },
2824 });
2825 }
2826 };
2827 _ = try p.parsePayload();
2828 const else_expr = try p.expectTypeExpr();
2829 return p.addNode(.{
2830 .tag = .@"while",
2831 .main_token = while_token,
2832 .data = .{
2833 .lhs = condition,
2834 .rhs = try p.addExtra(Node.While{
2835 .cont_expr = cont_expr,
2836 .then_expr = then_expr,
2837 .else_expr = else_expr,
2838 }),
2839 },
2840 });
2841}
2842
2843/// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
2844fn expectSwitchExpr(p: *Parse) !Node.Index {
2845 const switch_token = p.assertToken(.keyword_switch);
2846 _ = try p.expectToken(.l_paren);
2847 const expr_node = try p.expectExpr();
2848 _ = try p.expectToken(.r_paren);
2849 _ = try p.expectToken(.l_brace);
2850 const cases = try p.parseSwitchProngList();
2851 const trailing_comma = p.token_tags[p.tok_i - 1] == .comma;
2852 _ = try p.expectToken(.r_brace);
2853
2854 return p.addNode(.{
2855 .tag = if (trailing_comma) .switch_comma else .@"switch",
2856 .main_token = switch_token,
2857 .data = .{
2858 .lhs = expr_node,
2859 .rhs = try p.addExtra(Node.SubRange{
2860 .start = cases.start,
2861 .end = cases.end,
2862 }),
2863 },
2864 });
2865}
2866
2867/// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
2868///
2869/// AsmOutput <- COLON AsmOutputList AsmInput?
2870///
2871/// AsmInput <- COLON AsmInputList AsmClobbers?
2872///
2873/// AsmClobbers <- COLON StringList
2874///
2875/// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
2876///
2877/// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
2878///
2879/// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
2880fn expectAsmExpr(p: *Parse) !Node.Index {
2881 const asm_token = p.assertToken(.keyword_asm);
2882 _ = p.eatToken(.keyword_volatile);
2883 _ = try p.expectToken(.l_paren);
2884 const template = try p.expectExpr();
2885
2886 if (p.eatToken(.r_paren)) |rparen| {
2887 return p.addNode(.{
2888 .tag = .asm_simple,
2889 .main_token = asm_token,
2890 .data = .{
2891 .lhs = template,
2892 .rhs = rparen,
2893 },
2894 });
2895 }
2896
2897 _ = try p.expectToken(.colon);
2898
2899 const scratch_top = p.scratch.items.len;
2900 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2901
2902 while (true) {
2903 const output_item = try p.parseAsmOutputItem();
2904 if (output_item == 0) break;
2905 try p.scratch.append(p.gpa, output_item);
2906 switch (p.token_tags[p.tok_i]) {
2907 .comma => p.tok_i += 1,
2908 // All possible delimiters.
2909 .colon, .r_paren, .r_brace, .r_bracket => break,
2910 // Likely just a missing comma; give error but continue parsing.
2911 else => try p.warnExpected(.comma),
2912 }
2913 }
2914 if (p.eatToken(.colon)) |_| {
2915 while (true) {
2916 const input_item = try p.parseAsmInputItem();
2917 if (input_item == 0) break;
2918 try p.scratch.append(p.gpa, input_item);
2919 switch (p.token_tags[p.tok_i]) {
2920 .comma => p.tok_i += 1,
2921 // All possible delimiters.
2922 .colon, .r_paren, .r_brace, .r_bracket => break,
2923 // Likely just a missing comma; give error but continue parsing.
2924 else => try p.warnExpected(.comma),
2925 }
2926 }
2927 if (p.eatToken(.colon)) |_| {
2928 while (p.eatToken(.string_literal)) |_| {
2929 switch (p.token_tags[p.tok_i]) {
2930 .comma => p.tok_i += 1,
2931 .colon, .r_paren, .r_brace, .r_bracket => break,
2932 // Likely just a missing comma; give error but continue parsing.
2933 else => try p.warnExpected(.comma),
2934 }
2935 }
2936 }
2937 }
2938 const rparen = try p.expectToken(.r_paren);
2939 const span = try p.listToSpan(p.scratch.items[scratch_top..]);
2940 return p.addNode(.{
2941 .tag = .@"asm",
2942 .main_token = asm_token,
2943 .data = .{
2944 .lhs = template,
2945 .rhs = try p.addExtra(Node.Asm{
2946 .items_start = span.start,
2947 .items_end = span.end,
2948 .rparen = rparen,
2949 }),
2950 },
2951 });
2952}
2953
2954/// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
2955fn parseAsmOutputItem(p: *Parse) !Node.Index {
2956 _ = p.eatToken(.l_bracket) orelse return null_node;
2957 const identifier = try p.expectToken(.identifier);
2958 _ = try p.expectToken(.r_bracket);
2959 _ = try p.expectToken(.string_literal);
2960 _ = try p.expectToken(.l_paren);
2961 const type_expr: Node.Index = blk: {
2962 if (p.eatToken(.arrow)) |_| {
2963 break :blk try p.expectTypeExpr();
2964 } else {
2965 _ = try p.expectToken(.identifier);
2966 break :blk null_node;
2967 }
2968 };
2969 const rparen = try p.expectToken(.r_paren);
2970 return p.addNode(.{
2971 .tag = .asm_output,
2972 .main_token = identifier,
2973 .data = .{
2974 .lhs = type_expr,
2975 .rhs = rparen,
2976 },
2977 });
2978}
2979
2980/// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
2981fn parseAsmInputItem(p: *Parse) !Node.Index {
2982 _ = p.eatToken(.l_bracket) orelse return null_node;
2983 const identifier = try p.expectToken(.identifier);
2984 _ = try p.expectToken(.r_bracket);
2985 _ = try p.expectToken(.string_literal);
2986 _ = try p.expectToken(.l_paren);
2987 const expr = try p.expectExpr();
2988 const rparen = try p.expectToken(.r_paren);
2989 return p.addNode(.{
2990 .tag = .asm_input,
2991 .main_token = identifier,
2992 .data = .{
2993 .lhs = expr,
2994 .rhs = rparen,
2995 },
2996 });
2997}
2998
2999/// BreakLabel <- COLON IDENTIFIER
3000fn parseBreakLabel(p: *Parse) !TokenIndex {
3001 _ = p.eatToken(.colon) orelse return @as(TokenIndex, 0);
3002 return p.expectToken(.identifier);
3003}
3004
3005/// BlockLabel <- IDENTIFIER COLON
3006fn parseBlockLabel(p: *Parse) TokenIndex {
3007 if (p.token_tags[p.tok_i] == .identifier and
3008 p.token_tags[p.tok_i + 1] == .colon)
3009 {
3010 const identifier = p.tok_i;
3011 p.tok_i += 2;
3012 return identifier;
3013 }
3014 return null_node;
3015}
3016
3017/// FieldInit <- DOT IDENTIFIER EQUAL Expr
3018fn parseFieldInit(p: *Parse) !Node.Index {
3019 if (p.token_tags[p.tok_i + 0] == .period and
3020 p.token_tags[p.tok_i + 1] == .identifier and
3021 p.token_tags[p.tok_i + 2] == .equal)
3022 {
3023 p.tok_i += 3;
3024 return p.expectExpr();
3025 } else {
3026 return null_node;
3027 }
3028}
3029
3030fn expectFieldInit(p: *Parse) !Node.Index {
3031 if (p.token_tags[p.tok_i] != .period or
3032 p.token_tags[p.tok_i + 1] != .identifier or
3033 p.token_tags[p.tok_i + 2] != .equal)
3034 return p.fail(.expected_initializer);
3035
3036 p.tok_i += 3;
3037 return p.expectExpr();
3038}
3039
3040/// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
3041fn parseWhileContinueExpr(p: *Parse) !Node.Index {
3042 _ = p.eatToken(.colon) orelse {
3043 if (p.token_tags[p.tok_i] == .l_paren and
3044 p.tokensOnSameLine(p.tok_i - 1, p.tok_i))
3045 return p.fail(.expected_continue_expr);
3046 return null_node;
3047 };
3048 _ = try p.expectToken(.l_paren);
3049 const node = try p.parseAssignExpr();
3050 if (node == 0) return p.fail(.expected_expr_or_assignment);
3051 _ = try p.expectToken(.r_paren);
3052 return node;
3053}
3054
3055/// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
3056fn parseLinkSection(p: *Parse) !Node.Index {
3057 _ = p.eatToken(.keyword_linksection) orelse return null_node;
3058 _ = try p.expectToken(.l_paren);
3059 const expr_node = try p.expectExpr();
3060 _ = try p.expectToken(.r_paren);
3061 return expr_node;
3062}
3063
3064/// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
3065fn parseCallconv(p: *Parse) !Node.Index {
3066 _ = p.eatToken(.keyword_callconv) orelse return null_node;
3067 _ = try p.expectToken(.l_paren);
3068 const expr_node = try p.expectExpr();
3069 _ = try p.expectToken(.r_paren);
3070 return expr_node;
3071}
3072
3073/// AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN
3074fn parseAddrSpace(p: *Parse) !Node.Index {
3075 _ = p.eatToken(.keyword_addrspace) orelse return null_node;
3076 _ = try p.expectToken(.l_paren);
3077 const expr_node = try p.expectExpr();
3078 _ = try p.expectToken(.r_paren);
3079 return expr_node;
3080}
3081
3082/// This function can return null nodes and then still return nodes afterwards,
3083/// such as in the case of anytype and `...`. Caller must look for rparen to find
3084/// out when there are no more param decls left.
3085///
3086/// ParamDecl
3087/// <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
3088/// / DOT3
3089///
3090/// ParamType
3091/// <- KEYWORD_anytype
3092/// / TypeExpr
3093fn expectParamDecl(p: *Parse) !Node.Index {
3094 _ = try p.eatDocComments();
3095 switch (p.token_tags[p.tok_i]) {
3096 .keyword_noalias, .keyword_comptime => p.tok_i += 1,
3097 .ellipsis3 => {
3098 p.tok_i += 1;
3099 return null_node;
3100 },
3101 else => {},
3102 }
3103 if (p.token_tags[p.tok_i] == .identifier and
3104 p.token_tags[p.tok_i + 1] == .colon)
3105 {
3106 p.tok_i += 2;
3107 }
3108 switch (p.token_tags[p.tok_i]) {
3109 .keyword_anytype => {
3110 p.tok_i += 1;
3111 return null_node;
3112 },
3113 else => return p.expectTypeExpr(),
3114 }
3115}
3116
3117/// Payload <- PIPE IDENTIFIER PIPE
3118fn parsePayload(p: *Parse) !TokenIndex {
3119 _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0);
3120 const identifier = try p.expectToken(.identifier);
3121 _ = try p.expectToken(.pipe);
3122 return identifier;
3123}
3124
3125/// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
3126fn parsePtrPayload(p: *Parse) !TokenIndex {
3127 _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0);
3128 _ = p.eatToken(.asterisk);
3129 const identifier = try p.expectToken(.identifier);
3130 _ = try p.expectToken(.pipe);
3131 return identifier;
3132}
3133
3134/// Returns the first identifier token, if any.
3135///
3136/// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
3137fn parsePtrIndexPayload(p: *Parse) !TokenIndex {
3138 _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0);
3139 _ = p.eatToken(.asterisk);
3140 const identifier = try p.expectToken(.identifier);
3141 if (p.eatToken(.comma) != null) {
3142 _ = try p.expectToken(.identifier);
3143 }
3144 _ = try p.expectToken(.pipe);
3145 return identifier;
3146}
3147
3148/// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr
3149///
3150/// SwitchCase
3151/// <- SwitchItem (COMMA SwitchItem)* COMMA?
3152/// / KEYWORD_else
3153fn parseSwitchProng(p: *Parse) !Node.Index {
3154 const scratch_top = p.scratch.items.len;
3155 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3156
3157 const is_inline = p.eatToken(.keyword_inline) != null;
3158
3159 if (p.eatToken(.keyword_else) == null) {
3160 while (true) {
3161 const item = try p.parseSwitchItem();
3162 if (item == 0) break;
3163 try p.scratch.append(p.gpa, item);
3164 if (p.eatToken(.comma) == null) break;
3165 }
3166 if (scratch_top == p.scratch.items.len) {
3167 if (is_inline) p.tok_i -= 1;
3168 return null_node;
3169 }
3170 }
3171 const arrow_token = try p.expectToken(.equal_angle_bracket_right);
3172 _ = try p.parsePtrIndexPayload();
3173
3174 const items = p.scratch.items[scratch_top..];
3175 switch (items.len) {
3176 0 => return p.addNode(.{
3177 .tag = if (is_inline) .switch_case_inline_one else .switch_case_one,
3178 .main_token = arrow_token,
3179 .data = .{
3180 .lhs = 0,
3181 .rhs = try p.expectAssignExpr(),
3182 },
3183 }),
3184 1 => return p.addNode(.{
3185 .tag = if (is_inline) .switch_case_inline_one else .switch_case_one,
3186 .main_token = arrow_token,
3187 .data = .{
3188 .lhs = items[0],
3189 .rhs = try p.expectAssignExpr(),
3190 },
3191 }),
3192 else => return p.addNode(.{
3193 .tag = if (is_inline) .switch_case_inline else .switch_case,
3194 .main_token = arrow_token,
3195 .data = .{
3196 .lhs = try p.addExtra(try p.listToSpan(items)),
3197 .rhs = try p.expectAssignExpr(),
3198 },
3199 }),
3200 }
3201}
3202
3203/// SwitchItem <- Expr (DOT3 Expr)?
3204fn parseSwitchItem(p: *Parse) !Node.Index {
3205 const expr = try p.parseExpr();
3206 if (expr == 0) return null_node;
3207
3208 if (p.eatToken(.ellipsis3)) |token| {
3209 return p.addNode(.{
3210 .tag = .switch_range,
3211 .main_token = token,
3212 .data = .{
3213 .lhs = expr,
3214 .rhs = try p.expectExpr(),
3215 },
3216 });
3217 }
3218 return expr;
3219}
3220
3221const PtrModifiers = struct {
3222 align_node: Node.Index,
3223 addrspace_node: Node.Index,
3224 bit_range_start: Node.Index,
3225 bit_range_end: Node.Index,
3226};
3227
3228fn parsePtrModifiers(p: *Parse) !PtrModifiers {
3229 var result: PtrModifiers = .{
3230 .align_node = 0,
3231 .addrspace_node = 0,
3232 .bit_range_start = 0,
3233 .bit_range_end = 0,
3234 };
3235 var saw_const = false;
3236 var saw_volatile = false;
3237 var saw_allowzero = false;
3238 var saw_addrspace = false;
3239 while (true) {
3240 switch (p.token_tags[p.tok_i]) {
3241 .keyword_align => {
3242 if (result.align_node != 0) {
3243 try p.warn(.extra_align_qualifier);
3244 }
3245 p.tok_i += 1;
3246 _ = try p.expectToken(.l_paren);
3247 result.align_node = try p.expectExpr();
3248
3249 if (p.eatToken(.colon)) |_| {
3250 result.bit_range_start = try p.expectExpr();
3251 _ = try p.expectToken(.colon);
3252 result.bit_range_end = try p.expectExpr();
3253 }
3254
3255 _ = try p.expectToken(.r_paren);
3256 },
3257 .keyword_const => {
3258 if (saw_const) {
3259 try p.warn(.extra_const_qualifier);
3260 }
3261 p.tok_i += 1;
3262 saw_const = true;
3263 },
3264 .keyword_volatile => {
3265 if (saw_volatile) {
3266 try p.warn(.extra_volatile_qualifier);
3267 }
3268 p.tok_i += 1;
3269 saw_volatile = true;
3270 },
3271 .keyword_allowzero => {
3272 if (saw_allowzero) {
3273 try p.warn(.extra_allowzero_qualifier);
3274 }
3275 p.tok_i += 1;
3276 saw_allowzero = true;
3277 },
3278 .keyword_addrspace => {
3279 if (saw_addrspace) {
3280 try p.warn(.extra_addrspace_qualifier);
3281 }
3282 result.addrspace_node = try p.parseAddrSpace();
3283 },
3284 else => return result,
3285 }
3286 }
3287}
3288
3289/// SuffixOp
3290/// <- LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET
3291/// / DOT IDENTIFIER
3292/// / DOTASTERISK
3293/// / DOTQUESTIONMARK
3294fn parseSuffixOp(p: *Parse, lhs: Node.Index) !Node.Index {
3295 switch (p.token_tags[p.tok_i]) {
3296 .l_bracket => {
3297 const lbracket = p.nextToken();
3298 const index_expr = try p.expectExpr();
3299
3300 if (p.eatToken(.ellipsis2)) |_| {
3301 const end_expr = try p.parseExpr();
3302 if (p.eatToken(.colon)) |_| {
3303 const sentinel = try p.expectExpr();
3304 _ = try p.expectToken(.r_bracket);
3305 return p.addNode(.{
3306 .tag = .slice_sentinel,
3307 .main_token = lbracket,
3308 .data = .{
3309 .lhs = lhs,
3310 .rhs = try p.addExtra(Node.SliceSentinel{
3311 .start = index_expr,
3312 .end = end_expr,
3313 .sentinel = sentinel,
3314 }),
3315 },
3316 });
3317 }
3318 _ = try p.expectToken(.r_bracket);
3319 if (end_expr == 0) {
3320 return p.addNode(.{
3321 .tag = .slice_open,
3322 .main_token = lbracket,
3323 .data = .{
3324 .lhs = lhs,
3325 .rhs = index_expr,
3326 },
3327 });
3328 }
3329 return p.addNode(.{
3330 .tag = .slice,
3331 .main_token = lbracket,
3332 .data = .{
3333 .lhs = lhs,
3334 .rhs = try p.addExtra(Node.Slice{
3335 .start = index_expr,
3336 .end = end_expr,
3337 }),
3338 },
3339 });
3340 }
3341 _ = try p.expectToken(.r_bracket);
3342 return p.addNode(.{
3343 .tag = .array_access,
3344 .main_token = lbracket,
3345 .data = .{
3346 .lhs = lhs,
3347 .rhs = index_expr,
3348 },
3349 });
3350 },
3351 .period_asterisk => return p.addNode(.{
3352 .tag = .deref,
3353 .main_token = p.nextToken(),
3354 .data = .{
3355 .lhs = lhs,
3356 .rhs = undefined,
3357 },
3358 }),
3359 .invalid_periodasterisks => {
3360 try p.warn(.asterisk_after_ptr_deref);
3361 return p.addNode(.{
3362 .tag = .deref,
3363 .main_token = p.nextToken(),
3364 .data = .{
3365 .lhs = lhs,
3366 .rhs = undefined,
3367 },
3368 });
3369 },
3370 .period => switch (p.token_tags[p.tok_i + 1]) {
3371 .identifier => return p.addNode(.{
3372 .tag = .field_access,
3373 .main_token = p.nextToken(),
3374 .data = .{
3375 .lhs = lhs,
3376 .rhs = p.nextToken(),
3377 },
3378 }),
3379 .question_mark => return p.addNode(.{
3380 .tag = .unwrap_optional,
3381 .main_token = p.nextToken(),
3382 .data = .{
3383 .lhs = lhs,
3384 .rhs = p.nextToken(),
3385 },
3386 }),
3387 .l_brace => {
3388 // this a misplaced `.{`, handle the error somewhere else
3389 return null_node;
3390 },
3391 else => {
3392 p.tok_i += 1;
3393 try p.warn(.expected_suffix_op);
3394 return null_node;
3395 },
3396 },
3397 else => return null_node,
3398 }
3399}
3400
3401/// Caller must have already verified the first token.
3402///
3403/// ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE
3404///
3405/// ContainerDeclType
3406/// <- KEYWORD_struct (LPAREN Expr RPAREN)?
3407/// / KEYWORD_opaque
3408/// / KEYWORD_enum (LPAREN Expr RPAREN)?
3409/// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
3410fn parseContainerDeclAuto(p: *Parse) !Node.Index {
3411 const main_token = p.nextToken();
3412 const arg_expr = switch (p.token_tags[main_token]) {
3413 .keyword_opaque => null_node,
3414 .keyword_struct, .keyword_enum => blk: {
3415 if (p.eatToken(.l_paren)) |_| {
3416 const expr = try p.expectExpr();
3417 _ = try p.expectToken(.r_paren);
3418 break :blk expr;
3419 } else {
3420 break :blk null_node;
3421 }
3422 },
3423 .keyword_union => blk: {
3424 if (p.eatToken(.l_paren)) |_| {
3425 if (p.eatToken(.keyword_enum)) |_| {
3426 if (p.eatToken(.l_paren)) |_| {
3427 const enum_tag_expr = try p.expectExpr();
3428 _ = try p.expectToken(.r_paren);
3429 _ = try p.expectToken(.r_paren);
3430
3431 _ = try p.expectToken(.l_brace);
3432 const members = try p.parseContainerMembers();
3433 const members_span = try members.toSpan(p);
3434 _ = try p.expectToken(.r_brace);
3435 return p.addNode(.{
3436 .tag = switch (members.trailing) {
3437 true => .tagged_union_enum_tag_trailing,
3438 false => .tagged_union_enum_tag,
3439 },
3440 .main_token = main_token,
3441 .data = .{
3442 .lhs = enum_tag_expr,
3443 .rhs = try p.addExtra(members_span),
3444 },
3445 });
3446 } else {
3447 _ = try p.expectToken(.r_paren);
3448
3449 _ = try p.expectToken(.l_brace);
3450 const members = try p.parseContainerMembers();
3451 _ = try p.expectToken(.r_brace);
3452 if (members.len <= 2) {
3453 return p.addNode(.{
3454 .tag = switch (members.trailing) {
3455 true => .tagged_union_two_trailing,
3456 false => .tagged_union_two,
3457 },
3458 .main_token = main_token,
3459 .data = .{
3460 .lhs = members.lhs,
3461 .rhs = members.rhs,
3462 },
3463 });
3464 } else {
3465 const span = try members.toSpan(p);
3466 return p.addNode(.{
3467 .tag = switch (members.trailing) {
3468 true => .tagged_union_trailing,
3469 false => .tagged_union,
3470 },
3471 .main_token = main_token,
3472 .data = .{
3473 .lhs = span.start,
3474 .rhs = span.end,
3475 },
3476 });
3477 }
3478 }
3479 } else {
3480 const expr = try p.expectExpr();
3481 _ = try p.expectToken(.r_paren);
3482 break :blk expr;
3483 }
3484 } else {
3485 break :blk null_node;
3486 }
3487 },
3488 else => {
3489 p.tok_i -= 1;
3490 return p.fail(.expected_container);
3491 },
3492 };
3493 _ = try p.expectToken(.l_brace);
3494 const members = try p.parseContainerMembers();
3495 _ = try p.expectToken(.r_brace);
3496 if (arg_expr == 0) {
3497 if (members.len <= 2) {
3498 return p.addNode(.{
3499 .tag = switch (members.trailing) {
3500 true => .container_decl_two_trailing,
3501 false => .container_decl_two,
3502 },
3503 .main_token = main_token,
3504 .data = .{
3505 .lhs = members.lhs,
3506 .rhs = members.rhs,
3507 },
3508 });
3509 } else {
3510 const span = try members.toSpan(p);
3511 return p.addNode(.{
3512 .tag = switch (members.trailing) {
3513 true => .container_decl_trailing,
3514 false => .container_decl,
3515 },
3516 .main_token = main_token,
3517 .data = .{
3518 .lhs = span.start,
3519 .rhs = span.end,
3520 },
3521 });
3522 }
3523 } else {
3524 const span = try members.toSpan(p);
3525 return p.addNode(.{
3526 .tag = switch (members.trailing) {
3527 true => .container_decl_arg_trailing,
3528 false => .container_decl_arg,
3529 },
3530 .main_token = main_token,
3531 .data = .{
3532 .lhs = arg_expr,
3533 .rhs = try p.addExtra(Node.SubRange{
3534 .start = span.start,
3535 .end = span.end,
3536 }),
3537 },
3538 });
3539 }
3540}
3541
3542/// Give a helpful error message for those transitioning from
3543/// C's 'struct Foo {};' to Zig's 'const Foo = struct {};'.
3544fn parseCStyleContainer(p: *Parse) Error!bool {
3545 const main_token = p.tok_i;
3546 switch (p.token_tags[p.tok_i]) {
3547 .keyword_enum, .keyword_union, .keyword_struct => {},
3548 else => return false,
3549 }
3550 const identifier = p.tok_i + 1;
3551 if (p.token_tags[identifier] != .identifier) return false;
3552 p.tok_i += 2;
3553
3554 try p.warnMsg(.{
3555 .tag = .c_style_container,
3556 .token = identifier,
3557 .extra = .{ .expected_tag = p.token_tags[main_token] },
3558 });
3559 try p.warnMsg(.{
3560 .tag = .zig_style_container,
3561 .is_note = true,
3562 .token = identifier,
3563 .extra = .{ .expected_tag = p.token_tags[main_token] },
3564 });
3565
3566 _ = try p.expectToken(.l_brace);
3567 _ = try p.parseContainerMembers();
3568 _ = try p.expectToken(.r_brace);
3569 try p.expectSemicolon(.expected_semi_after_decl, true);
3570 return true;
3571}
3572
3573/// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
3574///
3575/// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
3576fn parseByteAlign(p: *Parse) !Node.Index {
3577 _ = p.eatToken(.keyword_align) orelse return null_node;
3578 _ = try p.expectToken(.l_paren);
3579 const expr = try p.expectExpr();
3580 _ = try p.expectToken(.r_paren);
3581 return expr;
3582}
3583
3584/// SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
3585fn parseSwitchProngList(p: *Parse) !Node.SubRange {
3586 const scratch_top = p.scratch.items.len;
3587 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3588
3589 while (true) {
3590 const item = try parseSwitchProng(p);
3591 if (item == 0) break;
3592
3593 try p.scratch.append(p.gpa, item);
3594
3595 switch (p.token_tags[p.tok_i]) {
3596 .comma => p.tok_i += 1,
3597 // All possible delimiters.
3598 .colon, .r_paren, .r_brace, .r_bracket => break,
3599 // Likely just a missing comma; give error but continue parsing.
3600 else => try p.warn(.expected_comma_after_switch_prong),
3601 }
3602 }
3603 return p.listToSpan(p.scratch.items[scratch_top..]);
3604}
3605
3606/// ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
3607fn parseParamDeclList(p: *Parse) !SmallSpan {
3608 _ = try p.expectToken(.l_paren);
3609 const scratch_top = p.scratch.items.len;
3610 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3611 var varargs: union(enum) { none, seen, nonfinal: TokenIndex } = .none;
3612 while (true) {
3613 if (p.eatToken(.r_paren)) |_| break;
3614 if (varargs == .seen) varargs = .{ .nonfinal = p.tok_i };
3615 const param = try p.expectParamDecl();
3616 if (param != 0) {
3617 try p.scratch.append(p.gpa, param);
3618 } else if (p.token_tags[p.tok_i - 1] == .ellipsis3) {
3619 if (varargs == .none) varargs = .seen;
3620 }
3621 switch (p.token_tags[p.tok_i]) {
3622 .comma => p.tok_i += 1,
3623 .r_paren => {
3624 p.tok_i += 1;
3625 break;
3626 },
3627 .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren),
3628 // Likely just a missing comma; give error but continue parsing.
3629 else => try p.warn(.expected_comma_after_param),
3630 }
3631 }
3632 if (varargs == .nonfinal) {
3633 try p.warnMsg(.{ .tag = .varargs_nonfinal, .token = varargs.nonfinal });
3634 }
3635 const params = p.scratch.items[scratch_top..];
3636 return switch (params.len) {
3637 0 => SmallSpan{ .zero_or_one = 0 },
3638 1 => SmallSpan{ .zero_or_one = params[0] },
3639 else => SmallSpan{ .multi = try p.listToSpan(params) },
3640 };
3641}
3642
3643/// FnCallArguments <- LPAREN ExprList RPAREN
3644///
3645/// ExprList <- (Expr COMMA)* Expr?
3646fn parseBuiltinCall(p: *Parse) !Node.Index {
3647 const builtin_token = p.assertToken(.builtin);
3648 if (p.token_tags[p.nextToken()] != .l_paren) {
3649 p.tok_i -= 1;
3650 try p.warn(.expected_param_list);
3651 // Pretend this was an identifier so we can continue parsing.
3652 return p.addNode(.{
3653 .tag = .identifier,
3654 .main_token = builtin_token,
3655 .data = .{
3656 .lhs = undefined,
3657 .rhs = undefined,
3658 },
3659 });
3660 }
3661 const scratch_top = p.scratch.items.len;
3662 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3663 while (true) {
3664 if (p.eatToken(.r_paren)) |_| break;
3665 const param = try p.expectExpr();
3666 try p.scratch.append(p.gpa, param);
3667 switch (p.token_tags[p.tok_i]) {
3668 .comma => p.tok_i += 1,
3669 .r_paren => {
3670 p.tok_i += 1;
3671 break;
3672 },
3673 // Likely just a missing comma; give error but continue parsing.
3674 else => try p.warn(.expected_comma_after_arg),
3675 }
3676 }
3677 const comma = (p.token_tags[p.tok_i - 2] == .comma);
3678 const params = p.scratch.items[scratch_top..];
3679 switch (params.len) {
3680 0 => return p.addNode(.{
3681 .tag = .builtin_call_two,
3682 .main_token = builtin_token,
3683 .data = .{
3684 .lhs = 0,
3685 .rhs = 0,
3686 },
3687 }),
3688 1 => return p.addNode(.{
3689 .tag = if (comma) .builtin_call_two_comma else .builtin_call_two,
3690 .main_token = builtin_token,
3691 .data = .{
3692 .lhs = params[0],
3693 .rhs = 0,
3694 },
3695 }),
3696 2 => return p.addNode(.{
3697 .tag = if (comma) .builtin_call_two_comma else .builtin_call_two,
3698 .main_token = builtin_token,
3699 .data = .{
3700 .lhs = params[0],
3701 .rhs = params[1],
3702 },
3703 }),
3704 else => {
3705 const span = try p.listToSpan(params);
3706 return p.addNode(.{
3707 .tag = if (comma) .builtin_call_comma else .builtin_call,
3708 .main_token = builtin_token,
3709 .data = .{
3710 .lhs = span.start,
3711 .rhs = span.end,
3712 },
3713 });
3714 },
3715 }
3716}
3717
3718/// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
3719fn parseIf(p: *Parse, comptime bodyParseFn: fn (p: *Parse) Error!Node.Index) !Node.Index {
3720 const if_token = p.eatToken(.keyword_if) orelse return null_node;
3721 _ = try p.expectToken(.l_paren);
3722 const condition = try p.expectExpr();
3723 _ = try p.expectToken(.r_paren);
3724 _ = try p.parsePtrPayload();
3725
3726 const then_expr = try bodyParseFn(p);
3727 assert(then_expr != 0);
3728
3729 _ = p.eatToken(.keyword_else) orelse return p.addNode(.{
3730 .tag = .if_simple,
3731 .main_token = if_token,
3732 .data = .{
3733 .lhs = condition,
3734 .rhs = then_expr,
3735 },
3736 });
3737 _ = try p.parsePayload();
3738 const else_expr = try bodyParseFn(p);
3739 assert(then_expr != 0);
3740
3741 return p.addNode(.{
3742 .tag = .@"if",
3743 .main_token = if_token,
3744 .data = .{
3745 .lhs = condition,
3746 .rhs = try p.addExtra(Node.If{
3747 .then_expr = then_expr,
3748 .else_expr = else_expr,
3749 }),
3750 },
3751 });
3752}
3753
3754/// Skips over doc comment tokens. Returns the first one, if any.
3755fn eatDocComments(p: *Parse) !?TokenIndex {
3756 if (p.eatToken(.doc_comment)) |tok| {
3757 var first_line = tok;
3758 if (tok > 0 and tokensOnSameLine(p, tok - 1, tok)) {
3759 try p.warnMsg(.{
3760 .tag = .same_line_doc_comment,
3761 .token = tok,
3762 });
3763 first_line = p.eatToken(.doc_comment) orelse return null;
3764 }
3765 while (p.eatToken(.doc_comment)) |_| {}
3766 return first_line;
3767 }
3768 return null;
3769}
3770
3771fn tokensOnSameLine(p: *Parse, token1: TokenIndex, token2: TokenIndex) bool {
3772 return std.mem.indexOfScalar(u8, p.source[p.token_starts[token1]..p.token_starts[token2]], '\n') == null;
3773}
3774
3775fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex {
3776 return if (p.token_tags[p.tok_i] == tag) p.nextToken() else null;
3777}
3778
3779fn assertToken(p: *Parse, tag: Token.Tag) TokenIndex {
3780 const token = p.nextToken();
3781 assert(p.token_tags[token] == tag);
3782 return token;
3783}
3784
3785fn expectToken(p: *Parse, tag: Token.Tag) Error!TokenIndex {
3786 if (p.token_tags[p.tok_i] != tag) {
3787 return p.failMsg(.{
3788 .tag = .expected_token,
3789 .token = p.tok_i,
3790 .extra = .{ .expected_tag = tag },
3791 });
3792 }
3793 return p.nextToken();
3794}
3795
3796fn expectSemicolon(p: *Parse, error_tag: AstError.Tag, recoverable: bool) Error!void {
3797 if (p.token_tags[p.tok_i] == .semicolon) {
3798 _ = p.nextToken();
3799 return;
3800 }
3801 try p.warn(error_tag);
3802 if (!recoverable) return error.ParseError;
3803}
3804
3805fn nextToken(p: *Parse) TokenIndex {
3806 const result = p.tok_i;
3807 p.tok_i += 1;
3808 return result;
3809}
3810
3811const null_node: Node.Index = 0;
3812
3813const Parse = @This();
3814const std = @import("../std.zig");
3815const assert = std.debug.assert;
3816const Allocator = std.mem.Allocator;
3817const Ast = std.zig.Ast;
3818const Node = Ast.Node;
3819const AstError = Ast.Error;
3820const TokenIndex = Ast.TokenIndex;
3821const Token = std.zig.Token;
3822
3823test {
3824 _ = @import("parser_test.zig");
3825}
lib/std/zig/parse.zig deleted-3852
...@@ -1,3852 +0,0 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4const Ast = std.zig.Ast;
5const Node = Ast.Node;
6const AstError = Ast.Error;
7const TokenIndex = Ast.TokenIndex;
8const Token = std.zig.Token;
9
10pub const Error = error{ParseError} || Allocator.Error;
11
12/// Result should be freed with tree.deinit() when there are
13/// no more references to any of the tokens or nodes.
14pub fn parse(gpa: Allocator, source: [:0]const u8) Allocator.Error!Ast {
15 var tokens = Ast.TokenList{};
16 defer tokens.deinit(gpa);
17
18 // Empirically, the zig std lib has an 8:1 ratio of source bytes to token count.
19 const estimated_token_count = source.len / 8;
20 try tokens.ensureTotalCapacity(gpa, estimated_token_count);
21
22 var tokenizer = std.zig.Tokenizer.init(source);
23 while (true) {
24 const token = tokenizer.next();
25 try tokens.append(gpa, .{
26 .tag = token.tag,
27 .start = @intCast(u32, token.loc.start),
28 });
29 if (token.tag == .eof) break;
30 }
31
32 var parser: Parser = .{
33 .source = source,
34 .gpa = gpa,
35 .token_tags = tokens.items(.tag),
36 .token_starts = tokens.items(.start),
37 .errors = .{},
38 .nodes = .{},
39 .extra_data = .{},
40 .scratch = .{},
41 .tok_i = 0,
42 };
43 defer parser.errors.deinit(gpa);
44 defer parser.nodes.deinit(gpa);
45 defer parser.extra_data.deinit(gpa);
46 defer parser.scratch.deinit(gpa);
47
48 // Empirically, Zig source code has a 2:1 ratio of tokens to AST nodes.
49 // Make sure at least 1 so we can use appendAssumeCapacity on the root node below.
50 const estimated_node_count = (tokens.len + 2) / 2;
51 try parser.nodes.ensureTotalCapacity(gpa, estimated_node_count);
52
53 try parser.parseRoot();
54
55 // TODO experiment with compacting the MultiArrayList slices here
56 return Ast{
57 .source = source,
58 .tokens = tokens.toOwnedSlice(),
59 .nodes = parser.nodes.toOwnedSlice(),
60 .extra_data = try parser.extra_data.toOwnedSlice(gpa),
61 .errors = try parser.errors.toOwnedSlice(gpa),
62 };
63}
64
65const null_node: Node.Index = 0;
66
67/// Represents in-progress parsing, will be converted to an Ast after completion.
68const Parser = struct {
69 gpa: Allocator,
70 source: []const u8,
71 token_tags: []const Token.Tag,
72 token_starts: []const Ast.ByteOffset,
73 tok_i: TokenIndex,
74 errors: std.ArrayListUnmanaged(AstError),
75 nodes: Ast.NodeList,
76 extra_data: std.ArrayListUnmanaged(Node.Index),
77 scratch: std.ArrayListUnmanaged(Node.Index),
78
79 const SmallSpan = union(enum) {
80 zero_or_one: Node.Index,
81 multi: Node.SubRange,
82 };
83
84 const Members = struct {
85 len: usize,
86 lhs: Node.Index,
87 rhs: Node.Index,
88 trailing: bool,
89
90 fn toSpan(self: Members, p: *Parser) !Node.SubRange {
91 if (self.len <= 2) {
92 const nodes = [2]Node.Index{ self.lhs, self.rhs };
93 return p.listToSpan(nodes[0..self.len]);
94 } else {
95 return Node.SubRange{ .start = self.lhs, .end = self.rhs };
96 }
97 }
98 };
99
100 fn listToSpan(p: *Parser, list: []const Node.Index) !Node.SubRange {
101 try p.extra_data.appendSlice(p.gpa, list);
102 return Node.SubRange{
103 .start = @intCast(Node.Index, p.extra_data.items.len - list.len),
104 .end = @intCast(Node.Index, p.extra_data.items.len),
105 };
106 }
107
108 fn addNode(p: *Parser, elem: Ast.NodeList.Elem) Allocator.Error!Node.Index {
109 const result = @intCast(Node.Index, p.nodes.len);
110 try p.nodes.append(p.gpa, elem);
111 return result;
112 }
113
114 fn setNode(p: *Parser, i: usize, elem: Ast.NodeList.Elem) Node.Index {
115 p.nodes.set(i, elem);
116 return @intCast(Node.Index, i);
117 }
118
119 fn reserveNode(p: *Parser, tag: Ast.Node.Tag) !usize {
120 try p.nodes.resize(p.gpa, p.nodes.len + 1);
121 p.nodes.items(.tag)[p.nodes.len - 1] = tag;
122 return p.nodes.len - 1;
123 }
124
125 fn unreserveNode(p: *Parser, node_index: usize) void {
126 if (p.nodes.len == node_index) {
127 p.nodes.resize(p.gpa, p.nodes.len - 1) catch unreachable;
128 } else {
129 // There is zombie node left in the tree, let's make it as inoffensive as possible
130 // (sadly there's no no-op node)
131 p.nodes.items(.tag)[node_index] = .unreachable_literal;
132 p.nodes.items(.main_token)[node_index] = p.tok_i;
133 }
134 }
135
136 fn addExtra(p: *Parser, extra: anytype) Allocator.Error!Node.Index {
137 const fields = std.meta.fields(@TypeOf(extra));
138 try p.extra_data.ensureUnusedCapacity(p.gpa, fields.len);
139 const result = @intCast(u32, p.extra_data.items.len);
140 inline for (fields) |field| {
141 comptime assert(field.type == Node.Index);
142 p.extra_data.appendAssumeCapacity(@field(extra, field.name));
143 }
144 return result;
145 }
146
147 fn warnExpected(p: *Parser, expected_token: Token.Tag) error{OutOfMemory}!void {
148 @setCold(true);
149 try p.warnMsg(.{
150 .tag = .expected_token,
151 .token = p.tok_i,
152 .extra = .{ .expected_tag = expected_token },
153 });
154 }
155
156 fn warn(p: *Parser, error_tag: AstError.Tag) error{OutOfMemory}!void {
157 @setCold(true);
158 try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i });
159 }
160
161 fn warnMsg(p: *Parser, msg: Ast.Error) error{OutOfMemory}!void {
162 @setCold(true);
163 switch (msg.tag) {
164 .expected_semi_after_decl,
165 .expected_semi_after_stmt,
166 .expected_comma_after_field,
167 .expected_comma_after_arg,
168 .expected_comma_after_param,
169 .expected_comma_after_initializer,
170 .expected_comma_after_switch_prong,
171 .expected_semi_or_else,
172 .expected_semi_or_lbrace,
173 .expected_token,
174 .expected_block,
175 .expected_block_or_assignment,
176 .expected_block_or_expr,
177 .expected_block_or_field,
178 .expected_expr,
179 .expected_expr_or_assignment,
180 .expected_fn,
181 .expected_inlinable,
182 .expected_labelable,
183 .expected_param_list,
184 .expected_prefix_expr,
185 .expected_primary_type_expr,
186 .expected_pub_item,
187 .expected_return_type,
188 .expected_suffix_op,
189 .expected_type_expr,
190 .expected_var_decl,
191 .expected_var_decl_or_fn,
192 .expected_loop_payload,
193 .expected_container,
194 => if (msg.token != 0 and !p.tokensOnSameLine(msg.token - 1, msg.token)) {
195 var copy = msg;
196 copy.token_is_prev = true;
197 copy.token -= 1;
198 return p.errors.append(p.gpa, copy);
199 },
200 else => {},
201 }
202 try p.errors.append(p.gpa, msg);
203 }
204
205 fn fail(p: *Parser, tag: Ast.Error.Tag) error{ ParseError, OutOfMemory } {
206 @setCold(true);
207 return p.failMsg(.{ .tag = tag, .token = p.tok_i });
208 }
209
210 fn failExpected(p: *Parser, expected_token: Token.Tag) error{ ParseError, OutOfMemory } {
211 @setCold(true);
212 return p.failMsg(.{
213 .tag = .expected_token,
214 .token = p.tok_i,
215 .extra = .{ .expected_tag = expected_token },
216 });
217 }
218
219 fn failMsg(p: *Parser, msg: Ast.Error) error{ ParseError, OutOfMemory } {
220 @setCold(true);
221 try p.warnMsg(msg);
222 return error.ParseError;
223 }
224
225 /// Root <- skip container_doc_comment? ContainerMembers eof
226 fn parseRoot(p: *Parser) !void {
227 // Root node must be index 0.
228 p.nodes.appendAssumeCapacity(.{
229 .tag = .root,
230 .main_token = 0,
231 .data = undefined,
232 });
233 const root_members = try p.parseContainerMembers();
234 const root_decls = try root_members.toSpan(p);
235 if (p.token_tags[p.tok_i] != .eof) {
236 try p.warnExpected(.eof);
237 }
238 p.nodes.items(.data)[0] = .{
239 .lhs = root_decls.start,
240 .rhs = root_decls.end,
241 };
242 }
243
244 /// ContainerMembers <- ContainerDeclarations (ContainerField COMMA)* (ContainerField / ContainerDeclarations)
245 ///
246 /// ContainerDeclarations
247 /// <- TestDecl ContainerDeclarations
248 /// / ComptimeDecl ContainerDeclarations
249 /// / doc_comment? KEYWORD_pub? Decl ContainerDeclarations
250 /// /
251 ///
252 /// ComptimeDecl <- KEYWORD_comptime Block
253 fn parseContainerMembers(p: *Parser) !Members {
254 const scratch_top = p.scratch.items.len;
255 defer p.scratch.shrinkRetainingCapacity(scratch_top);
256
257 var field_state: union(enum) {
258 /// No fields have been seen.
259 none,
260 /// Currently parsing fields.
261 seen,
262 /// Saw fields and then a declaration after them.
263 /// Payload is first token of previous declaration.
264 end: Node.Index,
265 /// There was a declaration between fields, don't report more errors.
266 err,
267 } = .none;
268
269 var last_field: TokenIndex = undefined;
270
271 // Skip container doc comments.
272 while (p.eatToken(.container_doc_comment)) |_| {}
273
274 var trailing = false;
275 while (true) {
276 const doc_comment = try p.eatDocComments();
277
278 switch (p.token_tags[p.tok_i]) {
279 .keyword_test => {
280 if (doc_comment) |some| {
281 try p.warnMsg(.{ .tag = .test_doc_comment, .token = some });
282 }
283 const test_decl_node = try p.expectTestDeclRecoverable();
284 if (test_decl_node != 0) {
285 if (field_state == .seen) {
286 field_state = .{ .end = test_decl_node };
287 }
288 try p.scratch.append(p.gpa, test_decl_node);
289 }
290 trailing = false;
291 },
292 .keyword_comptime => switch (p.token_tags[p.tok_i + 1]) {
293 .l_brace => {
294 if (doc_comment) |some| {
295 try p.warnMsg(.{ .tag = .comptime_doc_comment, .token = some });
296 }
297 const comptime_token = p.nextToken();
298 const block = p.parseBlock() catch |err| switch (err) {
299 error.OutOfMemory => return error.OutOfMemory,
300 error.ParseError => blk: {
301 p.findNextContainerMember();
302 break :blk null_node;
303 },
304 };
305 if (block != 0) {
306 const comptime_node = try p.addNode(.{
307 .tag = .@"comptime",
308 .main_token = comptime_token,
309 .data = .{
310 .lhs = block,
311 .rhs = undefined,
312 },
313 });
314 if (field_state == .seen) {
315 field_state = .{ .end = comptime_node };
316 }
317 try p.scratch.append(p.gpa, comptime_node);
318 }
319 trailing = false;
320 },
321 else => {
322 const identifier = p.tok_i;
323 defer last_field = identifier;
324 const container_field = p.expectContainerField() catch |err| switch (err) {
325 error.OutOfMemory => return error.OutOfMemory,
326 error.ParseError => {
327 p.findNextContainerMember();
328 continue;
329 },
330 };
331 switch (field_state) {
332 .none => field_state = .seen,
333 .err, .seen => {},
334 .end => |node| {
335 try p.warnMsg(.{
336 .tag = .decl_between_fields,
337 .token = p.nodes.items(.main_token)[node],
338 });
339 try p.warnMsg(.{
340 .tag = .previous_field,
341 .is_note = true,
342 .token = last_field,
343 });
344 try p.warnMsg(.{
345 .tag = .next_field,
346 .is_note = true,
347 .token = identifier,
348 });
349 // Continue parsing; error will be reported later.
350 field_state = .err;
351 },
352 }
353 try p.scratch.append(p.gpa, container_field);
354 switch (p.token_tags[p.tok_i]) {
355 .comma => {
356 p.tok_i += 1;
357 trailing = true;
358 continue;
359 },
360 .r_brace, .eof => {
361 trailing = false;
362 break;
363 },
364 else => {},
365 }
366 // There is not allowed to be a decl after a field with no comma.
367 // Report error but recover parser.
368 try p.warn(.expected_comma_after_field);
369 p.findNextContainerMember();
370 },
371 },
372 .keyword_pub => {
373 p.tok_i += 1;
374 const top_level_decl = try p.expectTopLevelDeclRecoverable();
375 if (top_level_decl != 0) {
376 if (field_state == .seen) {
377 field_state = .{ .end = top_level_decl };
378 }
379 try p.scratch.append(p.gpa, top_level_decl);
380 }
381 trailing = p.token_tags[p.tok_i - 1] == .semicolon;
382 },
383 .keyword_usingnamespace => {
384 const node = try p.expectUsingNamespaceRecoverable();
385 if (node != 0) {
386 if (field_state == .seen) {
387 field_state = .{ .end = node };
388 }
389 try p.scratch.append(p.gpa, node);
390 }
391 trailing = p.token_tags[p.tok_i - 1] == .semicolon;
392 },
393 .keyword_const,
394 .keyword_var,
395 .keyword_threadlocal,
396 .keyword_export,
397 .keyword_extern,
398 .keyword_inline,
399 .keyword_noinline,
400 .keyword_fn,
401 => {
402 const top_level_decl = try p.expectTopLevelDeclRecoverable();
403 if (top_level_decl != 0) {
404 if (field_state == .seen) {
405 field_state = .{ .end = top_level_decl };
406 }
407 try p.scratch.append(p.gpa, top_level_decl);
408 }
409 trailing = p.token_tags[p.tok_i - 1] == .semicolon;
410 },
411 .eof, .r_brace => {
412 if (doc_comment) |tok| {
413 try p.warnMsg(.{
414 .tag = .unattached_doc_comment,
415 .token = tok,
416 });
417 }
418 break;
419 },
420 else => {
421 const c_container = p.parseCStyleContainer() catch |err| switch (err) {
422 error.OutOfMemory => return error.OutOfMemory,
423 error.ParseError => false,
424 };
425 if (c_container) continue;
426
427 const identifier = p.tok_i;
428 defer last_field = identifier;
429 const container_field = p.expectContainerField() catch |err| switch (err) {
430 error.OutOfMemory => return error.OutOfMemory,
431 error.ParseError => {
432 p.findNextContainerMember();
433 continue;
434 },
435 };
436 switch (field_state) {
437 .none => field_state = .seen,
438 .err, .seen => {},
439 .end => |node| {
440 try p.warnMsg(.{
441 .tag = .decl_between_fields,
442 .token = p.nodes.items(.main_token)[node],
443 });
444 try p.warnMsg(.{
445 .tag = .previous_field,
446 .is_note = true,
447 .token = last_field,
448 });
449 try p.warnMsg(.{
450 .tag = .next_field,
451 .is_note = true,
452 .token = identifier,
453 });
454 // Continue parsing; error will be reported later.
455 field_state = .err;
456 },
457 }
458 try p.scratch.append(p.gpa, container_field);
459 switch (p.token_tags[p.tok_i]) {
460 .comma => {
461 p.tok_i += 1;
462 trailing = true;
463 continue;
464 },
465 .r_brace, .eof => {
466 trailing = false;
467 break;
468 },
469 else => {},
470 }
471 // There is not allowed to be a decl after a field with no comma.
472 // Report error but recover parser.
473 try p.warn(.expected_comma_after_field);
474 if (p.token_tags[p.tok_i] == .semicolon and p.token_tags[identifier] == .identifier) {
475 try p.warnMsg(.{
476 .tag = .var_const_decl,
477 .is_note = true,
478 .token = identifier,
479 });
480 }
481 p.findNextContainerMember();
482 continue;
483 },
484 }
485 }
486
487 const items = p.scratch.items[scratch_top..];
488 switch (items.len) {
489 0 => return Members{
490 .len = 0,
491 .lhs = 0,
492 .rhs = 0,
493 .trailing = trailing,
494 },
495 1 => return Members{
496 .len = 1,
497 .lhs = items[0],
498 .rhs = 0,
499 .trailing = trailing,
500 },
501 2 => return Members{
502 .len = 2,
503 .lhs = items[0],
504 .rhs = items[1],
505 .trailing = trailing,
506 },
507 else => {
508 const span = try p.listToSpan(items);
509 return Members{
510 .len = items.len,
511 .lhs = span.start,
512 .rhs = span.end,
513 .trailing = trailing,
514 };
515 },
516 }
517 }
518
519 /// Attempts to find next container member by searching for certain tokens
520 fn findNextContainerMember(p: *Parser) void {
521 var level: u32 = 0;
522 while (true) {
523 const tok = p.nextToken();
524 switch (p.token_tags[tok]) {
525 // Any of these can start a new top level declaration.
526 .keyword_test,
527 .keyword_comptime,
528 .keyword_pub,
529 .keyword_export,
530 .keyword_extern,
531 .keyword_inline,
532 .keyword_noinline,
533 .keyword_usingnamespace,
534 .keyword_threadlocal,
535 .keyword_const,
536 .keyword_var,
537 .keyword_fn,
538 => {
539 if (level == 0) {
540 p.tok_i -= 1;
541 return;
542 }
543 },
544 .identifier => {
545 if (p.token_tags[tok + 1] == .comma and level == 0) {
546 p.tok_i -= 1;
547 return;
548 }
549 },
550 .comma, .semicolon => {
551 // this decl was likely meant to end here
552 if (level == 0) {
553 return;
554 }
555 },
556 .l_paren, .l_bracket, .l_brace => level += 1,
557 .r_paren, .r_bracket => {
558 if (level != 0) level -= 1;
559 },
560 .r_brace => {
561 if (level == 0) {
562 // end of container, exit
563 p.tok_i -= 1;
564 return;
565 }
566 level -= 1;
567 },
568 .eof => {
569 p.tok_i -= 1;
570 return;
571 },
572 else => {},
573 }
574 }
575 }
576
577 /// Attempts to find the next statement by searching for a semicolon
578 fn findNextStmt(p: *Parser) void {
579 var level: u32 = 0;
580 while (true) {
581 const tok = p.nextToken();
582 switch (p.token_tags[tok]) {
583 .l_brace => level += 1,
584 .r_brace => {
585 if (level == 0) {
586 p.tok_i -= 1;
587 return;
588 }
589 level -= 1;
590 },
591 .semicolon => {
592 if (level == 0) {
593 return;
594 }
595 },
596 .eof => {
597 p.tok_i -= 1;
598 return;
599 },
600 else => {},
601 }
602 }
603 }
604
605 /// TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
606 fn expectTestDecl(p: *Parser) !Node.Index {
607 const test_token = p.assertToken(.keyword_test);
608 const name_token = switch (p.token_tags[p.nextToken()]) {
609 .string_literal, .identifier => p.tok_i - 1,
610 else => blk: {
611 p.tok_i -= 1;
612 break :blk null;
613 },
614 };
615 const block_node = try p.parseBlock();
616 if (block_node == 0) return p.fail(.expected_block);
617 return p.addNode(.{
618 .tag = .test_decl,
619 .main_token = test_token,
620 .data = .{
621 .lhs = name_token orelse 0,
622 .rhs = block_node,
623 },
624 });
625 }
626
627 fn expectTestDeclRecoverable(p: *Parser) error{OutOfMemory}!Node.Index {
628 return p.expectTestDecl() catch |err| switch (err) {
629 error.OutOfMemory => return error.OutOfMemory,
630 error.ParseError => {
631 p.findNextContainerMember();
632 return null_node;
633 },
634 };
635 }
636
637 /// Decl
638 /// <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block)
639 /// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
640 /// / KEYWORD_usingnamespace Expr SEMICOLON
641 fn expectTopLevelDecl(p: *Parser) !Node.Index {
642 const extern_export_inline_token = p.nextToken();
643 var is_extern: bool = false;
644 var expect_fn: bool = false;
645 var expect_var_or_fn: bool = false;
646 switch (p.token_tags[extern_export_inline_token]) {
647 .keyword_extern => {
648 _ = p.eatToken(.string_literal);
649 is_extern = true;
650 expect_var_or_fn = true;
651 },
652 .keyword_export => expect_var_or_fn = true,
653 .keyword_inline, .keyword_noinline => expect_fn = true,
654 else => p.tok_i -= 1,
655 }
656 const fn_proto = try p.parseFnProto();
657 if (fn_proto != 0) {
658 switch (p.token_tags[p.tok_i]) {
659 .semicolon => {
660 p.tok_i += 1;
661 return fn_proto;
662 },
663 .l_brace => {
664 if (is_extern) {
665 try p.warnMsg(.{ .tag = .extern_fn_body, .token = extern_export_inline_token });
666 return null_node;
667 }
668 const fn_decl_index = try p.reserveNode(.fn_decl);
669 errdefer p.unreserveNode(fn_decl_index);
670
671 const body_block = try p.parseBlock();
672 assert(body_block != 0);
673 return p.setNode(fn_decl_index, .{
674 .tag = .fn_decl,
675 .main_token = p.nodes.items(.main_token)[fn_proto],
676 .data = .{
677 .lhs = fn_proto,
678 .rhs = body_block,
679 },
680 });
681 },
682 else => {
683 // Since parseBlock only return error.ParseError on
684 // a missing '}' we can assume this function was
685 // supposed to end here.
686 try p.warn(.expected_semi_or_lbrace);
687 return null_node;
688 },
689 }
690 }
691 if (expect_fn) {
692 try p.warn(.expected_fn);
693 return error.ParseError;
694 }
695
696 const thread_local_token = p.eatToken(.keyword_threadlocal);
697 const var_decl = try p.parseVarDecl();
698 if (var_decl != 0) {
699 try p.expectSemicolon(.expected_semi_after_decl, false);
700 return var_decl;
701 }
702 if (thread_local_token != null) {
703 return p.fail(.expected_var_decl);
704 }
705 if (expect_var_or_fn) {
706 return p.fail(.expected_var_decl_or_fn);
707 }
708 if (p.token_tags[p.tok_i] != .keyword_usingnamespace) {
709 return p.fail(.expected_pub_item);
710 }
711 return p.expectUsingNamespace();
712 }
713
714 fn expectTopLevelDeclRecoverable(p: *Parser) error{OutOfMemory}!Node.Index {
715 return p.expectTopLevelDecl() catch |err| switch (err) {
716 error.OutOfMemory => return error.OutOfMemory,
717 error.ParseError => {
718 p.findNextContainerMember();
719 return null_node;
720 },
721 };
722 }
723
724 fn expectUsingNamespace(p: *Parser) !Node.Index {
725 const usingnamespace_token = p.assertToken(.keyword_usingnamespace);
726 const expr = try p.expectExpr();
727 try p.expectSemicolon(.expected_semi_after_decl, false);
728 return p.addNode(.{
729 .tag = .@"usingnamespace",
730 .main_token = usingnamespace_token,
731 .data = .{
732 .lhs = expr,
733 .rhs = undefined,
734 },
735 });
736 }
737
738 fn expectUsingNamespaceRecoverable(p: *Parser) error{OutOfMemory}!Node.Index {
739 return p.expectUsingNamespace() catch |err| switch (err) {
740 error.OutOfMemory => return error.OutOfMemory,
741 error.ParseError => {
742 p.findNextContainerMember();
743 return null_node;
744 },
745 };
746 }
747
748 /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
749 fn parseFnProto(p: *Parser) !Node.Index {
750 const fn_token = p.eatToken(.keyword_fn) orelse return null_node;
751
752 // We want the fn proto node to be before its children in the array.
753 const fn_proto_index = try p.reserveNode(.fn_proto);
754 errdefer p.unreserveNode(fn_proto_index);
755
756 _ = p.eatToken(.identifier);
757 const params = try p.parseParamDeclList();
758 const align_expr = try p.parseByteAlign();
759 const addrspace_expr = try p.parseAddrSpace();
760 const section_expr = try p.parseLinkSection();
761 const callconv_expr = try p.parseCallconv();
762 _ = p.eatToken(.bang);
763
764 const return_type_expr = try p.parseTypeExpr();
765 if (return_type_expr == 0) {
766 // most likely the user forgot to specify the return type.
767 // Mark return type as invalid and try to continue.
768 try p.warn(.expected_return_type);
769 }
770
771 if (align_expr == 0 and section_expr == 0 and callconv_expr == 0 and addrspace_expr == 0) {
772 switch (params) {
773 .zero_or_one => |param| return p.setNode(fn_proto_index, .{
774 .tag = .fn_proto_simple,
775 .main_token = fn_token,
776 .data = .{
777 .lhs = param,
778 .rhs = return_type_expr,
779 },
780 }),
781 .multi => |span| {
782 return p.setNode(fn_proto_index, .{
783 .tag = .fn_proto_multi,
784 .main_token = fn_token,
785 .data = .{
786 .lhs = try p.addExtra(Node.SubRange{
787 .start = span.start,
788 .end = span.end,
789 }),
790 .rhs = return_type_expr,
791 },
792 });
793 },
794 }
795 }
796 switch (params) {
797 .zero_or_one => |param| return p.setNode(fn_proto_index, .{
798 .tag = .fn_proto_one,
799 .main_token = fn_token,
800 .data = .{
801 .lhs = try p.addExtra(Node.FnProtoOne{
802 .param = param,
803 .align_expr = align_expr,
804 .addrspace_expr = addrspace_expr,
805 .section_expr = section_expr,
806 .callconv_expr = callconv_expr,
807 }),
808 .rhs = return_type_expr,
809 },
810 }),
811 .multi => |span| {
812 return p.setNode(fn_proto_index, .{
813 .tag = .fn_proto,
814 .main_token = fn_token,
815 .data = .{
816 .lhs = try p.addExtra(Node.FnProto{
817 .params_start = span.start,
818 .params_end = span.end,
819 .align_expr = align_expr,
820 .addrspace_expr = addrspace_expr,
821 .section_expr = section_expr,
822 .callconv_expr = callconv_expr,
823 }),
824 .rhs = return_type_expr,
825 },
826 });
827 },
828 }
829 }
830
831 /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? (EQUAL Expr)? SEMICOLON
832 fn parseVarDecl(p: *Parser) !Node.Index {
833 const mut_token = p.eatToken(.keyword_const) orelse
834 p.eatToken(.keyword_var) orelse
835 return null_node;
836
837 _ = try p.expectToken(.identifier);
838 const type_node: Node.Index = if (p.eatToken(.colon) == null) 0 else try p.expectTypeExpr();
839 const align_node = try p.parseByteAlign();
840 const addrspace_node = try p.parseAddrSpace();
841 const section_node = try p.parseLinkSection();
842 const init_node: Node.Index = switch (p.token_tags[p.tok_i]) {
843 .equal_equal => blk: {
844 try p.warn(.wrong_equal_var_decl);
845 p.tok_i += 1;
846 break :blk try p.expectExpr();
847 },
848 .equal => blk: {
849 p.tok_i += 1;
850 break :blk try p.expectExpr();
851 },
852 else => 0,
853 };
854 if (section_node == 0 and addrspace_node == 0) {
855 if (align_node == 0) {
856 return p.addNode(.{
857 .tag = .simple_var_decl,
858 .main_token = mut_token,
859 .data = .{
860 .lhs = type_node,
861 .rhs = init_node,
862 },
863 });
864 } else if (type_node == 0) {
865 return p.addNode(.{
866 .tag = .aligned_var_decl,
867 .main_token = mut_token,
868 .data = .{
869 .lhs = align_node,
870 .rhs = init_node,
871 },
872 });
873 } else {
874 return p.addNode(.{
875 .tag = .local_var_decl,
876 .main_token = mut_token,
877 .data = .{
878 .lhs = try p.addExtra(Node.LocalVarDecl{
879 .type_node = type_node,
880 .align_node = align_node,
881 }),
882 .rhs = init_node,
883 },
884 });
885 }
886 } else {
887 return p.addNode(.{
888 .tag = .global_var_decl,
889 .main_token = mut_token,
890 .data = .{
891 .lhs = try p.addExtra(Node.GlobalVarDecl{
892 .type_node = type_node,
893 .align_node = align_node,
894 .addrspace_node = addrspace_node,
895 .section_node = section_node,
896 }),
897 .rhs = init_node,
898 },
899 });
900 }
901 }
902
903 /// ContainerField
904 /// <- doc_comment? KEYWORD_comptime? IDENTIFIER (COLON TypeExpr)? ByteAlign? (EQUAL Expr)?
905 /// / doc_comment? KEYWORD_comptime? (IDENTIFIER COLON)? !KEYWORD_fn TypeExpr ByteAlign? (EQUAL Expr)?
906 fn expectContainerField(p: *Parser) !Node.Index {
907 var main_token = p.tok_i;
908 _ = p.eatToken(.keyword_comptime);
909 const tuple_like = p.token_tags[p.tok_i] != .identifier or p.token_tags[p.tok_i + 1] != .colon;
910 if (!tuple_like) {
911 main_token = p.assertToken(.identifier);
912 }
913
914 var align_expr: Node.Index = 0;
915 var type_expr: Node.Index = 0;
916 if (p.eatToken(.colon) != null or tuple_like) {
917 type_expr = try p.expectTypeExpr();
918 align_expr = try p.parseByteAlign();
919 }
920
921 const value_expr: Node.Index = if (p.eatToken(.equal) == null) 0 else try p.expectExpr();
922
923 if (align_expr == 0) {
924 return p.addNode(.{
925 .tag = .container_field_init,
926 .main_token = main_token,
927 .data = .{
928 .lhs = type_expr,
929 .rhs = value_expr,
930 },
931 });
932 } else if (value_expr == 0) {
933 return p.addNode(.{
934 .tag = .container_field_align,
935 .main_token = main_token,
936 .data = .{
937 .lhs = type_expr,
938 .rhs = align_expr,
939 },
940 });
941 } else {
942 return p.addNode(.{
943 .tag = .container_field,
944 .main_token = main_token,
945 .data = .{
946 .lhs = type_expr,
947 .rhs = try p.addExtra(Node.ContainerField{
948 .value_expr = value_expr,
949 .align_expr = align_expr,
950 }),
951 },
952 });
953 }
954 }
955
956 /// Statement
957 /// <- KEYWORD_comptime? VarDecl
958 /// / KEYWORD_comptime BlockExprStatement
959 /// / KEYWORD_nosuspend BlockExprStatement
960 /// / KEYWORD_suspend BlockExprStatement
961 /// / KEYWORD_defer BlockExprStatement
962 /// / KEYWORD_errdefer Payload? BlockExprStatement
963 /// / IfStatement
964 /// / LabeledStatement
965 /// / SwitchExpr
966 /// / AssignExpr SEMICOLON
967 fn parseStatement(p: *Parser, allow_defer_var: bool) Error!Node.Index {
968 const comptime_token = p.eatToken(.keyword_comptime);
969
970 if (allow_defer_var) {
971 const var_decl = try p.parseVarDecl();
972 if (var_decl != 0) {
973 try p.expectSemicolon(.expected_semi_after_decl, true);
974 return var_decl;
975 }
976 }
977
978 if (comptime_token) |token| {
979 return p.addNode(.{
980 .tag = .@"comptime",
981 .main_token = token,
982 .data = .{
983 .lhs = try p.expectBlockExprStatement(),
984 .rhs = undefined,
985 },
986 });
987 }
988
989 switch (p.token_tags[p.tok_i]) {
990 .keyword_nosuspend => {
991 return p.addNode(.{
992 .tag = .@"nosuspend",
993 .main_token = p.nextToken(),
994 .data = .{
995 .lhs = try p.expectBlockExprStatement(),
996 .rhs = undefined,
997 },
998 });
999 },
1000 .keyword_suspend => {
1001 const token = p.nextToken();
1002 const block_expr = try p.expectBlockExprStatement();
1003 return p.addNode(.{
1004 .tag = .@"suspend",
1005 .main_token = token,
1006 .data = .{
1007 .lhs = block_expr,
1008 .rhs = undefined,
1009 },
1010 });
1011 },
1012 .keyword_defer => if (allow_defer_var) return p.addNode(.{
1013 .tag = .@"defer",
1014 .main_token = p.nextToken(),
1015 .data = .{
1016 .lhs = undefined,
1017 .rhs = try p.expectBlockExprStatement(),
1018 },
1019 }),
1020 .keyword_errdefer => if (allow_defer_var) return p.addNode(.{
1021 .tag = .@"errdefer",
1022 .main_token = p.nextToken(),
1023 .data = .{
1024 .lhs = try p.parsePayload(),
1025 .rhs = try p.expectBlockExprStatement(),
1026 },
1027 }),
1028 .keyword_switch => return p.expectSwitchExpr(),
1029 .keyword_if => return p.expectIfStatement(),
1030 .keyword_enum, .keyword_struct, .keyword_union => {
1031 const identifier = p.tok_i + 1;
1032 if (try p.parseCStyleContainer()) {
1033 // Return something so that `expectStatement` is happy.
1034 return p.addNode(.{
1035 .tag = .identifier,
1036 .main_token = identifier,
1037 .data = .{
1038 .lhs = undefined,
1039 .rhs = undefined,
1040 },
1041 });
1042 }
1043 },
1044 else => {},
1045 }
1046
1047 const labeled_statement = try p.parseLabeledStatement();
1048 if (labeled_statement != 0) return labeled_statement;
1049
1050 const assign_expr = try p.parseAssignExpr();
1051 if (assign_expr != 0) {
1052 try p.expectSemicolon(.expected_semi_after_stmt, true);
1053 return assign_expr;
1054 }
1055
1056 return null_node;
1057 }
1058
1059 fn expectStatement(p: *Parser, allow_defer_var: bool) !Node.Index {
1060 const statement = try p.parseStatement(allow_defer_var);
1061 if (statement == 0) {
1062 return p.fail(.expected_statement);
1063 }
1064 return statement;
1065 }
1066
1067 /// If a parse error occurs, reports an error, but then finds the next statement
1068 /// and returns that one instead. If a parse error occurs but there is no following
1069 /// statement, returns 0.
1070 fn expectStatementRecoverable(p: *Parser) Error!Node.Index {
1071 while (true) {
1072 return p.expectStatement(true) catch |err| switch (err) {
1073 error.OutOfMemory => return error.OutOfMemory,
1074 error.ParseError => {
1075 p.findNextStmt(); // Try to skip to the next statement.
1076 switch (p.token_tags[p.tok_i]) {
1077 .r_brace => return null_node,
1078 .eof => return error.ParseError,
1079 else => continue,
1080 }
1081 },
1082 };
1083 }
1084 }
1085
1086 /// IfStatement
1087 /// <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
1088 /// / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
1089 fn expectIfStatement(p: *Parser) !Node.Index {
1090 const if_token = p.assertToken(.keyword_if);
1091 _ = try p.expectToken(.l_paren);
1092 const condition = try p.expectExpr();
1093 _ = try p.expectToken(.r_paren);
1094 _ = try p.parsePtrPayload();
1095
1096 // TODO propose to change the syntax so that semicolons are always required
1097 // inside if statements, even if there is an `else`.
1098 var else_required = false;
1099 const then_expr = blk: {
1100 const block_expr = try p.parseBlockExpr();
1101 if (block_expr != 0) break :blk block_expr;
1102 const assign_expr = try p.parseAssignExpr();
1103 if (assign_expr == 0) {
1104 return p.fail(.expected_block_or_assignment);
1105 }
1106 if (p.eatToken(.semicolon)) |_| {
1107 return p.addNode(.{
1108 .tag = .if_simple,
1109 .main_token = if_token,
1110 .data = .{
1111 .lhs = condition,
1112 .rhs = assign_expr,
1113 },
1114 });
1115 }
1116 else_required = true;
1117 break :blk assign_expr;
1118 };
1119 _ = p.eatToken(.keyword_else) orelse {
1120 if (else_required) {
1121 try p.warn(.expected_semi_or_else);
1122 }
1123 return p.addNode(.{
1124 .tag = .if_simple,
1125 .main_token = if_token,
1126 .data = .{
1127 .lhs = condition,
1128 .rhs = then_expr,
1129 },
1130 });
1131 };
1132 _ = try p.parsePayload();
1133 const else_expr = try p.expectStatement(false);
1134 return p.addNode(.{
1135 .tag = .@"if",
1136 .main_token = if_token,
1137 .data = .{
1138 .lhs = condition,
1139 .rhs = try p.addExtra(Node.If{
1140 .then_expr = then_expr,
1141 .else_expr = else_expr,
1142 }),
1143 },
1144 });
1145 }
1146
1147 /// LabeledStatement <- BlockLabel? (Block / LoopStatement)
1148 fn parseLabeledStatement(p: *Parser) !Node.Index {
1149 const label_token = p.parseBlockLabel();
1150 const block = try p.parseBlock();
1151 if (block != 0) return block;
1152
1153 const loop_stmt = try p.parseLoopStatement();
1154 if (loop_stmt != 0) return loop_stmt;
1155
1156 if (label_token != 0) {
1157 const after_colon = p.tok_i;
1158 const node = try p.parseTypeExpr();
1159 if (node != 0) {
1160 const a = try p.parseByteAlign();
1161 const b = try p.parseAddrSpace();
1162 const c = try p.parseLinkSection();
1163 const d = if (p.eatToken(.equal) == null) 0 else try p.expectExpr();
1164 if (a != 0 or b != 0 or c != 0 or d != 0) {
1165 return p.failMsg(.{ .tag = .expected_var_const, .token = label_token });
1166 }
1167 }
1168 return p.failMsg(.{ .tag = .expected_labelable, .token = after_colon });
1169 }
1170
1171 return null_node;
1172 }
1173
1174 /// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
1175 fn parseLoopStatement(p: *Parser) !Node.Index {
1176 const inline_token = p.eatToken(.keyword_inline);
1177
1178 const for_statement = try p.parseForStatement();
1179 if (for_statement != 0) return for_statement;
1180
1181 const while_statement = try p.parseWhileStatement();
1182 if (while_statement != 0) return while_statement;
1183
1184 if (inline_token == null) return null_node;
1185
1186 // If we've seen "inline", there should have been a "for" or "while"
1187 return p.fail(.expected_inlinable);
1188 }
1189
1190 /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
1191 ///
1192 /// ForStatement
1193 /// <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
1194 /// / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
1195 fn parseForStatement(p: *Parser) !Node.Index {
1196 const for_token = p.eatToken(.keyword_for) orelse return null_node;
1197 _ = try p.expectToken(.l_paren);
1198 const array_expr = try p.expectExpr();
1199 _ = try p.expectToken(.r_paren);
1200 const found_payload = try p.parsePtrIndexPayload();
1201 if (found_payload == 0) try p.warn(.expected_loop_payload);
1202
1203 // TODO propose to change the syntax so that semicolons are always required
1204 // inside while statements, even if there is an `else`.
1205 var else_required = false;
1206 const then_expr = blk: {
1207 const block_expr = try p.parseBlockExpr();
1208 if (block_expr != 0) break :blk block_expr;
1209 const assign_expr = try p.parseAssignExpr();
1210 if (assign_expr == 0) {
1211 return p.fail(.expected_block_or_assignment);
1212 }
1213 if (p.eatToken(.semicolon)) |_| {
1214 return p.addNode(.{
1215 .tag = .for_simple,
1216 .main_token = for_token,
1217 .data = .{
1218 .lhs = array_expr,
1219 .rhs = assign_expr,
1220 },
1221 });
1222 }
1223 else_required = true;
1224 break :blk assign_expr;
1225 };
1226 _ = p.eatToken(.keyword_else) orelse {
1227 if (else_required) {
1228 try p.warn(.expected_semi_or_else);
1229 }
1230 return p.addNode(.{
1231 .tag = .for_simple,
1232 .main_token = for_token,
1233 .data = .{
1234 .lhs = array_expr,
1235 .rhs = then_expr,
1236 },
1237 });
1238 };
1239 return p.addNode(.{
1240 .tag = .@"for",
1241 .main_token = for_token,
1242 .data = .{
1243 .lhs = array_expr,
1244 .rhs = try p.addExtra(Node.If{
1245 .then_expr = then_expr,
1246 .else_expr = try p.expectStatement(false),
1247 }),
1248 },
1249 });
1250 }
1251
1252 /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
1253 ///
1254 /// WhileStatement
1255 /// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
1256 /// / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
1257 fn parseWhileStatement(p: *Parser) !Node.Index {
1258 const while_token = p.eatToken(.keyword_while) orelse return null_node;
1259 _ = try p.expectToken(.l_paren);
1260 const condition = try p.expectExpr();
1261 _ = try p.expectToken(.r_paren);
1262 _ = try p.parsePtrPayload();
1263 const cont_expr = try p.parseWhileContinueExpr();
1264
1265 // TODO propose to change the syntax so that semicolons are always required
1266 // inside while statements, even if there is an `else`.
1267 var else_required = false;
1268 const then_expr = blk: {
1269 const block_expr = try p.parseBlockExpr();
1270 if (block_expr != 0) break :blk block_expr;
1271 const assign_expr = try p.parseAssignExpr();
1272 if (assign_expr == 0) {
1273 return p.fail(.expected_block_or_assignment);
1274 }
1275 if (p.eatToken(.semicolon)) |_| {
1276 if (cont_expr == 0) {
1277 return p.addNode(.{
1278 .tag = .while_simple,
1279 .main_token = while_token,
1280 .data = .{
1281 .lhs = condition,
1282 .rhs = assign_expr,
1283 },
1284 });
1285 } else {
1286 return p.addNode(.{
1287 .tag = .while_cont,
1288 .main_token = while_token,
1289 .data = .{
1290 .lhs = condition,
1291 .rhs = try p.addExtra(Node.WhileCont{
1292 .cont_expr = cont_expr,
1293 .then_expr = assign_expr,
1294 }),
1295 },
1296 });
1297 }
1298 }
1299 else_required = true;
1300 break :blk assign_expr;
1301 };
1302 _ = p.eatToken(.keyword_else) orelse {
1303 if (else_required) {
1304 try p.warn(.expected_semi_or_else);
1305 }
1306 if (cont_expr == 0) {
1307 return p.addNode(.{
1308 .tag = .while_simple,
1309 .main_token = while_token,
1310 .data = .{
1311 .lhs = condition,
1312 .rhs = then_expr,
1313 },
1314 });
1315 } else {
1316 return p.addNode(.{
1317 .tag = .while_cont,
1318 .main_token = while_token,
1319 .data = .{
1320 .lhs = condition,
1321 .rhs = try p.addExtra(Node.WhileCont{
1322 .cont_expr = cont_expr,
1323 .then_expr = then_expr,
1324 }),
1325 },
1326 });
1327 }
1328 };
1329 _ = try p.parsePayload();
1330 const else_expr = try p.expectStatement(false);
1331 return p.addNode(.{
1332 .tag = .@"while",
1333 .main_token = while_token,
1334 .data = .{
1335 .lhs = condition,
1336 .rhs = try p.addExtra(Node.While{
1337 .cont_expr = cont_expr,
1338 .then_expr = then_expr,
1339 .else_expr = else_expr,
1340 }),
1341 },
1342 });
1343 }
1344
1345 /// BlockExprStatement
1346 /// <- BlockExpr
1347 /// / AssignExpr SEMICOLON
1348 fn parseBlockExprStatement(p: *Parser) !Node.Index {
1349 const block_expr = try p.parseBlockExpr();
1350 if (block_expr != 0) {
1351 return block_expr;
1352 }
1353 const assign_expr = try p.parseAssignExpr();
1354 if (assign_expr != 0) {
1355 try p.expectSemicolon(.expected_semi_after_stmt, true);
1356 return assign_expr;
1357 }
1358 return null_node;
1359 }
1360
1361 fn expectBlockExprStatement(p: *Parser) !Node.Index {
1362 const node = try p.parseBlockExprStatement();
1363 if (node == 0) {
1364 return p.fail(.expected_block_or_expr);
1365 }
1366 return node;
1367 }
1368
1369 /// BlockExpr <- BlockLabel? Block
1370 fn parseBlockExpr(p: *Parser) Error!Node.Index {
1371 switch (p.token_tags[p.tok_i]) {
1372 .identifier => {
1373 if (p.token_tags[p.tok_i + 1] == .colon and
1374 p.token_tags[p.tok_i + 2] == .l_brace)
1375 {
1376 p.tok_i += 2;
1377 return p.parseBlock();
1378 } else {
1379 return null_node;
1380 }
1381 },
1382 .l_brace => return p.parseBlock(),
1383 else => return null_node,
1384 }
1385 }
1386
1387 /// AssignExpr <- Expr (AssignOp Expr)?
1388 ///
1389 /// AssignOp
1390 /// <- ASTERISKEQUAL
1391 /// / ASTERISKPIPEEQUAL
1392 /// / SLASHEQUAL
1393 /// / PERCENTEQUAL
1394 /// / PLUSEQUAL
1395 /// / PLUSPIPEEQUAL
1396 /// / MINUSEQUAL
1397 /// / MINUSPIPEEQUAL
1398 /// / LARROW2EQUAL
1399 /// / LARROW2PIPEEQUAL
1400 /// / RARROW2EQUAL
1401 /// / AMPERSANDEQUAL
1402 /// / CARETEQUAL
1403 /// / PIPEEQUAL
1404 /// / ASTERISKPERCENTEQUAL
1405 /// / PLUSPERCENTEQUAL
1406 /// / MINUSPERCENTEQUAL
1407 /// / EQUAL
1408 fn parseAssignExpr(p: *Parser) !Node.Index {
1409 const expr = try p.parseExpr();
1410 if (expr == 0) return null_node;
1411
1412 const tag: Node.Tag = switch (p.token_tags[p.tok_i]) {
1413 .asterisk_equal => .assign_mul,
1414 .slash_equal => .assign_div,
1415 .percent_equal => .assign_mod,
1416 .plus_equal => .assign_add,
1417 .minus_equal => .assign_sub,
1418 .angle_bracket_angle_bracket_left_equal => .assign_shl,
1419 .angle_bracket_angle_bracket_left_pipe_equal => .assign_shl_sat,
1420 .angle_bracket_angle_bracket_right_equal => .assign_shr,
1421 .ampersand_equal => .assign_bit_and,
1422 .caret_equal => .assign_bit_xor,
1423 .pipe_equal => .assign_bit_or,
1424 .asterisk_percent_equal => .assign_mul_wrap,
1425 .plus_percent_equal => .assign_add_wrap,
1426 .minus_percent_equal => .assign_sub_wrap,
1427 .asterisk_pipe_equal => .assign_mul_sat,
1428 .plus_pipe_equal => .assign_add_sat,
1429 .minus_pipe_equal => .assign_sub_sat,
1430 .equal => .assign,
1431 else => return expr,
1432 };
1433 return p.addNode(.{
1434 .tag = tag,
1435 .main_token = p.nextToken(),
1436 .data = .{
1437 .lhs = expr,
1438 .rhs = try p.expectExpr(),
1439 },
1440 });
1441 }
1442
1443 fn expectAssignExpr(p: *Parser) !Node.Index {
1444 const expr = try p.parseAssignExpr();
1445 if (expr == 0) {
1446 return p.fail(.expected_expr_or_assignment);
1447 }
1448 return expr;
1449 }
1450
1451 fn parseExpr(p: *Parser) Error!Node.Index {
1452 return p.parseExprPrecedence(0);
1453 }
1454
1455 fn expectExpr(p: *Parser) Error!Node.Index {
1456 const node = try p.parseExpr();
1457 if (node == 0) {
1458 return p.fail(.expected_expr);
1459 } else {
1460 return node;
1461 }
1462 }
1463
1464 const Assoc = enum {
1465 left,
1466 none,
1467 };
1468
1469 const OperInfo = struct {
1470 prec: i8,
1471 tag: Node.Tag,
1472 assoc: Assoc = Assoc.left,
1473 };
1474
1475 // A table of binary operator information. Higher precedence numbers are
1476 // stickier. All operators at the same precedence level should have the same
1477 // associativity.
1478 const operTable = std.enums.directEnumArrayDefault(Token.Tag, OperInfo, .{ .prec = -1, .tag = Node.Tag.root }, 0, .{
1479 .keyword_or = .{ .prec = 10, .tag = .bool_or },
1480
1481 .keyword_and = .{ .prec = 20, .tag = .bool_and },
1482
1483 .equal_equal = .{ .prec = 30, .tag = .equal_equal, .assoc = Assoc.none },
1484 .bang_equal = .{ .prec = 30, .tag = .bang_equal, .assoc = Assoc.none },
1485 .angle_bracket_left = .{ .prec = 30, .tag = .less_than, .assoc = Assoc.none },
1486 .angle_bracket_right = .{ .prec = 30, .tag = .greater_than, .assoc = Assoc.none },
1487 .angle_bracket_left_equal = .{ .prec = 30, .tag = .less_or_equal, .assoc = Assoc.none },
1488 .angle_bracket_right_equal = .{ .prec = 30, .tag = .greater_or_equal, .assoc = Assoc.none },
1489
1490 .ampersand = .{ .prec = 40, .tag = .bit_and },
1491 .caret = .{ .prec = 40, .tag = .bit_xor },
1492 .pipe = .{ .prec = 40, .tag = .bit_or },
1493 .keyword_orelse = .{ .prec = 40, .tag = .@"orelse" },
1494 .keyword_catch = .{ .prec = 40, .tag = .@"catch" },
1495
1496 .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .shl },
1497 .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .shl_sat },
1498 .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .shr },
1499
1500 .plus = .{ .prec = 60, .tag = .add },
1501 .minus = .{ .prec = 60, .tag = .sub },
1502 .plus_plus = .{ .prec = 60, .tag = .array_cat },
1503 .plus_percent = .{ .prec = 60, .tag = .add_wrap },
1504 .minus_percent = .{ .prec = 60, .tag = .sub_wrap },
1505 .plus_pipe = .{ .prec = 60, .tag = .add_sat },
1506 .minus_pipe = .{ .prec = 60, .tag = .sub_sat },
1507
1508 .pipe_pipe = .{ .prec = 70, .tag = .merge_error_sets },
1509 .asterisk = .{ .prec = 70, .tag = .mul },
1510 .slash = .{ .prec = 70, .tag = .div },
1511 .percent = .{ .prec = 70, .tag = .mod },
1512 .asterisk_asterisk = .{ .prec = 70, .tag = .array_mult },
1513 .asterisk_percent = .{ .prec = 70, .tag = .mul_wrap },
1514 .asterisk_pipe = .{ .prec = 70, .tag = .mul_sat },
1515 });
1516
1517 fn parseExprPrecedence(p: *Parser, min_prec: i32) Error!Node.Index {
1518 assert(min_prec >= 0);
1519 var node = try p.parsePrefixExpr();
1520 if (node == 0) {
1521 return null_node;
1522 }
1523
1524 var banned_prec: i8 = -1;
1525
1526 while (true) {
1527 const tok_tag = p.token_tags[p.tok_i];
1528 const info = operTable[@intCast(usize, @enumToInt(tok_tag))];
1529 if (info.prec < min_prec) {
1530 break;
1531 }
1532 if (info.prec == banned_prec) {
1533 return p.fail(.chained_comparison_operators);
1534 }
1535
1536 const oper_token = p.nextToken();
1537 // Special-case handling for "catch"
1538 if (tok_tag == .keyword_catch) {
1539 _ = try p.parsePayload();
1540 }
1541 const rhs = try p.parseExprPrecedence(info.prec + 1);
1542 if (rhs == 0) {
1543 try p.warn(.expected_expr);
1544 return node;
1545 }
1546
1547 {
1548 const tok_len = tok_tag.lexeme().?.len;
1549 const char_before = p.source[p.token_starts[oper_token] - 1];
1550 const char_after = p.source[p.token_starts[oper_token] + tok_len];
1551 if (tok_tag == .ampersand and char_after == '&') {
1552 // without types we don't know if '&&' was intended as 'bitwise_and address_of', or a c-style logical_and
1553 // The best the parser can do is recommend changing it to 'and' or ' & &'
1554 try p.warnMsg(.{ .tag = .invalid_ampersand_ampersand, .token = oper_token });
1555 } else if (std.ascii.isWhitespace(char_before) != std.ascii.isWhitespace(char_after)) {
1556 try p.warnMsg(.{ .tag = .mismatched_binary_op_whitespace, .token = oper_token });
1557 }
1558 }
1559
1560 node = try p.addNode(.{
1561 .tag = info.tag,
1562 .main_token = oper_token,
1563 .data = .{
1564 .lhs = node,
1565 .rhs = rhs,
1566 },
1567 });
1568
1569 if (info.assoc == Assoc.none) {
1570 banned_prec = info.prec;
1571 }
1572 }
1573
1574 return node;
1575 }
1576
1577 /// PrefixExpr <- PrefixOp* PrimaryExpr
1578 ///
1579 /// PrefixOp
1580 /// <- EXCLAMATIONMARK
1581 /// / MINUS
1582 /// / TILDE
1583 /// / MINUSPERCENT
1584 /// / AMPERSAND
1585 /// / KEYWORD_try
1586 /// / KEYWORD_await
1587 fn parsePrefixExpr(p: *Parser) Error!Node.Index {
1588 const tag: Node.Tag = switch (p.token_tags[p.tok_i]) {
1589 .bang => .bool_not,
1590 .minus => .negation,
1591 .tilde => .bit_not,
1592 .minus_percent => .negation_wrap,
1593 .ampersand => .address_of,
1594 .keyword_try => .@"try",
1595 .keyword_await => .@"await",
1596 else => return p.parsePrimaryExpr(),
1597 };
1598 return p.addNode(.{
1599 .tag = tag,
1600 .main_token = p.nextToken(),
1601 .data = .{
1602 .lhs = try p.expectPrefixExpr(),
1603 .rhs = undefined,
1604 },
1605 });
1606 }
1607
1608 fn expectPrefixExpr(p: *Parser) Error!Node.Index {
1609 const node = try p.parsePrefixExpr();
1610 if (node == 0) {
1611 return p.fail(.expected_prefix_expr);
1612 }
1613 return node;
1614 }
1615
1616 /// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
1617 ///
1618 /// PrefixTypeOp
1619 /// <- QUESTIONMARK
1620 /// / KEYWORD_anyframe MINUSRARROW
1621 /// / SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
1622 /// / PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
1623 /// / ArrayTypeStart
1624 ///
1625 /// SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
1626 ///
1627 /// PtrTypeStart
1628 /// <- ASTERISK
1629 /// / ASTERISK2
1630 /// / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
1631 ///
1632 /// ArrayTypeStart <- LBRACKET Expr (COLON Expr)? RBRACKET
1633 fn parseTypeExpr(p: *Parser) Error!Node.Index {
1634 switch (p.token_tags[p.tok_i]) {
1635 .question_mark => return p.addNode(.{
1636 .tag = .optional_type,
1637 .main_token = p.nextToken(),
1638 .data = .{
1639 .lhs = try p.expectTypeExpr(),
1640 .rhs = undefined,
1641 },
1642 }),
1643 .keyword_anyframe => switch (p.token_tags[p.tok_i + 1]) {
1644 .arrow => return p.addNode(.{
1645 .tag = .anyframe_type,
1646 .main_token = p.nextToken(),
1647 .data = .{
1648 .lhs = p.nextToken(),
1649 .rhs = try p.expectTypeExpr(),
1650 },
1651 }),
1652 else => return p.parseErrorUnionExpr(),
1653 },
1654 .asterisk => {
1655 const asterisk = p.nextToken();
1656 const mods = try p.parsePtrModifiers();
1657 const elem_type = try p.expectTypeExpr();
1658 if (mods.bit_range_start != 0) {
1659 return p.addNode(.{
1660 .tag = .ptr_type_bit_range,
1661 .main_token = asterisk,
1662 .data = .{
1663 .lhs = try p.addExtra(Node.PtrTypeBitRange{
1664 .sentinel = 0,
1665 .align_node = mods.align_node,
1666 .addrspace_node = mods.addrspace_node,
1667 .bit_range_start = mods.bit_range_start,
1668 .bit_range_end = mods.bit_range_end,
1669 }),
1670 .rhs = elem_type,
1671 },
1672 });
1673 } else if (mods.addrspace_node != 0) {
1674 return p.addNode(.{
1675 .tag = .ptr_type,
1676 .main_token = asterisk,
1677 .data = .{
1678 .lhs = try p.addExtra(Node.PtrType{
1679 .sentinel = 0,
1680 .align_node = mods.align_node,
1681 .addrspace_node = mods.addrspace_node,
1682 }),
1683 .rhs = elem_type,
1684 },
1685 });
1686 } else {
1687 return p.addNode(.{
1688 .tag = .ptr_type_aligned,
1689 .main_token = asterisk,
1690 .data = .{
1691 .lhs = mods.align_node,
1692 .rhs = elem_type,
1693 },
1694 });
1695 }
1696 },
1697 .asterisk_asterisk => {
1698 const asterisk = p.nextToken();
1699 const mods = try p.parsePtrModifiers();
1700 const elem_type = try p.expectTypeExpr();
1701 const inner: Node.Index = inner: {
1702 if (mods.bit_range_start != 0) {
1703 break :inner try p.addNode(.{
1704 .tag = .ptr_type_bit_range,
1705 .main_token = asterisk,
1706 .data = .{
1707 .lhs = try p.addExtra(Node.PtrTypeBitRange{
1708 .sentinel = 0,
1709 .align_node = mods.align_node,
1710 .addrspace_node = mods.addrspace_node,
1711 .bit_range_start = mods.bit_range_start,
1712 .bit_range_end = mods.bit_range_end,
1713 }),
1714 .rhs = elem_type,
1715 },
1716 });
1717 } else if (mods.addrspace_node != 0) {
1718 break :inner try p.addNode(.{
1719 .tag = .ptr_type,
1720 .main_token = asterisk,
1721 .data = .{
1722 .lhs = try p.addExtra(Node.PtrType{
1723 .sentinel = 0,
1724 .align_node = mods.align_node,
1725 .addrspace_node = mods.addrspace_node,
1726 }),
1727 .rhs = elem_type,
1728 },
1729 });
1730 } else {
1731 break :inner try p.addNode(.{
1732 .tag = .ptr_type_aligned,
1733 .main_token = asterisk,
1734 .data = .{
1735 .lhs = mods.align_node,
1736 .rhs = elem_type,
1737 },
1738 });
1739 }
1740 };
1741 return p.addNode(.{
1742 .tag = .ptr_type_aligned,
1743 .main_token = asterisk,
1744 .data = .{
1745 .lhs = 0,
1746 .rhs = inner,
1747 },
1748 });
1749 },
1750 .l_bracket => switch (p.token_tags[p.tok_i + 1]) {
1751 .asterisk => {
1752 _ = p.nextToken();
1753 const asterisk = p.nextToken();
1754 var sentinel: Node.Index = 0;
1755 if (p.eatToken(.identifier)) |ident| {
1756 const ident_slice = p.source[p.token_starts[ident]..p.token_starts[ident + 1]];
1757 if (!std.mem.eql(u8, std.mem.trimRight(u8, ident_slice, &std.ascii.whitespace), "c")) {
1758 p.tok_i -= 1;
1759 }
1760 } else if (p.eatToken(.colon)) |_| {
1761 sentinel = try p.expectExpr();
1762 }
1763 _ = try p.expectToken(.r_bracket);
1764 const mods = try p.parsePtrModifiers();
1765 const elem_type = try p.expectTypeExpr();
1766 if (mods.bit_range_start == 0) {
1767 if (sentinel == 0 and mods.addrspace_node == 0) {
1768 return p.addNode(.{
1769 .tag = .ptr_type_aligned,
1770 .main_token = asterisk,
1771 .data = .{
1772 .lhs = mods.align_node,
1773 .rhs = elem_type,
1774 },
1775 });
1776 } else if (mods.align_node == 0 and mods.addrspace_node == 0) {
1777 return p.addNode(.{
1778 .tag = .ptr_type_sentinel,
1779 .main_token = asterisk,
1780 .data = .{
1781 .lhs = sentinel,
1782 .rhs = elem_type,
1783 },
1784 });
1785 } else {
1786 return p.addNode(.{
1787 .tag = .ptr_type,
1788 .main_token = asterisk,
1789 .data = .{
1790 .lhs = try p.addExtra(Node.PtrType{
1791 .sentinel = sentinel,
1792 .align_node = mods.align_node,
1793 .addrspace_node = mods.addrspace_node,
1794 }),
1795 .rhs = elem_type,
1796 },
1797 });
1798 }
1799 } else {
1800 return p.addNode(.{
1801 .tag = .ptr_type_bit_range,
1802 .main_token = asterisk,
1803 .data = .{
1804 .lhs = try p.addExtra(Node.PtrTypeBitRange{
1805 .sentinel = sentinel,
1806 .align_node = mods.align_node,
1807 .addrspace_node = mods.addrspace_node,
1808 .bit_range_start = mods.bit_range_start,
1809 .bit_range_end = mods.bit_range_end,
1810 }),
1811 .rhs = elem_type,
1812 },
1813 });
1814 }
1815 },
1816 else => {
1817 const lbracket = p.nextToken();
1818 const len_expr = try p.parseExpr();
1819 const sentinel: Node.Index = if (p.eatToken(.colon)) |_|
1820 try p.expectExpr()
1821 else
1822 0;
1823 _ = try p.expectToken(.r_bracket);
1824 if (len_expr == 0) {
1825 const mods = try p.parsePtrModifiers();
1826 const elem_type = try p.expectTypeExpr();
1827 if (mods.bit_range_start != 0) {
1828 try p.warnMsg(.{
1829 .tag = .invalid_bit_range,
1830 .token = p.nodes.items(.main_token)[mods.bit_range_start],
1831 });
1832 }
1833 if (sentinel == 0 and mods.addrspace_node == 0) {
1834 return p.addNode(.{
1835 .tag = .ptr_type_aligned,
1836 .main_token = lbracket,
1837 .data = .{
1838 .lhs = mods.align_node,
1839 .rhs = elem_type,
1840 },
1841 });
1842 } else if (mods.align_node == 0 and mods.addrspace_node == 0) {
1843 return p.addNode(.{
1844 .tag = .ptr_type_sentinel,
1845 .main_token = lbracket,
1846 .data = .{
1847 .lhs = sentinel,
1848 .rhs = elem_type,
1849 },
1850 });
1851 } else {
1852 return p.addNode(.{
1853 .tag = .ptr_type,
1854 .main_token = lbracket,
1855 .data = .{
1856 .lhs = try p.addExtra(Node.PtrType{
1857 .sentinel = sentinel,
1858 .align_node = mods.align_node,
1859 .addrspace_node = mods.addrspace_node,
1860 }),
1861 .rhs = elem_type,
1862 },
1863 });
1864 }
1865 } else {
1866 switch (p.token_tags[p.tok_i]) {
1867 .keyword_align,
1868 .keyword_const,
1869 .keyword_volatile,
1870 .keyword_allowzero,
1871 .keyword_addrspace,
1872 => return p.fail(.ptr_mod_on_array_child_type),
1873 else => {},
1874 }
1875 const elem_type = try p.expectTypeExpr();
1876 if (sentinel == 0) {
1877 return p.addNode(.{
1878 .tag = .array_type,
1879 .main_token = lbracket,
1880 .data = .{
1881 .lhs = len_expr,
1882 .rhs = elem_type,
1883 },
1884 });
1885 } else {
1886 return p.addNode(.{
1887 .tag = .array_type_sentinel,
1888 .main_token = lbracket,
1889 .data = .{
1890 .lhs = len_expr,
1891 .rhs = try p.addExtra(.{
1892 .elem_type = elem_type,
1893 .sentinel = sentinel,
1894 }),
1895 },
1896 });
1897 }
1898 }
1899 },
1900 },
1901 else => return p.parseErrorUnionExpr(),
1902 }
1903 }
1904
1905 fn expectTypeExpr(p: *Parser) Error!Node.Index {
1906 const node = try p.parseTypeExpr();
1907 if (node == 0) {
1908 return p.fail(.expected_type_expr);
1909 }
1910 return node;
1911 }
1912
1913 /// PrimaryExpr
1914 /// <- AsmExpr
1915 /// / IfExpr
1916 /// / KEYWORD_break BreakLabel? Expr?
1917 /// / KEYWORD_comptime Expr
1918 /// / KEYWORD_nosuspend Expr
1919 /// / KEYWORD_continue BreakLabel?
1920 /// / KEYWORD_resume Expr
1921 /// / KEYWORD_return Expr?
1922 /// / BlockLabel? LoopExpr
1923 /// / Block
1924 /// / CurlySuffixExpr
1925 fn parsePrimaryExpr(p: *Parser) !Node.Index {
1926 switch (p.token_tags[p.tok_i]) {
1927 .keyword_asm => return p.expectAsmExpr(),
1928 .keyword_if => return p.parseIfExpr(),
1929 .keyword_break => {
1930 p.tok_i += 1;
1931 return p.addNode(.{
1932 .tag = .@"break",
1933 .main_token = p.tok_i - 1,
1934 .data = .{
1935 .lhs = try p.parseBreakLabel(),
1936 .rhs = try p.parseExpr(),
1937 },
1938 });
1939 },
1940 .keyword_continue => {
1941 p.tok_i += 1;
1942 return p.addNode(.{
1943 .tag = .@"continue",
1944 .main_token = p.tok_i - 1,
1945 .data = .{
1946 .lhs = try p.parseBreakLabel(),
1947 .rhs = undefined,
1948 },
1949 });
1950 },
1951 .keyword_comptime => {
1952 p.tok_i += 1;
1953 return p.addNode(.{
1954 .tag = .@"comptime",
1955 .main_token = p.tok_i - 1,
1956 .data = .{
1957 .lhs = try p.expectExpr(),
1958 .rhs = undefined,
1959 },
1960 });
1961 },
1962 .keyword_nosuspend => {
1963 p.tok_i += 1;
1964 return p.addNode(.{
1965 .tag = .@"nosuspend",
1966 .main_token = p.tok_i - 1,
1967 .data = .{
1968 .lhs = try p.expectExpr(),
1969 .rhs = undefined,
1970 },
1971 });
1972 },
1973 .keyword_resume => {
1974 p.tok_i += 1;
1975 return p.addNode(.{
1976 .tag = .@"resume",
1977 .main_token = p.tok_i - 1,
1978 .data = .{
1979 .lhs = try p.expectExpr(),
1980 .rhs = undefined,
1981 },
1982 });
1983 },
1984 .keyword_return => {
1985 p.tok_i += 1;
1986 return p.addNode(.{
1987 .tag = .@"return",
1988 .main_token = p.tok_i - 1,
1989 .data = .{
1990 .lhs = try p.parseExpr(),
1991 .rhs = undefined,
1992 },
1993 });
1994 },
1995 .identifier => {
1996 if (p.token_tags[p.tok_i + 1] == .colon) {
1997 switch (p.token_tags[p.tok_i + 2]) {
1998 .keyword_inline => {
1999 p.tok_i += 3;
2000 switch (p.token_tags[p.tok_i]) {
2001 .keyword_for => return p.parseForExpr(),
2002 .keyword_while => return p.parseWhileExpr(),
2003 else => return p.fail(.expected_inlinable),
2004 }
2005 },
2006 .keyword_for => {
2007 p.tok_i += 2;
2008 return p.parseForExpr();
2009 },
2010 .keyword_while => {
2011 p.tok_i += 2;
2012 return p.parseWhileExpr();
2013 },
2014 .l_brace => {
2015 p.tok_i += 2;
2016 return p.parseBlock();
2017 },
2018 else => return p.parseCurlySuffixExpr(),
2019 }
2020 } else {
2021 return p.parseCurlySuffixExpr();
2022 }
2023 },
2024 .keyword_inline => {
2025 p.tok_i += 1;
2026 switch (p.token_tags[p.tok_i]) {
2027 .keyword_for => return p.parseForExpr(),
2028 .keyword_while => return p.parseWhileExpr(),
2029 else => return p.fail(.expected_inlinable),
2030 }
2031 },
2032 .keyword_for => return p.parseForExpr(),
2033 .keyword_while => return p.parseWhileExpr(),
2034 .l_brace => return p.parseBlock(),
2035 else => return p.parseCurlySuffixExpr(),
2036 }
2037 }
2038
2039 /// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
2040 fn parseIfExpr(p: *Parser) !Node.Index {
2041 return p.parseIf(expectExpr);
2042 }
2043
2044 /// Block <- LBRACE Statement* RBRACE
2045 fn parseBlock(p: *Parser) !Node.Index {
2046 const lbrace = p.eatToken(.l_brace) orelse return null_node;
2047 const scratch_top = p.scratch.items.len;
2048 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2049 while (true) {
2050 if (p.token_tags[p.tok_i] == .r_brace) break;
2051 const statement = try p.expectStatementRecoverable();
2052 if (statement == 0) break;
2053 try p.scratch.append(p.gpa, statement);
2054 }
2055 _ = try p.expectToken(.r_brace);
2056 const semicolon = (p.token_tags[p.tok_i - 2] == .semicolon);
2057 const statements = p.scratch.items[scratch_top..];
2058 switch (statements.len) {
2059 0 => return p.addNode(.{
2060 .tag = .block_two,
2061 .main_token = lbrace,
2062 .data = .{
2063 .lhs = 0,
2064 .rhs = 0,
2065 },
2066 }),
2067 1 => return p.addNode(.{
2068 .tag = if (semicolon) .block_two_semicolon else .block_two,
2069 .main_token = lbrace,
2070 .data = .{
2071 .lhs = statements[0],
2072 .rhs = 0,
2073 },
2074 }),
2075 2 => return p.addNode(.{
2076 .tag = if (semicolon) .block_two_semicolon else .block_two,
2077 .main_token = lbrace,
2078 .data = .{
2079 .lhs = statements[0],
2080 .rhs = statements[1],
2081 },
2082 }),
2083 else => {
2084 const span = try p.listToSpan(statements);
2085 return p.addNode(.{
2086 .tag = if (semicolon) .block_semicolon else .block,
2087 .main_token = lbrace,
2088 .data = .{
2089 .lhs = span.start,
2090 .rhs = span.end,
2091 },
2092 });
2093 },
2094 }
2095 }
2096
2097 /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
2098 ///
2099 /// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
2100 fn parseForExpr(p: *Parser) !Node.Index {
2101 const for_token = p.eatToken(.keyword_for) orelse return null_node;
2102 _ = try p.expectToken(.l_paren);
2103 const array_expr = try p.expectExpr();
2104 _ = try p.expectToken(.r_paren);
2105 const found_payload = try p.parsePtrIndexPayload();
2106 if (found_payload == 0) try p.warn(.expected_loop_payload);
2107
2108 const then_expr = try p.expectExpr();
2109 _ = p.eatToken(.keyword_else) orelse {
2110 return p.addNode(.{
2111 .tag = .for_simple,
2112 .main_token = for_token,
2113 .data = .{
2114 .lhs = array_expr,
2115 .rhs = then_expr,
2116 },
2117 });
2118 };
2119 const else_expr = try p.expectExpr();
2120 return p.addNode(.{
2121 .tag = .@"for",
2122 .main_token = for_token,
2123 .data = .{
2124 .lhs = array_expr,
2125 .rhs = try p.addExtra(Node.If{
2126 .then_expr = then_expr,
2127 .else_expr = else_expr,
2128 }),
2129 },
2130 });
2131 }
2132
2133 /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
2134 ///
2135 /// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
2136 fn parseWhileExpr(p: *Parser) !Node.Index {
2137 const while_token = p.eatToken(.keyword_while) orelse return null_node;
2138 _ = try p.expectToken(.l_paren);
2139 const condition = try p.expectExpr();
2140 _ = try p.expectToken(.r_paren);
2141 _ = try p.parsePtrPayload();
2142 const cont_expr = try p.parseWhileContinueExpr();
2143
2144 const then_expr = try p.expectExpr();
2145 _ = p.eatToken(.keyword_else) orelse {
2146 if (cont_expr == 0) {
2147 return p.addNode(.{
2148 .tag = .while_simple,
2149 .main_token = while_token,
2150 .data = .{
2151 .lhs = condition,
2152 .rhs = then_expr,
2153 },
2154 });
2155 } else {
2156 return p.addNode(.{
2157 .tag = .while_cont,
2158 .main_token = while_token,
2159 .data = .{
2160 .lhs = condition,
2161 .rhs = try p.addExtra(Node.WhileCont{
2162 .cont_expr = cont_expr,
2163 .then_expr = then_expr,
2164 }),
2165 },
2166 });
2167 }
2168 };
2169 _ = try p.parsePayload();
2170 const else_expr = try p.expectExpr();
2171 return p.addNode(.{
2172 .tag = .@"while",
2173 .main_token = while_token,
2174 .data = .{
2175 .lhs = condition,
2176 .rhs = try p.addExtra(Node.While{
2177 .cont_expr = cont_expr,
2178 .then_expr = then_expr,
2179 .else_expr = else_expr,
2180 }),
2181 },
2182 });
2183 }
2184
2185 /// CurlySuffixExpr <- TypeExpr InitList?
2186 ///
2187 /// InitList
2188 /// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
2189 /// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
2190 /// / LBRACE RBRACE
2191 fn parseCurlySuffixExpr(p: *Parser) !Node.Index {
2192 const lhs = try p.parseTypeExpr();
2193 if (lhs == 0) return null_node;
2194 const lbrace = p.eatToken(.l_brace) orelse return lhs;
2195
2196 // If there are 0 or 1 items, we can use ArrayInitOne/StructInitOne;
2197 // otherwise we use the full ArrayInit/StructInit.
2198
2199 const scratch_top = p.scratch.items.len;
2200 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2201 const field_init = try p.parseFieldInit();
2202 if (field_init != 0) {
2203 try p.scratch.append(p.gpa, field_init);
2204 while (true) {
2205 switch (p.token_tags[p.tok_i]) {
2206 .comma => p.tok_i += 1,
2207 .r_brace => {
2208 p.tok_i += 1;
2209 break;
2210 },
2211 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2212 // Likely just a missing comma; give error but continue parsing.
2213 else => try p.warn(.expected_comma_after_initializer),
2214 }
2215 if (p.eatToken(.r_brace)) |_| break;
2216 const next = try p.expectFieldInit();
2217 try p.scratch.append(p.gpa, next);
2218 }
2219 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2220 const inits = p.scratch.items[scratch_top..];
2221 switch (inits.len) {
2222 0 => unreachable,
2223 1 => return p.addNode(.{
2224 .tag = if (comma) .struct_init_one_comma else .struct_init_one,
2225 .main_token = lbrace,
2226 .data = .{
2227 .lhs = lhs,
2228 .rhs = inits[0],
2229 },
2230 }),
2231 else => return p.addNode(.{
2232 .tag = if (comma) .struct_init_comma else .struct_init,
2233 .main_token = lbrace,
2234 .data = .{
2235 .lhs = lhs,
2236 .rhs = try p.addExtra(try p.listToSpan(inits)),
2237 },
2238 }),
2239 }
2240 }
2241
2242 while (true) {
2243 if (p.eatToken(.r_brace)) |_| break;
2244 const elem_init = try p.expectExpr();
2245 try p.scratch.append(p.gpa, elem_init);
2246 switch (p.token_tags[p.tok_i]) {
2247 .comma => p.tok_i += 1,
2248 .r_brace => {
2249 p.tok_i += 1;
2250 break;
2251 },
2252 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2253 // Likely just a missing comma; give error but continue parsing.
2254 else => try p.warn(.expected_comma_after_initializer),
2255 }
2256 }
2257 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2258 const inits = p.scratch.items[scratch_top..];
2259 switch (inits.len) {
2260 0 => return p.addNode(.{
2261 .tag = .struct_init_one,
2262 .main_token = lbrace,
2263 .data = .{
2264 .lhs = lhs,
2265 .rhs = 0,
2266 },
2267 }),
2268 1 => return p.addNode(.{
2269 .tag = if (comma) .array_init_one_comma else .array_init_one,
2270 .main_token = lbrace,
2271 .data = .{
2272 .lhs = lhs,
2273 .rhs = inits[0],
2274 },
2275 }),
2276 else => return p.addNode(.{
2277 .tag = if (comma) .array_init_comma else .array_init,
2278 .main_token = lbrace,
2279 .data = .{
2280 .lhs = lhs,
2281 .rhs = try p.addExtra(try p.listToSpan(inits)),
2282 },
2283 }),
2284 }
2285 }
2286
2287 /// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
2288 fn parseErrorUnionExpr(p: *Parser) !Node.Index {
2289 const suffix_expr = try p.parseSuffixExpr();
2290 if (suffix_expr == 0) return null_node;
2291 const bang = p.eatToken(.bang) orelse return suffix_expr;
2292 return p.addNode(.{
2293 .tag = .error_union,
2294 .main_token = bang,
2295 .data = .{
2296 .lhs = suffix_expr,
2297 .rhs = try p.expectTypeExpr(),
2298 },
2299 });
2300 }
2301
2302 /// SuffixExpr
2303 /// <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
2304 /// / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
2305 ///
2306 /// FnCallArguments <- LPAREN ExprList RPAREN
2307 ///
2308 /// ExprList <- (Expr COMMA)* Expr?
2309 fn parseSuffixExpr(p: *Parser) !Node.Index {
2310 if (p.eatToken(.keyword_async)) |_| {
2311 var res = try p.expectPrimaryTypeExpr();
2312 while (true) {
2313 const node = try p.parseSuffixOp(res);
2314 if (node == 0) break;
2315 res = node;
2316 }
2317 const lparen = p.eatToken(.l_paren) orelse {
2318 try p.warn(.expected_param_list);
2319 return res;
2320 };
2321 const scratch_top = p.scratch.items.len;
2322 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2323 while (true) {
2324 if (p.eatToken(.r_paren)) |_| break;
2325 const param = try p.expectExpr();
2326 try p.scratch.append(p.gpa, param);
2327 switch (p.token_tags[p.tok_i]) {
2328 .comma => p.tok_i += 1,
2329 .r_paren => {
2330 p.tok_i += 1;
2331 break;
2332 },
2333 .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren),
2334 // Likely just a missing comma; give error but continue parsing.
2335 else => try p.warn(.expected_comma_after_arg),
2336 }
2337 }
2338 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2339 const params = p.scratch.items[scratch_top..];
2340 switch (params.len) {
2341 0 => return p.addNode(.{
2342 .tag = if (comma) .async_call_one_comma else .async_call_one,
2343 .main_token = lparen,
2344 .data = .{
2345 .lhs = res,
2346 .rhs = 0,
2347 },
2348 }),
2349 1 => return p.addNode(.{
2350 .tag = if (comma) .async_call_one_comma else .async_call_one,
2351 .main_token = lparen,
2352 .data = .{
2353 .lhs = res,
2354 .rhs = params[0],
2355 },
2356 }),
2357 else => return p.addNode(.{
2358 .tag = if (comma) .async_call_comma else .async_call,
2359 .main_token = lparen,
2360 .data = .{
2361 .lhs = res,
2362 .rhs = try p.addExtra(try p.listToSpan(params)),
2363 },
2364 }),
2365 }
2366 }
2367
2368 var res = try p.parsePrimaryTypeExpr();
2369 if (res == 0) return res;
2370 while (true) {
2371 const suffix_op = try p.parseSuffixOp(res);
2372 if (suffix_op != 0) {
2373 res = suffix_op;
2374 continue;
2375 }
2376 const lparen = p.eatToken(.l_paren) orelse return res;
2377 const scratch_top = p.scratch.items.len;
2378 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2379 while (true) {
2380 if (p.eatToken(.r_paren)) |_| break;
2381 const param = try p.expectExpr();
2382 try p.scratch.append(p.gpa, param);
2383 switch (p.token_tags[p.tok_i]) {
2384 .comma => p.tok_i += 1,
2385 .r_paren => {
2386 p.tok_i += 1;
2387 break;
2388 },
2389 .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren),
2390 // Likely just a missing comma; give error but continue parsing.
2391 else => try p.warn(.expected_comma_after_arg),
2392 }
2393 }
2394 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2395 const params = p.scratch.items[scratch_top..];
2396 res = switch (params.len) {
2397 0 => try p.addNode(.{
2398 .tag = if (comma) .call_one_comma else .call_one,
2399 .main_token = lparen,
2400 .data = .{
2401 .lhs = res,
2402 .rhs = 0,
2403 },
2404 }),
2405 1 => try p.addNode(.{
2406 .tag = if (comma) .call_one_comma else .call_one,
2407 .main_token = lparen,
2408 .data = .{
2409 .lhs = res,
2410 .rhs = params[0],
2411 },
2412 }),
2413 else => try p.addNode(.{
2414 .tag = if (comma) .call_comma else .call,
2415 .main_token = lparen,
2416 .data = .{
2417 .lhs = res,
2418 .rhs = try p.addExtra(try p.listToSpan(params)),
2419 },
2420 }),
2421 };
2422 }
2423 }
2424
2425 /// PrimaryTypeExpr
2426 /// <- BUILTINIDENTIFIER FnCallArguments
2427 /// / CHAR_LITERAL
2428 /// / ContainerDecl
2429 /// / DOT IDENTIFIER
2430 /// / DOT InitList
2431 /// / ErrorSetDecl
2432 /// / FLOAT
2433 /// / FnProto
2434 /// / GroupedExpr
2435 /// / LabeledTypeExpr
2436 /// / IDENTIFIER
2437 /// / IfTypeExpr
2438 /// / INTEGER
2439 /// / KEYWORD_comptime TypeExpr
2440 /// / KEYWORD_error DOT IDENTIFIER
2441 /// / KEYWORD_anyframe
2442 /// / KEYWORD_unreachable
2443 /// / STRINGLITERAL
2444 /// / SwitchExpr
2445 ///
2446 /// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
2447 ///
2448 /// ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE
2449 ///
2450 /// InitList
2451 /// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
2452 /// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
2453 /// / LBRACE RBRACE
2454 ///
2455 /// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
2456 ///
2457 /// GroupedExpr <- LPAREN Expr RPAREN
2458 ///
2459 /// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
2460 ///
2461 /// LabeledTypeExpr
2462 /// <- BlockLabel Block
2463 /// / BlockLabel? LoopTypeExpr
2464 ///
2465 /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
2466 fn parsePrimaryTypeExpr(p: *Parser) !Node.Index {
2467 switch (p.token_tags[p.tok_i]) {
2468 .char_literal => return p.addNode(.{
2469 .tag = .char_literal,
2470 .main_token = p.nextToken(),
2471 .data = .{
2472 .lhs = undefined,
2473 .rhs = undefined,
2474 },
2475 }),
2476 .number_literal => return p.addNode(.{
2477 .tag = .number_literal,
2478 .main_token = p.nextToken(),
2479 .data = .{
2480 .lhs = undefined,
2481 .rhs = undefined,
2482 },
2483 }),
2484 .keyword_unreachable => return p.addNode(.{
2485 .tag = .unreachable_literal,
2486 .main_token = p.nextToken(),
2487 .data = .{
2488 .lhs = undefined,
2489 .rhs = undefined,
2490 },
2491 }),
2492 .keyword_anyframe => return p.addNode(.{
2493 .tag = .anyframe_literal,
2494 .main_token = p.nextToken(),
2495 .data = .{
2496 .lhs = undefined,
2497 .rhs = undefined,
2498 },
2499 }),
2500 .string_literal => {
2501 const main_token = p.nextToken();
2502 return p.addNode(.{
2503 .tag = .string_literal,
2504 .main_token = main_token,
2505 .data = .{
2506 .lhs = undefined,
2507 .rhs = undefined,
2508 },
2509 });
2510 },
2511
2512 .builtin => return p.parseBuiltinCall(),
2513 .keyword_fn => return p.parseFnProto(),
2514 .keyword_if => return p.parseIf(expectTypeExpr),
2515 .keyword_switch => return p.expectSwitchExpr(),
2516
2517 .keyword_extern,
2518 .keyword_packed,
2519 => {
2520 p.tok_i += 1;
2521 return p.parseContainerDeclAuto();
2522 },
2523
2524 .keyword_struct,
2525 .keyword_opaque,
2526 .keyword_enum,
2527 .keyword_union,
2528 => return p.parseContainerDeclAuto(),
2529
2530 .keyword_comptime => return p.addNode(.{
2531 .tag = .@"comptime",
2532 .main_token = p.nextToken(),
2533 .data = .{
2534 .lhs = try p.expectTypeExpr(),
2535 .rhs = undefined,
2536 },
2537 }),
2538 .multiline_string_literal_line => {
2539 const first_line = p.nextToken();
2540 while (p.token_tags[p.tok_i] == .multiline_string_literal_line) {
2541 p.tok_i += 1;
2542 }
2543 return p.addNode(.{
2544 .tag = .multiline_string_literal,
2545 .main_token = first_line,
2546 .data = .{
2547 .lhs = first_line,
2548 .rhs = p.tok_i - 1,
2549 },
2550 });
2551 },
2552 .identifier => switch (p.token_tags[p.tok_i + 1]) {
2553 .colon => switch (p.token_tags[p.tok_i + 2]) {
2554 .keyword_inline => {
2555 p.tok_i += 3;
2556 switch (p.token_tags[p.tok_i]) {
2557 .keyword_for => return p.parseForTypeExpr(),
2558 .keyword_while => return p.parseWhileTypeExpr(),
2559 else => return p.fail(.expected_inlinable),
2560 }
2561 },
2562 .keyword_for => {
2563 p.tok_i += 2;
2564 return p.parseForTypeExpr();
2565 },
2566 .keyword_while => {
2567 p.tok_i += 2;
2568 return p.parseWhileTypeExpr();
2569 },
2570 .l_brace => {
2571 p.tok_i += 2;
2572 return p.parseBlock();
2573 },
2574 else => return p.addNode(.{
2575 .tag = .identifier,
2576 .main_token = p.nextToken(),
2577 .data = .{
2578 .lhs = undefined,
2579 .rhs = undefined,
2580 },
2581 }),
2582 },
2583 else => return p.addNode(.{
2584 .tag = .identifier,
2585 .main_token = p.nextToken(),
2586 .data = .{
2587 .lhs = undefined,
2588 .rhs = undefined,
2589 },
2590 }),
2591 },
2592 .keyword_inline => {
2593 p.tok_i += 1;
2594 switch (p.token_tags[p.tok_i]) {
2595 .keyword_for => return p.parseForTypeExpr(),
2596 .keyword_while => return p.parseWhileTypeExpr(),
2597 else => return p.fail(.expected_inlinable),
2598 }
2599 },
2600 .keyword_for => return p.parseForTypeExpr(),
2601 .keyword_while => return p.parseWhileTypeExpr(),
2602 .period => switch (p.token_tags[p.tok_i + 1]) {
2603 .identifier => return p.addNode(.{
2604 .tag = .enum_literal,
2605 .data = .{
2606 .lhs = p.nextToken(), // dot
2607 .rhs = undefined,
2608 },
2609 .main_token = p.nextToken(), // identifier
2610 }),
2611 .l_brace => {
2612 const lbrace = p.tok_i + 1;
2613 p.tok_i = lbrace + 1;
2614
2615 // If there are 0, 1, or 2 items, we can use ArrayInitDotTwo/StructInitDotTwo;
2616 // otherwise we use the full ArrayInitDot/StructInitDot.
2617
2618 const scratch_top = p.scratch.items.len;
2619 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2620 const field_init = try p.parseFieldInit();
2621 if (field_init != 0) {
2622 try p.scratch.append(p.gpa, field_init);
2623 while (true) {
2624 switch (p.token_tags[p.tok_i]) {
2625 .comma => p.tok_i += 1,
2626 .r_brace => {
2627 p.tok_i += 1;
2628 break;
2629 },
2630 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2631 // Likely just a missing comma; give error but continue parsing.
2632 else => try p.warn(.expected_comma_after_initializer),
2633 }
2634 if (p.eatToken(.r_brace)) |_| break;
2635 const next = try p.expectFieldInit();
2636 try p.scratch.append(p.gpa, next);
2637 }
2638 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2639 const inits = p.scratch.items[scratch_top..];
2640 switch (inits.len) {
2641 0 => unreachable,
2642 1 => return p.addNode(.{
2643 .tag = if (comma) .struct_init_dot_two_comma else .struct_init_dot_two,
2644 .main_token = lbrace,
2645 .data = .{
2646 .lhs = inits[0],
2647 .rhs = 0,
2648 },
2649 }),
2650 2 => return p.addNode(.{
2651 .tag = if (comma) .struct_init_dot_two_comma else .struct_init_dot_two,
2652 .main_token = lbrace,
2653 .data = .{
2654 .lhs = inits[0],
2655 .rhs = inits[1],
2656 },
2657 }),
2658 else => {
2659 const span = try p.listToSpan(inits);
2660 return p.addNode(.{
2661 .tag = if (comma) .struct_init_dot_comma else .struct_init_dot,
2662 .main_token = lbrace,
2663 .data = .{
2664 .lhs = span.start,
2665 .rhs = span.end,
2666 },
2667 });
2668 },
2669 }
2670 }
2671
2672 while (true) {
2673 if (p.eatToken(.r_brace)) |_| break;
2674 const elem_init = try p.expectExpr();
2675 try p.scratch.append(p.gpa, elem_init);
2676 switch (p.token_tags[p.tok_i]) {
2677 .comma => p.tok_i += 1,
2678 .r_brace => {
2679 p.tok_i += 1;
2680 break;
2681 },
2682 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2683 // Likely just a missing comma; give error but continue parsing.
2684 else => try p.warn(.expected_comma_after_initializer),
2685 }
2686 }
2687 const comma = (p.token_tags[p.tok_i - 2] == .comma);
2688 const inits = p.scratch.items[scratch_top..];
2689 switch (inits.len) {
2690 0 => return p.addNode(.{
2691 .tag = .struct_init_dot_two,
2692 .main_token = lbrace,
2693 .data = .{
2694 .lhs = 0,
2695 .rhs = 0,
2696 },
2697 }),
2698 1 => return p.addNode(.{
2699 .tag = if (comma) .array_init_dot_two_comma else .array_init_dot_two,
2700 .main_token = lbrace,
2701 .data = .{
2702 .lhs = inits[0],
2703 .rhs = 0,
2704 },
2705 }),
2706 2 => return p.addNode(.{
2707 .tag = if (comma) .array_init_dot_two_comma else .array_init_dot_two,
2708 .main_token = lbrace,
2709 .data = .{
2710 .lhs = inits[0],
2711 .rhs = inits[1],
2712 },
2713 }),
2714 else => {
2715 const span = try p.listToSpan(inits);
2716 return p.addNode(.{
2717 .tag = if (comma) .array_init_dot_comma else .array_init_dot,
2718 .main_token = lbrace,
2719 .data = .{
2720 .lhs = span.start,
2721 .rhs = span.end,
2722 },
2723 });
2724 },
2725 }
2726 },
2727 else => return null_node,
2728 },
2729 .keyword_error => switch (p.token_tags[p.tok_i + 1]) {
2730 .l_brace => {
2731 const error_token = p.tok_i;
2732 p.tok_i += 2;
2733 while (true) {
2734 if (p.eatToken(.r_brace)) |_| break;
2735 _ = try p.eatDocComments();
2736 _ = try p.expectToken(.identifier);
2737 switch (p.token_tags[p.tok_i]) {
2738 .comma => p.tok_i += 1,
2739 .r_brace => {
2740 p.tok_i += 1;
2741 break;
2742 },
2743 .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace),
2744 // Likely just a missing comma; give error but continue parsing.
2745 else => try p.warn(.expected_comma_after_field),
2746 }
2747 }
2748 return p.addNode(.{
2749 .tag = .error_set_decl,
2750 .main_token = error_token,
2751 .data = .{
2752 .lhs = undefined,
2753 .rhs = p.tok_i - 1, // rbrace
2754 },
2755 });
2756 },
2757 else => {
2758 const main_token = p.nextToken();
2759 const period = p.eatToken(.period);
2760 if (period == null) try p.warnExpected(.period);
2761 const identifier = p.eatToken(.identifier);
2762 if (identifier == null) try p.warnExpected(.identifier);
2763 return p.addNode(.{
2764 .tag = .error_value,
2765 .main_token = main_token,
2766 .data = .{
2767 .lhs = period orelse 0,
2768 .rhs = identifier orelse 0,
2769 },
2770 });
2771 },
2772 },
2773 .l_paren => return p.addNode(.{
2774 .tag = .grouped_expression,
2775 .main_token = p.nextToken(),
2776 .data = .{
2777 .lhs = try p.expectExpr(),
2778 .rhs = try p.expectToken(.r_paren),
2779 },
2780 }),
2781 else => return null_node,
2782 }
2783 }
2784
2785 fn expectPrimaryTypeExpr(p: *Parser) !Node.Index {
2786 const node = try p.parsePrimaryTypeExpr();
2787 if (node == 0) {
2788 return p.fail(.expected_primary_type_expr);
2789 }
2790 return node;
2791 }
2792
2793 /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
2794 ///
2795 /// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
2796 fn parseForTypeExpr(p: *Parser) !Node.Index {
2797 const for_token = p.eatToken(.keyword_for) orelse return null_node;
2798 _ = try p.expectToken(.l_paren);
2799 const array_expr = try p.expectExpr();
2800 _ = try p.expectToken(.r_paren);
2801 const found_payload = try p.parsePtrIndexPayload();
2802 if (found_payload == 0) try p.warn(.expected_loop_payload);
2803
2804 const then_expr = try p.expectTypeExpr();
2805 _ = p.eatToken(.keyword_else) orelse {
2806 return p.addNode(.{
2807 .tag = .for_simple,
2808 .main_token = for_token,
2809 .data = .{
2810 .lhs = array_expr,
2811 .rhs = then_expr,
2812 },
2813 });
2814 };
2815 const else_expr = try p.expectTypeExpr();
2816 return p.addNode(.{
2817 .tag = .@"for",
2818 .main_token = for_token,
2819 .data = .{
2820 .lhs = array_expr,
2821 .rhs = try p.addExtra(Node.If{
2822 .then_expr = then_expr,
2823 .else_expr = else_expr,
2824 }),
2825 },
2826 });
2827 }
2828
2829 /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
2830 ///
2831 /// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
2832 fn parseWhileTypeExpr(p: *Parser) !Node.Index {
2833 const while_token = p.eatToken(.keyword_while) orelse return null_node;
2834 _ = try p.expectToken(.l_paren);
2835 const condition = try p.expectExpr();
2836 _ = try p.expectToken(.r_paren);
2837 _ = try p.parsePtrPayload();
2838 const cont_expr = try p.parseWhileContinueExpr();
2839
2840 const then_expr = try p.expectTypeExpr();
2841 _ = p.eatToken(.keyword_else) orelse {
2842 if (cont_expr == 0) {
2843 return p.addNode(.{
2844 .tag = .while_simple,
2845 .main_token = while_token,
2846 .data = .{
2847 .lhs = condition,
2848 .rhs = then_expr,
2849 },
2850 });
2851 } else {
2852 return p.addNode(.{
2853 .tag = .while_cont,
2854 .main_token = while_token,
2855 .data = .{
2856 .lhs = condition,
2857 .rhs = try p.addExtra(Node.WhileCont{
2858 .cont_expr = cont_expr,
2859 .then_expr = then_expr,
2860 }),
2861 },
2862 });
2863 }
2864 };
2865 _ = try p.parsePayload();
2866 const else_expr = try p.expectTypeExpr();
2867 return p.addNode(.{
2868 .tag = .@"while",
2869 .main_token = while_token,
2870 .data = .{
2871 .lhs = condition,
2872 .rhs = try p.addExtra(Node.While{
2873 .cont_expr = cont_expr,
2874 .then_expr = then_expr,
2875 .else_expr = else_expr,
2876 }),
2877 },
2878 });
2879 }
2880
2881 /// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
2882 fn expectSwitchExpr(p: *Parser) !Node.Index {
2883 const switch_token = p.assertToken(.keyword_switch);
2884 _ = try p.expectToken(.l_paren);
2885 const expr_node = try p.expectExpr();
2886 _ = try p.expectToken(.r_paren);
2887 _ = try p.expectToken(.l_brace);
2888 const cases = try p.parseSwitchProngList();
2889 const trailing_comma = p.token_tags[p.tok_i - 1] == .comma;
2890 _ = try p.expectToken(.r_brace);
2891
2892 return p.addNode(.{
2893 .tag = if (trailing_comma) .switch_comma else .@"switch",
2894 .main_token = switch_token,
2895 .data = .{
2896 .lhs = expr_node,
2897 .rhs = try p.addExtra(Node.SubRange{
2898 .start = cases.start,
2899 .end = cases.end,
2900 }),
2901 },
2902 });
2903 }
2904
2905 /// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
2906 ///
2907 /// AsmOutput <- COLON AsmOutputList AsmInput?
2908 ///
2909 /// AsmInput <- COLON AsmInputList AsmClobbers?
2910 ///
2911 /// AsmClobbers <- COLON StringList
2912 ///
2913 /// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
2914 ///
2915 /// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
2916 ///
2917 /// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
2918 fn expectAsmExpr(p: *Parser) !Node.Index {
2919 const asm_token = p.assertToken(.keyword_asm);
2920 _ = p.eatToken(.keyword_volatile);
2921 _ = try p.expectToken(.l_paren);
2922 const template = try p.expectExpr();
2923
2924 if (p.eatToken(.r_paren)) |rparen| {
2925 return p.addNode(.{
2926 .tag = .asm_simple,
2927 .main_token = asm_token,
2928 .data = .{
2929 .lhs = template,
2930 .rhs = rparen,
2931 },
2932 });
2933 }
2934
2935 _ = try p.expectToken(.colon);
2936
2937 const scratch_top = p.scratch.items.len;
2938 defer p.scratch.shrinkRetainingCapacity(scratch_top);
2939
2940 while (true) {
2941 const output_item = try p.parseAsmOutputItem();
2942 if (output_item == 0) break;
2943 try p.scratch.append(p.gpa, output_item);
2944 switch (p.token_tags[p.tok_i]) {
2945 .comma => p.tok_i += 1,
2946 // All possible delimiters.
2947 .colon, .r_paren, .r_brace, .r_bracket => break,
2948 // Likely just a missing comma; give error but continue parsing.
2949 else => try p.warnExpected(.comma),
2950 }
2951 }
2952 if (p.eatToken(.colon)) |_| {
2953 while (true) {
2954 const input_item = try p.parseAsmInputItem();
2955 if (input_item == 0) break;
2956 try p.scratch.append(p.gpa, input_item);
2957 switch (p.token_tags[p.tok_i]) {
2958 .comma => p.tok_i += 1,
2959 // All possible delimiters.
2960 .colon, .r_paren, .r_brace, .r_bracket => break,
2961 // Likely just a missing comma; give error but continue parsing.
2962 else => try p.warnExpected(.comma),
2963 }
2964 }
2965 if (p.eatToken(.colon)) |_| {
2966 while (p.eatToken(.string_literal)) |_| {
2967 switch (p.token_tags[p.tok_i]) {
2968 .comma => p.tok_i += 1,
2969 .colon, .r_paren, .r_brace, .r_bracket => break,
2970 // Likely just a missing comma; give error but continue parsing.
2971 else => try p.warnExpected(.comma),
2972 }
2973 }
2974 }
2975 }
2976 const rparen = try p.expectToken(.r_paren);
2977 const span = try p.listToSpan(p.scratch.items[scratch_top..]);
2978 return p.addNode(.{
2979 .tag = .@"asm",
2980 .main_token = asm_token,
2981 .data = .{
2982 .lhs = template,
2983 .rhs = try p.addExtra(Node.Asm{
2984 .items_start = span.start,
2985 .items_end = span.end,
2986 .rparen = rparen,
2987 }),
2988 },
2989 });
2990 }
2991
2992 /// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
2993 fn parseAsmOutputItem(p: *Parser) !Node.Index {
2994 _ = p.eatToken(.l_bracket) orelse return null_node;
2995 const identifier = try p.expectToken(.identifier);
2996 _ = try p.expectToken(.r_bracket);
2997 _ = try p.expectToken(.string_literal);
2998 _ = try p.expectToken(.l_paren);
2999 const type_expr: Node.Index = blk: {
3000 if (p.eatToken(.arrow)) |_| {
3001 break :blk try p.expectTypeExpr();
3002 } else {
3003 _ = try p.expectToken(.identifier);
3004 break :blk null_node;
3005 }
3006 };
3007 const rparen = try p.expectToken(.r_paren);
3008 return p.addNode(.{
3009 .tag = .asm_output,
3010 .main_token = identifier,
3011 .data = .{
3012 .lhs = type_expr,
3013 .rhs = rparen,
3014 },
3015 });
3016 }
3017
3018 /// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
3019 fn parseAsmInputItem(p: *Parser) !Node.Index {
3020 _ = p.eatToken(.l_bracket) orelse return null_node;
3021 const identifier = try p.expectToken(.identifier);
3022 _ = try p.expectToken(.r_bracket);
3023 _ = try p.expectToken(.string_literal);
3024 _ = try p.expectToken(.l_paren);
3025 const expr = try p.expectExpr();
3026 const rparen = try p.expectToken(.r_paren);
3027 return p.addNode(.{
3028 .tag = .asm_input,
3029 .main_token = identifier,
3030 .data = .{
3031 .lhs = expr,
3032 .rhs = rparen,
3033 },
3034 });
3035 }
3036
3037 /// BreakLabel <- COLON IDENTIFIER
3038 fn parseBreakLabel(p: *Parser) !TokenIndex {
3039 _ = p.eatToken(.colon) orelse return @as(TokenIndex, 0);
3040 return p.expectToken(.identifier);
3041 }
3042
3043 /// BlockLabel <- IDENTIFIER COLON
3044 fn parseBlockLabel(p: *Parser) TokenIndex {
3045 if (p.token_tags[p.tok_i] == .identifier and
3046 p.token_tags[p.tok_i + 1] == .colon)
3047 {
3048 const identifier = p.tok_i;
3049 p.tok_i += 2;
3050 return identifier;
3051 }
3052 return null_node;
3053 }
3054
3055 /// FieldInit <- DOT IDENTIFIER EQUAL Expr
3056 fn parseFieldInit(p: *Parser) !Node.Index {
3057 if (p.token_tags[p.tok_i + 0] == .period and
3058 p.token_tags[p.tok_i + 1] == .identifier and
3059 p.token_tags[p.tok_i + 2] == .equal)
3060 {
3061 p.tok_i += 3;
3062 return p.expectExpr();
3063 } else {
3064 return null_node;
3065 }
3066 }
3067
3068 fn expectFieldInit(p: *Parser) !Node.Index {
3069 if (p.token_tags[p.tok_i] != .period or
3070 p.token_tags[p.tok_i + 1] != .identifier or
3071 p.token_tags[p.tok_i + 2] != .equal)
3072 return p.fail(.expected_initializer);
3073
3074 p.tok_i += 3;
3075 return p.expectExpr();
3076 }
3077
3078 /// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
3079 fn parseWhileContinueExpr(p: *Parser) !Node.Index {
3080 _ = p.eatToken(.colon) orelse {
3081 if (p.token_tags[p.tok_i] == .l_paren and
3082 p.tokensOnSameLine(p.tok_i - 1, p.tok_i))
3083 return p.fail(.expected_continue_expr);
3084 return null_node;
3085 };
3086 _ = try p.expectToken(.l_paren);
3087 const node = try p.parseAssignExpr();
3088 if (node == 0) return p.fail(.expected_expr_or_assignment);
3089 _ = try p.expectToken(.r_paren);
3090 return node;
3091 }
3092
3093 /// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
3094 fn parseLinkSection(p: *Parser) !Node.Index {
3095 _ = p.eatToken(.keyword_linksection) orelse return null_node;
3096 _ = try p.expectToken(.l_paren);
3097 const expr_node = try p.expectExpr();
3098 _ = try p.expectToken(.r_paren);
3099 return expr_node;
3100 }
3101
3102 /// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
3103 fn parseCallconv(p: *Parser) !Node.Index {
3104 _ = p.eatToken(.keyword_callconv) orelse return null_node;
3105 _ = try p.expectToken(.l_paren);
3106 const expr_node = try p.expectExpr();
3107 _ = try p.expectToken(.r_paren);
3108 return expr_node;
3109 }
3110
3111 /// AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN
3112 fn parseAddrSpace(p: *Parser) !Node.Index {
3113 _ = p.eatToken(.keyword_addrspace) orelse return null_node;
3114 _ = try p.expectToken(.l_paren);
3115 const expr_node = try p.expectExpr();
3116 _ = try p.expectToken(.r_paren);
3117 return expr_node;
3118 }
3119
3120 /// This function can return null nodes and then still return nodes afterwards,
3121 /// such as in the case of anytype and `...`. Caller must look for rparen to find
3122 /// out when there are no more param decls left.
3123 ///
3124 /// ParamDecl
3125 /// <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
3126 /// / DOT3
3127 ///
3128 /// ParamType
3129 /// <- KEYWORD_anytype
3130 /// / TypeExpr
3131 fn expectParamDecl(p: *Parser) !Node.Index {
3132 _ = try p.eatDocComments();
3133 switch (p.token_tags[p.tok_i]) {
3134 .keyword_noalias, .keyword_comptime => p.tok_i += 1,
3135 .ellipsis3 => {
3136 p.tok_i += 1;
3137 return null_node;
3138 },
3139 else => {},
3140 }
3141 if (p.token_tags[p.tok_i] == .identifier and
3142 p.token_tags[p.tok_i + 1] == .colon)
3143 {
3144 p.tok_i += 2;
3145 }
3146 switch (p.token_tags[p.tok_i]) {
3147 .keyword_anytype => {
3148 p.tok_i += 1;
3149 return null_node;
3150 },
3151 else => return p.expectTypeExpr(),
3152 }
3153 }
3154
3155 /// Payload <- PIPE IDENTIFIER PIPE
3156 fn parsePayload(p: *Parser) !TokenIndex {
3157 _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0);
3158 const identifier = try p.expectToken(.identifier);
3159 _ = try p.expectToken(.pipe);
3160 return identifier;
3161 }
3162
3163 /// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
3164 fn parsePtrPayload(p: *Parser) !TokenIndex {
3165 _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0);
3166 _ = p.eatToken(.asterisk);
3167 const identifier = try p.expectToken(.identifier);
3168 _ = try p.expectToken(.pipe);
3169 return identifier;
3170 }
3171
3172 /// Returns the first identifier token, if any.
3173 ///
3174 /// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
3175 fn parsePtrIndexPayload(p: *Parser) !TokenIndex {
3176 _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0);
3177 _ = p.eatToken(.asterisk);
3178 const identifier = try p.expectToken(.identifier);
3179 if (p.eatToken(.comma) != null) {
3180 _ = try p.expectToken(.identifier);
3181 }
3182 _ = try p.expectToken(.pipe);
3183 return identifier;
3184 }
3185
3186 /// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr
3187 ///
3188 /// SwitchCase
3189 /// <- SwitchItem (COMMA SwitchItem)* COMMA?
3190 /// / KEYWORD_else
3191 fn parseSwitchProng(p: *Parser) !Node.Index {
3192 const scratch_top = p.scratch.items.len;
3193 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3194
3195 const is_inline = p.eatToken(.keyword_inline) != null;
3196
3197 if (p.eatToken(.keyword_else) == null) {
3198 while (true) {
3199 const item = try p.parseSwitchItem();
3200 if (item == 0) break;
3201 try p.scratch.append(p.gpa, item);
3202 if (p.eatToken(.comma) == null) break;
3203 }
3204 if (scratch_top == p.scratch.items.len) {
3205 if (is_inline) p.tok_i -= 1;
3206 return null_node;
3207 }
3208 }
3209 const arrow_token = try p.expectToken(.equal_angle_bracket_right);
3210 _ = try p.parsePtrIndexPayload();
3211
3212 const items = p.scratch.items[scratch_top..];
3213 switch (items.len) {
3214 0 => return p.addNode(.{
3215 .tag = if (is_inline) .switch_case_inline_one else .switch_case_one,
3216 .main_token = arrow_token,
3217 .data = .{
3218 .lhs = 0,
3219 .rhs = try p.expectAssignExpr(),
3220 },
3221 }),
3222 1 => return p.addNode(.{
3223 .tag = if (is_inline) .switch_case_inline_one else .switch_case_one,
3224 .main_token = arrow_token,
3225 .data = .{
3226 .lhs = items[0],
3227 .rhs = try p.expectAssignExpr(),
3228 },
3229 }),
3230 else => return p.addNode(.{
3231 .tag = if (is_inline) .switch_case_inline else .switch_case,
3232 .main_token = arrow_token,
3233 .data = .{
3234 .lhs = try p.addExtra(try p.listToSpan(items)),
3235 .rhs = try p.expectAssignExpr(),
3236 },
3237 }),
3238 }
3239 }
3240
3241 /// SwitchItem <- Expr (DOT3 Expr)?
3242 fn parseSwitchItem(p: *Parser) !Node.Index {
3243 const expr = try p.parseExpr();
3244 if (expr == 0) return null_node;
3245
3246 if (p.eatToken(.ellipsis3)) |token| {
3247 return p.addNode(.{
3248 .tag = .switch_range,
3249 .main_token = token,
3250 .data = .{
3251 .lhs = expr,
3252 .rhs = try p.expectExpr(),
3253 },
3254 });
3255 }
3256 return expr;
3257 }
3258
3259 const PtrModifiers = struct {
3260 align_node: Node.Index,
3261 addrspace_node: Node.Index,
3262 bit_range_start: Node.Index,
3263 bit_range_end: Node.Index,
3264 };
3265
3266 fn parsePtrModifiers(p: *Parser) !PtrModifiers {
3267 var result: PtrModifiers = .{
3268 .align_node = 0,
3269 .addrspace_node = 0,
3270 .bit_range_start = 0,
3271 .bit_range_end = 0,
3272 };
3273 var saw_const = false;
3274 var saw_volatile = false;
3275 var saw_allowzero = false;
3276 var saw_addrspace = false;
3277 while (true) {
3278 switch (p.token_tags[p.tok_i]) {
3279 .keyword_align => {
3280 if (result.align_node != 0) {
3281 try p.warn(.extra_align_qualifier);
3282 }
3283 p.tok_i += 1;
3284 _ = try p.expectToken(.l_paren);
3285 result.align_node = try p.expectExpr();
3286
3287 if (p.eatToken(.colon)) |_| {
3288 result.bit_range_start = try p.expectExpr();
3289 _ = try p.expectToken(.colon);
3290 result.bit_range_end = try p.expectExpr();
3291 }
3292
3293 _ = try p.expectToken(.r_paren);
3294 },
3295 .keyword_const => {
3296 if (saw_const) {
3297 try p.warn(.extra_const_qualifier);
3298 }
3299 p.tok_i += 1;
3300 saw_const = true;
3301 },
3302 .keyword_volatile => {
3303 if (saw_volatile) {
3304 try p.warn(.extra_volatile_qualifier);
3305 }
3306 p.tok_i += 1;
3307 saw_volatile = true;
3308 },
3309 .keyword_allowzero => {
3310 if (saw_allowzero) {
3311 try p.warn(.extra_allowzero_qualifier);
3312 }
3313 p.tok_i += 1;
3314 saw_allowzero = true;
3315 },
3316 .keyword_addrspace => {
3317 if (saw_addrspace) {
3318 try p.warn(.extra_addrspace_qualifier);
3319 }
3320 result.addrspace_node = try p.parseAddrSpace();
3321 },
3322 else => return result,
3323 }
3324 }
3325 }
3326
3327 /// SuffixOp
3328 /// <- LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET
3329 /// / DOT IDENTIFIER
3330 /// / DOTASTERISK
3331 /// / DOTQUESTIONMARK
3332 fn parseSuffixOp(p: *Parser, lhs: Node.Index) !Node.Index {
3333 switch (p.token_tags[p.tok_i]) {
3334 .l_bracket => {
3335 const lbracket = p.nextToken();
3336 const index_expr = try p.expectExpr();
3337
3338 if (p.eatToken(.ellipsis2)) |_| {
3339 const end_expr = try p.parseExpr();
3340 if (p.eatToken(.colon)) |_| {
3341 const sentinel = try p.expectExpr();
3342 _ = try p.expectToken(.r_bracket);
3343 return p.addNode(.{
3344 .tag = .slice_sentinel,
3345 .main_token = lbracket,
3346 .data = .{
3347 .lhs = lhs,
3348 .rhs = try p.addExtra(Node.SliceSentinel{
3349 .start = index_expr,
3350 .end = end_expr,
3351 .sentinel = sentinel,
3352 }),
3353 },
3354 });
3355 }
3356 _ = try p.expectToken(.r_bracket);
3357 if (end_expr == 0) {
3358 return p.addNode(.{
3359 .tag = .slice_open,
3360 .main_token = lbracket,
3361 .data = .{
3362 .lhs = lhs,
3363 .rhs = index_expr,
3364 },
3365 });
3366 }
3367 return p.addNode(.{
3368 .tag = .slice,
3369 .main_token = lbracket,
3370 .data = .{
3371 .lhs = lhs,
3372 .rhs = try p.addExtra(Node.Slice{
3373 .start = index_expr,
3374 .end = end_expr,
3375 }),
3376 },
3377 });
3378 }
3379 _ = try p.expectToken(.r_bracket);
3380 return p.addNode(.{
3381 .tag = .array_access,
3382 .main_token = lbracket,
3383 .data = .{
3384 .lhs = lhs,
3385 .rhs = index_expr,
3386 },
3387 });
3388 },
3389 .period_asterisk => return p.addNode(.{
3390 .tag = .deref,
3391 .main_token = p.nextToken(),
3392 .data = .{
3393 .lhs = lhs,
3394 .rhs = undefined,
3395 },
3396 }),
3397 .invalid_periodasterisks => {
3398 try p.warn(.asterisk_after_ptr_deref);
3399 return p.addNode(.{
3400 .tag = .deref,
3401 .main_token = p.nextToken(),
3402 .data = .{
3403 .lhs = lhs,
3404 .rhs = undefined,
3405 },
3406 });
3407 },
3408 .period => switch (p.token_tags[p.tok_i + 1]) {
3409 .identifier => return p.addNode(.{
3410 .tag = .field_access,
3411 .main_token = p.nextToken(),
3412 .data = .{
3413 .lhs = lhs,
3414 .rhs = p.nextToken(),
3415 },
3416 }),
3417 .question_mark => return p.addNode(.{
3418 .tag = .unwrap_optional,
3419 .main_token = p.nextToken(),
3420 .data = .{
3421 .lhs = lhs,
3422 .rhs = p.nextToken(),
3423 },
3424 }),
3425 .l_brace => {
3426 // this a misplaced `.{`, handle the error somewhere else
3427 return null_node;
3428 },
3429 else => {
3430 p.tok_i += 1;
3431 try p.warn(.expected_suffix_op);
3432 return null_node;
3433 },
3434 },
3435 else => return null_node,
3436 }
3437 }
3438
3439 /// Caller must have already verified the first token.
3440 ///
3441 /// ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE
3442 ///
3443 /// ContainerDeclType
3444 /// <- KEYWORD_struct (LPAREN Expr RPAREN)?
3445 /// / KEYWORD_opaque
3446 /// / KEYWORD_enum (LPAREN Expr RPAREN)?
3447 /// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
3448 fn parseContainerDeclAuto(p: *Parser) !Node.Index {
3449 const main_token = p.nextToken();
3450 const arg_expr = switch (p.token_tags[main_token]) {
3451 .keyword_opaque => null_node,
3452 .keyword_struct, .keyword_enum => blk: {
3453 if (p.eatToken(.l_paren)) |_| {
3454 const expr = try p.expectExpr();
3455 _ = try p.expectToken(.r_paren);
3456 break :blk expr;
3457 } else {
3458 break :blk null_node;
3459 }
3460 },
3461 .keyword_union => blk: {
3462 if (p.eatToken(.l_paren)) |_| {
3463 if (p.eatToken(.keyword_enum)) |_| {
3464 if (p.eatToken(.l_paren)) |_| {
3465 const enum_tag_expr = try p.expectExpr();
3466 _ = try p.expectToken(.r_paren);
3467 _ = try p.expectToken(.r_paren);
3468
3469 _ = try p.expectToken(.l_brace);
3470 const members = try p.parseContainerMembers();
3471 const members_span = try members.toSpan(p);
3472 _ = try p.expectToken(.r_brace);
3473 return p.addNode(.{
3474 .tag = switch (members.trailing) {
3475 true => .tagged_union_enum_tag_trailing,
3476 false => .tagged_union_enum_tag,
3477 },
3478 .main_token = main_token,
3479 .data = .{
3480 .lhs = enum_tag_expr,
3481 .rhs = try p.addExtra(members_span),
3482 },
3483 });
3484 } else {
3485 _ = try p.expectToken(.r_paren);
3486
3487 _ = try p.expectToken(.l_brace);
3488 const members = try p.parseContainerMembers();
3489 _ = try p.expectToken(.r_brace);
3490 if (members.len <= 2) {
3491 return p.addNode(.{
3492 .tag = switch (members.trailing) {
3493 true => .tagged_union_two_trailing,
3494 false => .tagged_union_two,
3495 },
3496 .main_token = main_token,
3497 .data = .{
3498 .lhs = members.lhs,
3499 .rhs = members.rhs,
3500 },
3501 });
3502 } else {
3503 const span = try members.toSpan(p);
3504 return p.addNode(.{
3505 .tag = switch (members.trailing) {
3506 true => .tagged_union_trailing,
3507 false => .tagged_union,
3508 },
3509 .main_token = main_token,
3510 .data = .{
3511 .lhs = span.start,
3512 .rhs = span.end,
3513 },
3514 });
3515 }
3516 }
3517 } else {
3518 const expr = try p.expectExpr();
3519 _ = try p.expectToken(.r_paren);
3520 break :blk expr;
3521 }
3522 } else {
3523 break :blk null_node;
3524 }
3525 },
3526 else => {
3527 p.tok_i -= 1;
3528 return p.fail(.expected_container);
3529 },
3530 };
3531 _ = try p.expectToken(.l_brace);
3532 const members = try p.parseContainerMembers();
3533 _ = try p.expectToken(.r_brace);
3534 if (arg_expr == 0) {
3535 if (members.len <= 2) {
3536 return p.addNode(.{
3537 .tag = switch (members.trailing) {
3538 true => .container_decl_two_trailing,
3539 false => .container_decl_two,
3540 },
3541 .main_token = main_token,
3542 .data = .{
3543 .lhs = members.lhs,
3544 .rhs = members.rhs,
3545 },
3546 });
3547 } else {
3548 const span = try members.toSpan(p);
3549 return p.addNode(.{
3550 .tag = switch (members.trailing) {
3551 true => .container_decl_trailing,
3552 false => .container_decl,
3553 },
3554 .main_token = main_token,
3555 .data = .{
3556 .lhs = span.start,
3557 .rhs = span.end,
3558 },
3559 });
3560 }
3561 } else {
3562 const span = try members.toSpan(p);
3563 return p.addNode(.{
3564 .tag = switch (members.trailing) {
3565 true => .container_decl_arg_trailing,
3566 false => .container_decl_arg,
3567 },
3568 .main_token = main_token,
3569 .data = .{
3570 .lhs = arg_expr,
3571 .rhs = try p.addExtra(Node.SubRange{
3572 .start = span.start,
3573 .end = span.end,
3574 }),
3575 },
3576 });
3577 }
3578 }
3579
3580 /// Give a helpful error message for those transitioning from
3581 /// C's 'struct Foo {};' to Zig's 'const Foo = struct {};'.
3582 fn parseCStyleContainer(p: *Parser) Error!bool {
3583 const main_token = p.tok_i;
3584 switch (p.token_tags[p.tok_i]) {
3585 .keyword_enum, .keyword_union, .keyword_struct => {},
3586 else => return false,
3587 }
3588 const identifier = p.tok_i + 1;
3589 if (p.token_tags[identifier] != .identifier) return false;
3590 p.tok_i += 2;
3591
3592 try p.warnMsg(.{
3593 .tag = .c_style_container,
3594 .token = identifier,
3595 .extra = .{ .expected_tag = p.token_tags[main_token] },
3596 });
3597 try p.warnMsg(.{
3598 .tag = .zig_style_container,
3599 .is_note = true,
3600 .token = identifier,
3601 .extra = .{ .expected_tag = p.token_tags[main_token] },
3602 });
3603
3604 _ = try p.expectToken(.l_brace);
3605 _ = try p.parseContainerMembers();
3606 _ = try p.expectToken(.r_brace);
3607 try p.expectSemicolon(.expected_semi_after_decl, true);
3608 return true;
3609 }
3610
3611 /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
3612 ///
3613 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
3614 fn parseByteAlign(p: *Parser) !Node.Index {
3615 _ = p.eatToken(.keyword_align) orelse return null_node;
3616 _ = try p.expectToken(.l_paren);
3617 const expr = try p.expectExpr();
3618 _ = try p.expectToken(.r_paren);
3619 return expr;
3620 }
3621
3622 /// SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
3623 fn parseSwitchProngList(p: *Parser) !Node.SubRange {
3624 const scratch_top = p.scratch.items.len;
3625 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3626
3627 while (true) {
3628 const item = try parseSwitchProng(p);
3629 if (item == 0) break;
3630
3631 try p.scratch.append(p.gpa, item);
3632
3633 switch (p.token_tags[p.tok_i]) {
3634 .comma => p.tok_i += 1,
3635 // All possible delimiters.
3636 .colon, .r_paren, .r_brace, .r_bracket => break,
3637 // Likely just a missing comma; give error but continue parsing.
3638 else => try p.warn(.expected_comma_after_switch_prong),
3639 }
3640 }
3641 return p.listToSpan(p.scratch.items[scratch_top..]);
3642 }
3643
3644 /// ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
3645 fn parseParamDeclList(p: *Parser) !SmallSpan {
3646 _ = try p.expectToken(.l_paren);
3647 const scratch_top = p.scratch.items.len;
3648 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3649 var varargs: union(enum) { none, seen, nonfinal: TokenIndex } = .none;
3650 while (true) {
3651 if (p.eatToken(.r_paren)) |_| break;
3652 if (varargs == .seen) varargs = .{ .nonfinal = p.tok_i };
3653 const param = try p.expectParamDecl();
3654 if (param != 0) {
3655 try p.scratch.append(p.gpa, param);
3656 } else if (p.token_tags[p.tok_i - 1] == .ellipsis3) {
3657 if (varargs == .none) varargs = .seen;
3658 }
3659 switch (p.token_tags[p.tok_i]) {
3660 .comma => p.tok_i += 1,
3661 .r_paren => {
3662 p.tok_i += 1;
3663 break;
3664 },
3665 .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren),
3666 // Likely just a missing comma; give error but continue parsing.
3667 else => try p.warn(.expected_comma_after_param),
3668 }
3669 }
3670 if (varargs == .nonfinal) {
3671 try p.warnMsg(.{ .tag = .varargs_nonfinal, .token = varargs.nonfinal });
3672 }
3673 const params = p.scratch.items[scratch_top..];
3674 return switch (params.len) {
3675 0 => SmallSpan{ .zero_or_one = 0 },
3676 1 => SmallSpan{ .zero_or_one = params[0] },
3677 else => SmallSpan{ .multi = try p.listToSpan(params) },
3678 };
3679 }
3680
3681 /// FnCallArguments <- LPAREN ExprList RPAREN
3682 ///
3683 /// ExprList <- (Expr COMMA)* Expr?
3684 fn parseBuiltinCall(p: *Parser) !Node.Index {
3685 const builtin_token = p.assertToken(.builtin);
3686 if (p.token_tags[p.nextToken()] != .l_paren) {
3687 p.tok_i -= 1;
3688 try p.warn(.expected_param_list);
3689 // Pretend this was an identifier so we can continue parsing.
3690 return p.addNode(.{
3691 .tag = .identifier,
3692 .main_token = builtin_token,
3693 .data = .{
3694 .lhs = undefined,
3695 .rhs = undefined,
3696 },
3697 });
3698 }
3699 const scratch_top = p.scratch.items.len;
3700 defer p.scratch.shrinkRetainingCapacity(scratch_top);
3701 while (true) {
3702 if (p.eatToken(.r_paren)) |_| break;
3703 const param = try p.expectExpr();
3704 try p.scratch.append(p.gpa, param);
3705 switch (p.token_tags[p.tok_i]) {
3706 .comma => p.tok_i += 1,
3707 .r_paren => {
3708 p.tok_i += 1;
3709 break;
3710 },
3711 // Likely just a missing comma; give error but continue parsing.
3712 else => try p.warn(.expected_comma_after_arg),
3713 }
3714 }
3715 const comma = (p.token_tags[p.tok_i - 2] == .comma);
3716 const params = p.scratch.items[scratch_top..];
3717 switch (params.len) {
3718 0 => return p.addNode(.{
3719 .tag = .builtin_call_two,
3720 .main_token = builtin_token,
3721 .data = .{
3722 .lhs = 0,
3723 .rhs = 0,
3724 },
3725 }),
3726 1 => return p.addNode(.{
3727 .tag = if (comma) .builtin_call_two_comma else .builtin_call_two,
3728 .main_token = builtin_token,
3729 .data = .{
3730 .lhs = params[0],
3731 .rhs = 0,
3732 },
3733 }),
3734 2 => return p.addNode(.{
3735 .tag = if (comma) .builtin_call_two_comma else .builtin_call_two,
3736 .main_token = builtin_token,
3737 .data = .{
3738 .lhs = params[0],
3739 .rhs = params[1],
3740 },
3741 }),
3742 else => {
3743 const span = try p.listToSpan(params);
3744 return p.addNode(.{
3745 .tag = if (comma) .builtin_call_comma else .builtin_call,
3746 .main_token = builtin_token,
3747 .data = .{
3748 .lhs = span.start,
3749 .rhs = span.end,
3750 },
3751 });
3752 },
3753 }
3754 }
3755
3756 /// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
3757 fn parseIf(p: *Parser, comptime bodyParseFn: fn (p: *Parser) Error!Node.Index) !Node.Index {
3758 const if_token = p.eatToken(.keyword_if) orelse return null_node;
3759 _ = try p.expectToken(.l_paren);
3760 const condition = try p.expectExpr();
3761 _ = try p.expectToken(.r_paren);
3762 _ = try p.parsePtrPayload();
3763
3764 const then_expr = try bodyParseFn(p);
3765 assert(then_expr != 0);
3766
3767 _ = p.eatToken(.keyword_else) orelse return p.addNode(.{
3768 .tag = .if_simple,
3769 .main_token = if_token,
3770 .data = .{
3771 .lhs = condition,
3772 .rhs = then_expr,
3773 },
3774 });
3775 _ = try p.parsePayload();
3776 const else_expr = try bodyParseFn(p);
3777 assert(then_expr != 0);
3778
3779 return p.addNode(.{
3780 .tag = .@"if",
3781 .main_token = if_token,
3782 .data = .{
3783 .lhs = condition,
3784 .rhs = try p.addExtra(Node.If{
3785 .then_expr = then_expr,
3786 .else_expr = else_expr,
3787 }),
3788 },
3789 });
3790 }
3791
3792 /// Skips over doc comment tokens. Returns the first one, if any.
3793 fn eatDocComments(p: *Parser) !?TokenIndex {
3794 if (p.eatToken(.doc_comment)) |tok| {
3795 var first_line = tok;
3796 if (tok > 0 and tokensOnSameLine(p, tok - 1, tok)) {
3797 try p.warnMsg(.{
3798 .tag = .same_line_doc_comment,
3799 .token = tok,
3800 });
3801 first_line = p.eatToken(.doc_comment) orelse return null;
3802 }
3803 while (p.eatToken(.doc_comment)) |_| {}
3804 return first_line;
3805 }
3806 return null;
3807 }
3808
3809 fn tokensOnSameLine(p: *Parser, token1: TokenIndex, token2: TokenIndex) bool {
3810 return std.mem.indexOfScalar(u8, p.source[p.token_starts[token1]..p.token_starts[token2]], '\n') == null;
3811 }
3812
3813 fn eatToken(p: *Parser, tag: Token.Tag) ?TokenIndex {
3814 return if (p.token_tags[p.tok_i] == tag) p.nextToken() else null;
3815 }
3816
3817 fn assertToken(p: *Parser, tag: Token.Tag) TokenIndex {
3818 const token = p.nextToken();
3819 assert(p.token_tags[token] == tag);
3820 return token;
3821 }
3822
3823 fn expectToken(p: *Parser, tag: Token.Tag) Error!TokenIndex {
3824 if (p.token_tags[p.tok_i] != tag) {
3825 return p.failMsg(.{
3826 .tag = .expected_token,
3827 .token = p.tok_i,
3828 .extra = .{ .expected_tag = tag },
3829 });
3830 }
3831 return p.nextToken();
3832 }
3833
3834 fn expectSemicolon(p: *Parser, error_tag: AstError.Tag, recoverable: bool) Error!void {
3835 if (p.token_tags[p.tok_i] == .semicolon) {
3836 _ = p.nextToken();
3837 return;
3838 }
3839 try p.warn(error_tag);
3840 if (!recoverable) return error.ParseError;
3841 }
3842
3843 fn nextToken(p: *Parser) TokenIndex {
3844 const result = p.tok_i;
3845 p.tok_i += 1;
3846 return result;
3847 }
3848};
3849
3850test {
3851 _ = @import("parser_test.zig");
3852}
lib/std/zig/parser_test.zig+2-2
...@@ -6073,7 +6073,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined;...@@ -6073,7 +6073,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined;
6073fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 {6073fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 {
6074 const stderr = io.getStdErr().writer();6074 const stderr = io.getStdErr().writer();
60756075
6076 var tree = try std.zig.parse(allocator, source);6076 var tree = try std.zig.Ast.parse(allocator, source, .zig);
6077 defer tree.deinit(allocator);6077 defer tree.deinit(allocator);
60786078
6079 for (tree.errors) |parse_error| {6079 for (tree.errors) |parse_error| {
...@@ -6124,7 +6124,7 @@ fn testCanonical(source: [:0]const u8) !void {...@@ -6124,7 +6124,7 @@ fn testCanonical(source: [:0]const u8) !void {
6124const Error = std.zig.Ast.Error.Tag;6124const Error = std.zig.Ast.Error.Tag;
61256125
6126fn testError(source: [:0]const u8, expected_errors: []const Error) !void {6126fn testError(source: [:0]const u8, expected_errors: []const Error) !void {
6127 var tree = try std.zig.parse(std.testing.allocator, source);6127 var tree = try std.zig.Ast.parse(std.testing.allocator, source, .zig);
6128 defer tree.deinit(std.testing.allocator);6128 defer tree.deinit(std.testing.allocator);
61296129
6130 std.testing.expectEqual(expected_errors.len, tree.errors.len) catch |err| {6130 std.testing.expectEqual(expected_errors.len, tree.errors.len) catch |err| {
lib/std/zig/perf_test.zig+1-2
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const mem = std.mem;2const mem = std.mem;
3const Tokenizer = std.zig.Tokenizer;3const Tokenizer = std.zig.Tokenizer;
4const Parser = std.zig.Parser;
5const io = std.io;4const io = std.io;
6const fmtIntSizeBin = std.fmt.fmtIntSizeBin;5const fmtIntSizeBin = std.fmt.fmtIntSizeBin;
76
...@@ -34,6 +33,6 @@ pub fn main() !void {...@@ -34,6 +33,6 @@ pub fn main() !void {
34fn testOnce() usize {33fn testOnce() usize {
35 var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);34 var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
36 var allocator = fixed_buf_alloc.allocator();35 var allocator = fixed_buf_alloc.allocator();
37 _ = std.zig.parse(allocator, source) catch @panic("parse failure");36 _ = std.zig.Ast.parse(allocator, source, .zig) catch @panic("parse failure");
38 return fixed_buf_alloc.end_index;37 return fixed_buf_alloc.end_index;
39}38}
src/Compilation.zig+1-1
...@@ -385,7 +385,7 @@ pub const AllErrors = struct {...@@ -385,7 +385,7 @@ pub const AllErrors = struct {
385 count: u32 = 1,385 count: u32 = 1,
386 /// Does not include the trailing newline.386 /// Does not include the trailing newline.
387 source_line: ?[]const u8,387 source_line: ?[]const u8,
388 notes: []Message = &.{},388 notes: []const Message = &.{},
389 reference_trace: []Message = &.{},389 reference_trace: []Message = &.{},
390390
391 /// Splits the error message up into lines to properly indent them391 /// Splits the error message up into lines to properly indent them
src/Manifest.zig created+499
...@@ -0,0 +1,499 @@
1pub const basename = "build.zig.zon";
2pub const Hash = std.crypto.hash.sha2.Sha256;
3
4pub const Dependency = struct {
5 url: []const u8,
6 url_tok: Ast.TokenIndex,
7 hash: ?[]const u8,
8 hash_tok: Ast.TokenIndex,
9};
10
11pub const ErrorMessage = struct {
12 msg: []const u8,
13 tok: Ast.TokenIndex,
14 off: u32,
15};
16
17pub const MultihashFunction = enum(u16) {
18 identity = 0x00,
19 sha1 = 0x11,
20 @"sha2-256" = 0x12,
21 @"sha2-512" = 0x13,
22 @"sha3-512" = 0x14,
23 @"sha3-384" = 0x15,
24 @"sha3-256" = 0x16,
25 @"sha3-224" = 0x17,
26 @"sha2-384" = 0x20,
27 @"sha2-256-trunc254-padded" = 0x1012,
28 @"sha2-224" = 0x1013,
29 @"sha2-512-224" = 0x1014,
30 @"sha2-512-256" = 0x1015,
31 @"blake2b-256" = 0xb220,
32 _,
33};
34
35pub const multihash_function: MultihashFunction = switch (Hash) {
36 std.crypto.hash.sha2.Sha256 => .@"sha2-256",
37 else => @compileError("unreachable"),
38};
39comptime {
40 // We avoid unnecessary uleb128 code in hexDigest by asserting here the
41 // values are small enough to be contained in the one-byte encoding.
42 assert(@enumToInt(multihash_function) < 127);
43 assert(Hash.digest_length < 127);
44}
45pub const multihash_len = 1 + 1 + Hash.digest_length;
46
47name: []const u8,
48version: std.SemanticVersion,
49dependencies: std.StringArrayHashMapUnmanaged(Dependency),
50
51errors: []ErrorMessage,
52arena_state: std.heap.ArenaAllocator.State,
53
54pub const Error = Allocator.Error;
55
56pub fn parse(gpa: Allocator, ast: std.zig.Ast) Error!Manifest {
57 const node_tags = ast.nodes.items(.tag);
58 const node_datas = ast.nodes.items(.data);
59 assert(node_tags[0] == .root);
60 const main_node_index = node_datas[0].lhs;
61
62 var arena_instance = std.heap.ArenaAllocator.init(gpa);
63 errdefer arena_instance.deinit();
64
65 var p: Parse = .{
66 .gpa = gpa,
67 .ast = ast,
68 .arena = arena_instance.allocator(),
69 .errors = .{},
70
71 .name = undefined,
72 .version = undefined,
73 .dependencies = .{},
74 .buf = .{},
75 };
76 defer p.buf.deinit(gpa);
77 defer p.errors.deinit(gpa);
78 defer p.dependencies.deinit(gpa);
79
80 p.parseRoot(main_node_index) catch |err| switch (err) {
81 error.ParseFailure => assert(p.errors.items.len > 0),
82 else => |e| return e,
83 };
84
85 return .{
86 .name = p.name,
87 .version = p.version,
88 .dependencies = try p.dependencies.clone(p.arena),
89 .errors = try p.arena.dupe(ErrorMessage, p.errors.items),
90 .arena_state = arena_instance.state,
91 };
92}
93
94pub fn deinit(man: *Manifest, gpa: Allocator) void {
95 man.arena_state.promote(gpa).deinit();
96 man.* = undefined;
97}
98
99const hex_charset = "0123456789abcdef";
100
101pub fn hex64(x: u64) [16]u8 {
102 var result: [16]u8 = undefined;
103 var i: usize = 0;
104 while (i < 8) : (i += 1) {
105 const byte = @truncate(u8, x >> @intCast(u6, 8 * i));
106 result[i * 2 + 0] = hex_charset[byte >> 4];
107 result[i * 2 + 1] = hex_charset[byte & 15];
108 }
109 return result;
110}
111
112test hex64 {
113 const s = "[" ++ hex64(0x12345678_abcdef00) ++ "]";
114 try std.testing.expectEqualStrings("[00efcdab78563412]", s);
115}
116
117pub fn hexDigest(digest: [Hash.digest_length]u8) [multihash_len * 2]u8 {
118 var result: [multihash_len * 2]u8 = undefined;
119
120 result[0] = hex_charset[@enumToInt(multihash_function) >> 4];
121 result[1] = hex_charset[@enumToInt(multihash_function) & 15];
122
123 result[2] = hex_charset[Hash.digest_length >> 4];
124 result[3] = hex_charset[Hash.digest_length & 15];
125
126 for (digest) |byte, i| {
127 result[4 + i * 2] = hex_charset[byte >> 4];
128 result[5 + i * 2] = hex_charset[byte & 15];
129 }
130 return result;
131}
132
133const Parse = struct {
134 gpa: Allocator,
135 ast: std.zig.Ast,
136 arena: Allocator,
137 buf: std.ArrayListUnmanaged(u8),
138 errors: std.ArrayListUnmanaged(ErrorMessage),
139
140 name: []const u8,
141 version: std.SemanticVersion,
142 dependencies: std.StringArrayHashMapUnmanaged(Dependency),
143
144 const InnerError = error{ ParseFailure, OutOfMemory };
145
146 fn parseRoot(p: *Parse, node: Ast.Node.Index) !void {
147 const ast = p.ast;
148 const main_tokens = ast.nodes.items(.main_token);
149 const main_token = main_tokens[node];
150
151 var buf: [2]Ast.Node.Index = undefined;
152 const struct_init = ast.fullStructInit(&buf, node) orelse {
153 return fail(p, main_token, "expected top level expression to be a struct", .{});
154 };
155
156 var have_name = false;
157 var have_version = false;
158
159 for (struct_init.ast.fields) |field_init| {
160 const name_token = ast.firstToken(field_init) - 2;
161 const field_name = try identifierTokenString(p, name_token);
162 // We could get fancy with reflection and comptime logic here but doing
163 // things manually provides an opportunity to do any additional verification
164 // that is desirable on a per-field basis.
165 if (mem.eql(u8, field_name, "dependencies")) {
166 try parseDependencies(p, field_init);
167 } else if (mem.eql(u8, field_name, "name")) {
168 p.name = try parseString(p, field_init);
169 have_name = true;
170 } else if (mem.eql(u8, field_name, "version")) {
171 const version_text = try parseString(p, field_init);
172 p.version = std.SemanticVersion.parse(version_text) catch |err| v: {
173 try appendError(p, main_tokens[field_init], "unable to parse semantic version: {s}", .{@errorName(err)});
174 break :v undefined;
175 };
176 have_version = true;
177 } else {
178 // Ignore unknown fields so that we can add fields in future zig
179 // versions without breaking older zig versions.
180 }
181 }
182
183 if (!have_name) {
184 try appendError(p, main_token, "missing top-level 'name' field", .{});
185 }
186
187 if (!have_version) {
188 try appendError(p, main_token, "missing top-level 'version' field", .{});
189 }
190 }
191
192 fn parseDependencies(p: *Parse, node: Ast.Node.Index) !void {
193 const ast = p.ast;
194 const main_tokens = ast.nodes.items(.main_token);
195
196 var buf: [2]Ast.Node.Index = undefined;
197 const struct_init = ast.fullStructInit(&buf, node) orelse {
198 const tok = main_tokens[node];
199 return fail(p, tok, "expected dependencies expression to be a struct", .{});
200 };
201
202 for (struct_init.ast.fields) |field_init| {
203 const name_token = ast.firstToken(field_init) - 2;
204 const dep_name = try identifierTokenString(p, name_token);
205 const dep = try parseDependency(p, field_init);
206 try p.dependencies.put(p.gpa, dep_name, dep);
207 }
208 }
209
210 fn parseDependency(p: *Parse, node: Ast.Node.Index) !Dependency {
211 const ast = p.ast;
212 const main_tokens = ast.nodes.items(.main_token);
213
214 var buf: [2]Ast.Node.Index = undefined;
215 const struct_init = ast.fullStructInit(&buf, node) orelse {
216 const tok = main_tokens[node];
217 return fail(p, tok, "expected dependency expression to be a struct", .{});
218 };
219
220 var dep: Dependency = .{
221 .url = undefined,
222 .url_tok = undefined,
223 .hash = null,
224 .hash_tok = undefined,
225 };
226 var have_url = false;
227
228 for (struct_init.ast.fields) |field_init| {
229 const name_token = ast.firstToken(field_init) - 2;
230 const field_name = try identifierTokenString(p, name_token);
231 // We could get fancy with reflection and comptime logic here but doing
232 // things manually provides an opportunity to do any additional verification
233 // that is desirable on a per-field basis.
234 if (mem.eql(u8, field_name, "url")) {
235 dep.url = parseString(p, field_init) catch |err| switch (err) {
236 error.ParseFailure => continue,
237 else => |e| return e,
238 };
239 dep.url_tok = main_tokens[field_init];
240 have_url = true;
241 } else if (mem.eql(u8, field_name, "hash")) {
242 dep.hash = parseHash(p, field_init) catch |err| switch (err) {
243 error.ParseFailure => continue,
244 else => |e| return e,
245 };
246 dep.hash_tok = main_tokens[field_init];
247 } else {
248 // Ignore unknown fields so that we can add fields in future zig
249 // versions without breaking older zig versions.
250 }
251 }
252
253 if (!have_url) {
254 try appendError(p, main_tokens[node], "dependency is missing 'url' field", .{});
255 }
256
257 return dep;
258 }
259
260 fn parseString(p: *Parse, node: Ast.Node.Index) ![]const u8 {
261 const ast = p.ast;
262 const node_tags = ast.nodes.items(.tag);
263 const main_tokens = ast.nodes.items(.main_token);
264 if (node_tags[node] != .string_literal) {
265 return fail(p, main_tokens[node], "expected string literal", .{});
266 }
267 const str_lit_token = main_tokens[node];
268 const token_bytes = ast.tokenSlice(str_lit_token);
269 p.buf.clearRetainingCapacity();
270 try parseStrLit(p, str_lit_token, &p.buf, token_bytes, 0);
271 const duped = try p.arena.dupe(u8, p.buf.items);
272 return duped;
273 }
274
275 fn parseHash(p: *Parse, node: Ast.Node.Index) ![]const u8 {
276 const ast = p.ast;
277 const main_tokens = ast.nodes.items(.main_token);
278 const tok = main_tokens[node];
279 const h = try parseString(p, node);
280
281 if (h.len >= 2) {
282 const their_multihash_func = std.fmt.parseInt(u8, h[0..2], 16) catch |err| {
283 return fail(p, tok, "invalid multihash value: unable to parse hash function: {s}", .{
284 @errorName(err),
285 });
286 };
287 if (@intToEnum(MultihashFunction, their_multihash_func) != multihash_function) {
288 return fail(p, tok, "unsupported hash function: only sha2-256 is supported", .{});
289 }
290 }
291
292 const hex_multihash_len = 2 * Manifest.multihash_len;
293 if (h.len != hex_multihash_len) {
294 return fail(p, tok, "wrong hash size. expected: {d}, found: {d}", .{
295 hex_multihash_len, h.len,
296 });
297 }
298
299 return h;
300 }
301
302 /// TODO: try to DRY this with AstGen.identifierTokenString
303 fn identifierTokenString(p: *Parse, token: Ast.TokenIndex) InnerError![]const u8 {
304 const ast = p.ast;
305 const token_tags = ast.tokens.items(.tag);
306 assert(token_tags[token] == .identifier);
307 const ident_name = ast.tokenSlice(token);
308 if (!mem.startsWith(u8, ident_name, "@")) {
309 return ident_name;
310 }
311 p.buf.clearRetainingCapacity();
312 try parseStrLit(p, token, &p.buf, ident_name, 1);
313 const duped = try p.arena.dupe(u8, p.buf.items);
314 return duped;
315 }
316
317 /// TODO: try to DRY this with AstGen.parseStrLit
318 fn parseStrLit(
319 p: *Parse,
320 token: Ast.TokenIndex,
321 buf: *std.ArrayListUnmanaged(u8),
322 bytes: []const u8,
323 offset: u32,
324 ) InnerError!void {
325 const raw_string = bytes[offset..];
326 var buf_managed = buf.toManaged(p.gpa);
327 const result = std.zig.string_literal.parseWrite(buf_managed.writer(), raw_string);
328 buf.* = buf_managed.moveToUnmanaged();
329 switch (try result) {
330 .success => {},
331 .failure => |err| try p.appendStrLitError(err, token, bytes, offset),
332 }
333 }
334
335 /// TODO: try to DRY this with AstGen.failWithStrLitError
336 fn appendStrLitError(
337 p: *Parse,
338 err: std.zig.string_literal.Error,
339 token: Ast.TokenIndex,
340 bytes: []const u8,
341 offset: u32,
342 ) Allocator.Error!void {
343 const raw_string = bytes[offset..];
344 switch (err) {
345 .invalid_escape_character => |bad_index| {
346 try p.appendErrorOff(
347 token,
348 offset + @intCast(u32, bad_index),
349 "invalid escape character: '{c}'",
350 .{raw_string[bad_index]},
351 );
352 },
353 .expected_hex_digit => |bad_index| {
354 try p.appendErrorOff(
355 token,
356 offset + @intCast(u32, bad_index),
357 "expected hex digit, found '{c}'",
358 .{raw_string[bad_index]},
359 );
360 },
361 .empty_unicode_escape_sequence => |bad_index| {
362 try p.appendErrorOff(
363 token,
364 offset + @intCast(u32, bad_index),
365 "empty unicode escape sequence",
366 .{},
367 );
368 },
369 .expected_hex_digit_or_rbrace => |bad_index| {
370 try p.appendErrorOff(
371 token,
372 offset + @intCast(u32, bad_index),
373 "expected hex digit or '}}', found '{c}'",
374 .{raw_string[bad_index]},
375 );
376 },
377 .invalid_unicode_codepoint => |bad_index| {
378 try p.appendErrorOff(
379 token,
380 offset + @intCast(u32, bad_index),
381 "unicode escape does not correspond to a valid codepoint",
382 .{},
383 );
384 },
385 .expected_lbrace => |bad_index| {
386 try p.appendErrorOff(
387 token,
388 offset + @intCast(u32, bad_index),
389 "expected '{{', found '{c}",
390 .{raw_string[bad_index]},
391 );
392 },
393 .expected_rbrace => |bad_index| {
394 try p.appendErrorOff(
395 token,
396 offset + @intCast(u32, bad_index),
397 "expected '}}', found '{c}",
398 .{raw_string[bad_index]},
399 );
400 },
401 .expected_single_quote => |bad_index| {
402 try p.appendErrorOff(
403 token,
404 offset + @intCast(u32, bad_index),
405 "expected single quote ('), found '{c}",
406 .{raw_string[bad_index]},
407 );
408 },
409 .invalid_character => |bad_index| {
410 try p.appendErrorOff(
411 token,
412 offset + @intCast(u32, bad_index),
413 "invalid byte in string or character literal: '{c}'",
414 .{raw_string[bad_index]},
415 );
416 },
417 }
418 }
419
420 fn fail(
421 p: *Parse,
422 tok: Ast.TokenIndex,
423 comptime fmt: []const u8,
424 args: anytype,
425 ) InnerError {
426 try appendError(p, tok, fmt, args);
427 return error.ParseFailure;
428 }
429
430 fn appendError(p: *Parse, tok: Ast.TokenIndex, comptime fmt: []const u8, args: anytype) !void {
431 return appendErrorOff(p, tok, 0, fmt, args);
432 }
433
434 fn appendErrorOff(
435 p: *Parse,
436 tok: Ast.TokenIndex,
437 byte_offset: u32,
438 comptime fmt: []const u8,
439 args: anytype,
440 ) Allocator.Error!void {
441 try p.errors.append(p.gpa, .{
442 .msg = try std.fmt.allocPrint(p.arena, fmt, args),
443 .tok = tok,
444 .off = byte_offset,
445 });
446 }
447};
448
449const Manifest = @This();
450const std = @import("std");
451const mem = std.mem;
452const Allocator = std.mem.Allocator;
453const assert = std.debug.assert;
454const Ast = std.zig.Ast;
455const testing = std.testing;
456
457test "basic" {
458 const gpa = testing.allocator;
459
460 const example =
461 \\.{
462 \\ .name = "foo",
463 \\ .version = "3.2.1",
464 \\ .dependencies = .{
465 \\ .bar = .{
466 \\ .url = "https://example.com/baz.tar.gz",
467 \\ .hash = "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f",
468 \\ },
469 \\ },
470 \\}
471 ;
472
473 var ast = try std.zig.Ast.parse(gpa, example, .zon);
474 defer ast.deinit(gpa);
475
476 try testing.expect(ast.errors.len == 0);
477
478 var manifest = try Manifest.parse(gpa, ast);
479 defer manifest.deinit(gpa);
480
481 try testing.expectEqualStrings("foo", manifest.name);
482
483 try testing.expectEqual(@as(std.SemanticVersion, .{
484 .major = 3,
485 .minor = 2,
486 .patch = 1,
487 }), manifest.version);
488
489 try testing.expect(manifest.dependencies.count() == 1);
490 try testing.expectEqualStrings("bar", manifest.dependencies.keys()[0]);
491 try testing.expectEqualStrings(
492 "https://example.com/baz.tar.gz",
493 manifest.dependencies.values()[0].url,
494 );
495 try testing.expectEqualStrings(
496 "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f",
497 manifest.dependencies.values()[0].hash orelse return error.TestFailed,
498 );
499}
src/Module.zig+3-3
...@@ -2057,7 +2057,7 @@ pub const File = struct {...@@ -2057,7 +2057,7 @@ pub const File = struct {
2057 if (file.tree_loaded) return &file.tree;2057 if (file.tree_loaded) return &file.tree;
20582058
2059 const source = try file.getSource(gpa);2059 const source = try file.getSource(gpa);
2060 file.tree = try std.zig.parse(gpa, source.bytes);2060 file.tree = try Ast.parse(gpa, source.bytes, .zig);
2061 file.tree_loaded = true;2061 file.tree_loaded = true;
2062 return &file.tree;2062 return &file.tree;
2063 }2063 }
...@@ -3662,7 +3662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {...@@ -3662,7 +3662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
3662 file.source = source;3662 file.source = source;
3663 file.source_loaded = true;3663 file.source_loaded = true;
36643664
3665 file.tree = try std.zig.parse(gpa, source);3665 file.tree = try Ast.parse(gpa, source, .zig);
3666 defer if (!file.tree_loaded) file.tree.deinit(gpa);3666 defer if (!file.tree_loaded) file.tree.deinit(gpa);
36673667
3668 if (file.tree.errors.len != 0) {3668 if (file.tree.errors.len != 0) {
...@@ -3977,7 +3977,7 @@ pub fn populateBuiltinFile(mod: *Module) !void {...@@ -3977,7 +3977,7 @@ pub fn populateBuiltinFile(mod: *Module) !void {
3977 else => |e| return e,3977 else => |e| return e,
3978 }3978 }
39793979
3980 file.tree = try std.zig.parse(gpa, file.source);3980 file.tree = try Ast.parse(gpa, file.source, .zig);
3981 file.tree_loaded = true;3981 file.tree_loaded = true;
3982 assert(file.tree.errors.len == 0); // builtin.zig must parse3982 assert(file.tree.errors.len == 0); // builtin.zig must parse
39833983
src/Package.zig+136-211
...@@ -6,8 +6,8 @@ const fs = std.fs;...@@ -6,8 +6,8 @@ const fs = std.fs;
6const mem = std.mem;6const mem = std.mem;
7const Allocator = mem.Allocator;7const Allocator = mem.Allocator;
8const assert = std.debug.assert;8const assert = std.debug.assert;
9const Hash = std.crypto.hash.sha2.Sha256;
10const log = std.log.scoped(.package);9const log = std.log.scoped(.package);
10const main = @import("main.zig");
1111
12const Compilation = @import("Compilation.zig");12const Compilation = @import("Compilation.zig");
13const Module = @import("Module.zig");13const Module = @import("Module.zig");
...@@ -15,6 +15,7 @@ const ThreadPool = @import("ThreadPool.zig");...@@ -15,6 +15,7 @@ const ThreadPool = @import("ThreadPool.zig");
15const WaitGroup = @import("WaitGroup.zig");15const WaitGroup = @import("WaitGroup.zig");
16const Cache = @import("Cache.zig");16const Cache = @import("Cache.zig");
17const build_options = @import("build_options");17const build_options = @import("build_options");
18const Manifest = @import("Manifest.zig");
1819
19pub const Table = std.StringHashMapUnmanaged(*Package);20pub const Table = std.StringHashMapUnmanaged(*Package);
2021
...@@ -141,10 +142,10 @@ pub fn addAndAdopt(parent: *Package, gpa: Allocator, child: *Package) !void {...@@ -141,10 +142,10 @@ pub fn addAndAdopt(parent: *Package, gpa: Allocator, child: *Package) !void {
141}142}
142143
143pub const build_zig_basename = "build.zig";144pub const build_zig_basename = "build.zig";
144pub const ini_basename = build_zig_basename ++ ".ini";
145145
146pub fn fetchAndAddDependencies(146pub fn fetchAndAddDependencies(
147 pkg: *Package,147 pkg: *Package,
148 arena: Allocator,
148 thread_pool: *ThreadPool,149 thread_pool: *ThreadPool,
149 http_client: *std.http.Client,150 http_client: *std.http.Client,
150 directory: Compilation.Directory,151 directory: Compilation.Directory,
...@@ -153,89 +154,77 @@ pub fn fetchAndAddDependencies(...@@ -153,89 +154,77 @@ pub fn fetchAndAddDependencies(
153 dependencies_source: *std.ArrayList(u8),154 dependencies_source: *std.ArrayList(u8),
154 build_roots_source: *std.ArrayList(u8),155 build_roots_source: *std.ArrayList(u8),
155 name_prefix: []const u8,156 name_prefix: []const u8,
157 color: main.Color,
156) !void {158) !void {
157 const max_bytes = 10 * 1024 * 1024;159 const max_bytes = 10 * 1024 * 1024;
158 const gpa = thread_pool.allocator;160 const gpa = thread_pool.allocator;
159 const build_zig_ini = directory.handle.readFileAlloc(gpa, ini_basename, max_bytes) catch |err| switch (err) {161 const build_zig_zon_bytes = directory.handle.readFileAllocOptions(
162 arena,
163 Manifest.basename,
164 max_bytes,
165 null,
166 1,
167 0,
168 ) catch |err| switch (err) {
160 error.FileNotFound => {169 error.FileNotFound => {
161 // Handle the same as no dependencies.170 // Handle the same as no dependencies.
162 return;171 return;
163 },172 },
164 else => |e| return e,173 else => |e| return e,
165 };174 };
166 defer gpa.free(build_zig_ini);
167175
168 const ini: std.Ini = .{ .bytes = build_zig_ini };176 var ast = try std.zig.Ast.parse(gpa, build_zig_zon_bytes, .zon);
169 var any_error = false;177 defer ast.deinit(gpa);
170 var it = ini.iterateSection("\n[dependency]\n");
171 while (it.next()) |dep| {
172 var line_it = mem.split(u8, dep, "\n");
173 var opt_name: ?[]const u8 = null;
174 var opt_url: ?[]const u8 = null;
175 var expected_hash: ?[]const u8 = null;
176 while (line_it.next()) |kv| {
177 const eq_pos = mem.indexOfScalar(u8, kv, '=') orelse continue;
178 const key = kv[0..eq_pos];
179 const value = kv[eq_pos + 1 ..];
180 if (mem.eql(u8, key, "name")) {
181 opt_name = value;
182 } else if (mem.eql(u8, key, "url")) {
183 opt_url = value;
184 } else if (mem.eql(u8, key, "hash")) {
185 expected_hash = value;
186 } else {
187 const loc = std.zig.findLineColumn(ini.bytes, @ptrToInt(key.ptr) - @ptrToInt(ini.bytes.ptr));
188 std.log.warn("{s}/{s}:{d}:{d} unrecognized key: '{s}'", .{
189 directory.path orelse ".",
190 "build.zig.ini",
191 loc.line,
192 loc.column,
193 key,
194 });
195 }
196 }
197178
198 const name = opt_name orelse {179 if (ast.errors.len > 0) {
199 const loc = std.zig.findLineColumn(ini.bytes, @ptrToInt(dep.ptr) - @ptrToInt(ini.bytes.ptr));180 const file_path = try directory.join(arena, &.{Manifest.basename});
200 std.log.err("{s}/{s}:{d}:{d} missing key: 'name'", .{181 try main.printErrsMsgToStdErr(gpa, arena, ast, file_path, color);
201 directory.path orelse ".",182 return error.PackageFetchFailed;
202 "build.zig.ini",183 }
203 loc.line,
204 loc.column,
205 });
206 any_error = true;
207 continue;
208 };
209184
210 const url = opt_url orelse {185 var manifest = try Manifest.parse(gpa, ast);
211 const loc = std.zig.findLineColumn(ini.bytes, @ptrToInt(dep.ptr) - @ptrToInt(ini.bytes.ptr));186 defer manifest.deinit(gpa);
212 std.log.err("{s}/{s}:{d}:{d} missing key: 'name'", .{187
213 directory.path orelse ".",188 if (manifest.errors.len > 0) {
214 "build.zig.ini",189 const ttyconf: std.debug.TTY.Config = switch (color) {
215 loc.line,190 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
216 loc.column,191 .on => .escape_codes,
217 });192 .off => .no_color,
218 any_error = true;
219 continue;
220 };193 };
194 const file_path = try directory.join(arena, &.{Manifest.basename});
195 for (manifest.errors) |msg| {
196 Report.renderErrorMessage(ast, file_path, ttyconf, msg, &.{});
197 }
198 return error.PackageFetchFailed;
199 }
200
201 const report: Report = .{
202 .ast = &ast,
203 .directory = directory,
204 .color = color,
205 .arena = arena,
206 };
207
208 var any_error = false;
209 const deps_list = manifest.dependencies.values();
210 for (manifest.dependencies.keys()) |name, i| {
211 const dep = deps_list[i];
221212
222 const sub_prefix = try std.fmt.allocPrint(gpa, "{s}{s}.", .{ name_prefix, name });213 const sub_prefix = try std.fmt.allocPrint(arena, "{s}{s}.", .{ name_prefix, name });
223 defer gpa.free(sub_prefix);
224 const fqn = sub_prefix[0 .. sub_prefix.len - 1];214 const fqn = sub_prefix[0 .. sub_prefix.len - 1];
225215
226 const sub_pkg = try fetchAndUnpack(216 const sub_pkg = try fetchAndUnpack(
227 thread_pool,217 thread_pool,
228 http_client,218 http_client,
229 global_cache_directory,219 global_cache_directory,
230 url,220 dep,
231 expected_hash,221 report,
232 ini,
233 directory,
234 build_roots_source,222 build_roots_source,
235 fqn,223 fqn,
236 );224 );
237225
238 try pkg.fetchAndAddDependencies(226 try pkg.fetchAndAddDependencies(
227 arena,
239 thread_pool,228 thread_pool,
240 http_client,229 http_client,
241 sub_pkg.root_src_directory,230 sub_pkg.root_src_directory,
...@@ -244,6 +233,7 @@ pub fn fetchAndAddDependencies(...@@ -244,6 +233,7 @@ pub fn fetchAndAddDependencies(
244 dependencies_source,233 dependencies_source,
245 build_roots_source,234 build_roots_source,
246 sub_prefix,235 sub_prefix,
236 color,
247 );237 );
248238
249 try addAndAdopt(pkg, gpa, sub_pkg);239 try addAndAdopt(pkg, gpa, sub_pkg);
...@@ -253,7 +243,7 @@ pub fn fetchAndAddDependencies(...@@ -253,7 +243,7 @@ pub fn fetchAndAddDependencies(
253 });243 });
254 }244 }
255245
256 if (any_error) return error.InvalidBuildZigIniFile;246 if (any_error) return error.InvalidBuildManifestFile;
257}247}
258248
259pub fn createFilePkg(249pub fn createFilePkg(
...@@ -264,7 +254,7 @@ pub fn createFilePkg(...@@ -264,7 +254,7 @@ pub fn createFilePkg(
264 contents: []const u8,254 contents: []const u8,
265) !*Package {255) !*Package {
266 const rand_int = std.crypto.random.int(u64);256 const rand_int = std.crypto.random.int(u64);
267 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int);257 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ Manifest.hex64(rand_int);
268 {258 {
269 var tmp_dir = try cache_directory.handle.makeOpenPath(tmp_dir_sub_path, .{});259 var tmp_dir = try cache_directory.handle.makeOpenPath(tmp_dir_sub_path, .{});
270 defer tmp_dir.close();260 defer tmp_dir.close();
...@@ -282,14 +272,73 @@ pub fn createFilePkg(...@@ -282,14 +272,73 @@ pub fn createFilePkg(
282 return createWithDir(gpa, name, cache_directory, o_dir_sub_path, basename);272 return createWithDir(gpa, name, cache_directory, o_dir_sub_path, basename);
283}273}
284274
275const Report = struct {
276 ast: *const std.zig.Ast,
277 directory: Compilation.Directory,
278 color: main.Color,
279 arena: Allocator,
280
281 fn fail(
282 report: Report,
283 tok: std.zig.Ast.TokenIndex,
284 comptime fmt_string: []const u8,
285 fmt_args: anytype,
286 ) error{ PackageFetchFailed, OutOfMemory } {
287 return failWithNotes(report, &.{}, tok, fmt_string, fmt_args);
288 }
289
290 fn failWithNotes(
291 report: Report,
292 notes: []const Compilation.AllErrors.Message,
293 tok: std.zig.Ast.TokenIndex,
294 comptime fmt_string: []const u8,
295 fmt_args: anytype,
296 ) error{ PackageFetchFailed, OutOfMemory } {
297 const ttyconf: std.debug.TTY.Config = switch (report.color) {
298 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
299 .on => .escape_codes,
300 .off => .no_color,
301 };
302 const file_path = try report.directory.join(report.arena, &.{Manifest.basename});
303 renderErrorMessage(report.ast.*, file_path, ttyconf, .{
304 .tok = tok,
305 .off = 0,
306 .msg = try std.fmt.allocPrint(report.arena, fmt_string, fmt_args),
307 }, notes);
308 return error.PackageFetchFailed;
309 }
310
311 fn renderErrorMessage(
312 ast: std.zig.Ast,
313 file_path: []const u8,
314 ttyconf: std.debug.TTY.Config,
315 msg: Manifest.ErrorMessage,
316 notes: []const Compilation.AllErrors.Message,
317 ) void {
318 const token_starts = ast.tokens.items(.start);
319 const start_loc = ast.tokenLocation(0, msg.tok);
320 Compilation.AllErrors.Message.renderToStdErr(.{ .src = .{
321 .msg = msg.msg,
322 .src_path = file_path,
323 .line = @intCast(u32, start_loc.line),
324 .column = @intCast(u32, start_loc.column),
325 .span = .{
326 .start = token_starts[msg.tok],
327 .end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
328 .main = token_starts[msg.tok] + msg.off,
329 },
330 .source_line = ast.source[start_loc.line_start..start_loc.line_end],
331 .notes = notes,
332 } }, ttyconf);
333 }
334};
335
285fn fetchAndUnpack(336fn fetchAndUnpack(
286 thread_pool: *ThreadPool,337 thread_pool: *ThreadPool,
287 http_client: *std.http.Client,338 http_client: *std.http.Client,
288 global_cache_directory: Compilation.Directory,339 global_cache_directory: Compilation.Directory,
289 url: []const u8,340 dep: Manifest.Dependency,
290 expected_hash: ?[]const u8,341 report: Report,
291 ini: std.Ini,
292 comp_directory: Compilation.Directory,
293 build_roots_source: *std.ArrayList(u8),342 build_roots_source: *std.ArrayList(u8),
294 fqn: []const u8,343 fqn: []const u8,
295) !*Package {344) !*Package {
...@@ -298,37 +347,8 @@ fn fetchAndUnpack(...@@ -298,37 +347,8 @@ fn fetchAndUnpack(
298347
299 // Check if the expected_hash is already present in the global package348 // Check if the expected_hash is already present in the global package
300 // cache, and thereby avoid both fetching and unpacking.349 // cache, and thereby avoid both fetching and unpacking.
301 if (expected_hash) |h| cached: {350 if (dep.hash) |h| cached: {
302 const hex_multihash_len = 2 * multihash_len;351 const hex_multihash_len = 2 * Manifest.multihash_len;
303 if (h.len >= 2) {
304 const their_multihash_func = std.fmt.parseInt(u8, h[0..2], 16) catch |err| {
305 return reportError(
306 ini,
307 comp_directory,
308 h.ptr,
309 "invalid multihash value: unable to parse hash function: {s}",
310 .{@errorName(err)},
311 );
312 };
313 if (@intToEnum(MultihashFunction, their_multihash_func) != multihash_function) {
314 return reportError(
315 ini,
316 comp_directory,
317 h.ptr,
318 "unsupported hash function: only sha2-256 is supported",
319 .{},
320 );
321 }
322 }
323 if (h.len != hex_multihash_len) {
324 return reportError(
325 ini,
326 comp_directory,
327 h.ptr,
328 "wrong hash size. expected: {d}, found: {d}",
329 .{ hex_multihash_len, h.len },
330 );
331 }
332 const hex_digest = h[0..hex_multihash_len];352 const hex_digest = h[0..hex_multihash_len];
333 const pkg_dir_sub_path = "p" ++ s ++ hex_digest;353 const pkg_dir_sub_path = "p" ++ s ++ hex_digest;
334 var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) {354 var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) {
...@@ -366,10 +386,10 @@ fn fetchAndUnpack(...@@ -366,10 +386,10 @@ fn fetchAndUnpack(
366 return ptr;386 return ptr;
367 }387 }
368388
369 const uri = try std.Uri.parse(url);389 const uri = try std.Uri.parse(dep.url);
370390
371 const rand_int = std.crypto.random.int(u64);391 const rand_int = std.crypto.random.int(u64);
372 const tmp_dir_sub_path = "tmp" ++ s ++ hex64(rand_int);392 const tmp_dir_sub_path = "tmp" ++ s ++ Manifest.hex64(rand_int);
373393
374 const actual_hash = a: {394 const actual_hash = a: {
375 var tmp_directory: Compilation.Directory = d: {395 var tmp_directory: Compilation.Directory = d: {
...@@ -398,13 +418,9 @@ fn fetchAndUnpack(...@@ -398,13 +418,9 @@ fn fetchAndUnpack(
398 // by default, so the same logic applies for buffering the reader as for gzip.418 // by default, so the same logic applies for buffering the reader as for gzip.
399 try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);419 try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);
400 } else {420 } else {
401 return reportError(421 return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{
402 ini,422 uri.path,
403 comp_directory,423 });
404 uri.path.ptr,
405 "unknown file extension for path '{s}'",
406 .{uri.path},
407 );
408 }424 }
409425
410 // TODO: delete files not included in the package prior to computing the package hash.426 // TODO: delete files not included in the package prior to computing the package hash.
...@@ -415,28 +431,21 @@ fn fetchAndUnpack(...@@ -415,28 +431,21 @@ fn fetchAndUnpack(
415 break :a try computePackageHash(thread_pool, .{ .dir = tmp_directory.handle });431 break :a try computePackageHash(thread_pool, .{ .dir = tmp_directory.handle });
416 };432 };
417433
418 const pkg_dir_sub_path = "p" ++ s ++ hexDigest(actual_hash);434 const pkg_dir_sub_path = "p" ++ s ++ Manifest.hexDigest(actual_hash);
419 try renameTmpIntoCache(global_cache_directory.handle, tmp_dir_sub_path, pkg_dir_sub_path);435 try renameTmpIntoCache(global_cache_directory.handle, tmp_dir_sub_path, pkg_dir_sub_path);
420436
421 const actual_hex = hexDigest(actual_hash);437 const actual_hex = Manifest.hexDigest(actual_hash);
422 if (expected_hash) |h| {438 if (dep.hash) |h| {
423 if (!mem.eql(u8, h, &actual_hex)) {439 if (!mem.eql(u8, h, &actual_hex)) {
424 return reportError(440 return report.fail(dep.hash_tok, "hash mismatch: expected: {s}, found: {s}", .{
425 ini,441 h, actual_hex,
426 comp_directory,442 });
427 h.ptr,
428 "hash mismatch: expected: {s}, found: {s}",
429 .{ h, actual_hex },
430 );
431 }443 }
432 } else {444 } else {
433 return reportError(445 const notes: [1]Compilation.AllErrors.Message = .{.{ .plain = .{
434 ini,446 .msg = try std.fmt.allocPrint(report.arena, "expected .hash = \"{s}\",", .{&actual_hex}),
435 comp_directory,447 } }};
436 url.ptr,448 return report.failWithNotes(&notes, dep.url_tok, "url field is missing corresponding hash field", .{});
437 "url field is missing corresponding hash field: hash={s}",
438 .{&actual_hex},
439 );
440 }449 }
441450
442 const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});451 const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
...@@ -471,29 +480,9 @@ fn unpackTarball(...@@ -471,29 +480,9 @@ fn unpackTarball(
471 });480 });
472}481}
473482
474fn reportError(
475 ini: std.Ini,
476 comp_directory: Compilation.Directory,
477 src_ptr: [*]const u8,
478 comptime fmt_string: []const u8,
479 fmt_args: anytype,
480) error{PackageFetchFailed} {
481 const loc = std.zig.findLineColumn(ini.bytes, @ptrToInt(src_ptr) - @ptrToInt(ini.bytes.ptr));
482 if (comp_directory.path) |p| {
483 std.debug.print("{s}{c}{s}:{d}:{d}: error: " ++ fmt_string ++ "\n", .{
484 p, fs.path.sep, ini_basename, loc.line + 1, loc.column + 1,
485 } ++ fmt_args);
486 } else {
487 std.debug.print("{s}:{d}:{d}: error: " ++ fmt_string ++ "\n", .{
488 ini_basename, loc.line + 1, loc.column + 1,
489 } ++ fmt_args);
490 }
491 return error.PackageFetchFailed;
492}
493
494const HashedFile = struct {483const HashedFile = struct {
495 path: []const u8,484 path: []const u8,
496 hash: [Hash.digest_length]u8,485 hash: [Manifest.Hash.digest_length]u8,
497 failure: Error!void,486 failure: Error!void,
498487
499 const Error = fs.File.OpenError || fs.File.ReadError || fs.File.StatError;488 const Error = fs.File.OpenError || fs.File.ReadError || fs.File.StatError;
...@@ -507,7 +496,7 @@ const HashedFile = struct {...@@ -507,7 +496,7 @@ const HashedFile = struct {
507fn computePackageHash(496fn computePackageHash(
508 thread_pool: *ThreadPool,497 thread_pool: *ThreadPool,
509 pkg_dir: fs.IterableDir,498 pkg_dir: fs.IterableDir,
510) ![Hash.digest_length]u8 {499) ![Manifest.Hash.digest_length]u8 {
511 const gpa = thread_pool.allocator;500 const gpa = thread_pool.allocator;
512501
513 // We'll use an arena allocator for the path name strings since they all502 // We'll use an arena allocator for the path name strings since they all
...@@ -550,7 +539,7 @@ fn computePackageHash(...@@ -550,7 +539,7 @@ fn computePackageHash(
550539
551 std.sort.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan);540 std.sort.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan);
552541
553 var hasher = Hash.init(.{});542 var hasher = Manifest.Hash.init(.{});
554 var any_failures = false;543 var any_failures = false;
555 for (all_files.items) |hashed_file| {544 for (all_files.items) |hashed_file| {
556 hashed_file.failure catch |err| {545 hashed_file.failure catch |err| {
...@@ -571,7 +560,7 @@ fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void {...@@ -571,7 +560,7 @@ fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void {
571fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void {560fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void {
572 var buf: [8000]u8 = undefined;561 var buf: [8000]u8 = undefined;
573 var file = try dir.openFile(hashed_file.path, .{});562 var file = try dir.openFile(hashed_file.path, .{});
574 var hasher = Hash.init(.{});563 var hasher = Manifest.Hash.init(.{});
575 hasher.update(hashed_file.path);564 hasher.update(hashed_file.path);
576 hasher.update(&.{ 0, @boolToInt(try isExecutable(file)) });565 hasher.update(&.{ 0, @boolToInt(try isExecutable(file)) });
577 while (true) {566 while (true) {
...@@ -595,52 +584,6 @@ fn isExecutable(file: fs.File) !bool {...@@ -595,52 +584,6 @@ fn isExecutable(file: fs.File) !bool {
595 }584 }
596}585}
597586
598const hex_charset = "0123456789abcdef";
599
600fn hex64(x: u64) [16]u8 {
601 var result: [16]u8 = undefined;
602 var i: usize = 0;
603 while (i < 8) : (i += 1) {
604 const byte = @truncate(u8, x >> @intCast(u6, 8 * i));
605 result[i * 2 + 0] = hex_charset[byte >> 4];
606 result[i * 2 + 1] = hex_charset[byte & 15];
607 }
608 return result;
609}
610
611test hex64 {
612 const s = "[" ++ hex64(0x12345678_abcdef00) ++ "]";
613 try std.testing.expectEqualStrings("[00efcdab78563412]", s);
614}
615
616const multihash_function: MultihashFunction = switch (Hash) {
617 std.crypto.hash.sha2.Sha256 => .@"sha2-256",
618 else => @compileError("unreachable"),
619};
620comptime {
621 // We avoid unnecessary uleb128 code in hexDigest by asserting here the
622 // values are small enough to be contained in the one-byte encoding.
623 assert(@enumToInt(multihash_function) < 127);
624 assert(Hash.digest_length < 127);
625}
626const multihash_len = 1 + 1 + Hash.digest_length;
627
628fn hexDigest(digest: [Hash.digest_length]u8) [multihash_len * 2]u8 {
629 var result: [multihash_len * 2]u8 = undefined;
630
631 result[0] = hex_charset[@enumToInt(multihash_function) >> 4];
632 result[1] = hex_charset[@enumToInt(multihash_function) & 15];
633
634 result[2] = hex_charset[Hash.digest_length >> 4];
635 result[3] = hex_charset[Hash.digest_length & 15];
636
637 for (digest) |byte, i| {
638 result[4 + i * 2] = hex_charset[byte >> 4];
639 result[5 + i * 2] = hex_charset[byte & 15];
640 }
641 return result;
642}
643
644fn renameTmpIntoCache(587fn renameTmpIntoCache(
645 cache_dir: fs.Dir,588 cache_dir: fs.Dir,
646 tmp_dir_sub_path: []const u8,589 tmp_dir_sub_path: []const u8,
...@@ -669,21 +612,3 @@ fn renameTmpIntoCache(...@@ -669,21 +612,3 @@ fn renameTmpIntoCache(
669 break;612 break;
670 }613 }
671}614}
672
673const MultihashFunction = enum(u16) {
674 identity = 0x00,
675 sha1 = 0x11,
676 @"sha2-256" = 0x12,
677 @"sha2-512" = 0x13,
678 @"sha3-512" = 0x14,
679 @"sha3-384" = 0x15,
680 @"sha3-256" = 0x16,
681 @"sha3-224" = 0x17,
682 @"sha2-384" = 0x20,
683 @"sha2-256-trunc254-padded" = 0x1012,
684 @"sha2-224" = 0x1013,
685 @"sha2-512-224" = 0x1014,
686 @"sha2-512-256" = 0x1015,
687 @"blake2b-256" = 0xb220,
688 _,
689};
src/main.zig+15-12
...@@ -3915,6 +3915,7 @@ pub const usage_build =...@@ -3915,6 +3915,7 @@ pub const usage_build =
3915;3915;
39163916
3917pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {3917pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
3918 var color: Color = .auto;
3918 var prominent_compile_errors: bool = false;3919 var prominent_compile_errors: bool = false;
39193920
3920 // We want to release all the locks before executing the child process, so we make a nice3921 // We want to release all the locks before executing the child process, so we make a nice
...@@ -4117,6 +4118,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4117,6 +4118,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4117 // Here we borrow main package's table and will replace it with a fresh4118 // Here we borrow main package's table and will replace it with a fresh
4118 // one after this process completes.4119 // one after this process completes.
4119 main_pkg.fetchAndAddDependencies(4120 main_pkg.fetchAndAddDependencies(
4121 arena,
4120 &thread_pool,4122 &thread_pool,
4121 &http_client,4123 &http_client,
4122 build_directory,4124 build_directory,
...@@ -4125,6 +4127,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4125,6 +4127,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4125 &dependencies_source,4127 &dependencies_source,
4126 &build_roots_source,4128 &build_roots_source,
4127 "",4129 "",
4130 color,
4128 ) catch |err| switch (err) {4131 ) catch |err| switch (err) {
4129 error.PackageFetchFailed => process.exit(1),4132 error.PackageFetchFailed => process.exit(1),
4130 else => |e| return e,4133 else => |e| return e,
...@@ -4361,12 +4364,12 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -4361,12 +4364,12 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
4361 };4364 };
4362 defer gpa.free(source_code);4365 defer gpa.free(source_code);
43634366
4364 var tree = std.zig.parse(gpa, source_code) catch |err| {4367 var tree = Ast.parse(gpa, source_code, .zig) catch |err| {
4365 fatal("error parsing stdin: {}", .{err});4368 fatal("error parsing stdin: {}", .{err});
4366 };4369 };
4367 defer tree.deinit(gpa);4370 defer tree.deinit(gpa);
43684371
4369 try printErrsMsgToStdErr(gpa, arena, tree.errors, tree, "<stdin>", color);4372 try printErrsMsgToStdErr(gpa, arena, tree, "<stdin>", color);
4370 var has_ast_error = false;4373 var has_ast_error = false;
4371 if (check_ast_flag) {4374 if (check_ast_flag) {
4372 const Module = @import("Module.zig");4375 const Module = @import("Module.zig");
...@@ -4566,10 +4569,10 @@ fn fmtPathFile(...@@ -4566,10 +4569,10 @@ fn fmtPathFile(
4566 // Add to set after no longer possible to get error.IsDir.4569 // Add to set after no longer possible to get error.IsDir.
4567 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;4570 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
45684571
4569 var tree = try std.zig.parse(fmt.gpa, source_code);4572 var tree = try Ast.parse(fmt.gpa, source_code, .zig);
4570 defer tree.deinit(fmt.gpa);4573 defer tree.deinit(fmt.gpa);
45714574
4572 try printErrsMsgToStdErr(fmt.gpa, fmt.arena, tree.errors, tree, file_path, fmt.color);4575 try printErrsMsgToStdErr(fmt.gpa, fmt.arena, tree, file_path, fmt.color);
4573 if (tree.errors.len != 0) {4576 if (tree.errors.len != 0) {
4574 fmt.any_error = true;4577 fmt.any_error = true;
4575 return;4578 return;
...@@ -4649,14 +4652,14 @@ fn fmtPathFile(...@@ -4649,14 +4652,14 @@ fn fmtPathFile(
4649 }4652 }
4650}4653}
46514654
4652fn printErrsMsgToStdErr(4655pub fn printErrsMsgToStdErr(
4653 gpa: mem.Allocator,4656 gpa: mem.Allocator,
4654 arena: mem.Allocator,4657 arena: mem.Allocator,
4655 parse_errors: []const Ast.Error,
4656 tree: Ast,4658 tree: Ast,
4657 path: []const u8,4659 path: []const u8,
4658 color: Color,4660 color: Color,
4659) !void {4661) !void {
4662 const parse_errors: []const Ast.Error = tree.errors;
4660 var i: usize = 0;4663 var i: usize = 0;
4661 while (i < parse_errors.len) : (i += 1) {4664 while (i < parse_errors.len) : (i += 1) {
4662 const parse_error = parse_errors[i];4665 const parse_error = parse_errors[i];
...@@ -5312,11 +5315,11 @@ pub fn cmdAstCheck(...@@ -5312,11 +5315,11 @@ pub fn cmdAstCheck(
5312 file.pkg = try Package.create(gpa, "root", null, file.sub_file_path);5315 file.pkg = try Package.create(gpa, "root", null, file.sub_file_path);
5313 defer file.pkg.destroy(gpa);5316 defer file.pkg.destroy(gpa);
53145317
5315 file.tree = try std.zig.parse(gpa, file.source);5318 file.tree = try Ast.parse(gpa, file.source, .zig);
5316 file.tree_loaded = true;5319 file.tree_loaded = true;
5317 defer file.tree.deinit(gpa);5320 defer file.tree.deinit(gpa);
53185321
5319 try printErrsMsgToStdErr(gpa, arena, file.tree.errors, file.tree, file.sub_file_path, color);5322 try printErrsMsgToStdErr(gpa, arena, file.tree, file.sub_file_path, color);
5320 if (file.tree.errors.len != 0) {5323 if (file.tree.errors.len != 0) {
5321 process.exit(1);5324 process.exit(1);
5322 }5325 }
...@@ -5438,11 +5441,11 @@ pub fn cmdChangelist(...@@ -5438,11 +5441,11 @@ pub fn cmdChangelist(
5438 file.source = source;5441 file.source = source;
5439 file.source_loaded = true;5442 file.source_loaded = true;
54405443
5441 file.tree = try std.zig.parse(gpa, file.source);5444 file.tree = try Ast.parse(gpa, file.source, .zig);
5442 file.tree_loaded = true;5445 file.tree_loaded = true;
5443 defer file.tree.deinit(gpa);5446 defer file.tree.deinit(gpa);
54445447
5445 try printErrsMsgToStdErr(gpa, arena, file.tree.errors, file.tree, old_source_file, .auto);5448 try printErrsMsgToStdErr(gpa, arena, file.tree, old_source_file, .auto);
5446 if (file.tree.errors.len != 0) {5449 if (file.tree.errors.len != 0) {
5447 process.exit(1);5450 process.exit(1);
5448 }5451 }
...@@ -5476,10 +5479,10 @@ pub fn cmdChangelist(...@@ -5476,10 +5479,10 @@ pub fn cmdChangelist(
5476 if (new_amt != new_stat.size)5479 if (new_amt != new_stat.size)
5477 return error.UnexpectedEndOfFile;5480 return error.UnexpectedEndOfFile;
54785481
5479 var new_tree = try std.zig.parse(gpa, new_source);5482 var new_tree = try Ast.parse(gpa, new_source, .zig);
5480 defer new_tree.deinit(gpa);5483 defer new_tree.deinit(gpa);
54815484
5482 try printErrsMsgToStdErr(gpa, arena, new_tree.errors, new_tree, new_source_file, .auto);5485 try printErrsMsgToStdErr(gpa, arena, new_tree, new_source_file, .auto);
5483 if (new_tree.errors.len != 0) {5486 if (new_tree.errors.len != 0) {
5484 process.exit(1);5487 process.exit(1);
5485 }5488 }