authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2026-03-22 16:34:04-04:00
committergravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2026-03-25 17:27:18-04:00
log2aee0cd6b95d8154a8245d2d1d7be3f6058c659c
tree4e6e9d02ff455bc37ea52f4731e3b1f883c38960
parentebca8c2dbba5b4c0db9910d4cbd854d679114a8d

add an ast smith

This generates zig ASTs from `testing.Smith` and is based off the langref's PEG. The choice to not build the Ast while generating and instead parsing it afterwards makes the smith more versatile by not being tied to a single implementation at a cost of efficiency. Additionally, a new function `boolWeighted` was added to `Smith` due to its frequent use in `AstSmith`.

4 files changed, 2649 insertions(+), 6 deletions(-)

lib/std/Build/abi.zig+8-4
......@@ -235,7 +235,8 @@ pub const fuzz = struct {
235235 max: u64,
236236 weight: u64,
237237
238 fn intFromValue(x: anytype) u64 {
238 /// `inline` to propogate comptimeness
239 inline fn intFromValue(x: anytype) u64 {
239240 const T = @TypeOf(x);
240241 return switch (@typeInfo(T)) {
241242 .comptime_int => x,
......@@ -269,11 +270,13 @@ pub const fuzz = struct {
269270 };
270271 }
271272
272 pub fn value(T: type, x: T, weight: u64) Weight {
273 /// `inline` to propogate comptimeness
274 pub inline fn value(T: type, x: T, weight: u64) Weight {
273275 return .{ .min = intFromValue(x), .max = intFromValue(x), .weight = weight };
274276 }
275277
276 pub fn rangeAtMost(T: type, at_least: T, at_most: T, weight: u64) Weight {
278 /// `inline` to propogate comptimeness
279 pub inline fn rangeAtMost(T: type, at_least: T, at_most: T, weight: u64) Weight {
277280 std.debug.assert(intFromValue(at_least) <= intFromValue(at_most));
278281 return .{
279282 .min = intFromValue(at_least),
......@@ -282,7 +285,8 @@ pub const fuzz = struct {
282285 };
283286 }
284287
285 pub fn rangeLessThan(T: type, at_least: T, less_than: T, weight: u64) Weight {
288 /// `inline` to propogate comptimeness
289 pub inline fn rangeLessThan(T: type, at_least: T, less_than: T, weight: u64) Weight {
286290 std.debug.assert(intFromValue(at_least) < intFromValue(less_than));
287291 return .{
288292 .min = intFromValue(at_least),
lib/std/testing/Smith.zig+37-2
......@@ -205,6 +205,24 @@ pub noinline fn valueRangeLessThan(s: *Smith, T: type, at_least: T, less_than: T
205205 return s.valueRangeLessThanWithHash(T, at_least, less_than, firstHash());
206206}
207207
208/// It is asserted `len` is nonzero.
209/// It is asserted `len` fits within 64 bits.
210//
211// `noinline` to capture a unique return address
212pub noinline fn index(s: *Smith, len: usize) usize {
213 @disableInstrumentation();
214 return s.indexWithHash(len, firstHash());
215}
216
217/// It is asserted that the weight of `false` is non-zero.
218/// It is asserted that the weight of `true` is non-zero.
219//
220// `noinline` to capture a unique return address
221pub noinline fn boolWeighted(s: *Smith, false_weight: u64, true_weight: u64) bool {
222 @disableInstrumentation();
223 return s.boolWeightedWithHash(false_weight, true_weight, firstHash());
224}
225
208226/// This is similar to `value(bool)` however it is gauraunteed to eventually
209227/// return `true` and provides the fuzzer with an extra hint about the data.
210228//
......@@ -228,6 +246,7 @@ pub noinline fn eosWeighted(s: *Smith, weights: []const Weight) bool {
228246/// This is similar to `value(bool)` however it is gauraunteed to eventually
229247/// return `true` and provides the fuzzer with an extra hint about the data.
230248///
249/// It is asserted that the weight of `false` is non-zero.
231250/// It is asserted that the weight of `true` is non-zero.
232251//
233252// `noinline` to capture a unique return address
......@@ -463,6 +482,24 @@ pub fn valueRangeLessThanWithHash(s: *Smith, T: type, at_least: T, less_than: T,
463482 return s.valueWeightedWithHash(T, &.{.rangeLessThan(T, at_least, less_than, 1)}, hash);
464483}
465484
485/// It is asserted `len` is nonzero.
486/// It is asserted `len` fits within 64 bits.
487pub fn indexWithHash(s: *Smith, len: usize, hash: u32) usize {
488 @disableInstrumentation();
489 assert(len != 0);
490 return @intCast(s.valueWeightedWithHash(u64, &.{.rangeLessThan(u64, 0, @intCast(len), 1)}, hash));
491}
492
493/// It is asserted that the weight of `false` is non-zero.
494/// It is asserted that the weight of `true` is non-zero.
495pub fn boolWeightedWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool {
496 @disableInstrumentation();
497 return s.valueWeightedWithHash(bool, &.{
498 .value(bool, false, false_weight),
499 .value(bool, true, true_weight),
500 }, hash);
501}
502
466503/// This is similar to `value(bool)` however it is gauraunteed to eventually
467504/// return `true` and provides the fuzzer with an extra hint about the data.
468505pub fn eosWithHash(s: *Smith, hash: u32) bool {
......@@ -504,8 +541,6 @@ pub fn eosWeightedWithHash(s: *Smith, weights: []const Weight, hash: u32) bool {
504541///
505542/// It is asserted that the weight of `false` is non-zero.
506543/// It is asserted that the weight of `true` is non-zero.
507//
508// `noinline` to capture a unique return address
509544pub fn eosWeightedSimpleWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool {
510545 @disableInstrumentation();
511546 return s.eosWeightedWithHash(&.{
lib/std/zig.zig+2
......@@ -23,6 +23,7 @@ pub const primitives = @import("zig/primitives.zig");
2323pub const isPrimitive = primitives.isPrimitive;
2424pub const Ast = @import("zig/Ast.zig");
2525pub const AstGen = @import("zig/AstGen.zig");
26pub const AstSmith = @import("zig/AstSmith.zig");
2627pub const Zir = @import("zig/Zir.zig");
2728pub const Zoir = @import("zig/Zoir.zig");
2829pub const ZonGen = @import("zig/ZonGen.zig");
......@@ -1165,6 +1166,7 @@ pub const ClangCliParam = struct {
11651166test {
11661167 _ = Ast;
11671168 _ = AstRlAnnotate;
1169 _ = AstSmith;
11681170 _ = BuiltinFn;
11691171 _ = Client;
11701172 _ = ErrorBundle;
lib/std/zig/AstSmith.zig created+2602
......@@ -0,0 +1,2602 @@
1//! Generates a valid AST and corresponding source.
2//!
3//! This is based directly off grammer.peg
4
5const std = @import("../std.zig");
6const assert = std.debug.assert;
7const Token = std.zig.Token;
8const Smith = std.testing.Smith;
9const Weight = Smith.Weight;
10const AstSmith = @This();
11
12smith: *Smith,
13
14source_buf: [16384]u8,
15source_len: usize,
16
17token_tag_buf: [2048]Token.Tag,
18token_start_buf: [2048]std.zig.Ast.ByteOffset,
19tokens_len: usize,
20
21/// For `.asterisk`, this also includes `.asterisk2`
22not_token: ?Token.Tag,
23not_token_comptime: bool,
24/// ExprSuffix
25/// <- KEYWORD_or
26/// / KEYWORD_and
27/// / CompareOp
28/// / BitwiseOp
29/// / BitShiftOp
30/// / AdditionOp
31/// / MultiplyOp
32/// / EXCLAMATIONMARK
33/// / SuffixOp
34/// / FnCallArguments
35not_expr_suffix: bool,
36/// LabelableExpr
37/// <- Block
38/// / SwitchExpr
39/// / LoopExpr
40not_labelable_expr: ?enum { colon, expr },
41not_label: bool,
42not_break_label: bool,
43not_block_expr: bool,
44not_expr_statement: bool,
45
46prev_ids_buf: [256]struct { start: u16, len: u16 },
47/// This may be larger than `prev_ids` in which case,
48/// x % prev_ids.len = next index
49/// @min(x, prev_ids) = length
50prev_ids_len: usize,
51
52/// `generate` must be called on the returned value before any other methods
53pub fn init(smith: *Smith) AstSmith {
54 return .{
55 .smith = smith,
56
57 .source_buf = undefined,
58 .source_len = 0,
59
60 .token_tag_buf = undefined,
61 .token_start_buf = undefined,
62 .tokens_len = 0,
63
64 .not_token = null,
65 .not_token_comptime = false,
66 .not_expr_suffix = false,
67 .not_labelable_expr = null,
68 .not_label = false,
69 .not_break_label = false,
70 .not_block_expr = false,
71 .not_expr_statement = false,
72
73 .prev_ids_buf = undefined,
74 .prev_ids_len = 0,
75 };
76}
77
78pub fn source(t: *AstSmith) [:0]u8 {
79 return t.source_buf[0..t.source_len :0];
80}
81
82/// The Slice is not backed by a MultiArrayList, so calling deinit or toMultiArrayList is illegal.
83pub fn tokens(t: *AstSmith) std.zig.Ast.TokenList.Slice {
84 var slice: std.zig.Ast.TokenList.Slice = .{
85 .ptrs = undefined,
86 .len = t.tokens_len,
87 .capacity = t.tokens_len,
88 };
89 comptime assert(slice.ptrs.len == 2);
90 slice.ptrs[@intFromEnum(std.zig.Ast.TokenList.Field.tag)] = @ptrCast(&t.token_tag_buf);
91 slice.ptrs[@intFromEnum(std.zig.Ast.TokenList.Field.start)] = @ptrCast(&t.token_start_buf);
92 return slice;
93}
94
95pub const Error = error{ OutOfMemory, SkipZigTest };
96const SourceError = error{SkipZigTest};
97
98pub fn generate(a: *AstSmith, gpa: std.mem.Allocator) Error!std.zig.Ast {
99 try a.generateSource();
100 const ast = try std.zig.Ast.parseTokens(gpa, a.source(), a.tokens(), .zig);
101 assert(ast.errors.len == 0);
102 return ast;
103}
104
105pub fn generateSource(a: *AstSmith) SourceError!void {
106 try a.pegRoot();
107 try a.ensureSourceCapacity(1);
108 a.source_buf[a.source_len] = 0;
109 try a.addTokenTag(.eof);
110}
111
112/// For choices which can introduce a variable number of expressions, this should be used to reduce
113/// unbounded recursion.
114//
115// `inline` to propogate caller's return address
116inline fn smithListItemBool(a: *AstSmith) bool {
117 return a.smith.boolWeighted(63, 1);
118}
119
120/// For choices which can introduce a variable number of expressions, this should be used to reduce
121/// unbounded recursion.
122//
123// `inline` to propogate caller's return address
124inline fn smithListItemEos(a: *AstSmith) bool {
125 return a.smith.eosWeightedSimple(1, 63);
126}
127
128fn sourceCapacity(a: *AstSmith) []u8 {
129 return a.source_buf[a.source_len..];
130}
131
132fn sourceCapacityLen(a: *AstSmith) usize {
133 return a.source_buf.len - a.source_len;
134}
135
136fn ensureSourceCapacity(a: *AstSmith, n: usize) SourceError!void {
137 if (a.sourceCapacityLen() < n) return error.SkipZigTest;
138}
139
140fn addSourceByte(a: *AstSmith, byte: u8) SourceError!void {
141 try a.ensureSourceCapacity(1);
142 a.addSourceByteAssumeCapacity(byte);
143}
144
145fn addSourceByteAssumeCapacity(a: *AstSmith, byte: u8) void {
146 a.sourceCapacity()[0] = byte;
147 a.source_len += 1;
148}
149
150fn addSource(a: *AstSmith, bytes: []const u8) SourceError!void {
151 try a.ensureSourceCapacity(bytes.len);
152 a.addSourceAssumeCapacity(bytes);
153}
154
155fn addSourceAssumeCapacity(a: *AstSmith, bytes: []const u8) void {
156 @memcpy(a.sourceCapacity()[0..bytes.len], bytes);
157 a.source_len += bytes.len;
158}
159
160fn addSourceAsSlice(a: *AstSmith, len: usize) SourceError![]u8 {
161 try a.ensureSourceCapacity(len);
162 return a.addSourceAsSliceAssumeCapacity(len);
163}
164
165fn addSourceAsSliceAssumeCapacity(a: *AstSmith, len: usize) []u8 {
166 const slice = a.sourceCapacity()[0..len];
167 a.source_len += len;
168 return slice;
169}
170
171fn tokenCapacityLen(a: *AstSmith) usize {
172 return a.token_tag_buf.len - a.tokens_len;
173}
174
175fn ensureTokenCapacity(a: *AstSmith, n: usize) SourceError!void {
176 if (a.tokenCapacityLen() < n) return error.SkipZigTest;
177}
178
179fn isAlphanumeric(c: u8) bool {
180 return switch (c) {
181 '_', 'a'...'z', 'A'...'Z', '0'...'9' => true,
182 else => false,
183 };
184}
185
186/// For tokens starting with alphanumerics, this ensures
187/// previous tokens followed by end_of_word aren't altered.
188///
189/// end_of_word <- ![a-zA-Z0-9_] skip
190fn preservePegEndOfWord(a: *AstSmith) SourceError!void {
191 if (a.source_len > 0 and isAlphanumeric(a.source_buf[a.source_len - 1])) {
192 try a.addSourceByte(' ');
193 }
194}
195
196/// Assumes the token has not been written yet
197fn addTokenTag(a: *AstSmith, tag: Token.Tag) SourceError!void {
198 assert(tag != a.not_token);
199 if (a.not_token == .asterisk) assert(tag != .asterisk_asterisk);
200 a.not_token = null;
201
202 if (a.not_token_comptime) assert(tag != .keyword_comptime);
203 a.not_token_comptime = false;
204
205 if (a.not_label and tag == .identifier) {
206 a.not_token = .colon;
207 }
208 a.not_label = false;
209
210 if (a.not_break_label and tag == .colon) {
211 a.not_token = .identifier;
212 }
213 a.not_break_label = false;
214
215 if (a.not_labelable_expr) |part| switch (part) {
216 .colon => a.not_labelable_expr = if (tag == .colon) .expr else null,
217 .expr => switch (tag) {
218 .l_brace => unreachable,
219 .keyword_inline => {},
220 .keyword_for => unreachable,
221 .keyword_while => unreachable,
222 .keyword_switch => unreachable,
223 else => a.not_labelable_expr = null,
224 },
225 };
226
227 a.not_expr_suffix = false;
228 a.not_block_expr = false;
229 a.not_expr_statement = false;
230
231 try a.ensureTokenCapacity(1);
232 a.token_tag_buf[a.tokens_len] = tag;
233 a.token_start_buf[a.tokens_len] = @intCast(a.source_len);
234 a.tokens_len += 1;
235}
236
237/// Asserts the token has a lexeme (those without have corresponding methods)
238fn pegToken(a: *AstSmith, tag: Token.Tag) SourceError!void {
239 const lexeme = tag.lexeme().?;
240
241 switch (lexeme[0]) {
242 '_', 'a'...'z', 'A'...'Z', '0'...'9' => try a.preservePegEndOfWord(),
243 '*' => if (a.tokens_len > 0 and a.source_buf[a.source_len - 1] == '*' and
244 a.token_tag_buf[a.tokens_len - 1] != .asterisk_asterisk)
245 {
246 try a.addSourceByte(' ');
247 },
248 '.' => if (a.tokens_len > 0 and switch (a.source_buf[a.source_len - 1]) {
249 '.' => true,
250 '0'...'9', 'a'...'z', 'A'...'Z' => a.token_tag_buf[a.tokens_len - 1] == .number_literal,
251 else => false,
252 }) {
253 try a.addSourceByte(' ');
254 },
255 '+', '-' => if (a.tokens_len > 0 and a.token_tag_buf[a.tokens_len - 1] == .number_literal and
256 switch (a.source_buf[a.source_len - 1]) {
257 'e', 'E', 'p', 'P' => true,
258 else => false,
259 })
260 {
261 // Would otherwise be tokenized as the sign of a float's exponent
262 //
263 // e.g. "0xFE" ++ "+" ++ "2" (number_literal, plus, number_literal)
264 try a.addSourceByte(' ');
265 },
266 else => {},
267 }
268
269 if (isAlphanumeric(lexeme[0])) try a.preservePegEndOfWord();
270
271 try a.addTokenTag(tag);
272 try a.addSource(lexeme);
273 try a.pegSkip();
274}
275
276/// Asserts `a.source_len != 0`
277fn pegTokenWhitespaceAround(a: *AstSmith, tag: Token.Tag) SourceError!void {
278 switch (a.source_buf[a.source_len - 1]) {
279 ' ', '\n' => {},
280 else => try a.addSourceByte(' '),
281 }
282 try a.addTokenTag(tag);
283 try a.addSource(tag.lexeme().?);
284 switch (a.smith.value(enum { space, line_break, cr_line_break })) {
285 // This is not the same as 'skip' since comments are not whitespace
286 .space => try a.addSourceByte(' '),
287 .line_break => try a.addSourceByte('\n'),
288 .cr_line_break => try a.addSource("\r\n"),
289 }
290 try a.pegSkip();
291}
292
293/// Root <- skip ContainerMembers eof
294fn pegRoot(a: *AstSmith) SourceError!void {
295 try a.pegSkip();
296 try a.pegContainerMembers();
297}
298
299/// ContainerMembers <- container_doc_comment? ContainerDeclaration* (ContainerField COMMA)*
300/// (ContainerField / ContainerDeclaration*)
301fn pegContainerMembers(a: *AstSmith) SourceError!void {
302 if (a.smith.boolWeighted(63, 1)) {
303 try a.pegContainerDocComment();
304 }
305 while (!a.smithListItemEos()) {
306 try a.pegContainerDeclaration();
307 }
308 while (!a.smithListItemEos()) {
309 try a.pegContainerField();
310 try a.pegToken(.comma);
311 }
312 if (a.smithListItemBool()) {
313 if (a.smith.value(bool)) {
314 try a.pegContainerField();
315 } else while (true) {
316 try a.pegContainerDeclaration();
317 if (a.smithListItemEos()) break;
318 }
319 }
320}
321
322/// ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl
323fn pegContainerDeclaration(a: *AstSmith) SourceError!void {
324 switch (a.smith.value(enum { TestDecl, ComptimeDecl, Decl })) {
325 .TestDecl => try a.pegTestDecl(),
326 .ComptimeDecl => try a.pegComptimeDecl(),
327 .Decl => {
328 try a.pegMaybeDocComment();
329 if (a.smith.value(bool)) {
330 try a.pegToken(.keyword_pub);
331 }
332 try a.pegDecl();
333 },
334 }
335}
336
337/// KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
338fn pegTestDecl(a: *AstSmith) SourceError!void {
339 try a.pegToken(.keyword_test);
340 switch (a.smith.value(enum { none, string, id })) {
341 .none => {},
342 .string => try a.pegStringLiteralSingle(),
343 .id => try a.pegIdentifier(),
344 }
345 try a.pegBlock();
346}
347
348/// ComptimeDecl <- KEYWORD_comptime Block
349fn pegComptimeDecl(a: *AstSmith) SourceError!void {
350 try a.pegToken(.keyword_comptime);
351 try a.pegBlock();
352}
353
354/// Decl
355/// <- (KEYWORD_export / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
356/// / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON
357/// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal?
358/// GlobalVarDecl
359fn pegDecl(a: *AstSmith) SourceError!void {
360 const Modifier = enum(u8) {
361 none,
362 @"export",
363 @"extern",
364 extern_library,
365 @"inline",
366 @"noinline",
367 };
368 const is_fn = a.smith.value(bool);
369 const fn_modifiers = Smith.baselineWeights(Modifier);
370 const var_modifiers: []const Weight = &.{.rangeAtMost(Modifier, .none, .extern_library, 1)};
371 const modifier = a.smith.valueWeighted(Modifier, if (is_fn) fn_modifiers else var_modifiers);
372
373 switch (modifier) {
374 .none => {},
375 .@"export" => try a.pegToken(.keyword_export),
376 .@"extern" => try a.pegToken(.keyword_extern),
377 .extern_library => {
378 try a.pegToken(.keyword_extern);
379 try a.pegStringLiteralSingle();
380 },
381 .@"inline" => try a.pegToken(.keyword_inline),
382 .@"noinline" => try a.pegToken(.keyword_noinline),
383 }
384
385 if (is_fn) {
386 try a.pegFnProto();
387 if (modifier == .@"extern" or modifier == .extern_library or a.smith.value(bool)) {
388 try a.pegToken(.semicolon);
389 } else {
390 try a.pegBlock();
391 }
392 } else {
393 if (a.smith.value(bool)) try a.pegToken(.keyword_threadlocal);
394 try a.pegGlobalVarDecl();
395 }
396}
397
398/// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace?
399/// LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix
400fn pegFnProto(a: *AstSmith) SourceError!void {
401 try a.pegToken(.keyword_fn);
402 if (a.smith.value(bool)) {
403 try a.pegIdentifier();
404 }
405 try a.pegToken(.l_paren);
406 try a.pegParamDeclList();
407 try a.pegToken(.r_paren);
408 if (a.smith.value(bool)) {
409 try a.pegByteAlign();
410 }
411 if (a.smith.value(bool)) {
412 try a.pegAddrSpace();
413 }
414 if (a.smith.value(bool)) {
415 try a.pegLinkSection();
416 }
417 if (a.smith.value(bool)) {
418 try a.pegCallConv();
419 }
420 if (a.smith.value(bool)) {
421 try a.pegToken(.bang);
422 }
423 try a.pegTypeExpr();
424 a.not_expr_suffix = true;
425}
426
427/// VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign?
428/// AddrSpace? LinkSection?
429fn pegVarDeclProto(a: *AstSmith) SourceError!void {
430 try a.pegToken(if (a.smith.value(bool)) .keyword_var else .keyword_const);
431 try a.pegIdentifier();
432
433 if (a.smith.value(bool)) {
434 try a.pegToken(.colon);
435 try a.pegTypeExpr();
436 }
437
438 if (a.smith.value(bool)) {
439 try a.pegByteAlign();
440 }
441
442 if (a.smith.value(bool)) {
443 try a.pegAddrSpace();
444 }
445
446 if (a.smith.value(bool)) {
447 try a.pegLinkSection();
448 }
449}
450
451/// GlobalVarDecl <- VarDeclProto (EQUAL Expr)? SEMICOLON
452fn pegGlobalVarDecl(a: *AstSmith) SourceError!void {
453 try a.pegVarDeclProto();
454 if (a.smithListItemBool()) {
455 try a.pegToken(.equal);
456 try a.pegExpr();
457 }
458 try a.pegToken(.semicolon);
459}
460
461/// ContainerField <- doc_comment? (KEYWORD_comptime / !KEYWORD_comptime) !KEYWORD_fn
462/// (IDENTIFIER COLON !(IDENTIFIER COLON)) TypeExpr ByteAlign? (EQUAL Expr)?
463fn pegContainerField(a: *AstSmith) SourceError!void {
464 try a.pegMaybeDocComment();
465 if (a.smith.value(bool)) {
466 try a.pegToken(.keyword_comptime);
467 }
468 if (a.smith.value(bool)) {
469 try a.pegIdentifier();
470 try a.pegToken(.colon);
471 } else {
472 a.not_token = .keyword_fn;
473 a.not_token_comptime = true;
474 a.not_label = true;
475 }
476 try a.pegTypeExpr();
477 if (a.smith.value(bool)) {
478 try a.pegByteAlign();
479 }
480 if (a.smith.value(bool)) {
481 try a.pegToken(.equal);
482 try a.pegExpr();
483 }
484}
485
486/// BlockStatement
487/// <- Statement
488/// / KEYWORD_defer BlockExprStatement
489/// / KEYWORD_errdefer Payload? BlockExprStatement
490/// / !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement
491fn pegBlockStatement(a: *AstSmith) SourceError!void {
492 const Kind = enum {
493 statement,
494 defer_statement,
495 errdefer_statement,
496 var_assign,
497 comptime_var_assign,
498 };
499 const weights = Smith.baselineWeights(Kind) ++ &[1]Weight{.value(Kind, .statement, 4)};
500 switch (a.smith.valueWeighted(Kind, weights)) {
501 .statement => try a.pegStatement(),
502 .defer_statement, .errdefer_statement => |kind| {
503 try a.pegToken(switch (kind) {
504 .defer_statement => .keyword_defer,
505 .errdefer_statement => .keyword_errdefer,
506 else => unreachable,
507 });
508 try a.pegBlockExprStatement();
509 },
510 .var_assign, .comptime_var_assign => |kind| {
511 a.not_expr_statement = true;
512 if (kind == .comptime_var_assign) {
513 try a.pegToken(.keyword_comptime);
514 a.not_block_expr = true;
515 }
516 try a.pegVarAssignStatement();
517 },
518 }
519}
520
521/// Statement
522/// <- ExprStatement
523/// / KEYWORD_suspend BlockExprStatement
524/// / !ExprStatement (KEYWORD_comptime !BlockExpr)? AssignExpr SEMICOLON
525///
526/// ExprStatement
527/// <- IfStatement
528/// / LabeledStatement
529/// / KEYWORD_nosuspend BlockExprStatement
530/// / KEYWORD_comptime BlockExpr
531fn pegStatement(a: *AstSmith) SourceError!void {
532 switch (a.smith.value(enum {
533 if_statement,
534 labeled_statement,
535 comptime_block_expr,
536
537 nosuspend_statement,
538 suspend_statement,
539 assign_expr,
540 comptime_assign_expr,
541 })) {
542 .if_statement => try a.pegIfStatement(),
543 .labeled_statement => try a.pegLabeledStatement(),
544 .comptime_block_expr => {
545 try a.pegToken(.keyword_comptime);
546 try a.pegBlockExpr();
547 },
548
549 .nosuspend_statement,
550 .suspend_statement,
551 => |kind| {
552 try a.pegToken(switch (kind) {
553 .nosuspend_statement => .keyword_nosuspend,
554 .suspend_statement => .keyword_suspend,
555 else => unreachable,
556 });
557 try a.pegBlockExprStatement();
558 },
559 .assign_expr, .comptime_assign_expr => |kind| {
560 a.not_expr_statement = true;
561 if (kind == .comptime_assign_expr) {
562 try a.pegToken(.keyword_comptime);
563 a.not_block_expr = true;
564 }
565 try a.pegAssignExpr();
566 try a.pegToken(.semicolon);
567 },
568 }
569}
570
571/// IfStatement
572/// <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
573/// / IfPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
574fn pegIfStatement(a: *AstSmith) SourceError!void {
575 try a.pegIfPrefix();
576 const is_assign = a.smith.value(bool);
577 if (!is_assign) {
578 try a.pegBlockExpr();
579 } else {
580 a.not_block_expr = true;
581 try a.pegAssignExpr();
582 }
583 if (a.not_token != .keyword_else and a.smithListItemBool()) {
584 try a.pegToken(.keyword_else);
585 if (a.smith.value(bool)) {
586 try a.pegPayload();
587 }
588 try a.pegStatement();
589 } else if (is_assign) {
590 try a.pegToken(.semicolon);
591 } else {
592 a.not_token = .keyword_else;
593 }
594}
595
596/// LabeledStatement <- BlockLabel? (Block / LoopStatement / SwitchExpr)
597fn pegLabeledStatement(a: *AstSmith) SourceError!void {
598 if (a.smith.value(bool)) {
599 try a.pegBlockLabel();
600 }
601 switch (a.smith.value(enum { block, loop_statement, switch_expr })) {
602 .block => try a.pegBlock(),
603 .loop_statement => try a.pegLoopStatement(),
604 .switch_expr => try a.pegSwitchExpr(),
605 }
606}
607
608/// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
609fn pegLoopStatement(a: *AstSmith) SourceError!void {
610 if (a.smith.value(bool)) {
611 try a.pegToken(.keyword_inline);
612 }
613 if (a.smith.value(bool)) {
614 try a.pegForStatement();
615 } else {
616 try a.pegWhileStatement();
617 }
618}
619
620/// ForStatement
621/// <- ForPrefix BlockExpr ( KEYWORD_else Statement / !KEYWORD_else )
622/// / ForPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Statement )
623fn pegForStatement(a: *AstSmith) SourceError!void {
624 try a.pegForPrefix();
625 const is_assign = a.smith.value(bool);
626 if (!is_assign) {
627 try a.pegBlockExpr();
628 } else {
629 a.not_block_expr = true;
630 try a.pegAssignExpr();
631 }
632 if (a.not_token != .keyword_else and a.smithListItemBool()) {
633 try a.pegToken(.keyword_else);
634 try a.pegStatement();
635 } else if (is_assign) {
636 try a.pegToken(.semicolon);
637 } else {
638 a.not_token = .keyword_else;
639 }
640}
641
642/// WhileStatement
643/// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
644/// / WhilePrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
645fn pegWhileStatement(a: *AstSmith) SourceError!void {
646 try a.pegWhilePrefix();
647 const is_assign = a.smith.value(bool);
648 if (!is_assign) {
649 try a.pegBlockExpr();
650 } else {
651 a.not_block_expr = true;
652 try a.pegAssignExpr();
653 }
654 if (a.not_token != .keyword_else and a.smithListItemBool()) {
655 try a.pegToken(.keyword_else);
656 if (a.smith.value(bool)) {
657 try a.pegPayload();
658 }
659 try a.pegStatement();
660 } else if (is_assign) {
661 try a.pegToken(.semicolon);
662 } else {
663 a.not_token = .keyword_else;
664 }
665}
666
667/// BlockExprStatement
668/// <- BlockExpr
669/// / !BlockExpr AssignExpr SEMICOLON
670fn pegBlockExprStatement(a: *AstSmith) SourceError!void {
671 if (a.smith.value(bool)) {
672 try a.pegBlockExpr();
673 } else {
674 a.not_block_expr = true;
675 try a.pegAssignExpr();
676 try a.pegToken(.semicolon);
677 }
678}
679
680/// BlockExpr <- BlockLabel? Block
681fn pegBlockExpr(a: *AstSmith) SourceError!void {
682 if (a.smith.value(bool)) {
683 try a.pegBlockLabel();
684 }
685 try a.pegBlock();
686}
687
688/// VarAssignStatement <- (Expr / VarDeclProto) (COMMA (Expr / VarDeclProto))* EQUAL Expr SEMICOLON
689fn pegVarAssignStatement(a: *AstSmith) SourceError!void {
690 while (true) {
691 if (a.smith.value(bool)) {
692 try a.pegVarDeclProto();
693 } else {
694 try a.pegExpr();
695 }
696
697 if (a.smithListItemEos()) {
698 break;
699 } else {
700 try a.pegToken(.comma);
701 }
702 }
703
704 try a.pegToken(.equal);
705 try a.pegExpr();
706 try a.pegToken(.semicolon);
707}
708
709/// AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)?
710fn pegAssignExpr(a: *AstSmith) SourceError!void {
711 try a.pegExpr();
712 if (a.smith.value(bool)) {
713 if (!a.smithListItemBool()) {
714 try a.pegAssignOp();
715 } else {
716 while (true) {
717 try a.pegToken(.comma);
718 try a.pegExpr();
719 if (a.smithListItemEos()) break;
720 }
721 try a.pegToken(.equal);
722 }
723 try a.pegExpr();
724 }
725}
726
727/// SingleAssignExpr <- Expr (AssignOp Expr)?
728fn pegSingleAssignExpr(a: *AstSmith) SourceError!void {
729 try a.pegExpr();
730 if (a.smith.value(bool)) {
731 try a.pegAssignOp();
732 try a.pegExpr();
733 }
734}
735
736/// Expr <- BoolOrExpr
737const pegExpr = pegBoolOrExpr;
738
739/// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
740fn pegBoolOrExpr(a: *AstSmith) SourceError!void {
741 try a.pegBoolAndExpr();
742 while (!a.not_expr_suffix and !a.smithListItemEos()) {
743 try a.pegTokenWhitespaceAround(.keyword_or);
744 try a.pegBoolAndExpr();
745 }
746}
747
748/// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
749fn pegBoolAndExpr(a: *AstSmith) SourceError!void {
750 try a.pegCompareExpr();
751 while (!a.not_expr_suffix and !a.smithListItemEos()) {
752 try a.pegTokenWhitespaceAround(.keyword_and);
753 try a.pegCompareExpr();
754 }
755}
756
757/// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
758fn pegCompareExpr(a: *AstSmith) SourceError!void {
759 try a.pegBitwiseExpr();
760 if (!a.not_expr_suffix and a.smithListItemBool()) {
761 try a.pegCompareOp();
762 try a.pegBitwiseExpr();
763 }
764}
765
766/// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*
767fn pegBitwiseExpr(a: *AstSmith) SourceError!void {
768 try a.pegBitShiftExpr();
769 while (!a.not_expr_suffix and !a.smithListItemEos()) {
770 try a.pegBitwiseOp();
771 try a.pegBitShiftExpr();
772 }
773}
774
775/// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*
776fn pegBitShiftExpr(a: *AstSmith) SourceError!void {
777 try a.pegAdditionExpr();
778 while (!a.not_expr_suffix and !a.smithListItemEos()) {
779 try a.pegBitShiftOp();
780 try a.pegAdditionExpr();
781 }
782}
783
784/// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*
785fn pegAdditionExpr(a: *AstSmith) SourceError!void {
786 try a.pegMultiplyExpr();
787 while (!a.not_expr_suffix and !a.smithListItemEos()) {
788 try a.pegAdditionOp();
789 try a.pegMultiplyExpr();
790 }
791}
792
793/// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*
794fn pegMultiplyExpr(a: *AstSmith) SourceError!void {
795 try a.pegPrefixExpr();
796 while (!a.not_expr_suffix and !a.smithListItemEos()) {
797 try a.pegMultiplyOp();
798 try a.pegPrefixExpr();
799 }
800}
801
802/// PrefixExpr <- PrefixOp* PrimaryExpr
803fn pegPrefixExpr(a: *AstSmith) SourceError!void {
804 while (!a.smithListItemEos()) {
805 try a.pegPrefixOp();
806 }
807 try a.pegPrimaryExpr();
808}
809
810/// PrimaryExpr
811/// <- AsmExpr
812/// / IfExpr
813/// / KEYWORD_break (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart)
814/// / KEYWORD_comptime Expr !ExprSuffix
815/// / KEYWORD_nosuspend Expr !ExprSuffix
816/// / KEYWORD_continue (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart)
817/// / KEYWORD_resume Expr !ExprSuffix
818/// / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart)
819/// / BlockLabel? LoopExpr
820/// / Block
821/// / CurlySuffixExpr
822fn pegPrimaryExpr(a: *AstSmith) SourceError!void {
823 const Kind = enum(u8) {
824 curly_suffix_expr,
825 @"return",
826 @"continue",
827 @"break",
828 block,
829 asm_expr,
830 // Always contain more expressions
831 if_expr,
832 loop_expr,
833 @"resume",
834 @"comptime",
835 @"nosuspend",
836 };
837
838 switch (a.smith.valueWeighted(Kind, &.{
839 .value(Kind, .curly_suffix_expr, 75),
840 .rangeAtMost(Kind, .@"return", .asm_expr, 4),
841 .rangeAtMost(Kind, .if_expr, .@"nosuspend", 1),
842 })) {
843 .curly_suffix_expr => try a.pegCurlySuffixExpr(),
844
845 .block => if (a.not_labelable_expr != .expr and !a.not_block_expr and !a.not_expr_statement) {
846 try a.pegBlock();
847 } else {
848 // Group
849 try a.pegToken(.l_paren);
850 try a.pegBlock();
851 try a.pegToken(.r_paren);
852 },
853 .asm_expr => try a.pegAsmExpr(),
854 .if_expr => if (!a.not_expr_statement) {
855 try a.pegIfExpr();
856 } else {
857 // Group
858 try a.pegToken(.l_paren);
859 try a.pegIfExpr();
860 try a.pegToken(.r_paren);
861 },
862 .loop_expr => {
863 const group = a.not_labelable_expr == .expr or a.not_expr_statement;
864 if (group) try a.pegToken(.l_paren);
865 if (!a.not_label and a.not_token != .identifier and a.smith.value(bool)) {
866 try a.pegBlockLabel();
867 }
868 try a.pegLoopExpr();
869 if (group) try a.pegToken(.r_paren);
870 },
871
872 .@"return",
873 .@"comptime",
874 .@"nosuspend",
875 .@"resume",
876 .@"break",
877 .@"continue",
878 => |t| {
879 const group = a.not_expr_statement and (t == .@"nosuspend" or t == .@"comptime");
880 if (group) try a.pegToken(.l_paren);
881
882 const kw: Token.Tag, const label, const expr = switch (t) {
883 .@"return" => .{ .keyword_return, false, a.smithListItemBool() },
884 .@"comptime" => .{ .keyword_comptime, false, true },
885 .@"nosuspend" => .{ .keyword_nosuspend, false, true },
886 .@"resume" => .{ .keyword_resume, false, true },
887 .@"break" => .{ .keyword_break, a.smith.value(bool), a.smithListItemBool() },
888 .@"continue" => .{ .keyword_continue, a.smith.value(bool), a.smithListItemBool() },
889 else => unreachable,
890 };
891 try a.pegToken(kw);
892 if (label) {
893 try a.pegBreakLabel();
894 } else {
895 a.not_break_label = true;
896 }
897 if (expr) {
898 try a.pegExpr();
899 a.not_expr_suffix = true;
900 } else {
901 a.not_token = .asterisk;
902 }
903
904 if (group) try a.pegToken(.r_paren);
905 },
906 }
907}
908
909/// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
910fn pegIfExpr(a: *AstSmith) SourceError!void {
911 try a.pegIfPrefix();
912 try a.pegExpr();
913 const Else = enum { none, @"else", else_payload };
914 switch (if (a.not_token != .keyword_else) a.smith.value(Else) else .none) {
915 .none => a.not_token = .keyword_else,
916 .@"else" => {
917 try a.pegToken(.keyword_else);
918 try a.pegExpr();
919 },
920 .else_payload => {
921 try a.pegToken(.keyword_else);
922 try a.pegPayload();
923 try a.pegExpr();
924 },
925 }
926 a.not_expr_suffix = true;
927}
928
929/// Block <- LBRACE Statement* RBRACE
930fn pegBlock(a: *AstSmith) SourceError!void {
931 try a.pegToken(.l_brace);
932 while (!a.smithListItemEos()) {
933 try a.pegBlockStatement();
934 }
935 try a.pegToken(.r_brace);
936}
937
938/// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
939fn pegLoopExpr(a: *AstSmith) SourceError!void {
940 if (a.smith.value(bool)) {
941 try a.pegToken(.keyword_inline);
942 }
943
944 if (a.smith.value(bool)) {
945 try a.pegForExpr();
946 } else {
947 try a.pegWhileExpr();
948 }
949}
950
951/// ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix
952fn pegForExpr(a: *AstSmith) SourceError!void {
953 try a.pegForPrefix();
954 try a.pegExpr();
955 if (a.not_token != .keyword_else and a.smith.value(bool)) {
956 try a.pegToken(.keyword_else);
957 try a.pegExpr();
958 } else {
959 a.not_token = .keyword_else;
960 }
961 a.not_expr_suffix = true;
962}
963
964/// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
965fn pegWhileExpr(a: *AstSmith) SourceError!void {
966 try a.pegWhilePrefix();
967 try a.pegExpr();
968 const Else = enum { none, @"else", else_payload };
969 switch (if (a.not_token != .keyword_else) a.smith.value(Else) else .none) {
970 .none => a.not_token = .keyword_else,
971 .@"else" => {
972 try a.pegToken(.keyword_else);
973 try a.pegExpr();
974 },
975 .else_payload => {
976 try a.pegToken(.keyword_else);
977 try a.pegPayload();
978 try a.pegExpr();
979 },
980 }
981 a.not_expr_suffix = true;
982}
983
984/// CurlySuffixExpr <- TypeExpr InitList?
985fn pegCurlySuffixExpr(a: *AstSmith) SourceError!void {
986 try a.pegTypeExpr();
987 if (!a.not_expr_suffix and a.smith.value(bool)) {
988 try a.pegInitList();
989 }
990}
991
992/// InitList
993/// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
994/// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
995/// / LBRACE RBRACE
996fn pegInitList(a: *AstSmith) SourceError!void {
997 try a.pegToken(.l_brace);
998 if (a.smithListItemBool()) {
999 if (a.smith.value(bool)) {
1000 try a.pegFieldInit();
1001 while (!a.smithListItemEos()) {
1002 try a.pegToken(.comma);
1003 try a.pegFieldInit();
1004 }
1005 } else {
1006 try a.pegExpr();
1007 while (!a.smithListItemEos()) {
1008 try a.pegToken(.comma);
1009 try a.pegExpr();
1010 }
1011 }
1012 if (a.smith.value(bool)) {
1013 try a.pegToken(.comma);
1014 }
1015 }
1016 try a.pegToken(.r_brace);
1017}
1018
1019/// PrefixTypeOp* ErrorUnionExpr
1020fn pegTypeExpr(a: *AstSmith) SourceError!void {
1021 while (!a.smithListItemEos()) {
1022 try a.pegPrefixTypeOp();
1023 }
1024 try a.pegErrorUnionExpr();
1025}
1026
1027/// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
1028fn pegErrorUnionExpr(a: *AstSmith) SourceError!void {
1029 try a.pegSuffixExpr();
1030 if (!a.not_expr_suffix and a.smithListItemBool()) {
1031 try a.pegToken(.bang);
1032 try a.pegTypeExpr();
1033 }
1034}
1035
1036/// SuffixExpr
1037/// <- PrimaryTypeExpr (SuffixOp / FnCallArguments)*
1038fn pegSuffixExpr(a: *AstSmith) SourceError!void {
1039 try a.pegPrimaryTypeExpr();
1040 while (!a.not_expr_suffix and !a.smithListItemEos()) {
1041 if (a.smith.value(bool)) {
1042 try a.pegSuffixOp();
1043 } else {
1044 try a.pegFnCallArguments();
1045 }
1046 }
1047}
1048
1049/// PrimaryTypeExpr
1050/// <- BUILTINIDENTIFIER FnCallArguments
1051/// / CHAR_LITERAL
1052/// / ContainerDecl
1053/// / DOT IDENTIFIER
1054/// / DOT InitList
1055/// / ErrorSetDecl
1056/// / FLOAT
1057/// / FnProto
1058/// / GroupedExpr
1059/// / LabeledTypeExpr
1060/// / IDENTIFIER !(COLON LabelableExpr)
1061/// / IfTypeExpr
1062/// / INTEGER
1063/// / KEYWORD_comptime TypeExpr !ExprSuffix
1064/// / KEYWORD_error DOT IDENTIFIER
1065/// / KEYWORD_anyframe
1066/// / KEYWORD_unreachable
1067/// / STRINGLITERAL
1068fn pegPrimaryTypeExpr(a: *AstSmith) SourceError!void {
1069 const Kind = enum(u8) {
1070 identifier,
1071 float,
1072 integer,
1073 char_literal,
1074 string_literal,
1075 enum_literal,
1076 error_literal,
1077 unreachable_type,
1078 anyframe_type,
1079
1080 // Containing zero or more expressions
1081 builtin_call,
1082 array_literal,
1083 container_decl,
1084 fn_proto,
1085 error_set,
1086
1087 // Containing one or more epressions
1088 grouped,
1089 labeled_type_expr,
1090 if_type_expr,
1091 comptime_expr,
1092 };
1093
1094 switch (a.smith.valueWeighted(Kind, &.{
1095 .rangeAtMost(Kind, .identifier, .anyframe_type, 5),
1096 .rangeAtMost(Kind, .builtin_call, .error_set, 2),
1097 .rangeAtMost(Kind, .grouped, .comptime_expr, 1),
1098 })) {
1099 .identifier => if (a.not_token != .identifier) {
1100 try a.pegIdentifier();
1101 a.not_labelable_expr = .colon;
1102 } else {
1103 // Group
1104 try a.pegToken(.l_paren);
1105 try a.pegIdentifier();
1106 try a.pegToken(.r_paren);
1107 },
1108 .float => try a.pegFloat(),
1109 .integer => try a.pegInteger(),
1110 .char_literal => try a.pegCharLiteral(),
1111 .string_literal => try a.pegStringLiteral(),
1112 .enum_literal => {
1113 try a.pegToken(.period);
1114 try a.pegIdentifier();
1115 },
1116 .error_literal => {
1117 try a.pegToken(.keyword_error);
1118 try a.pegToken(.period);
1119 try a.pegIdentifier();
1120 },
1121 .unreachable_type => try a.pegToken(.keyword_unreachable),
1122 .anyframe_type => try a.pegToken(.keyword_anyframe),
1123
1124 .builtin_call => {
1125 try a.pegBuiltinIdentifier();
1126 try a.pegFnCallArguments();
1127 },
1128 .array_literal => {
1129 try a.pegToken(.period);
1130 try a.pegInitList();
1131 },
1132 .container_decl => try a.pegContainerDecl(),
1133 .fn_proto => if (a.not_token != .keyword_fn) {
1134 try a.pegFnProto();
1135 } else {
1136 // Group
1137 try a.pegToken(.l_paren);
1138 try a.pegFnProto();
1139 try a.pegToken(.r_paren);
1140 },
1141 .error_set => try a.pegErrorSetDecl(),
1142
1143 .grouped => try a.pegGroupedExpr(),
1144 .labeled_type_expr => try a.pegLabeledTypeExpr(),
1145 .if_type_expr => if (!a.not_expr_statement) {
1146 try a.pegIfTypeExpr();
1147 } else {
1148 // Group
1149 try a.pegToken(.l_paren);
1150 try a.pegIfTypeExpr();
1151 try a.pegToken(.r_paren);
1152 },
1153 .comptime_expr => if (!a.not_token_comptime and !a.not_expr_statement) {
1154 try a.pegToken(.keyword_comptime);
1155 try a.pegTypeExpr();
1156 } else {
1157 // Group
1158 try a.pegToken(.l_paren);
1159 try a.pegToken(.keyword_comptime);
1160 try a.pegTypeExpr();
1161 try a.pegToken(.r_paren);
1162 },
1163 }
1164}
1165
1166/// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
1167fn pegContainerDecl(a: *AstSmith) SourceError!void {
1168 switch (a.smith.value(enum { auto, @"extern", @"packed" })) {
1169 .auto => {},
1170 .@"extern" => try a.pegToken(.keyword_extern),
1171 .@"packed" => try a.pegToken(.keyword_packed),
1172 }
1173 try a.pegContainerDeclAuto();
1174}
1175
1176/// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
1177fn pegErrorSetDecl(a: *AstSmith) SourceError!void {
1178 try a.pegToken(.keyword_error);
1179 try a.pegToken(.l_brace);
1180 try a.pegIdentifierList();
1181 try a.pegToken(.r_brace);
1182}
1183
1184/// GroupedExpr <- LPAREN Expr RPAREN
1185fn pegGroupedExpr(a: *AstSmith) SourceError!void {
1186 try a.pegToken(.l_paren);
1187 try a.pegExpr();
1188 try a.pegToken(.r_paren);
1189}
1190
1191/// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
1192fn pegIfTypeExpr(a: *AstSmith) SourceError!void {
1193 try a.pegIfPrefix();
1194 try a.pegTypeExpr();
1195 const Else = enum { none, @"else", else_payload };
1196 switch (if (a.not_token != .keyword_else) a.smith.value(Else) else .none) {
1197 .none => a.not_token = .keyword_else,
1198 .@"else" => {
1199 try a.pegToken(.keyword_else);
1200 try a.pegTypeExpr();
1201 },
1202 .else_payload => {
1203 try a.pegToken(.keyword_else);
1204 try a.pegPayload();
1205 try a.pegTypeExpr();
1206 },
1207 }
1208 a.not_expr_suffix = true;
1209}
1210
1211/// LabeledTypeExpr
1212/// <- BlockLabel Block
1213/// / BlockLabel? LoopTypeExpr
1214/// / BlockLabel? SwitchExpr
1215fn pegLabeledTypeExpr(a: *AstSmith) SourceError!void {
1216 const kind = a.smith.value(enum { block, loop, @"switch" });
1217 const not_any = a.not_labelable_expr == .expr or a.not_expr_statement;
1218 const no_label = a.not_label or a.not_token == .identifier;
1219 const no_block = no_label or a.not_block_expr;
1220 const group = not_any or (kind == .block and no_block);
1221 if (group) try a.pegToken(.l_paren);
1222
1223 switch (kind) {
1224 .block => {
1225 try a.pegBlockLabel();
1226 try a.pegBlock();
1227 },
1228 .loop => {
1229 if (!no_label and a.smith.value(bool)) {
1230 try a.pegBlockLabel();
1231 }
1232 try a.pegLoopTypeExpr();
1233 },
1234 .@"switch" => {
1235 if (!no_label and a.smith.value(bool)) {
1236 try a.pegBlockLabel();
1237 }
1238 try a.pegSwitchExpr();
1239 },
1240 }
1241
1242 if (group) try a.pegToken(.r_paren);
1243}
1244
1245/// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
1246fn pegLoopTypeExpr(a: *AstSmith) SourceError!void {
1247 if (a.smith.value(bool)) {
1248 try a.pegToken(.keyword_inline);
1249 }
1250
1251 if (a.smith.value(bool)) {
1252 try a.pegForTypeExpr();
1253 } else {
1254 try a.pegWhileTypeExpr();
1255 }
1256}
1257
1258/// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix
1259fn pegForTypeExpr(a: *AstSmith) SourceError!void {
1260 try a.pegForPrefix();
1261 try a.pegTypeExpr();
1262 if (a.not_token != .keyword_else and a.smith.value(bool)) {
1263 try a.pegToken(.keyword_else);
1264 try a.pegTypeExpr();
1265 } else {
1266 a.not_token = .keyword_else;
1267 }
1268 a.not_expr_suffix = true;
1269}
1270
1271/// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
1272fn pegWhileTypeExpr(a: *AstSmith) SourceError!void {
1273 try a.pegWhilePrefix();
1274 try a.pegTypeExpr();
1275 const Else = enum { none, @"else", else_payload };
1276 switch (if (a.not_token != .keyword_else) a.smith.value(Else) else .none) {
1277 .none => a.not_token = .keyword_else,
1278 .@"else" => {
1279 try a.pegToken(.keyword_else);
1280 try a.pegTypeExpr();
1281 },
1282 .else_payload => {
1283 try a.pegToken(.keyword_else);
1284 try a.pegPayload();
1285 try a.pegTypeExpr();
1286 },
1287 }
1288 a.not_expr_suffix = true;
1289}
1290
1291/// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
1292fn pegSwitchExpr(a: *AstSmith) SourceError!void {
1293 try a.pegToken(.keyword_switch);
1294 try a.pegToken(.l_paren);
1295 try a.pegExpr();
1296 try a.pegToken(.r_paren);
1297
1298 try a.pegToken(.l_brace);
1299 try a.pegSwitchProngList();
1300 try a.pegToken(.r_brace);
1301}
1302
1303/// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
1304fn pegAsmExpr(a: *AstSmith) SourceError!void {
1305 try a.pegToken(.keyword_asm);
1306 if (a.smith.value(bool)) {
1307 try a.pegToken(.keyword_volatile);
1308 }
1309 try a.pegToken(.l_paren);
1310 try a.pegExpr();
1311 if (a.smith.value(bool)) {
1312 try a.pegAsmOutput();
1313 }
1314 try a.pegToken(.r_paren);
1315}
1316
1317/// AsmOutput <- COLON AsmOutputList AsmInput?
1318fn pegAsmOutput(a: *AstSmith) SourceError!void {
1319 try a.pegToken(.colon);
1320 try a.pegAsmOutputList();
1321 if (a.smith.value(bool)) {
1322 try a.pegAsmInput();
1323 }
1324}
1325
1326/// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
1327fn pegAsmOutputItem(a: *AstSmith) SourceError!void {
1328 try a.pegToken(.l_bracket);
1329 try a.pegIdentifier();
1330 try a.pegToken(.r_bracket);
1331 try a.pegStringLiteralSingle();
1332 try a.pegToken(.l_paren);
1333 if (a.smith.value(bool)) {
1334 try a.pegToken(.arrow);
1335 try a.pegTypeExpr();
1336 } else {
1337 try a.pegIdentifier();
1338 }
1339 try a.pegToken(.r_paren);
1340}
1341
1342/// AsmInput <- COLON AsmInputList AsmClobbers?
1343fn pegAsmInput(a: *AstSmith) SourceError!void {
1344 try a.pegToken(.colon);
1345 try a.pegAsmInputList();
1346 if (a.smith.value(bool)) {
1347 try a.pegAsmClobbers();
1348 }
1349}
1350
1351/// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN Expr RPAREN
1352fn pegAsmInputItem(a: *AstSmith) SourceError!void {
1353 try a.pegToken(.l_bracket);
1354 try a.pegIdentifier();
1355 try a.pegToken(.r_bracket);
1356 try a.pegStringLiteralSingle();
1357 try a.pegToken(.l_paren);
1358 try a.pegExpr();
1359 try a.pegToken(.r_paren);
1360}
1361
1362/// AsmClobbers <- COLON Expr
1363fn pegAsmClobbers(a: *AstSmith) SourceError!void {
1364 try a.pegToken(.colon);
1365 try a.pegExpr();
1366}
1367
1368/// BreakLabel <- COLON IDENTIFIER
1369fn pegBreakLabel(a: *AstSmith) SourceError!void {
1370 try a.pegToken(.colon);
1371 try a.pegIdentifier();
1372}
1373
1374/// BlockLabel <- IDENTIFIER COLON
1375fn pegBlockLabel(a: *AstSmith) SourceError!void {
1376 try a.pegIdentifier();
1377 try a.pegToken(.colon);
1378}
1379
1380/// FieldInit <- DOT IDENTIFIER EQUAL Expr
1381fn pegFieldInit(a: *AstSmith) SourceError!void {
1382 try a.pegToken(.period);
1383 try a.pegIdentifier();
1384 try a.pegToken(.equal);
1385 try a.pegExpr();
1386}
1387
1388/// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
1389fn pegWhileContinueExpr(a: *AstSmith) SourceError!void {
1390 try a.pegToken(.colon);
1391 try a.pegToken(.l_paren);
1392 try a.pegAssignExpr();
1393 try a.pegToken(.r_paren);
1394}
1395
1396/// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
1397fn pegLinkSection(a: *AstSmith) SourceError!void {
1398 try a.pegToken(.keyword_linksection);
1399 try a.pegToken(.l_paren);
1400 try a.pegExpr();
1401 try a.pegToken(.r_paren);
1402}
1403
1404/// AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN
1405fn pegAddrSpace(a: *AstSmith) SourceError!void {
1406 try a.pegToken(.keyword_addrspace);
1407 try a.pegToken(.l_paren);
1408 try a.pegExpr();
1409 try a.pegToken(.r_paren);
1410}
1411
1412/// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
1413fn pegCallConv(a: *AstSmith) SourceError!void {
1414 try a.pegToken(.keyword_callconv);
1415 try a.pegToken(.l_paren);
1416 try a.pegExpr();
1417 try a.pegToken(.r_paren);
1418}
1419
1420/// ParamDecl <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)?
1421/// ((IDENTIFIER COLON) / !KEYWORD_comptime !(IDENTIFIER COLON))
1422/// ParamType
1423fn pegParamDecl(a: *AstSmith) SourceError!void {
1424 try a.pegMaybeDocComment();
1425 const modifier = a.smith.value(enum { none, @"noalias", @"comptime" });
1426 switch (modifier) {
1427 .none => a.not_token_comptime = true,
1428 .@"noalias" => try a.pegToken(.keyword_noalias),
1429 .@"comptime" => try a.pegToken(.keyword_comptime),
1430 }
1431 if (a.smith.value(bool)) {
1432 try a.pegIdentifier();
1433 try a.pegToken(.colon);
1434 } else {
1435 a.not_label = true;
1436 }
1437 try a.pegParamType();
1438}
1439
1440/// ParamType
1441/// <- KEYWORD_anytype
1442/// / TypeExpr
1443fn pegParamType(a: *AstSmith) SourceError!void {
1444 if (a.smith.value(bool)) {
1445 try a.pegToken(.keyword_anytype);
1446 } else {
1447 try a.pegTypeExpr();
1448 }
1449}
1450
1451/// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
1452fn pegIfPrefix(a: *AstSmith) SourceError!void {
1453 try a.pegToken(.keyword_if);
1454 try a.pegToken(.l_paren);
1455 try a.pegExpr();
1456 try a.pegToken(.r_paren);
1457 try a.pegPtrPayload();
1458}
1459
1460/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
1461fn pegWhilePrefix(a: *AstSmith) SourceError!void {
1462 try a.pegToken(.keyword_while);
1463 try a.pegToken(.l_paren);
1464 try a.pegExpr();
1465 try a.pegToken(.r_paren);
1466
1467 if (a.smith.value(bool)) {
1468 try a.pegPtrPayload();
1469 }
1470
1471 if (a.smith.value(bool)) {
1472 try a.pegWhileContinueExpr();
1473 }
1474}
1475
1476/// ForPrefix <- KEYWORD_for LPAREN ForArgumentsList RPAREN PtrListPayload
1477///
1478/// An additional requirement checked in the Parser is that the number of
1479/// arguments and payload elements are the same.
1480fn pegForPrefix(a: *AstSmith) SourceError!void {
1481 try a.pegToken(.keyword_for);
1482 try a.pegToken(.l_paren);
1483 const n = try a.pegForArgumentsList();
1484 try a.pegToken(.r_paren);
1485 try a.pegPtrListPayload(n);
1486}
1487
1488/// Payload <- PIPE IDENTIFIER PIPE
1489fn pegPayload(a: *AstSmith) SourceError!void {
1490 try a.pegToken(.pipe);
1491 try a.pegIdentifier();
1492 try a.pegToken(.pipe);
1493}
1494
1495/// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
1496fn pegPtrPayload(a: *AstSmith) SourceError!void {
1497 try a.pegToken(.pipe);
1498 if (a.smith.value(bool)) {
1499 try a.pegToken(.asterisk);
1500 }
1501 try a.pegIdentifier();
1502 try a.pegToken(.pipe);
1503}
1504
1505/// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
1506fn pegPtrIndexPayload(a: *AstSmith) SourceError!void {
1507 try a.pegToken(.pipe);
1508 if (a.smith.value(bool)) {
1509 try a.pegToken(.asterisk);
1510 }
1511 try a.pegIdentifier();
1512 if (a.smith.value(bool)) {
1513 try a.pegToken(.comma);
1514 try a.pegIdentifier();
1515 }
1516 try a.pegToken(.pipe);
1517}
1518
1519/// PtrListPayload <- PIPE ASTERISK? IDENTIFIER (COMMA ASTERISK? IDENTIFIER)* COMMA? PIPE
1520fn pegPtrListPayload(a: *AstSmith, n: usize) SourceError!void {
1521 try a.pegToken(.pipe);
1522 if (a.smith.value(bool)) {
1523 try a.pegToken(.asterisk);
1524 }
1525 try a.pegIdentifier();
1526
1527 for (1..n) |_| {
1528 try a.pegToken(.comma);
1529 if (a.smith.value(bool)) {
1530 try a.pegToken(.asterisk);
1531 }
1532 try a.pegIdentifier();
1533 }
1534
1535 if (a.smith.value(bool)) {
1536 try a.pegToken(.comma);
1537 }
1538 try a.pegToken(.pipe);
1539}
1540
1541/// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? SingleAssignExpr
1542fn pegSwitchProng(a: *AstSmith) SourceError!void {
1543 if (a.smith.value(bool)) {
1544 try a.pegToken(.keyword_inline);
1545 }
1546 try a.pegSwitchCase();
1547 try a.pegToken(.equal_angle_bracket_right);
1548 if (a.smith.value(bool)) {
1549 try a.pegPtrIndexPayload();
1550 }
1551 try a.pegSingleAssignExpr();
1552}
1553
1554/// SwitchCase
1555/// <- SwitchItem (COMMA SwitchItem)* COMMA?
1556/// / KEYWORD_else
1557fn pegSwitchCase(a: *AstSmith) SourceError!void {
1558 if (a.smith.value(bool)) {
1559 try a.pegSwitchItem();
1560 while (!a.smithListItemEos()) {
1561 try a.pegToken(.comma);
1562 try a.pegSwitchItem();
1563 }
1564 if (a.smith.value(bool)) {
1565 try a.pegToken(.comma);
1566 }
1567 } else {
1568 try a.pegToken(.keyword_else);
1569 }
1570}
1571
1572/// SwitchItem <- Expr (DOT3 Expr)?
1573fn pegSwitchItem(a: *AstSmith) SourceError!void {
1574 try a.pegExpr();
1575 if (a.smith.value(bool)) {
1576 try a.pegToken(.ellipsis3);
1577 try a.pegExpr();
1578 }
1579}
1580
1581/// ForArgumentsList <- ForItem (COMMA ForItem)* COMMA?
1582fn pegForArgumentsList(a: *AstSmith) SourceError!usize {
1583 try a.pegForItem();
1584 var n: usize = 1;
1585 while (!a.smithListItemEos()) {
1586 try a.pegToken(.comma);
1587 try a.pegForItem();
1588 n += 1;
1589 }
1590 if (a.smith.value(bool)) {
1591 try a.pegToken(.comma);
1592 }
1593 return n;
1594}
1595
1596/// ForItem <- Expr (DOT2 Expr?)?
1597fn pegForItem(a: *AstSmith) SourceError!void {
1598 try a.pegExpr();
1599 const components = a.smith.valueRangeAtMost(u2, 0, 2);
1600 if (components >= 1) try a.pegToken(.ellipsis2);
1601 if (components >= 2) try a.pegExpr();
1602}
1603
1604/// AssignOp
1605/// <- ASTERISKEQUAL
1606/// / ASTERISKPIPEEQUAL
1607/// / SLASHEQUAL
1608/// / PERCENTEQUAL
1609/// / PLUSEQUAL
1610/// / PLUSPIPEEQUAL
1611/// / MINUSEQUAL
1612/// / MINUSPIPEEQUAL
1613/// / LARROW2EQUAL
1614/// / LARROW2PIPEEQUAL
1615/// / RARROW2EQUAL
1616/// / AMPERSANDEQUAL
1617/// / CARETEQUAL
1618/// / PIPEEQUAL
1619/// / ASTERISKPERCENTEQUAL
1620/// / PLUSPERCENTEQUAL
1621/// / MINUSPERCENTEQUAL
1622/// / EQUAL
1623fn pegAssignOp(a: *AstSmith) SourceError!void {
1624 const tags = [_]Token.Tag{
1625 .asterisk_equal,
1626 .asterisk_pipe_equal,
1627 .slash_equal,
1628 .percent_equal,
1629 .plus_equal,
1630 .plus_pipe_equal,
1631 .minus_equal,
1632 .minus_pipe_equal,
1633 .angle_bracket_angle_bracket_left_equal,
1634 .angle_bracket_angle_bracket_left_pipe_equal,
1635 .angle_bracket_angle_bracket_right_equal,
1636 .ampersand_equal,
1637 .caret_equal,
1638 .pipe_equal,
1639 .asterisk_percent_equal,
1640 .plus_percent_equal,
1641 .minus_percent_equal,
1642 .equal,
1643 };
1644 try a.pegToken(tags[a.smith.index(tags.len)]);
1645}
1646
1647/// CompareOp
1648/// <- EQUALEQUAL
1649/// / EXCLAMATIONMARKEQUAL
1650/// / LARROW
1651/// / RARROW
1652/// / LARROWEQUAL
1653/// / RARROWEQUAL
1654fn pegCompareOp(a: *AstSmith) SourceError!void {
1655 const tags = [_]Token.Tag{
1656 .equal_equal,
1657 .bang_equal,
1658 .angle_bracket_left,
1659 .angle_bracket_right,
1660 .angle_bracket_left_equal,
1661 .angle_bracket_right_equal,
1662 };
1663 try a.pegTokenWhitespaceAround(tags[a.smith.index(tags.len)]);
1664}
1665
1666/// BitwiseOp
1667/// <- AMPERSAND
1668/// / CARET
1669/// / PIPE
1670/// / KEYWORD_orelse
1671/// / KEYWORD_catch Payload?
1672fn pegBitwiseOp(a: *AstSmith) SourceError!void {
1673 const tags = [_]Token.Tag{
1674 .ampersand,
1675 .caret,
1676 .pipe,
1677 .keyword_orelse,
1678 .keyword_catch,
1679 };
1680 const tag = tags[a.smith.index(tags.len)];
1681 try a.pegTokenWhitespaceAround(tag);
1682 if (tag == .keyword_catch and a.smith.value(bool)) {
1683 try a.pegPayload();
1684 }
1685}
1686
1687/// BitShiftOp
1688/// <- LARROW2
1689/// / RARROW2
1690/// / LARROW2PIPE
1691fn pegBitShiftOp(a: *AstSmith) SourceError!void {
1692 const tags = [_]Token.Tag{
1693 .angle_bracket_angle_bracket_left,
1694 .angle_bracket_angle_bracket_right,
1695 .angle_bracket_angle_bracket_left_pipe,
1696 };
1697 try a.pegTokenWhitespaceAround(tags[a.smith.index(tags.len)]);
1698}
1699
1700/// AdditionOp
1701/// <- PLUS
1702/// / MINUS
1703/// / PLUS2
1704/// / PLUSPERCENT
1705/// / MINUSPERCENT
1706/// / PLUSPIPE
1707/// / MINUSPIPE
1708fn pegAdditionOp(a: *AstSmith) SourceError!void {
1709 const tags = [_]Token.Tag{
1710 .plus,
1711 .minus,
1712 .plus_plus,
1713 .plus_percent,
1714 .minus_percent,
1715 .plus_pipe,
1716 .minus_pipe,
1717 };
1718 try a.pegTokenWhitespaceAround(tags[a.smith.index(tags.len)]);
1719}
1720
1721/// MultiplyOp
1722/// <- PIPE2
1723/// / ASTERISK
1724/// / SLASH
1725/// / PERCENT
1726/// / ASTERISK2
1727/// / ASTERISKPERCENT
1728/// / ASTERISKPIPE
1729fn pegMultiplyOp(a: *AstSmith) SourceError!void {
1730 const tags = [_]Token.Tag{
1731 .asterisk,
1732 .asterisk_asterisk,
1733 .pipe_pipe,
1734 .slash,
1735 .percent,
1736 .asterisk_percent,
1737 .asterisk_pipe,
1738 };
1739 const start = @as(u8, 2) * @intFromBool(a.not_token == .asterisk);
1740 try a.pegTokenWhitespaceAround(tags[a.smith.valueRangeLessThan(u8, start, tags.len)]);
1741}
1742
1743/// PrefixOp
1744/// <- EXCLAMATIONMARK
1745/// / MINUS
1746/// / TILDE
1747/// / MINUSPERCENT
1748/// / AMPERSAND
1749/// / KEYWORD_try
1750fn pegPrefixOp(a: *AstSmith) SourceError!void {
1751 const tags = [_]Token.Tag{
1752 .bang,
1753 .minus,
1754 .tilde,
1755 .minus_percent,
1756 .ampersand,
1757 .keyword_try,
1758 };
1759 try a.pegToken(tags[a.smith.index(tags.len)]);
1760}
1761
1762/// PrefixTypeOp
1763/// <- QUESTIONMARK
1764/// / KEYWORD_anyframe MINUSRARROW
1765/// / (ManyPtrTypeStart / SliceTypeStart) KEYWORD_allowzero? ByteAlign? AddrSpace?
1766/// KEYWORD_const? KEYWORD_volatile?
1767/// / SinglePtrTypeStart KEYWORD_allowzero? BitAlign? AddrSpace?
1768/// KEYWORD_const? KEYWORD_volatile?
1769/// / ArrayTypeStart
1770fn pegPrefixTypeOp(a: *AstSmith) SourceError!void {
1771 switch (a.smith.value(enum {
1772 optional,
1773 anyframe_arrow,
1774 array,
1775 single_pointer,
1776 many_pointer,
1777 slice,
1778 })) {
1779 .optional => try a.pegToken(.question_mark),
1780 .anyframe_arrow => {
1781 try a.pegToken(.keyword_anyframe);
1782 try a.pegToken(.arrow);
1783 },
1784 .array => try a.pegArrayTypeStart(),
1785 .single_pointer, .many_pointer, .slice => |kind| {
1786 const is_single = kind == .single_pointer and a.not_token != .asterisk;
1787 if (is_single) {
1788 try a.pegSinglePtrTypeStart();
1789 } else if (kind == .many_pointer) {
1790 try a.pegManyPtrTypeStart();
1791 } else {
1792 try a.pegSliceTypeStart();
1793 }
1794
1795 if (a.smith.value(bool)) {
1796 try a.pegToken(.keyword_allowzero);
1797 }
1798 if (a.smith.value(bool)) {
1799 if (is_single) {
1800 try a.pegBitAlign();
1801 } else {
1802 try a.pegByteAlign();
1803 }
1804 }
1805 if (a.smith.value(bool)) {
1806 try a.pegAddrSpace();
1807 }
1808 if (a.smith.value(bool)) {
1809 try a.pegToken(.keyword_const);
1810 }
1811 if (a.smith.value(bool)) {
1812 try a.pegToken(.keyword_volatile);
1813 }
1814 },
1815 }
1816}
1817
1818/// SuffixOp
1819/// <- LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET
1820/// / DOT IDENTIFIER
1821/// / DOTASTERISK
1822/// / DOTQUESTIONMARK
1823fn pegSuffixOp(a: *AstSmith) SourceError!void {
1824 switch (a.smith.value(enum { slice, field, deref, unwrap })) {
1825 .slice => {
1826 try a.pegToken(.l_bracket);
1827 try a.pegExpr();
1828
1829 const components = a.smith.value(u2);
1830 if (components >= 1) try a.pegToken(.ellipsis2);
1831 if (components >= 2) try a.pegExpr();
1832 if (components >= 3) {
1833 try a.pegToken(.colon);
1834 try a.pegExpr();
1835 }
1836
1837 try a.pegToken(.r_bracket);
1838 },
1839 .field => {
1840 try a.pegToken(.period);
1841 try a.pegIdentifier();
1842 },
1843 .deref => try a.pegToken(.period_asterisk),
1844 .unwrap => {
1845 try a.pegToken(.period);
1846 try a.pegToken(.question_mark);
1847 },
1848 }
1849}
1850
1851/// FnCallArguments <- LPAREN ExprList RPAREN
1852fn pegFnCallArguments(a: *AstSmith) SourceError!void {
1853 try a.pegToken(.l_paren);
1854 try a.pegExprList();
1855 try a.pegToken(.r_paren);
1856}
1857
1858/// SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
1859fn pegSliceTypeStart(a: *AstSmith) SourceError!void {
1860 try a.pegToken(.l_bracket);
1861 if (a.smith.value(bool)) {
1862 try a.pegToken(.colon);
1863 try a.pegExpr();
1864 }
1865 try a.pegToken(.r_bracket);
1866}
1867
1868/// SinglePtrTypeStart <- ASTERISK / ASTERISK2
1869fn pegSinglePtrTypeStart(a: *AstSmith) SourceError!void {
1870 try a.pegToken(if (!a.smith.value(bool)) .asterisk else .asterisk_asterisk);
1871}
1872
1873/// ManyPtrTypeStart <- LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
1874fn pegManyPtrTypeStart(a: *AstSmith) SourceError!void {
1875 try a.pegToken(.l_bracket);
1876 try a.pegToken(.asterisk);
1877 switch (a.smith.value(enum { many, many_c, many_sentinel })) {
1878 .many => {},
1879 .many_c => {
1880 // No need for `preservePegEndOfWord` because the previous token is an asterisk
1881 try a.addTokenTag(.identifier);
1882 try a.addSourceByte('c');
1883 },
1884 .many_sentinel => {
1885 try a.pegToken(.colon);
1886 try a.pegExpr();
1887 },
1888 }
1889 try a.pegToken(.r_bracket);
1890}
1891
1892/// ArrayTypeStart <- LBRACKET !(ASTERISK / ASTERISK2) Expr (COLON Expr)? RBRACKET
1893fn pegArrayTypeStart(a: *AstSmith) SourceError!void {
1894 try a.pegToken(.l_bracket);
1895 a.not_token = .asterisk;
1896 try a.pegExpr();
1897 if (a.smith.value(bool)) {
1898 try a.pegToken(.colon);
1899 try a.pegExpr();
1900 }
1901 try a.pegToken(.r_bracket);
1902}
1903
1904/// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
1905fn pegContainerDeclAuto(a: *AstSmith) SourceError!void {
1906 try a.pegContainerDeclType();
1907 try a.pegToken(.l_brace);
1908 try a.pegContainerMembers();
1909 try a.pegToken(.r_brace);
1910}
1911
1912/// ContainerDeclType
1913/// <- KEYWORD_struct (LPAREN Expr RPAREN)?
1914/// / KEYWORD_opaque
1915/// / KEYWORD_enum (LPAREN Expr RPAREN)?
1916/// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / !KEYWORD_enum Expr) RPAREN)?
1917fn pegContainerDeclType(a: *AstSmith) SourceError!void {
1918 switch (a.smith.value(enum { @"struct", @"opaque", @"enum", @"union" })) {
1919 .@"struct", .@"enum" => |c| {
1920 const is_struct = c == .@"struct" or a.not_token == .keyword_enum;
1921 try a.pegToken(if (is_struct) .keyword_struct else .keyword_enum);
1922 if (a.smith.value(bool)) {
1923 try a.pegToken(.l_paren);
1924 try a.pegExpr();
1925 try a.pegToken(.r_paren);
1926 }
1927 },
1928 .@"opaque" => try a.pegToken(.keyword_opaque),
1929 .@"union" => {
1930 try a.pegToken(.keyword_union);
1931 switch (a.smith.value(enum { no_tag, expr_tag, enum_tag, enum_expr_tag })) {
1932 .no_tag => {},
1933 .expr_tag => {
1934 try a.pegToken(.l_paren);
1935 a.not_token = .keyword_enum;
1936 try a.pegExpr();
1937 try a.pegToken(.r_paren);
1938 },
1939 .enum_tag => {
1940 try a.pegToken(.l_paren);
1941 try a.pegToken(.keyword_enum);
1942 try a.pegToken(.r_paren);
1943 },
1944 .enum_expr_tag => {
1945 try a.pegToken(.l_paren);
1946 try a.pegToken(.keyword_enum);
1947 try a.pegToken(.l_paren);
1948 try a.pegExpr();
1949 try a.pegToken(.r_paren);
1950 try a.pegToken(.r_paren);
1951 },
1952 }
1953 },
1954 }
1955}
1956
1957/// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
1958fn pegByteAlign(a: *AstSmith) SourceError!void {
1959 try a.pegToken(.keyword_align);
1960 try a.pegToken(.l_paren);
1961 try a.pegExpr();
1962 try a.pegToken(.r_paren);
1963}
1964
1965/// BitAlign <- KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN
1966fn pegBitAlign(a: *AstSmith) SourceError!void {
1967 try a.pegToken(.keyword_align);
1968 try a.pegToken(.l_paren);
1969 try a.pegExpr();
1970 if (a.smith.value(bool)) {
1971 try a.pegToken(.colon);
1972 try a.pegExpr();
1973 try a.pegToken(.colon);
1974 try a.pegExpr();
1975 }
1976 try a.pegToken(.r_paren);
1977}
1978
1979/// IdentifierList <- (doc_comment? IDENTIFIER COMMA)* (doc_comment? IDENTIFIER)?
1980fn pegIdentifierList(a: *AstSmith) SourceError!void {
1981 while (!a.smith.eos()) {
1982 try a.pegMaybeDocComment();
1983 try a.pegIdentifier();
1984 try a.pegToken(.comma);
1985 }
1986 if (a.smith.value(bool)) {
1987 try a.pegMaybeDocComment();
1988 try a.pegIdentifier();
1989 }
1990}
1991
1992/// SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
1993fn pegSwitchProngList(a: *AstSmith) SourceError!void {
1994 while (!a.smithListItemEos()) {
1995 try a.pegSwitchProng();
1996 try a.pegToken(.comma);
1997 }
1998 if (a.smithListItemBool()) {
1999 try a.pegSwitchProng();
2000 }
2001}
2002
2003/// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
2004fn pegAsmOutputList(a: *AstSmith) SourceError!void {
2005 while (!a.smithListItemEos()) {
2006 try a.pegAsmOutputItem();
2007 try a.pegToken(.comma);
2008 }
2009 if (a.smithListItemBool()) {
2010 try a.pegAsmOutputItem();
2011 }
2012}
2013
2014/// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
2015fn pegAsmInputList(a: *AstSmith) SourceError!void {
2016 while (!a.smithListItemEos()) {
2017 try a.pegAsmInputItem();
2018 try a.pegToken(.comma);
2019 }
2020 if (a.smithListItemBool()) {
2021 try a.pegAsmInputItem();
2022 }
2023}
2024
2025/// ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
2026fn pegParamDeclList(a: *AstSmith) SourceError!void {
2027 while (!a.smithListItemEos()) {
2028 try a.pegParamDecl();
2029 try a.pegToken(.comma);
2030 }
2031 const Final = enum { none, dot3, dot3_comma, param };
2032 switch (a.smith.valueWeighted(Final, &.{
2033 .rangeLessThan(Final, .none, .param, 2),
2034 .value(Final, .param, 1),
2035 })) {
2036 .none => {},
2037 .dot3 => try a.pegToken(.ellipsis3),
2038 .dot3_comma => {
2039 try a.pegToken(.ellipsis3);
2040 try a.pegToken(.comma);
2041 },
2042 .param => try a.pegParamDecl(),
2043 }
2044}
2045
2046/// ExprList <- (Expr COMMA)* Expr?
2047fn pegExprList(a: *AstSmith) SourceError!void {
2048 while (!a.smithListItemEos()) {
2049 try a.pegExpr();
2050 try a.pegToken(.comma);
2051 }
2052 if (a.smithListItemBool()) {
2053 try a.pegExpr();
2054 }
2055}
2056
2057/// container_doc_comment <- ('//!' non_control_utf8* [ \n]* skip)+
2058fn pegContainerDocComment(a: *AstSmith) SourceError!void {
2059 while (true) {
2060 try a.addTokenTag(.container_doc_comment);
2061 try a.pegGenericLine("//!", .any);
2062 try a.pegSkip();
2063 if (a.smith.eos()) break;
2064 }
2065}
2066
2067/// doc_comment?
2068fn pegMaybeDocComment(a: *AstSmith) SourceError!void {
2069 // A specific hash is provided here since this function is likely to be inlined,
2070 // however having all doc comments with the same uid is beneficial.
2071 if (a.smith.boolWeightedWithHash(63, 1, 0x39b94392)) {
2072 try a.pegDocComment();
2073 }
2074}
2075
2076/// doc_comment <- ('///' non_control_utf8* [ \n]* skip)+
2077fn pegDocComment(a: *AstSmith) SourceError!void {
2078 if (a.source_len > 0 and a.source_buf[a.source_len - 1] != '\n') {
2079 try a.addSourceByte('\n');
2080 }
2081 while (true) {
2082 try a.addTokenTag(.doc_comment);
2083 try a.pegGenericLine("///", .doc_comment);
2084 try a.pegSkip();
2085 if (a.smith.eosWeightedSimple(1, 3)) break;
2086 }
2087}
2088
2089/// line_comment <- '//' ![!/] non_control_utf8* / '////' non_control_utf8*
2090fn pegLineComment(a: *AstSmith) SourceError!void {
2091 return a.pegGenericLine("//", .line_comment);
2092}
2093
2094/// line_string <- '\\\\' non_control_utf8* [ \n]*
2095fn pegLineString(a: *AstSmith) SourceError!void {
2096 try a.addTokenTag(.multiline_string_literal_line);
2097 return a.pegGenericLine("\\\\", .any);
2098}
2099
2100/// non_control_utf8 <- [\040-\377]
2101///
2102/// Used for line, doc, and container comments as well as
2103/// multiline string literal lines.
2104fn pegGenericLine(
2105 a: *AstSmith,
2106 prefix: []const u8,
2107 /// Adds constraints to what the line contains
2108 prefix_kind: enum { any, line_comment, doc_comment },
2109) SourceError!void {
2110 const cr = a.smith.value(bool);
2111 const newline_len = @intFromBool(cr) + @as(usize, 1);
2112
2113 try a.ensureSourceCapacity(prefix.len + newline_len);
2114 a.addSourceAssumeCapacity(prefix);
2115
2116 const line = a.variableChar(newline_len, 0, &.{
2117 .rangeAtMost(u8, ' ', 0x7f - 1, 1),
2118 .rangeAtMost(u8, 0x7f + 1, 0xff, 1),
2119 });
2120 if (line.len >= 1) switch (prefix_kind) {
2121 .any => {},
2122 .line_comment => {
2123 // Convert doc comments to quadruple slashes when possible;
2124 // Otherwise, and for container doc comments, erase the '/' or '!'
2125 if (line[0] == '/' and line.len >= 2) {
2126 line[1] = '/';
2127 } else if (line[0] == '/' or line[0] == '!') {
2128 line[0] = ' ';
2129 }
2130 },
2131 .doc_comment => {
2132 // Avoid quadruple slashes
2133 if (line[0] == '/') {
2134 line[0] = ' ';
2135 }
2136 },
2137 };
2138
2139 if (cr) a.addSourceByteAssumeCapacity('\r');
2140 a.addSourceByteAssumeCapacity('\n');
2141}
2142
2143/// skip <- ([ \n] / line_comment)*
2144fn pegSkip(a: *AstSmith) SourceError!void {
2145 if (a.smith.boolWeighted(63, 1)) {
2146 while (true) {
2147 const Kind = enum {
2148 space,
2149 line_break,
2150 cr_line_break,
2151 line_comment,
2152 line_comment_zig_fmt_off,
2153 line_comment_zig_fmt_on,
2154 };
2155
2156 const weights = Smith.baselineWeights(Kind) ++
2157 [_]Weight{.value(Kind, .space, 11)};
2158 switch (a.smith.valueWeighted(Kind, weights)) {
2159 .space => try a.addSourceByte(' '),
2160 .line_break => try a.addSourceByte('\n'),
2161 .cr_line_break => try a.addSource("\r\n"),
2162 .line_comment => try a.pegLineComment(),
2163 .line_comment_zig_fmt_off => try a.addSource("//zig fmt: off\n"),
2164 .line_comment_zig_fmt_on => try a.addSource("//zig fmt: on\n"),
2165 }
2166
2167 if (a.smith.eos()) break;
2168 }
2169 }
2170}
2171
2172const bin_weights: []const Weight = &.{.rangeAtMost(u8, '0', '1', 1)};
2173const oct_weights: []const Weight = &.{.rangeAtMost(u8, '0', '7', 1)};
2174const dec_weights: []const Weight = &.{.rangeAtMost(u8, '0', '9', 1)};
2175const hex_weights: []const Weight = &.{
2176 .rangeAtMost(u8, '0', '9', 1),
2177 .rangeAtMost(u8, 'a', 'f', 1),
2178 .rangeAtMost(u8, 'A', 'F', 1),
2179};
2180
2181/// Asserts enough capacity for at `min + reserved_capacity`
2182fn variableChar(
2183 a: *AstSmith,
2184 reserved_capacity: usize,
2185 min: usize,
2186 weights: []const Weight,
2187) []u8 {
2188 const capacity = a.sourceCapacity();
2189 const max_out = capacity.len - reserved_capacity;
2190
2191 const len_weights: [3]Weight = .{
2192 .rangeAtMost(u32, @intCast(min), @min(2, max_out), 32678),
2193 // For the below `.rangeAtMost` is not used because max may be less than min.
2194 // In this case, the weights are omitted.
2195 .{ .min = 3, .max = @min(16, max_out), .weight = 512 },
2196 // Still allow much longer sequences to test parsing overflows
2197 .{ .min = 17, .max = @min(256, max_out), .weight = 1 },
2198 };
2199 const n_weights = @as(usize, 1) + @intFromBool(max_out >= 3) + @intFromBool(max_out >= 17);
2200
2201 const len = a.smith.sliceWeighted(capacity, len_weights[0..n_weights], weights);
2202 a.source_len += len;
2203 return capacity[0..len];
2204}
2205
2206/// char_escape
2207/// <- "\\x" hex hex
2208/// / "\\u{" hex+ "}"
2209/// / "\\" [nr\\t'"]
2210/// char_char
2211/// <- multibyte_utf8
2212/// / char_escape
2213/// / ![\\'\n] non_control_ascii
2214///
2215/// string_char
2216/// <- multibyte_utf8
2217/// / char_escape
2218/// / ![\\"\n] non_control_ascii
2219fn pegChar(a: *AstSmith, quote: u8) SourceError!void {
2220 const Char = enum(u8) {
2221 ascii,
2222 unicode_2,
2223 unicode_3,
2224 unicode_4,
2225 hex_escape,
2226 unicode_escape,
2227 char_escape,
2228 };
2229 const weights = Smith.baselineWeights(Char) ++ &[_]Weight{.value(Char, .ascii, 32)};
2230 switch (a.smith.valueWeighted(Char, weights)) {
2231 .ascii => try a.addSourceByte(a.smith.valueWeighted(u8, &.{
2232 .rangeAtMost(u8, ' ', quote - 1, 1),
2233 .rangeAtMost(u8, quote + 1, '\\' - 1, 1),
2234 .rangeAtMost(u8, '\\' + 1, 0x7e, 1),
2235 })),
2236 .unicode_2 => assert(2 == std.unicode.wtf8Encode(
2237 a.smith.valueRangeLessThan(u21, 0x80, 0x800),
2238 try a.addSourceAsSlice(2),
2239 ) catch unreachable),
2240 .unicode_3 => assert(3 == std.unicode.wtf8Encode(
2241 a.smith.valueRangeLessThan(u21, 0x800, 0x10000),
2242 try a.addSourceAsSlice(3),
2243 ) catch unreachable),
2244 .unicode_4 => assert(4 == std.unicode.wtf8Encode(
2245 a.smith.valueRangeLessThan(u21, 0x10000, 0x110000),
2246 try a.addSourceAsSlice(4),
2247 ) catch unreachable),
2248 .hex_escape => {
2249 try a.ensureSourceCapacity(4);
2250 a.addSourceAssumeCapacity("\\x");
2251 a.smith.bytesWeighted(a.addSourceAsSliceAssumeCapacity(2), hex_weights);
2252 },
2253 .unicode_escape => {
2254 try a.ensureSourceCapacity(5);
2255 a.addSourceAssumeCapacity("\\u{");
2256 _ = a.variableChar(1, 1, hex_weights);
2257 a.addSourceByteAssumeCapacity('}');
2258 },
2259 .char_escape => {
2260 try a.ensureSourceCapacity(2);
2261 a.addSourceByteAssumeCapacity('\\');
2262 a.addSourceByteAssumeCapacity(a.smith.valueWeighted(u8, &.{
2263 .value(u8, 'n', 1),
2264 .value(u8, 'r', 1),
2265 .value(u8, 't', 1),
2266 .value(u8, '\\', 1),
2267 .value(u8, '\'', 1),
2268 .value(u8, '"', 1),
2269 }));
2270 },
2271 }
2272}
2273
2274/// CHAR_LITERAL <- ['] char_char ['] skip
2275fn pegCharLiteral(a: *AstSmith) SourceError!void {
2276 try a.addTokenTag(.char_literal);
2277 try a.addSourceByte('\'');
2278 try a.pegChar('\'');
2279 try a.addSourceByte('\'');
2280 try a.pegSkip();
2281}
2282
2283///FLOAT
2284/// <- '0x' hex_int '.' hex_int ([pP] [-+]? dec_int)? skip
2285/// / dec_int '.' dec_int ([eE] [-+]? dec_int)? skip
2286/// / '0x' hex_int [pP] [-+]? dec_int skip
2287/// / dec_int [eE] [-+]? dec_int skip
2288fn pegFloat(a: *AstSmith) SourceError!void {
2289 try a.preservePegEndOfWord();
2290 try a.addTokenTag(.number_literal);
2291
2292 const hex = a.smith.value(bool);
2293 const exp = a.smith.value(packed struct(u3) {
2294 kind: enum(u2) { none, no_sign, minus, plus },
2295 upper: bool,
2296 });
2297 const dot = exp.kind == .none or a.smith.value(bool);
2298
2299 var reserved: usize = @intFromBool(hex) * "0x".len + "0".len + @intFromBool(dot) * ".0".len +
2300 switch (exp.kind) {
2301 .none => 0,
2302 .no_sign => "e0".len,
2303 .minus => "e-0".len,
2304 .plus => "e+0".len,
2305 };
2306 try a.ensureSourceCapacity(reserved);
2307
2308 if (hex) {
2309 reserved -= 2;
2310 a.addSourceAssumeCapacity("0x");
2311 }
2312 const digits = if (hex) hex_weights else dec_weights;
2313
2314 reserved -= 1;
2315 _ = a.variableChar(reserved, 1, digits);
2316
2317 if (dot) {
2318 reserved -= 2;
2319 a.addSourceByteAssumeCapacity('.');
2320 _ = a.variableChar(reserved, 1, digits);
2321 }
2322
2323 if (exp.kind != .none) {
2324 reserved -= 1;
2325 const case_diff = @as(u8, 'a' - 'A') * @intFromBool(exp.upper);
2326 a.addSourceByteAssumeCapacity(@as(u8, if (hex) 'p' else 'e') - case_diff);
2327
2328 if (exp.kind != .no_sign) {
2329 reserved -= 1;
2330 a.addSourceByteAssumeCapacity(if (exp.kind == .plus) '+' else '-');
2331 }
2332
2333 reserved -= 1;
2334 assert(reserved == 0);
2335 _ = a.variableChar(reserved, 1, dec_weights);
2336 }
2337}
2338
2339///INTEGER
2340/// <- '0b' bin_int skip
2341/// / '0o' oct_int skip
2342/// / '0x' hex_int skip
2343/// / dec_int skip
2344fn pegInteger(a: *AstSmith) SourceError!void {
2345 try a.preservePegEndOfWord();
2346 try a.addTokenTag(.number_literal);
2347 const Base = enum { bin, dec, oct, hex };
2348 const base_weights: []const Weight = Smith.baselineWeights(Base) ++
2349 &[_]Weight{ .value(Base, .dec, 6), .value(Base, .hex, 2) };
2350 const digits, const prefix = switch (a.smith.valueWeighted(Base, base_weights)) {
2351 .bin => .{ bin_weights, "0b" },
2352 .oct => .{ oct_weights, "0o" },
2353 .dec => .{ dec_weights, "" },
2354 .hex => .{ hex_weights, "0x" },
2355 };
2356 try a.ensureSourceCapacity(prefix.len + 1);
2357 if (prefix.len != 0) a.addSourceAssumeCapacity(prefix);
2358 _ = a.variableChar(0, 1, digits);
2359}
2360
2361/// Does not include 'skip'. Does not add any token tag.
2362fn stringLiteralSingleInner(a: *AstSmith) SourceError!void {
2363 try a.addSourceByte('"');
2364 while (!a.smith.eosWeightedSimple(3, 1)) {
2365 try a.pegChar('"');
2366 }
2367 try a.addSourceByte('"');
2368}
2369
2370/// STRINGLITERALSINGLE <- ["] string_char* ["] skip
2371fn pegStringLiteralSingle(a: *AstSmith) SourceError!void {
2372 try a.addTokenTag(.string_literal);
2373 try a.stringLiteralSingleInner();
2374 try a.pegSkip();
2375}
2376
2377/// STRINGLITERAL
2378/// <- STRINGLITERALSINGLE
2379/// / (line_string skip)+
2380fn pegStringLiteral(a: *AstSmith) SourceError!void {
2381 if (a.smith.value(bool)) {
2382 try a.pegStringLiteralSingle();
2383 } else {
2384 while (true) {
2385 try a.pegLineString();
2386 try a.pegSkip();
2387 if (a.smith.eos()) break;
2388 }
2389 }
2390}
2391
2392const alphanumeric_weights: [4]Weight = .{
2393 .rangeAtMost(u8, '0', '9', 1),
2394 .rangeAtMost(u8, 'A', 'Z', 1),
2395 .rangeAtMost(u8, 'a', 'z', 1),
2396 .value(u8, '_', 1),
2397};
2398
2399/// IDENTIFIER
2400/// <- !keyword [A-Za-z_] [A-Za-z0-9_]* skip
2401/// / '@' STRINGLITERALSINGLE
2402fn pegIdentifier(a: *AstSmith) SourceError!void {
2403 const Kind = enum(u2) { underscore, regular_identifier, quoted_identifier, copy_identifier };
2404 const kind_weights: [4]Weight = .{
2405 .value(Kind, .underscore, 6),
2406 .value(Kind, .regular_identifier, 3),
2407 .value(Kind, .quoted_identifier, 1),
2408 .value(Kind, .copy_identifier, 6),
2409 };
2410 const n_weights = @as(usize, kind_weights.len) - @intFromBool(a.prev_ids_len == 0);
2411 const kind = a.smith.valueWeighted(Kind, kind_weights[0..n_weights]);
2412
2413 switch (kind) {
2414 .underscore => {
2415 try a.preservePegEndOfWord();
2416 try a.addTokenTag(.identifier);
2417 try a.addSourceByte('_');
2418 },
2419 .regular_identifier => {
2420 try a.preservePegEndOfWord();
2421 try a.addTokenTag(.identifier);
2422
2423 const start = a.source_len;
2424 try a.addSourceByte(a.smith.valueWeighted(u8, alphanumeric_weights[1..]));
2425 _ = a.variableChar(0, 0, &alphanumeric_weights);
2426
2427 if (Token.getKeyword(a.source_buf[start..a.source_len]) != null) {
2428 a.source_buf[start] = '_'; // No keywords start with '_'
2429 }
2430 },
2431 .quoted_identifier => {
2432 try a.addTokenTag(.identifier);
2433 try a.addSourceByte('@');
2434 try a.stringLiteralSingleInner();
2435 },
2436 .copy_identifier => {
2437 const n_prev = @min(a.prev_ids_len, a.prev_ids_buf.len);
2438 const prev_i = a.smith.valueRangeLessThan(u16, 0, n_prev);
2439 const prev = a.prev_ids_buf[prev_i];
2440
2441 if (a.source_buf[prev.start] != '@') try a.preservePegEndOfWord();
2442 try a.addTokenTag(.identifier);
2443 try a.addSource(a.source_buf[prev.start..][0..prev.len]);
2444 },
2445 }
2446 try a.pegSkip();
2447 if (kind != .copy_identifier) {
2448 const start = a.token_start_buf[a.tokens_len - 1];
2449 a.prev_ids_buf[a.prev_ids_len % a.prev_ids_buf.len] = .{
2450 .start = @intCast(start),
2451 .len = @intCast(a.source_len - start),
2452 };
2453 a.prev_ids_len += 1;
2454 }
2455}
2456
2457/// BUILTINIDENTIFIER <- '@'[A-Za-z_][A-Za-z0-9_]* skip
2458fn pegBuiltinIdentifier(a: *AstSmith) SourceError!void {
2459 try a.addTokenTag(.builtin);
2460 if (a.smith.boolWeighted(1, 31)) {
2461 if (a.smith.boolWeighted(1, 8)) {
2462 // Pointer cast (reordable with zig fmt)
2463 const ids = [_][]const u8{
2464 "@ptrCast",
2465 "@addrspaceCast",
2466 "@alignCast",
2467 "@constCast",
2468 "@volatileCast",
2469 };
2470 try a.addSource(ids[a.smith.index(ids.len)]);
2471 } else {
2472 const ids = std.zig.BuiltinFn.list.keys();
2473 try a.addSource(ids[a.smith.index(ids.len)]);
2474 }
2475 } else {
2476 try a.ensureSourceCapacity(2);
2477 a.addSourceByteAssumeCapacity('@');
2478 a.addSourceByteAssumeCapacity(a.smith.valueWeighted(u8, alphanumeric_weights[1..]));
2479 _ = a.variableChar(0, 0, &alphanumeric_weights);
2480 }
2481 try a.pegSkip();
2482}
2483
2484test AstSmith {
2485 try std.testing.fuzz({}, checkGenerated, .{});
2486}
2487
2488fn checkGenerated(_: void, smith: *Smith) !void {
2489 var a: AstSmith = .init(smith);
2490 try a.generateSource();
2491
2492 { // Check tokenization matches source
2493 errdefer a.logBadSource(null);
2494
2495 const token_tags = a.token_tag_buf[0..a.tokens_len];
2496 const token_starts = a.token_start_buf[0..a.tokens_len];
2497 try std.testing.expectEqual(Token.Tag.eof, token_tags[token_tags.len - 1]);
2498
2499 var tokenizer: std.zig.Tokenizer = .init(a.source());
2500 for (token_tags, token_starts) |tag, start| {
2501 const tok = tokenizer.next();
2502 try std.testing.expectEqual(tok.tag, tag);
2503 try std.testing.expectEqual(tok.loc.start, start);
2504 if (tag == .invalid) return error.InvalidToken;
2505 }
2506 }
2507
2508 var fba_buf: [1 << 18]u8 = undefined;
2509 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
2510 const ast = std.zig.Ast.parseTokens(fba.allocator(), a.source(), a.tokens(), .zig) catch
2511 return error.SkipZigTest;
2512
2513 errdefer a.logBadSource(ast);
2514 try std.testing.expectEqual(0, ast.errors.len);
2515}
2516
2517fn logBadSource(a: *AstSmith, ast: ?std.zig.Ast) void {
2518 var buf: [256]u8 = undefined;
2519 const ls = std.debug.lockStderr(&buf);
2520 defer std.debug.unlockStderr();
2521 a.logBadSourceInner(ls.terminal(), ast) catch {};
2522}
2523
2524fn logBadSourceInner(a: *AstSmith, t: std.Io.Terminal, ast: ?std.zig.Ast) std.Io.Writer.Error!void {
2525 try a.logSourceInner(t);
2526 const w = t.writer;
2527
2528 if (ast) |bad_ast| {
2529 try w.writeAll("=== Parse Errors ===\n");
2530 for (bad_ast.errors) |err| {
2531 const loc = bad_ast.tokenLocation(0, err.token);
2532 try w.print("{}:{}: ", .{ loc.line + 1, loc.column + 1 });
2533 try bad_ast.renderError(err, w);
2534 try w.writeByte('\n');
2535 }
2536 } else {
2537 t.setColor(.dim) catch {};
2538 try w.writeAll("=== Tokens ===\n");
2539 t.setColor(.reset) catch {};
2540 for (
2541 0..,
2542 a.token_tag_buf[0..a.tokens_len],
2543 a.token_start_buf[0..a.tokens_len],
2544 ) |i, tag, start| {
2545 try w.print("#{} @{}: {t}\n", .{ i, start, tag });
2546 }
2547
2548 t.setColor(.dim) catch {};
2549 try w.writeAll("\n=== Expected Tokens ===\n");
2550 t.setColor(.reset) catch {};
2551
2552 var tokenizer: std.zig.Tokenizer = .init(a.source());
2553 var i: usize = 0;
2554 while (true) {
2555 const tok = tokenizer.next();
2556 try w.print("#{} @{}-{}: {t}\n", .{ i, tok.loc.start, tok.loc.end, tok.tag });
2557 i += 1;
2558 if (tok.tag == .invalid or tok.tag == .eof) break;
2559 }
2560 }
2561}
2562
2563pub fn logSource(a: *AstSmith) void {
2564 var buf: [256]u8 = undefined;
2565 const ls = std.debug.lockStderr(&buf);
2566 defer std.debug.unlockStderr();
2567 a.logSourceInner(ls.terminal()) catch {};
2568}
2569
2570fn logSourceInner(a: *AstSmith, t: std.Io.Terminal) std.Io.Writer.Error!void {
2571 const w = t.writer;
2572
2573 t.setColor(.dim) catch {};
2574 try w.writeAll("=== Source ===\n");
2575 t.setColor(.reset) catch {};
2576
2577 var line: usize = 1;
2578 try w.print("{: >5} ", .{line});
2579 for (a.source()) |c| switch (c) {
2580 ' '...0x7e => try w.writeByte(c),
2581 '\n' => {
2582 line += 1;
2583 try w.print("\n{: >5} ", .{line});
2584 },
2585 '\r' => {
2586 t.setColor(.cyan) catch {};
2587 try w.writeAll("\\r");
2588 t.setColor(.reset) catch {};
2589 },
2590 '\t' => {
2591 t.setColor(.cyan) catch {};
2592 try w.writeAll("\\t");
2593 t.setColor(.reset) catch {};
2594 },
2595 else => {
2596 t.setColor(.cyan) catch {};
2597 try w.print("\\x{x:0>2}", .{c});
2598 t.setColor(.reset) catch {};
2599 },
2600 };
2601 try w.writeByte('\n');
2602}