authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:41:05+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:41:05+02:00
log3e0f55fcc7f90feb441b62cd8e84735646e50176
tree36e3a3ae87c954c83ee8bc485ff436a6bc8b63b3
parent022c76fc899d738e090d8a50e5591a1ec24ab779
parent0b52cac43465bdd920a506e43b716a8754bb3426

Merge pull request 'add an ast smith' (#31635) from gooncreeper/zig:ast-smith into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31635 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

12 files changed, 3243 insertions(+), 710 deletions(-)

doc/langref.html.in+74-55
......@@ -7944,58 +7944,60 @@ TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
79447944ComptimeDecl <- KEYWORD_comptime Block
79457945
79467946Decl
7947 <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
7947 <- (KEYWORD_export / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
7948 / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON
79487949 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl
79497950
7950FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
7951FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix
79517952
79527953VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
79537954
79547955GlobalVarDecl <- VarDeclProto (EQUAL Expr)? SEMICOLON
79557956
7956ContainerField <- doc_comment? KEYWORD_comptime? !KEYWORD_fn (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)?
7957ContainerField <- doc_comment? (KEYWORD_comptime / !KEYWORD_comptime) !KEYWORD_fn (IDENTIFIER COLON / !(IDENTIFIER COLON))? TypeExpr ByteAlign? (EQUAL Expr)?
79577958
79587959# *** Block Level ***
7959Statement
7960 <- KEYWORD_comptime ComptimeStatement
7961 / KEYWORD_nosuspend BlockExprStatement
7962 / KEYWORD_suspend BlockExprStatement
7960BlockStatement
7961 <- Statement
79637962 / KEYWORD_defer BlockExprStatement
79647963 / KEYWORD_errdefer Payload? BlockExprStatement
7965 / IfStatement
7966 / LabeledStatement
7967 / VarDeclExprStatement
7964 / !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement
79687965
7969ComptimeStatement
7970 <- BlockExpr
7971 / VarDeclExprStatement
7966Statement
7967 <- ExprStatement
7968 / KEYWORD_suspend BlockExprStatement
7969 / !ExprStatement (KEYWORD_comptime !BlockExpr)? AssignExpr SEMICOLON
7970
7971ExprStatement
7972 <- IfStatement
7973 / LabeledStatement
7974 / KEYWORD_nosuspend BlockExprStatement
7975 / KEYWORD_comptime BlockExpr
79727976
79737977IfStatement
79747978 <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
7975 / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
7979 / IfPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
79767980
79777981LabeledStatement <- BlockLabel? (Block / LoopStatement / SwitchExpr)
79787982
79797983LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
79807984
79817985ForStatement
7982 <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
7983 / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
7986 <- ForPrefix BlockExpr ( KEYWORD_else Statement / !KEYWORD_else )
7987 / ForPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Statement )
79847988
79857989WhileStatement
79867990 <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
7987 / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
7991 / WhilePrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
79887992
79897993BlockExprStatement
79907994 <- BlockExpr
7991 / AssignExpr SEMICOLON
7995 / !BlockExpr AssignExpr SEMICOLON
79927996
79937997BlockExpr <- BlockLabel? Block
79947998
7995# An expression, assignment, or any destructure, as a statement.
7996VarDeclExprStatement
7997 <- VarDeclProto (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON
7998 / Expr (AssignOp Expr / (COMMA (VarDeclProto / Expr))+ EQUAL Expr)? SEMICOLON
7999# An assignment or a destructure whose LHS are all lvalue expressions or variable declarations.
8000VarAssignStatement <- (VarDeclProto / Expr) (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON
79998001
80008002# *** Expression Level ***
80018003
......@@ -8025,25 +8027,25 @@ PrefixExpr <- PrefixOp* PrimaryExpr
80258027PrimaryExpr
80268028 <- AsmExpr
80278029 / IfExpr
8028 / KEYWORD_break BreakLabel? Expr?
8029 / KEYWORD_comptime Expr
8030 / KEYWORD_nosuspend Expr
8031 / KEYWORD_continue BreakLabel? Expr?
8032 / KEYWORD_resume Expr
8033 / KEYWORD_return Expr?
8030 / KEYWORD_break (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart)
8031 / KEYWORD_comptime Expr !ExprSuffix
8032 / KEYWORD_nosuspend Expr !ExprSuffix
8033 / KEYWORD_continue (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart)
8034 / KEYWORD_resume Expr !ExprSuffix
8035 / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart)
80348036 / BlockLabel? LoopExpr
80358037 / Block
80368038 / CurlySuffixExpr
80378039
8038IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
8040IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
80398041
8040Block <- LBRACE Statement* RBRACE
8042Block <- LBRACE BlockStatement* RBRACE
80418043
80428044LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
80438045
8044ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
8046ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix
80458047
8046WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
8048WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
80478049
80488050CurlySuffixExpr <- TypeExpr InitList?
80498051
......@@ -8070,10 +8072,10 @@ PrimaryTypeExpr
80708072 / FnProto
80718073 / GroupedExpr
80728074 / LabeledTypeExpr
8073 / IDENTIFIER
8075 / IDENTIFIER !(COLON LabelableExpr)
80748076 / IfTypeExpr
80758077 / INTEGER
8076 / KEYWORD_comptime TypeExpr
8078 / KEYWORD_comptime TypeExpr !ExprSuffix
80778079 / KEYWORD_error DOT IDENTIFIER
80788080 / KEYWORD_anyframe
80798081 / KEYWORD_unreachable
......@@ -8085,7 +8087,7 @@ ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
80858087
80868088GroupedExpr <- LPAREN Expr RPAREN
80878089
8088IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
8090IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
80898091
80908092LabeledTypeExpr
80918093 <- BlockLabel Block
......@@ -8094,9 +8096,9 @@ LabeledTypeExpr
80948096
80958097LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
80968098
8097ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
8099ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix
80988100
8099WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
8101WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
81008102
81018103SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
81028104
......@@ -8105,11 +8107,11 @@ AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
81058107
81068108AsmOutput <- COLON AsmOutputList AsmInput?
81078109
8108AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
8110AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
81098111
81108112AsmInput <- COLON AsmInputList AsmClobbers?
81118113
8112AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
8114AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN Expr RPAREN
81138115
81148116AsmClobbers <- COLON Expr
81158117
......@@ -8129,9 +8131,7 @@ AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN
81298131# Fn specific
81308132CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
81318133
8132ParamDecl
8133 <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
8134 / DOT3
8134ParamDecl <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime / !KEYWORD_comptime) (IDENTIFIER COLON / !(IDENTIFIER_COLON)) ParamType
81358135
81368136ParamType
81378137 <- KEYWORD_anytype
......@@ -8237,8 +8237,8 @@ PrefixOp
82378237PrefixTypeOp
82388238 <- QUESTIONMARK
82398239 / KEYWORD_anyframe MINUSRARROW
8240 / SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
8241 / PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
8240 / (ManyPtrTypeStart / SliceTypeStart) KEYWORD_allowzero? ByteAlign? AddrSpace? KEYWORD_const? KEYWORD_volatile?
8241 / SinglePtrTypeStart KEYWORD_allowzero? BitAlign? AddrSpace? KEYWORD_const? KEYWORD_volatile?
82428242 / ArrayTypeStart
82438243
82448244SuffixOp
......@@ -8249,15 +8249,31 @@ SuffixOp
82498249
82508250FnCallArguments <- LPAREN ExprList RPAREN
82518251
8252ExprSuffix
8253 <- KEYWORD_or
8254 / KEYWORD_and
8255 / CompareOp
8256 / BitwiseOp
8257 / BitShiftOp
8258 / AdditionOp
8259 / MultiplyOp
8260 / EXCLAMATIONMARK
8261 / SuffixOp
8262 / FnCallArguments
8263
8264LabelableExpr
8265 <- Block
8266 / SwitchExpr
8267 / LoopExpr
8268
82528269# Ptr specific
82538270SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
82548271
8255PtrTypeStart
8256 <- ASTERISK
8257 / ASTERISK2
8258 / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
8272SinglePtrTypeStart <- ASTERISK / ASTERISK2
8273
8274ManyPtrTypeStart <- LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
82598275
8260ArrayTypeStart <- LBRACKET Expr (COLON Expr)? RBRACKET
8276ArrayTypeStart <- LBRACKET Expr !(ASTERISK / ASTERISK2) (COLON Expr)? RBRACKET
82618277
82628278# ContainerDecl specific
82638279ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
......@@ -8266,11 +8282,13 @@ ContainerDeclType
82668282 <- KEYWORD_struct (LPAREN Expr RPAREN)?
82678283 / KEYWORD_opaque
82688284 / KEYWORD_enum (LPAREN Expr RPAREN)?
8269 / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
8285 / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / !KEYWORD_enum Expr) RPAREN)?
82708286
82718287# Alignment
82728288ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
82738289
8290BitAlign <- KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN
8291
82748292# Lists
82758293IdentifierList <- (doc_comment? IDENTIFIER COMMA)* (doc_comment? IDENTIFIER)?
82768294
......@@ -8280,7 +8298,7 @@ AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
82808298
82818299AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
82828300
8283ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
8301ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
82848302
82858303ExprList <- (Expr COMMA)* Expr?
82868304
......@@ -8337,6 +8355,7 @@ multibyte_utf8 <-
83378355 / oxC2_oxDF ox80_oxBF
83388356
83398357non_control_ascii <- [\040-\176]
8358non_control_utf8 <- [\040-\377]
83408359
83418360char_escape
83428361 <- "\\x" hex hex
......@@ -8352,10 +8371,10 @@ string_char
83528371 / char_escape
83538372 / ![\\"\n] non_control_ascii
83548373
8355container_doc_comment <- ('//!' [^\n]* [ \n]* skip)+
8356doc_comment <- ('///' [^\n]* [ \n]* skip)+
8357line_comment <- '//' ![!/][^\n]* / '////' [^\n]*
8358line_string <- ('\\\\' [^\n]* [ \n]*)+
8374container_doc_comment <- ('//!' non_control_utf8* [ \n]* skip)+
8375doc_comment <- ('///' non_control_utf8* [ \n]* skip)+
8376line_comment <- '//' ![!/] non_control_utf8* / '////' non_control_utf8*
8377line_string <- '\\\\' non_control_utf8* [ \n]*
83598378skip <- ([ \n] / line_comment)*
83608379
83618380CHAR_LITERAL <- ['] char_char ['] skip
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");
......@@ -1166,6 +1167,7 @@ pub const ClangCliParam = struct {
11661167test {
11671168 _ = Ast;
11681169 _ = AstRlAnnotate;
1170 _ = AstSmith;
11691171 _ = BuiltinFn;
11701172 _ = Client;
11711173 _ = ErrorBundle;
lib/std/zig/Ast.zig-4
......@@ -504,9 +504,6 @@ pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void
504504 .varargs_nonfinal => {
505505 return w.writeAll("function prototype has parameter after varargs");
506506 },
507 .expected_continue_expr => {
508 return w.writeAll("expected ':' before while continue expression");
509 },
510507
511508 .expected_semi_after_decl => {
512509 return w.writeAll("expected ';' after declaration");
......@@ -2888,7 +2885,6 @@ pub const Error = struct {
28882885 test_doc_comment,
28892886 comptime_doc_comment,
28902887 varargs_nonfinal,
2891 expected_continue_expr,
28922888 expected_semi_after_decl,
28932889 expected_semi_after_stmt,
28942890 expected_comma_after_field,
lib/std/zig/Ast/Render.zig+186-458
......@@ -338,9 +338,16 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
338338 try ais.maybeInsertNewline();
339339
340340 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
341 for (first_tok..last_tok + 1) |i| {
341 for (first_tok..last_tok) |i| {
342342 try renderToken(r, @intCast(i), .newline);
343343 }
344 if (space != .skip) {
345 try renderToken(r, last_tok, .newline);
346 } else {
347 try renderToken(r, last_tok, .skip);
348 try ais.insertNewline(); // A newline is part of the token, so it still needs
349 // rendered here.
350 }
344351
345352 const next_token = last_tok + 1;
346353 const next_token_tag = tree.tokenTag(next_token);
......@@ -645,10 +652,12 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
645652 const lhs, const rhs = tree.nodeData(node).node_and_node;
646653 const lbracket = tree.firstToken(rhs) - 1;
647654 const rbracket = tree.lastToken(rhs) + 1;
655 try renderExpression(r, lhs, .none);
656 // One lien check must come after rendering lhs since it can influence
657 // isLineOverIndented
648658 const one_line = tree.tokensOnSameLine(lbracket, rbracket) and
649 !becomesMultilineExpr(tree, rhs);
659 !try rendersMultiline(r, rhs);
650660 const inner_space = if (one_line) Space.none else Space.newline;
651 try renderExpression(r, lhs, .none);
652661 try ais.pushIndent(.normal);
653662 try renderToken(r, lbracket, inner_space); // [
654663 try renderExpression(r, rhs, inner_space);
......@@ -793,34 +802,47 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
793802 canonicalize: {
794803 if (params.len != 1) break :canonicalize;
795804
796 const CastKind = enum {
805 const CastKind = enum(u8) {
797806 ptrCast,
798807 alignCast,
799808 addrSpaceCast,
800809 constCast,
801810 volatileCast,
802811 };
803 const kind = meta.stringToEnum(CastKind, tree.tokenSlice(builtin_token)[1..]) orelse break :canonicalize;
812 const kind = meta.stringToEnum(
813 CastKind,
814 tree.tokenSlice(builtin_token)[1..],
815 ) orelse break :canonicalize;
804816
805817 var cast_map = std.EnumMap(CastKind, Ast.TokenIndex).init(.{});
806818 cast_map.put(kind, builtin_token);
807819
808820 var casts_before: usize = 0;
809 if (builtin_token >= 2) {
810 var prev_builtin_token = builtin_token - 2;
811 while (tree.tokenTag(prev_builtin_token) == .builtin) : (prev_builtin_token -= 2) {
812 const prev_kind = meta.stringToEnum(CastKind, tree.tokenSlice(prev_builtin_token)[1..]) orelse break;
813 if (cast_map.contains(prev_kind)) break :canonicalize;
814 cast_map.put(prev_kind, prev_builtin_token);
815 casts_before += 1;
816 }
821 var prev_builtin_token = builtin_token;
822 while (prev_builtin_token >= 2) {
823 prev_builtin_token -= 2;
824 if (tree.tokenTag(prev_builtin_token) != .builtin) break;
825 const builtin_name = tree.tokenSlice(prev_builtin_token)[1..];
826 const prev_kind = meta.stringToEnum(CastKind, builtin_name) orelse break;
827 if (cast_map.contains(prev_kind)) break :canonicalize;
828 // This must be checked after so that cast builtins as arguments to other
829 // builtins containing comments are reordered.
830 if (hasComment(tree, prev_builtin_token, prev_builtin_token + 2))
831 break :canonicalize;
832 cast_map.put(prev_kind, prev_builtin_token);
833 casts_before += 1;
817834 }
818835
819836 var next_builtin_token = builtin_token + 2;
820 while (tree.tokenTag(next_builtin_token) == .builtin) : (next_builtin_token += 2) {
821 const next_kind = meta.stringToEnum(CastKind, tree.tokenSlice(next_builtin_token)[1..]) orelse break;
837 while (true) {
838 if (hasComment(tree, next_builtin_token - 2, next_builtin_token))
839 break :canonicalize;
840 if (tree.tokenTag(next_builtin_token) != .builtin) break;
841 const builtin_name = tree.tokenSlice(next_builtin_token)[1..];
842 const next_kind = meta.stringToEnum(CastKind, builtin_name) orelse break;
822843 if (cast_map.contains(next_kind)) break :canonicalize;
823844 cast_map.put(next_kind, next_builtin_token);
845 next_builtin_token += 2;
824846 }
825847
826848 var it = cast_map.iterator();
......@@ -931,380 +953,61 @@ fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!v
931953 }
932954}
933955
934/// Same as becomesMultilineExpr, but returns false when `node == .none`
935fn optBecomesMultilineExpr(tree: Ast, node: Ast.Node.OptionalIndex) bool {
936 return if (node.unwrap()) |payload| becomesMultilineExpr(tree, payload) else false;
937}
956fn drainNoNewline(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
957 if (std.mem.indexOfScalar(u8, w.buffered(), '\n') != null) {
958 return error.WriteFailed;
959 }
938960
939/// May return false if `node` is already multiline
940fn becomesMultilineExpr(tree: Ast, node: Ast.Node.Index) bool {
941 // Conditions related to comments, doc comments, and multiline string literals are ignored
942 // since they always go to the end of the line, which already make them a multi-line
943 // expression (since they contain a newline).
944 switch (tree.nodeTag(node)) {
945 .identifier,
946 .number_literal,
947 .char_literal,
948 .unreachable_literal,
949 .anyframe_literal,
950 .string_literal,
951 .multiline_string_literal,
952 .error_value,
953 .enum_literal,
954 => return false,
955 .container_decl_trailing,
956 .container_decl_arg_trailing,
957 .container_decl_two_trailing,
958 .tagged_union_trailing,
959 .tagged_union_enum_tag_trailing,
960 .tagged_union_two_trailing,
961 .switch_comma,
962 .builtin_call_two_comma,
963 .builtin_call_comma,
964 .call_one_comma,
965 .call_comma,
966 .struct_init_one_comma,
967 .struct_init_dot_two_comma,
968 .struct_init_dot_comma,
969 .struct_init_comma,
970 .array_init_one_comma,
971 .array_init_dot_two_comma,
972 .array_init_dot_comma,
973 .array_init_comma,
974 // The following always have a non-zero amount of members
975 // which is also the condition for them to be multi-line.
976 .block,
977 .block_semicolon,
978 => return true,
979 .block_two,
980 .block_two_semicolon,
981 => return tree.nodeData(node).opt_node_and_opt_node[0] != .none,
982 .container_decl,
983 .container_decl_arg,
984 .container_decl_two,
985 .tagged_union,
986 .tagged_union_enum_tag,
987 .tagged_union_two,
988 => {
989 var buf: [2]Ast.Node.Index = undefined;
990 const full = tree.fullContainerDecl(&buf, node).?;
991 if (full.ast.arg.unwrap()) |arg| {
992 if (becomesMultilineExpr(tree, arg))
993 return true;
994 }
995 // This does the same checks as `isOneLineContainerDecl`, however it avoids unnecessary
996 // checks related to comments and multiline strings, which would mean the container is
997 // already multiple lines.
998 for (full.ast.members) |member| {
999 if (tree.fullContainerField(member)) |field_full| {
1000 for ([_]Ast.Node.OptionalIndex{
1001 field_full.ast.type_expr,
1002 field_full.ast.align_expr,
1003 field_full.ast.value_expr,
1004 }) |opt_expr| {
1005 if (opt_expr.unwrap()) |expr| {
1006 if (becomesMultilineExpr(tree, expr))
1007 return true;
1008 }
1009 }
1010 } else return true;
1011 }
1012 return false;
1013 },
1014 .error_set_decl => {
1015 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
1016 return !isOneLineErrorSetDecl(tree, lbrace, rbrace);
1017 },
1018 .@"switch" => {
1019 const op, const extra_index = tree.nodeData(node).node_and_extra;
1020 const case_range = tree.extraData(extra_index, Ast.Node.SubRange);
1021 return @intFromEnum(case_range.end) - @intFromEnum(case_range.start) != 0 or
1022 becomesMultilineExpr(tree, op);
1023 },
1024 .for_simple, .@"for" => {
1025 const full = tree.fullFor(node).?;
1026 if (becomesMultilineExpr(tree, full.ast.then_expr) or
1027 optBecomesMultilineExpr(tree, full.ast.else_expr))
1028 return true;
1029
1030 for (full.ast.inputs) |expr| {
1031 if (if (tree.nodeTag(expr) == .for_range) blk: {
1032 const lhs, const rhs = tree.nodeData(expr).node_and_opt_node;
1033 break :blk becomesMultilineExpr(tree, lhs) or optBecomesMultilineExpr(tree, rhs);
1034 } else becomesMultilineExpr(tree, expr))
1035 return true;
1036 }
1037 const final_input_expr = full.ast.inputs[full.ast.inputs.len - 1];
1038 if (tree.tokenTag(tree.lastToken(final_input_expr) + 1) == .comma)
1039 return true;
1040
1041 const token_tags = tree.tokens.items(.tag);
1042 const payload = full.payload_token;
1043 const pipe = std.mem.indexOfScalarPos(Token.Tag, token_tags, payload, .pipe).?;
1044 return token_tags[@intCast(pipe - 1)] == .comma;
1045 },
1046 .while_simple,
1047 .while_cont,
1048 .@"while",
1049 => {
1050 const full = tree.fullWhile(node).?;
1051 return becomesMultilineExpr(tree, full.ast.cond_expr) or
1052 becomesMultilineExpr(tree, full.ast.then_expr) or
1053 optBecomesMultilineExpr(tree, full.ast.cont_expr) or
1054 optBecomesMultilineExpr(tree, full.ast.else_expr);
1055 },
1056 .if_simple,
1057 .@"if",
1058 => {
1059 const full = tree.fullIf(node).?;
1060 return becomesMultilineExpr(tree, full.ast.cond_expr) or
1061 becomesMultilineExpr(tree, full.ast.then_expr) or
1062 optBecomesMultilineExpr(tree, full.ast.else_expr);
1063 },
1064 .fn_proto_simple,
1065 .fn_proto_multi,
1066 .fn_proto_one,
1067 .fn_proto,
1068 => {
1069 var buf: [1]Ast.Node.Index = undefined;
1070 const fn_proto = tree.fullFnProto(&buf, node).?;
1071
1072 for ([_]Ast.Node.OptionalIndex{
1073 fn_proto.ast.return_type,
1074 fn_proto.ast.align_expr,
1075 fn_proto.ast.addrspace_expr,
1076 fn_proto.ast.section_expr,
1077 fn_proto.ast.callconv_expr,
1078 }) |opt_expr| {
1079 if (opt_expr.unwrap()) |expr| {
1080 if (becomesMultilineExpr(tree, expr))
1081 return true;
1082 }
1083 }
1084 for (fn_proto.ast.params) |expr| {
1085 if (becomesMultilineExpr(tree, expr))
1086 return true;
1087 }
961 var n: usize = 0;
962 for (data[0 .. data.len - 1]) |v| {
963 if (std.mem.indexOfScalar(u8, v, '\n') != null) {
964 return error.WriteFailed;
965 }
966 n += v.len;
967 }
1088968
1089 const lparen = fn_proto.ast.fn_token + 1;
1090 const return_type = fn_proto.ast.return_type.unwrap().?;
1091 const maybe_bang = tree.firstToken(return_type) - 1;
1092 const rparen = fnProtoRparen(tree, fn_proto, maybe_bang);
1093 return !isOneLineFnProto(tree, fn_proto, lparen, rparen);
1094 },
1095 .asm_simple,
1096 => {
1097 const lhs = tree.nodeData(node).node_and_token[0];
1098 return becomesMultilineExpr(tree, lhs);
1099 },
1100 .@"asm",
1101 => {
1102 const lhs, const extra_index = tree.nodeData(node).node_and_extra;
1103 const asm_extra = tree.extraData(extra_index, Ast.Node.Asm);
1104 return @intFromEnum(asm_extra.items_end) - @intFromEnum(asm_extra.items_start) != 0 or
1105 becomesMultilineExpr(tree, lhs) or optBecomesMultilineExpr(tree, asm_extra.clobbers);
1106 },
1107 .array_type, .array_type_sentinel => {
1108 const array_type = tree.fullArrayType(node).?;
1109 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
1110 return !isOneLineArrayType(tree, array_type, rbracket) or
1111 becomesMultilineExpr(tree, array_type.ast.elem_type);
1112 },
1113 .array_access => {
1114 const lhs, const rhs = tree.nodeData(node).node_and_node;
1115 const lbracket = tree.firstToken(rhs) - 1;
1116 const rbracket = tree.lastToken(rhs) + 1;
1117 return !tree.tokensOnSameLine(lbracket, rbracket) or
1118 becomesMultilineExpr(tree, lhs) or
1119 becomesMultilineExpr(tree, rhs);
1120 },
1121 .call_one,
1122 .call,
1123 .builtin_call_two,
1124 .builtin_call,
1125 .array_init_one,
1126 .array_init_dot_two,
1127 .array_init_dot,
1128 .array_init,
1129 .struct_init_one,
1130 .struct_init_dot_two,
1131 .struct_init_dot,
1132 .struct_init,
1133 => |tag| {
1134 var buf: [2]Ast.Node.Index = undefined;
1135 const opt_lhs: Ast.Node.OptionalIndex, const items = switch (tag) {
1136 .call_one, .call => blk: {
1137 const full = tree.fullCall(buf[0..1], node).?;
1138 break :blk .{ full.ast.fn_expr.toOptional(), full.ast.params };
1139 },
1140 .builtin_call_two, .builtin_call => .{ .none, tree.builtinCallParams(&buf, node).? },
1141 .array_init_one,
1142 .array_init_dot_two,
1143 .array_init_dot,
1144 .array_init,
1145 => blk: {
1146 const full = tree.fullArrayInit(&buf, node).?;
1147 break :blk .{ full.ast.type_expr, full.ast.elements };
1148 },
1149 .struct_init_one,
1150 .struct_init_dot_two,
1151 .struct_init_dot,
1152 .struct_init,
1153 => blk: {
1154 const full = tree.fullStructInit(&buf, node).?;
1155 break :blk .{ full.ast.type_expr, full.ast.fields };
1156 },
1157 else => unreachable,
1158 };
1159 if (opt_lhs.unwrap()) |lhs| {
1160 if (becomesMultilineExpr(tree, lhs))
1161 return true;
1162 }
1163 for (items) |expr| {
1164 if (becomesMultilineExpr(tree, expr))
1165 return true;
1166 }
1167 return false;
1168 },
1169 .assign_destructure => {
1170 const full = tree.assignDestructure(node);
1171 for (full.ast.variables) |expr| {
1172 if (becomesMultilineExpr(tree, expr))
1173 return true;
1174 }
1175 return becomesMultilineExpr(tree, full.ast.value_expr);
1176 },
1177 .ptr_type_aligned,
1178 .ptr_type_sentinel,
1179 .ptr_type,
1180 .ptr_type_bit_range,
1181 => {
1182 const full = tree.fullPtrType(node).?;
1183 return becomesMultilineExpr(tree, full.ast.child_type) or
1184 optBecomesMultilineExpr(tree, full.ast.sentinel) or
1185 optBecomesMultilineExpr(tree, full.ast.align_node) or
1186 optBecomesMultilineExpr(tree, full.ast.addrspace_node) or
1187 optBecomesMultilineExpr(tree, full.ast.bit_range_start) or
1188 optBecomesMultilineExpr(tree, full.ast.bit_range_end);
1189 },
1190 .slice_open,
1191 .slice,
1192 .slice_sentinel,
1193 => {
1194 const full = tree.fullSlice(node).?;
1195 return becomesMultilineExpr(tree, full.ast.sliced) or
1196 becomesMultilineExpr(tree, full.ast.start) or
1197 optBecomesMultilineExpr(tree, full.ast.end) or
1198 optBecomesMultilineExpr(tree, full.ast.sentinel);
1199 },
1200 .@"comptime",
1201 .@"nosuspend",
1202 .@"suspend",
1203 .@"resume",
1204 .bit_not,
1205 .bool_not,
1206 .negation,
1207 .negation_wrap,
1208 .optional_type,
1209 .address_of,
1210 .deref,
1211 .@"try",
1212 => return becomesMultilineExpr(tree, tree.nodeData(node).node),
1213 .@"return" => return optBecomesMultilineExpr(tree, tree.nodeData(node).opt_node),
1214 .field_access,
1215 .unwrap_optional,
1216 .grouped_expression,
1217 => return becomesMultilineExpr(tree, tree.nodeData(node).node_and_token[0]),
1218 .add,
1219 .add_wrap,
1220 .add_sat,
1221 .array_cat,
1222 .array_mult,
1223 .bang_equal,
1224 .bit_and,
1225 .bit_or,
1226 .shl,
1227 .shl_sat,
1228 .shr,
1229 .bit_xor,
1230 .bool_and,
1231 .bool_or,
1232 .div,
1233 .equal_equal,
1234 .greater_or_equal,
1235 .greater_than,
1236 .less_or_equal,
1237 .less_than,
1238 .merge_error_sets,
1239 .mod,
1240 .mul,
1241 .mul_wrap,
1242 .mul_sat,
1243 .sub,
1244 .sub_wrap,
1245 .sub_sat,
1246 .@"orelse",
1247 .@"catch",
1248 .error_union,
1249 .assign,
1250 .assign_bit_and,
1251 .assign_bit_or,
1252 .assign_shl,
1253 .assign_shl_sat,
1254 .assign_shr,
1255 .assign_bit_xor,
1256 .assign_div,
1257 .assign_sub,
1258 .assign_sub_wrap,
1259 .assign_sub_sat,
1260 .assign_mod,
1261 .assign_add,
1262 .assign_add_wrap,
1263 .assign_add_sat,
1264 .assign_mul,
1265 .assign_mul_wrap,
1266 .assign_mul_sat,
1267 => {
1268 const lhs, const rhs = tree.nodeData(node).node_and_node;
1269 return becomesMultilineExpr(tree, lhs) or becomesMultilineExpr(tree, rhs);
1270 },
1271 .@"break", .@"continue" => {
1272 const opt_expr = tree.nodeData(node).opt_token_and_opt_node[1];
1273 return optBecomesMultilineExpr(tree, opt_expr);
1274 },
1275 .anyframe_type => return becomesMultilineExpr(tree, tree.nodeData(node).token_and_node[1]),
1276 .@"errdefer",
1277 .@"defer",
1278 .for_range,
1279 .switch_range,
1280 .switch_case_one,
1281 .switch_case_inline_one,
1282 .switch_case,
1283 .switch_case_inline,
1284 .asm_output,
1285 .asm_input,
1286 .fn_decl,
1287 .container_field,
1288 .container_field_init,
1289 .container_field_align,
1290 .root,
1291 .global_var_decl,
1292 .local_var_decl,
1293 .simple_var_decl,
1294 .aligned_var_decl,
1295 .test_decl,
1296 => unreachable,
969 const pattern = data[data.len - 1];
970 if (splat != 0 and std.mem.indexOfScalar(u8, pattern, '\n') != null) {
971 return error.WriteFailed;
1297972 }
973 n += pattern.len * splat;
974
975 w.end = 0;
976 return n;
1298977}
1299978
1300fn isOneLineArrayType(
1301 tree: Ast,
1302 array_type: Ast.full.ArrayType,
1303 rbracket: Ast.TokenIndex,
1304) bool {
1305 return tree.tokensOnSameLine(array_type.ast.lbracket, rbracket) and
1306 !becomesMultilineExpr(tree, array_type.ast.elem_count) and
1307 !optBecomesMultilineExpr(tree, array_type.ast.sentinel);
979fn rendersMultiline(r: *const Render, node: Ast.Node.Index) error{OutOfMemory}!bool {
980 var no_nl_buf: [64]u8 = undefined;
981 var no_nl_w: Writer = .{
982 .vtable = &.{ .drain = drainNoNewline },
983 .buffer = &no_nl_buf,
984 };
985
986 if (r.ais.disabled_offset != null) return true;
987 var sub_ais: AutoIndentingStream = .init(r.gpa, &no_nl_w, r.ais.indent_delta);
988 defer sub_ais.deinit();
989 // The following are needed to make sure isLineOverIndented is correct
990 sub_ais.indent_count = r.ais.indent_count;
991 sub_ais.applied_indent = r.ais.applied_indent;
992 sub_ais.current_line_empty = r.ais.current_line_empty;
993
994 var sub_r: Render = .{
995 .gpa = r.gpa,
996 .ais = &sub_ais,
997 .tree = r.tree,
998 .fixups = r.fixups,
999 };
1000
1001 renderExpression(&sub_r, node, .none) catch |e| return switch (e) {
1002 error.OutOfMemory => return error.OutOfMemory,
1003 error.WriteFailed => return true,
1004 };
1005 if (sub_ais.disabled_offset != null) return true;
1006 if (std.mem.indexOfScalar(u8, no_nl_w.buffered(), '\n') != null) {
1007 return true;
1008 }
1009
1010 return false;
13081011}
13091012
13101013fn renderArrayType(
......@@ -1315,7 +1018,9 @@ fn renderArrayType(
13151018 const tree = r.tree;
13161019 const ais = r.ais;
13171020 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
1318 const one_line = isOneLineArrayType(tree, array_type, rbracket);
1021 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket) and
1022 !try rendersMultiline(r, array_type.ast.elem_count) and
1023 (if (array_type.ast.sentinel.unwrap()) |s| !try rendersMultiline(r, s) else true);
13191024 const inner_space = if (one_line) Space.none else Space.newline;
13201025 try ais.pushIndent(.normal);
13211026 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
......@@ -2094,7 +1799,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi
20941799 break;
20951800 },
20961801 .keyword_noalias, .keyword_comptime => {
2097 try renderToken(r, last_param_token, .space);
1802 try renderToken(r, last_param_token, .maybe_space);
20981803 last_param_token += 1;
20991804 },
21001805 .identifier => {},
......@@ -2145,7 +1850,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi
21451850 break;
21461851 },
21471852 .keyword_noalias, .keyword_comptime => {
2148 try renderToken(r, last_param_token, .space);
1853 try renderToken(r, last_param_token, .maybe_space);
21491854 last_param_token += 1;
21501855 },
21511856 .identifier => {},
......@@ -2163,7 +1868,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi
21631868 {
21641869 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
21651870 last_param_token += 1;
2166 try renderToken(r, last_param_token, .space); // :
1871 try renderToken(r, last_param_token, .maybe_space); // :
21671872 last_param_token += 1;
21681873 }
21691874 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
......@@ -2504,6 +2209,10 @@ fn renderArrayInit(
25042209 try renderSpace(&sub_r, after_expr, tokenSliceForRender(tree, after_expr).len, .none);
25052210
25062211 buf.clearRetainingCapacity();
2212 // The following are needed to make sure isLineOverIndented is not influenced by
2213 // the previous element.
2214 sub_ais.indent_count = 0;
2215 sub_ais.applied_indent = 0;
25072216 }
25082217 }
25092218
......@@ -2749,8 +2458,47 @@ fn renderAsm(
27492458 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
27502459 }
27512460
2461 const render_colons: [3]?Ast.TokenIndex = colons: {
2462 var colons: [3]Ast.TokenIndex = undefined;
2463 var render: u2 = 0;
2464
2465 const rparen = asm_node.ast.rparen;
2466 filled: {
2467 colons[0] = tree.lastToken(asm_node.ast.template) + 1;
2468 if (colons[0] == rparen) break :filled;
2469
2470 if (asm_node.outputs.len != 0) {
2471 colons[1] = tree.lastToken(asm_node.outputs[asm_node.outputs.len - 1]) + 1;
2472 colons[1] += @intFromBool(tree.tokenTag(colons[1]) == .comma);
2473 render = 1;
2474 } else {
2475 colons[1] = colons[0] + 1;
2476 if (hasComment(tree, colons[0], colons[1])) render = 1;
2477 }
2478 if (colons[1] == rparen) break :filled;
2479
2480 // Next colon is not checked for here since it cannot present without clobbers
2481 if (asm_node.inputs.len != 0) {
2482 render = 2;
2483 } else {
2484 const colon_or_rparen = colons[1] + 1;
2485 if (hasComment(tree, colons[1], colon_or_rparen)) render = 2;
2486 }
2487
2488 if (asm_node.ast.clobbers.unwrap()) |clobbers| {
2489 colons[2] = tree.firstToken(clobbers) - 1;
2490 render = 3;
2491 }
2492 }
2493
2494 var opt_colons: [3]?Ast.TokenIndex = @splat(null);
2495 for (0..render) |i| opt_colons[i] = colons[i];
2496 break :colons opt_colons;
2497 };
2498
2499 try ais.forcePushIndent(.normal);
2500
27522501 if (asm_node.ast.items.len == 0) {
2753 try ais.forcePushIndent(.normal);
27542502 if (asm_node.ast.clobbers.unwrap()) |clobbers| {
27552503 // asm ("foo" ::: clobbers)
27562504 try renderExpression(r, asm_node.ast.template, .space);
......@@ -2764,99 +2512,78 @@ fn renderAsm(
27642512 return renderToken(r, asm_node.ast.rparen, space); // rparen
27652513 }
27662514
2767 // asm ("foo")
2768 try renderExpression(r, asm_node.ast.template, .none);
2769 ais.popIndent();
2770 return renderToken(r, asm_node.ast.rparen, space); // rparen
2515 if (render_colons[0] == null) {
2516 // asm ("foo")
2517 try renderExpression(r, asm_node.ast.template, .none);
2518 ais.popIndent();
2519 return renderToken(r, asm_node.ast.rparen, space); // rparen
2520 }
27712521 }
27722522
2773 try ais.forcePushIndent(.normal);
27742523 try renderExpression(r, asm_node.ast.template, .newline);
27752524 ais.forceLastIndent(); // Might have been dedented by a multiline string literal
27762525 assert(ais.current_line_empty);
27772526
2527 const prev_indent_delta = ais.indent_delta; // May be part of another asm expression
2528 // so indent_delta can't be unconditionally used
27782529 ais.setIndentDelta(asm_indent_delta);
2779 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
27802530
2781 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2782 try renderToken(r, colon1, .newline); // :
2783 break :colon2 colon1 + 1;
2784 } else colon2: {
2785 try renderToken(r, colon1, .space); // :
2531 rendered: {
2532 if (render_colons[0]) |colon1| {
2533 if (asm_node.outputs.len != 0) {
2534 try renderToken(r, colon1, .space);
2535 try ais.forcePushIndent(.normal);
27862536
2787 try ais.forcePushIndent(.normal);
2788 for (asm_node.outputs, 0..) |asm_output, i| {
2789 if (i + 1 < asm_node.outputs.len) {
2790 const next_asm_output = asm_node.outputs[i + 1];
2791 try renderAsmOutput(r, asm_output, .none);
2537 const final = asm_node.outputs.len - 1;
2538 for (asm_node.outputs[0..final], 0..) |asm_output, i| {
2539 try renderAsmOutput(r, asm_output, .none);
2540
2541 const next_start = tree.firstToken(asm_node.outputs[i + 1]);
2542 try renderToken(r, next_start - 1, .newline); // ,
2543 try renderExtraNewlineToken(r, next_start);
2544 }
27922545
2793 const comma = tree.firstToken(next_asm_output) - 1;
2794 try renderToken(r, comma, .newline); // ,
2795 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2796 } else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
27972546 try ais.pushSpace(.comma);
2798 try renderAsmOutput(r, asm_output, .comma);
2547 try renderAsmOutput(r, asm_node.outputs[final], .comma);
27992548 ais.popSpace();
28002549 ais.popIndent();
2801 ais.setIndentDelta(indent_delta);
2802 ais.popIndent();
2803 return renderToken(r, asm_node.ast.rparen, space); // rparen
28042550 } else {
2805 try ais.pushSpace(.comma);
2806 try renderAsmOutput(r, asm_output, .comma);
2807 ais.popSpace();
2808 const comma_or_colon = tree.lastToken(asm_output) + 1;
2809 ais.popIndent();
2810 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2811 .comma => comma_or_colon + 1,
2812 else => comma_or_colon,
2813 };
2551 try renderToken(r, colon1, .newline);
28142552 }
28152553 } else unreachable;
2816 };
28172554
2818 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2819 try renderToken(r, colon2, .newline); // :
2820 break :colon3 colon2 + 1;
2821 } else colon3: {
2822 try renderToken(r, colon2, .space); // :
2823 try ais.forcePushIndent(.normal);
2824 for (asm_node.inputs, 0..) |asm_input, i| {
2825 if (i + 1 < asm_node.inputs.len) {
2826 const next_asm_input = asm_node.inputs[i + 1];
2827 try renderAsmInput(r, asm_input, .none);
2828
2829 const first_token = tree.firstToken(next_asm_input);
2830 try renderToken(r, first_token - 1, .newline); // ,
2831 try renderExtraNewlineToken(r, first_token);
2832 } else if (asm_node.ast.clobbers == .none) {
2555 if (render_colons[1]) |colon2| {
2556 if (asm_node.inputs.len != 0) {
2557 try renderToken(r, colon2, .space);
2558 try ais.forcePushIndent(.normal);
2559
2560 const final = asm_node.inputs.len - 1;
2561 for (asm_node.inputs[0..final], 0..) |asm_input, i| {
2562 try renderAsmInput(r, asm_input, .none);
2563
2564 const next_start = tree.firstToken(asm_node.inputs[i + 1]);
2565 try renderToken(r, next_start - 1, .newline); // ,
2566 try renderExtraNewlineToken(r, next_start);
2567 }
2568
28332569 try ais.pushSpace(.comma);
2834 try renderAsmInput(r, asm_input, .comma);
2570 try renderAsmInput(r, asm_node.inputs[final], .comma);
28352571 ais.popSpace();
28362572 ais.popIndent();
2837 ais.setIndentDelta(indent_delta);
2838 ais.popIndent();
2839 return renderToken(r, asm_node.ast.rparen, space); // rparen
28402573 } else {
2841 try ais.pushSpace(.comma);
2842 try renderAsmInput(r, asm_input, .comma);
2843 ais.popSpace();
2844 const comma_or_colon = tree.lastToken(asm_input) + 1;
2845 ais.popIndent();
2846 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2847 .comma => comma_or_colon + 1,
2848 else => comma_or_colon,
2849 };
2574 try renderToken(r, colon2, .newline);
28502575 }
2576 } else break :rendered;
2577
2578 if (render_colons[2]) |colon3| {
2579 const clobbers = asm_node.ast.clobbers.unwrap().?;
2580 try renderToken(r, colon3, .maybe_space);
2581 try renderExpression(r, clobbers, .none);
2582 ais.forceLastIndent(); // Might have been dedented by a multiline string literal
28512583 }
2852 unreachable;
2853 };
2584 }
28542585
2855 try renderToken(r, colon3, .maybe_space); // :
2856 const clobbers = asm_node.ast.clobbers.unwrap().?;
2857 try renderExpression(r, clobbers, .none);
2858 ais.forceLastIndent(); // Might have been dedented by a multiline string literal
2859 ais.setIndentDelta(indent_delta);
2586 ais.setIndentDelta(prev_indent_delta);
28602587 ais.popIndent();
28612588 return renderToken(r, asm_node.ast.rparen, space); // rparen
28622589}
......@@ -3324,6 +3051,7 @@ fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
33243051 // Write with the canonical single space.
33253052 try ais.underlying_writer.writeAll("// zig fmt: on\n");
33263053 ais.disabled_offset = null;
3054 ais.resetLine();
33273055 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
33283056 // Write with the canonical single space.
33293057 try ais.writeAll("// zig fmt: off\n");
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}
lib/std/zig/Parse.zig+83-74
......@@ -257,7 +257,7 @@ fn parseContainerMembers(p: *Parse) Allocator.Error!Members {
257257 while (true) {
258258 const doc_comment = try p.eatDocComments();
259259
260 switch (p.tokenTag(p.tok_i)) {
260 sw: switch (p.tokenTag(p.tok_i)) {
261261 .keyword_test => {
262262 if (doc_comment) |some| {
263263 try p.warnMsg(.{ .tag = .test_doc_comment, .token = some });
......@@ -348,17 +348,7 @@ fn parseContainerMembers(p: *Parse) Allocator.Error!Members {
348348 p.findNextContainerMember();
349349 },
350350 },
351 .keyword_pub => {
352 p.tok_i += 1;
353 const opt_top_level_decl = try p.expectTopLevelDeclRecoverable();
354 if (opt_top_level_decl) |top_level_decl| {
355 if (field_state == .seen) {
356 field_state = .{ .end = top_level_decl };
357 }
358 try p.scratch.append(p.gpa, top_level_decl);
359 }
360 trailing = p.tokenTag(p.tok_i - 1) == .semicolon;
361 },
351 .keyword_pub,
362352 .keyword_const,
363353 .keyword_var,
364354 .keyword_threadlocal,
......@@ -367,7 +357,27 @@ fn parseContainerMembers(p: *Parse) Allocator.Error!Members {
367357 .keyword_inline,
368358 .keyword_noinline,
369359 .keyword_fn,
370 => {
360 => |t| {
361 if (t == .keyword_extern) {
362 switch (p.tokenTag(p.tok_i + 1)) {
363 .keyword_struct,
364 .keyword_union,
365 .keyword_enum,
366 .keyword_opaque,
367 => |ct| continue :sw ct,
368 else => {},
369 }
370 }
371 if (t == .keyword_inline) {
372 switch (p.tokenTag(p.tok_i + 1)) {
373 .keyword_for,
374 .keyword_while,
375 => |ct| continue :sw ct,
376 else => {},
377 }
378 }
379
380 p.tok_i += @intFromBool(t == .keyword_pub);
371381 const opt_top_level_decl = try p.expectTopLevelDeclRecoverable();
372382 if (opt_top_level_decl) |top_level_decl| {
373383 if (field_state == .seen) {
......@@ -588,7 +598,8 @@ fn expectTestDeclRecoverable(p: *Parse) error{OutOfMemory}!?Node.Index {
588598}
589599
590600/// Decl
591/// <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
601/// <- (KEYWORD_export / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
602/// / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON
592603/// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
593604fn expectTopLevelDecl(p: *Parse) !?Node.Index {
594605 const extern_export_inline_token = p.nextToken();
......@@ -665,7 +676,7 @@ fn expectTopLevelDeclRecoverable(p: *Parse) error{OutOfMemory}!?Node.Index {
665676 };
666677}
667678
668/// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
679/// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix
669680fn parseFnProto(p: *Parse) !?Node.Index {
670681 const fn_token = p.eatToken(.keyword_fn) orelse return null;
671682
......@@ -853,7 +864,7 @@ fn parseGlobalVarDecl(p: *Parse) !?Node.Index {
853864 return var_decl;
854865}
855866
856/// ContainerField <- doc_comment? KEYWORD_comptime? !KEYWORD_fn (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)?
867/// ContainerField <- doc_comment? (KEYWORD_comptime / !KEYWORD_comptime) !KEYWORD_fn (IDENTIFIER COLON / !(IDENTIFIER COLON))? TypeExpr ByteAlign? (EQUAL Expr)?
857868fn expectContainerField(p: *Parse) !Node.Index {
858869 _ = p.eatToken(.keyword_comptime);
859870 const main_token = p.tok_i;
......@@ -895,16 +906,23 @@ fn expectContainerField(p: *Parse) !Node.Index {
895906 }
896907}
897908
898/// Statement
899/// <- KEYWORD_comptime ComptimeStatement
900/// / KEYWORD_nosuspend BlockExprStatement
901/// / KEYWORD_suspend BlockExprStatement
909/// BlockStatement
910/// <- Statement
902911/// / KEYWORD_defer BlockExprStatement
903912/// / KEYWORD_errdefer Payload? BlockExprStatement
904/// / IfStatement
913/// / !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement
914///
915/// Statement
916/// <- ExprStatement
917/// / KEYWORD_suspend BlockExprStatement
918/// / !ExprStatement (KEYWORD_comptime !BlockExpr)? AssignExpr SEMICOLON
919///
920/// ExprStatement
921/// <- IfStatement
905922/// / LabeledStatement
906/// / VarDeclExprStatement
907fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
923/// / KEYWORD_nosuspend BlockExprStatement
924/// / KEYWORD_comptime BlockExpr
925fn expectStatement(p: *Parse, is_block_level: bool) Error!Node.Index {
908926 if (p.eatToken(.keyword_comptime)) |comptime_token| {
909927 const opt_block_expr = try p.parseBlockExpr();
910928 if (opt_block_expr) |block_expr| {
......@@ -915,7 +933,7 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
915933 });
916934 }
917935
918 if (allow_defer_var) {
936 if (is_block_level) {
919937 return p.expectVarDeclExprStatement(comptime_token);
920938 } else {
921939 const assign = try p.expectAssignExpr();
......@@ -949,12 +967,12 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
949967 .data = .{ .node = block_expr },
950968 });
951969 },
952 .keyword_defer => if (allow_defer_var) return p.addNode(.{
970 .keyword_defer => if (is_block_level) return p.addNode(.{
953971 .tag = .@"defer",
954972 .main_token = p.nextToken(),
955973 .data = .{ .node = try p.expectBlockExprStatement() },
956974 }),
957 .keyword_errdefer => if (allow_defer_var) return p.addNode(.{
975 .keyword_errdefer => if (is_block_level) return p.addNode(.{
958976 .tag = .@"errdefer",
959977 .main_token = p.nextToken(),
960978 .data = .{ .opt_token_and_node = .{
......@@ -979,7 +997,7 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
979997
980998 if (try p.parseLabeledStatement()) |labeled_statement| return labeled_statement;
981999
982 if (allow_defer_var) {
1000 if (is_block_level) {
9831001 return p.expectVarDeclExprStatement(null);
9841002 } else {
9851003 const assign = try p.expectAssignExpr();
......@@ -1007,8 +1025,10 @@ fn expectComptimeStatement(p: *Parse, comptime_token: TokenIndex) !Node.Index {
10071025}
10081026
10091027/// VarDeclExprStatement
1010/// <- VarDeclProto (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON
1011/// / Expr (AssignOp Expr / (COMMA (VarDeclProto / Expr))+ EQUAL Expr)? SEMICOLON
1028/// <- Expr
1029/// / VarAssignStatement
1030///
1031/// VarAssignStatement <- (VarDeclProto / Expr) (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON
10121032fn expectVarDeclExprStatement(p: *Parse, comptime_token: ?TokenIndex) !Node.Index {
10131033 const scratch_top = p.scratch.items.len;
10141034 defer p.scratch.shrinkRetainingCapacity(scratch_top);
......@@ -1140,7 +1160,7 @@ fn expectStatementRecoverable(p: *Parse) Error!?Node.Index {
11401160
11411161/// IfStatement
11421162/// <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
1143/// / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
1163/// / IfPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
11441164fn expectIfStatement(p: *Parse) !Node.Index {
11451165 const if_token = p.assertToken(.keyword_if);
11461166 _ = try p.expectToken(.l_paren);
......@@ -1235,8 +1255,8 @@ fn parseLoopStatement(p: *Parse) !?Node.Index {
12351255}
12361256
12371257/// ForStatement
1238/// <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
1239/// / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
1258/// <- ForPrefix BlockExpr ( KEYWORD_else Statement / !KEYWORD_else )
1259/// / ForPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Statement )
12401260fn parseForStatement(p: *Parse) !?Node.Index {
12411261 const for_token = p.eatToken(.keyword_for) orelse return null;
12421262
......@@ -1293,7 +1313,7 @@ fn parseForStatement(p: *Parse) !?Node.Index {
12931313///
12941314/// WhileStatement
12951315/// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
1296/// / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
1316/// / WhilePrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
12971317fn parseWhileStatement(p: *Parse) !?Node.Index {
12981318 const while_token = p.eatToken(.keyword_while) orelse return null;
12991319 _ = try p.expectToken(.l_paren);
......@@ -1383,7 +1403,7 @@ fn parseWhileStatement(p: *Parse) !?Node.Index {
13831403
13841404/// BlockExprStatement
13851405/// <- BlockExpr
1386/// / AssignExpr SEMICOLON
1406/// / !BlockExpr AssignExpr SEMICOLON
13871407fn parseBlockExprStatement(p: *Parse) !?Node.Index {
13881408 const block_expr = try p.parseBlockExpr();
13891409 if (block_expr) |expr| return expr;
......@@ -1685,18 +1705,20 @@ fn expectPrefixExpr(p: *Parse) Error!Node.Index {
16851705/// PrefixTypeOp
16861706/// <- QUESTIONMARK
16871707/// / KEYWORD_anyframe MINUSRARROW
1688/// / SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
1708/// / (ManyPtrTypeStart / SliceTypeStart) KEYWORD_allowzero? ByteAlign? AddrSpace? KEYWORD_const? KEYWORD_volatile?
1709/// / SinglePtrTypeStart KEYWORD_allowzero? BitAlign? AddrSpace? KEYWORD_const? KEYWORD_volatile?
16891710/// / PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
16901711/// / ArrayTypeStart
16911712///
16921713/// SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
16931714///
1694/// PtrTypeStart
1695/// <- ASTERISK
1696/// / ASTERISK2
1697/// / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
1715/// SinglePtrTypeStart <- ASTERISK / ASTERISK2
1716///
1717/// ManyPtrTypeStart <- LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
1718///
1719/// ArrayTypeStart <- LBRACKET Expr !(ASTERISK / ASTERISK2) (COLON Expr)? RBRACKET
16981720///
1699/// ArrayTypeStart <- LBRACKET Expr (COLON Expr)? RBRACKET
1721/// BitAlign <- KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN
17001722fn parseTypeExpr(p: *Parse) Error!?Node.Index {
17011723 switch (p.tokenTag(p.tok_i)) {
17021724 .question_mark => return try p.addNode(.{
......@@ -1962,12 +1984,12 @@ fn expectTypeExpr(p: *Parse) Error!Node.Index {
19621984/// PrimaryExpr
19631985/// <- AsmExpr
19641986/// / IfExpr
1965/// / KEYWORD_break BreakLabel? Expr?
1966/// / KEYWORD_comptime Expr
1967/// / KEYWORD_nosuspend Expr
1968/// / KEYWORD_continue BreakLabel? Expr?
1969/// / KEYWORD_resume Expr
1970/// / KEYWORD_return Expr?
1987/// / KEYWORD_break (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart)
1988/// / KEYWORD_comptime Expr !ExprSuffix
1989/// / KEYWORD_nosuspend Expr !ExprSuffix
1990/// / KEYWORD_continue (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart)
1991/// / KEYWORD_resume Expr !ExprSuffix
1992/// / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart)
19711993/// / BlockLabel? LoopExpr
19721994/// / Block
19731995/// / CurlySuffixExpr
......@@ -2042,10 +2064,6 @@ fn parsePrimaryExpr(p: *Parse) !?Node.Index {
20422064 p.tok_i += 2;
20432065 return try p.parseWhileExpr();
20442066 },
2045 .l_brace => {
2046 p.tok_i += 2;
2047 return try p.parseBlock();
2048 },
20492067 else => return try p.parseCurlySuffixExpr(),
20502068 }
20512069 } else {
......@@ -2067,12 +2085,12 @@ fn parsePrimaryExpr(p: *Parse) !?Node.Index {
20672085 }
20682086}
20692087
2070/// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
2088/// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
20712089fn parseIfExpr(p: *Parse) !?Node.Index {
20722090 return try p.parseIf(expectExpr);
20732091}
20742092
2075/// Block <- LBRACE Statement* RBRACE
2093/// Block <- LBRACE BlockStatement* RBRACE
20762094fn parseBlock(p: *Parse) !?Node.Index {
20772095 const lbrace = p.eatToken(.l_brace) orelse return null;
20782096 const scratch_top = p.scratch.items.len;
......@@ -2177,7 +2195,7 @@ fn forPrefix(p: *Parse) Error!usize {
21772195
21782196/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
21792197///
2180/// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
2198/// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffi
21812199fn parseWhileExpr(p: *Parse) !?Node.Index {
21822200 const while_token = p.eatToken(.keyword_while) orelse return null;
21832201 _ = try p.expectToken(.l_paren);
......@@ -2409,10 +2427,10 @@ fn parseSuffixExpr(p: *Parse) !?Node.Index {
24092427/// / FnProto
24102428/// / GroupedExpr
24112429/// / LabeledTypeExpr
2412/// / IDENTIFIER
2430/// / IDENTIFIER !(COLON LabelableExpr)
24132431/// / IfTypeExpr
24142432/// / INTEGER
2415/// / KEYWORD_comptime TypeExpr
2433/// / KEYWORD_comptime TypeExpr !ExprSuffix
24162434/// / KEYWORD_error DOT IDENTIFIER
24172435/// / KEYWORD_anyframe
24182436/// / KEYWORD_unreachable
......@@ -2431,7 +2449,7 @@ fn parseSuffixExpr(p: *Parse) !?Node.Index {
24312449///
24322450/// GroupedExpr <- LPAREN Expr RPAREN
24332451///
2434/// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
2452/// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
24352453///
24362454/// LabeledTypeExpr
24372455/// <- BlockLabel Block
......@@ -2711,7 +2729,7 @@ fn expectPrimaryTypeExpr(p: *Parse) !Node.Index {
27112729
27122730/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
27132731///
2714/// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
2732/// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
27152733fn parseWhileTypeExpr(p: *Parse) !?Node.Index {
27162734 const while_token = p.eatToken(.keyword_while) orelse return null;
27172735 _ = try p.expectToken(.l_paren);
......@@ -2876,7 +2894,7 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
28762894 });
28772895}
28782896
2879/// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
2897/// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
28802898fn parseAsmOutputItem(p: *Parse) !?Node.Index {
28812899 _ = p.eatToken(.l_bracket) orelse return null;
28822900 const identifier = try p.expectToken(.identifier);
......@@ -2902,7 +2920,7 @@ fn parseAsmOutputItem(p: *Parse) !?Node.Index {
29022920 });
29032921}
29042922
2905/// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
2923/// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN Expr RPAREN
29062924fn parseAsmInputItem(p: *Parse) !?Node.Index {
29072925 _ = p.eatToken(.l_bracket) orelse return null;
29082926 const identifier = try p.expectToken(.identifier);
......@@ -2923,9 +2941,7 @@ fn parseAsmInputItem(p: *Parse) !?Node.Index {
29232941
29242942/// BreakLabel <- COLON IDENTIFIER
29252943fn parseBreakLabel(p: *Parse) Error!OptionalTokenIndex {
2926 _ = p.eatToken(.colon) orelse return .none;
2927 const next_token = try p.expectToken(.identifier);
2928 return .fromToken(next_token);
2944 return if (p.eatTokens(&.{ .colon, .identifier })) |i| .fromToken(i + 1) else .none;
29292945}
29302946
29312947/// BlockLabel <- IDENTIFIER COLON
......@@ -2950,12 +2966,7 @@ fn expectFieldInit(p: *Parse) !Node.Index {
29502966
29512967/// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
29522968fn parseWhileContinueExpr(p: *Parse) !?Node.Index {
2953 _ = p.eatToken(.colon) orelse {
2954 if (p.tokenTag(p.tok_i) == .l_paren and
2955 p.tokensOnSameLine(p.tok_i - 1, p.tok_i))
2956 return p.fail(.expected_continue_expr);
2957 return null;
2958 };
2969 _ = p.eatToken(.colon) orelse return null;
29592970 _ = try p.expectToken(.l_paren);
29602971 const node = try p.parseAssignExpr() orelse return p.fail(.expected_expr_or_assignment);
29612972 _ = try p.expectToken(.r_paren);
......@@ -2993,9 +3004,7 @@ fn parseAddrSpace(p: *Parse) !?Node.Index {
29933004/// such as in the case of anytype and `...`. Caller must look for rparen to find
29943005/// out when there are no more param decls left.
29953006///
2996/// ParamDecl
2997/// <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
2998/// / DOT3
3007/// ParamDecl <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime / !KEYWORD_comptime) (IDENTIFIER COLON / !(IDENTIFIER_COLON)) ParamType
29993008///
30003009/// ParamType
30013010/// <- KEYWORD_anytype
......@@ -3482,7 +3491,7 @@ fn parseSwitchProngList(p: *Parse) !Node.SubRange {
34823491 return p.listToSpan(p.scratch.items[scratch_top..]);
34833492}
34843493
3485/// ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
3494/// ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
34863495fn parseParamDeclList(p: *Parse) !SmallSpan {
34873496 _ = try p.expectToken(.l_paren);
34883497 const scratch_top = p.scratch.items.len;
......@@ -3604,9 +3613,9 @@ fn parseIf(p: *Parse, comptime bodyParseFn: fn (p: *Parse) Error!Node.Index) !?N
36043613 });
36053614}
36063615
3607/// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
3616/// ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix
36083617///
3609/// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
3618/// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix
36103619fn parseFor(p: *Parse, comptime bodyParseFn: fn (p: *Parse) Error!Node.Index) !?Node.Index {
36113620 const for_token = p.eatToken(.keyword_for) orelse return null;
36123621
lib/std/zig/parser_test.zig+236-112
......@@ -5472,17 +5472,11 @@ test "zig fmt: while continue expr" {
54725472 \\ while (i > 0)
54735473 \\ (i * 2);
54745474 \\}
5475 \\T: (while (true) ({
5476 \\ break usize;
5477 \\})),
54755478 \\
54765479 );
5477 try testError(
5478 \\test {
5479 \\ while (i > 0) (i -= 1) {
5480 \\ print("test123", .{});
5481 \\ }
5482 \\}
5483 , &[_]Error{
5484 .expected_continue_expr,
5485 });
54865480}
54875481
54885482test "zig fmt: canonicalize symbols (simple)" {
......@@ -6139,6 +6133,16 @@ test "zig fmt: do not canonicalize invalid cast builtins" {
61396133 );
61406134}
61416135
6136test "zig fmt: canonicalize cast builtins at file start" {
6137 try testTransform(
6138 \\@alignCast(@ptrCast(a)),
6139 \\
6140 ,
6141 \\@ptrCast(@alignCast(a)),
6142 \\
6143 );
6144}
6145
61426146test "zig fmt: extern addrspace in struct" {
61436147 try testCanonical(
61446148 \\const namespace = struct {
......@@ -6838,6 +6842,123 @@ test "zig fmt: error set with extra newline before comma" {
68386842 );
68396843}
68406844
6845test "zig fmt: extern container in tuple" {
6846 try testCanonical(
6847 \\const T = struct {
6848 \\ extern struct {},
6849 \\ extern union {},
6850 \\ extern enum {},
6851 \\};
6852 \\
6853 );
6854}
6855
6856test "zig fmt: break followed by colon" {
6857 try testCanonical(
6858 \\const a = [if (cond) len else break:0]u8;
6859 \\
6860 );
6861}
6862
6863test "zig fmt: array init of labeled block" {
6864 try testCanonical(
6865 \\const a = blk: {
6866 \\ break :blk T;
6867 \\}{ .a = false };
6868 \\
6869 );
6870}
6871
6872test "zig fmt: nested asm indentation" {
6873 try testCanonical(
6874 \\const A = asm (""
6875 \\ : [_] "" (_),
6876 \\ :
6877 \\ : asm (""
6878 \\ : [_] "" (_),
6879 \\ ));
6880 \\
6881 );
6882}
6883
6884test "zig fmt: asm with zig fmt on" {
6885 try testCanonical(
6886 \\// zig fmt: off
6887 \\const A = asm("a" // zig fmt: on
6888 \\ : [_] "" (_),
6889 \\);
6890 \\
6891 );
6892}
6893
6894test "zig fmt: array init with multiline string literal with fmt on/off" {
6895 try testCanonical(
6896 \\const array = .{
6897 \\ \\
6898 \\ // zig fmt: on
6899 \\ // zig fmt: off
6900 \\};
6901 \\
6902 );
6903}
6904
6905test "zig fmt: render extra colons with comments" {
6906 try testCanonical(
6907 \\const a = asm (""
6908 \\ : // testing
6909 \\);
6910 \\const b = asm (""
6911 \\ : // testing
6912 \\ : // testing
6913 \\);
6914 \\const c = asm (""
6915 \\ :
6916 \\ : // testing
6917 \\);
6918 \\
6919 );
6920}
6921
6922test "zig fmt: cast builtins are not reordered with comments" {
6923 try testCanonical(
6924 \\const a = @volatileCast(@constCast( // ...
6925 \\ @alignCast(@ptrCast(a))));
6926 \\
6927 \\const b = @alignCast(@ptrCast( // zig fmt: off
6928 \\ c));
6929 \\
6930 );
6931}
6932
6933test "zig fmt: inner over-indented if expressions becoming multiline" {
6934 try testTransform(
6935 \\const a = (b or
6936 \\c) and [if (d) {}]T; // If the if-statement is kept on the same line it becomes multiline
6937 \\const a = (b or
6938 \\c)[if (d) {}]; // If the if-statement is kept on the same line it becomes multiline
6939 \\const a = .{a, b, (c or
6940 \\d), if (d) {}, e, f, g,};
6941 \\
6942 ,
6943 \\const a = (b or
6944 \\ c) and [
6945 \\ if (d) {}
6946 \\]T; // If the if-statement is kept on the same line it becomes multiline
6947 \\const a = (b or
6948 \\ c)[
6949 \\ if (d) {}
6950 \\]; // If the if-statement is kept on the same line it becomes multiline
6951 \\const a = .{
6952 \\ a, b,
6953 \\ (c or
6954 \\ d),
6955 \\ if (d) {}, e,
6956 \\ f, g,
6957 \\};
6958 \\
6959 );
6960}
6961
68416962test "recovery: top level" {
68426963 try testError(
68436964 \\test "" {inline}
......@@ -7258,83 +7379,61 @@ test "zig fmt: fuzz" {
72587379 try std.testing.fuzz({}, fuzzRender, .{});
72597380}
72607381
7261fn parseTokens(
7262 fba: Allocator,
7263 source: [:0]const u8,
7264) error{ SkipZigTest, OutOfMemory }!struct {
7265 toks: std.zig.Ast.TokenList,
7266 maybe_rewritable: bool,
7267 skip_idempotency: bool,
7268} {
7382fn isRewritable(source: []const u8, tokens: std.zig.Ast.TokenList.Slice) !bool {
72697383 @disableInstrumentation();
7384
72707385 // Byte-order marker is stripped
72717386 var maybe_rewritable = std.mem.startsWith(u8, source, "\xEF\xBB\xBF");
7272 var skip_idempotency = false; // This should be able to be removed once all the bugs are fixed
7273
7274 var tokens: std.zig.Ast.TokenList = .{};
7275 try tokens.ensureTotalCapacity(fba, source.len / 2);
7276 var tokenizer: std.zig.Tokenizer = .init(source);
7277 while (true) {
7278 const tok = tokenizer.next();
7279 switch (tok.tag) {
7280 .invalid,
7281 .invalid_periodasterisks,
7282 => return error.SkipZigTest,
7283 // Extra colons can be removed
7284 .keyword_asm,
7285 // Qualifiers can be reordered
7286 // keyword_const is intentionally excluded since it is used in other contexts and
7287 // having only one qualifier will never lead to reordering.
7288 .keyword_addrspace,
7289 .keyword_align,
7290 .keyword_allowzero,
7291 .keyword_callconv,
7292 .keyword_linksection,
7293 .keyword_volatile,
7294 => maybe_rewritable = true,
7295 .builtin,
7296 // Pointer casts can be reordered
7297 => for ([_][]const u8{
7298 "ptrCast",
7299 "alignCast",
7300 "addrSpaceCast",
7301 "constCast",
7302 "volatileCast",
7303 }) |id| {
7304 if (std.mem.eql(u8, source[tok.loc.start + 1 .. tok.loc.end], id)) {
7305 maybe_rewritable = false;
7306 }
7307 },
7308 // Quoted identifiers can be unquoted
7309 .identifier => maybe_rewritable = maybe_rewritable or source[tok.loc.start] == '@',
7310 else => {},
7311 // #23754
7312 .container_doc_comment,
7313 => if (std.mem.endsWith(Token.Tag, tokens.items(.tag), &.{.l_brace})) {
7314 return error.SkipZigTest;
7315 },
7316 // #24507
7317 .keyword_inline,
7318 .keyword_for,
7319 .keyword_while,
7320 .l_brace,
7321 => if (std.mem.endsWith(Token.Tag, tokens.items(.tag), &.{ .identifier, .colon })) {
7387 // The above variable can not yet be replaced by returns since error.SkipZigTest still needs to
7388 // be checked for.
7389
7390 for (0.., tokens.items(.tag), tokens.items(.start)) |i, tag, start| switch (tag) {
7391 // Extra colons can be removed
7392 .keyword_asm,
7393 // Qualifiers can be reordered
7394 // keyword_const is intentionally excluded since it is used in other contexts and
7395 // having only one qualifier will never lead to reordering.
7396 .keyword_addrspace,
7397 .keyword_align,
7398 .keyword_allowzero,
7399 .keyword_callconv,
7400 .keyword_linksection,
7401 .keyword_volatile,
7402 => maybe_rewritable = true,
7403 .builtin,
7404 // Pointer casts can be reordered
7405 => for ([_][]const u8{
7406 "ptrCast",
7407 "alignCast",
7408 "addrSpaceCast",
7409 "constCast",
7410 "volatileCast",
7411 }) |id| {
7412 if (std.mem.startsWith(u8, source[start + 1 ..], id)) {
73227413 maybe_rewritable = true;
7323 skip_idempotency = true;
7324 },
7325 }
7326 try tokens.append(fba, .{
7327 .tag = tok.tag,
7328 .start = @intCast(tok.loc.start),
7329 });
7330 if (tok.tag == .eof)
7331 break;
7332 }
7333 return .{
7334 .toks = tokens,
7335 .maybe_rewritable = maybe_rewritable,
7336 .skip_idempotency = skip_idempotency,
7414 }
7415 },
7416 // Quoted identifiers can be unquoted
7417 .identifier => if (source[start] == '@') {
7418 maybe_rewritable = true;
7419 },
7420 else => {},
7421 // #23754
7422 .container_doc_comment,
7423 => if (std.mem.endsWith(Token.Tag, tokens.items(.tag)[0..i], &.{.l_brace})) {
7424 return error.SkipZigTest; // Can cause I.B.
7425 },
7426 // #24507
7427 .keyword_inline,
7428 .keyword_for,
7429 .keyword_while,
7430 .l_brace,
7431 => if (std.mem.endsWith(Token.Tag, tokens.items(.tag)[0..i], &.{ .identifier, .colon })) {
7432 return error.SkipZigTest; // Can cause I.B. due to double rendering of zig fmt on/off
7433 },
73377434 };
7435
7436 return maybe_rewritable;
73387437}
73397438
73407439/// Checks equivelence of non-whitespace characters.
......@@ -7447,34 +7546,29 @@ fn reparseTokens(
74477546fn fuzzRender(_: void, smith: *std.testing.Smith) !void {
74487547 @disableInstrumentation();
74497548
7450 var src_buf: [512]u8 = undefined;
7451 const src_len = smith.sliceWeighted(&src_buf, &.{
7452 .rangeLessThan(u32, 0, 32, 256),
7453 .rangeLessThan(u32, 32, 64, 64),
7454 .rangeLessThan(u32, 64, src_buf.len, 1),
7455 }, &.{
7456 .rangeAtMost(u8, 0x20, 0x7e, 8),
7457 .value(u8, '\n', 32),
7458 .value(u8, '\t', 8),
7459 .value(u8, '\r', 4),
7460 .rangeAtMost(u8, 0x7f, 0xff, 1),
7461 });
7462 src_buf[src_len] = 0;
7463
7549 var ast_smith: std.zig.AstSmith = .init(smith);
7550 try ast_smith.generateSource();
74647551 var fba_ctx = std.heap.FixedBufferAllocator.init(&fixed_buffer_mem);
7465 fuzzRenderInner(src_buf[0..src_len :0], fba_ctx.allocator()) catch |e| return switch (e) {
7466 error.OutOfMemory => {},
7467 else => e,
7552 var opt_rendered: ?[]const u8 = null;
7553 fuzzRenderInner(&ast_smith, fba_ctx.allocator(), &opt_rendered) catch |e| switch (e) {
7554 error.SkipZigTest, error.OutOfMemory, error.WriteFailed => return error.SkipZigTest,
7555 else => |failure| {
7556 ast_smith.logSource();
7557 if (opt_rendered) |rendered| {
7558 logRenderedSource(rendered);
7559 }
7560 return failure;
7561 },
74687562 };
74697563}
74707564
7471fn fuzzRenderInner(source: [:0]const u8, fba: Allocator) !void {
7565fn fuzzRenderInner(ast_smith: *std.zig.AstSmith, fba: Allocator, opt_rendered: *?[]const u8) !void {
74727566 @disableInstrumentation();
74737567
7474 const src_toks = try parseTokens(fba, source);
7475 const src_tree = try std.zig.Ast.parseTokens(fba, source, src_toks.toks.slice(), .zig);
7476 if (src_tree.errors.len != 0)
7477 return;
7568 const source = ast_smith.source();
7569 const src_rewritable = try isRewritable(source, ast_smith.tokens());
7570 const src_tree = try std.zig.Ast.parseTokens(fba, source, ast_smith.tokens(), .zig);
7571 std.debug.assert(src_tree.errors.len == 0);
74787572 for (src_tree.nodes.items(.tag)) |tag| switch (tag) {
74797573 // #24507 (`switch(x) { inline for (a) |a| a => {} }` to
74807574 // `switch(x) { { inline for (a) |a| a => {} }` since
......@@ -7490,15 +7584,16 @@ fn fuzzRenderInner(source: [:0]const u8, fba: Allocator) !void {
74907584 // list to save space which is useless for fixed buffer allocators.
74917585 try rendered_w.writer.writeByte(0);
74927586 const rendered = rendered_w.written()[0 .. rendered_w.written().len - 1 :0];
7587 opt_rendered.* = rendered;
74937588
74947589 // First check that the non-whitespace characters match. This ensures that
74957590 // identifier names, numbers, comments, et cetera are preserved.
7496 if (!src_toks.maybe_rewritable and isRewritten(source, rendered))
7591 if (!src_rewritable and isRewritten(source, rendered))
74977592 return error.Rewritten;
74987593 // Next check that the tokens are the same since whitespace removal can change the tokens
7499 const src_tags = src_toks.toks.items(.tag);
7594 const src_tags = ast_smith.tokens().items(.tag);
75007595 const rendered_toks = try reparseTokens(fba, rendered, src_tags[0 .. src_tags.len - 1 :.eof]);
7501 if (!src_toks.maybe_rewritable and rendered_toks.rewritten)
7596 if (!src_rewritable and rendered_toks.rewritten)
75027597 return error.Rewritten;
75037598
75047599 // Rerender the tree to check idempotency and that new commas
......@@ -7506,10 +7601,39 @@ fn fuzzRenderInner(source: [:0]const u8, fba: Allocator) !void {
75067601 const rendered_tree = try std.zig.Ast.parseTokens(fba, rendered, rendered_toks.toks.slice(), .zig);
75077602 if (rendered_tree.errors.len != 0)
75087603 return error.Rewritten;
7509 if (!src_toks.skip_idempotency) {
7510 var rerendered_w: std.Io.Writer.Allocating = .init(fba);
7511 try rerendered_w.ensureUnusedCapacity(source.len);
7512 try rendered_tree.render(fba, &rerendered_w.writer, .{});
7513 try std.testing.expectEqualStrings(rendered, rerendered_w.written());
7514 }
7604 var rerendered_w: std.Io.Writer.Allocating = .init(fba);
7605 try rerendered_w.ensureUnusedCapacity(source.len);
7606 try rendered_tree.render(fba, &rerendered_w.writer, .{});
7607 try std.testing.expectEqualStrings(rendered, rerendered_w.written());
7608}
7609
7610fn logRenderedSource(source: []const u8) void {
7611 var buf: [256]u8 = undefined;
7612 const ls = std.debug.lockStderr(&buf);
7613 defer std.debug.unlockStderr();
7614 logRenderedSourceInner(source, ls.terminal()) catch {};
7615}
7616
7617fn logRenderedSourceInner(source: []const u8, t: std.Io.Terminal) std.Io.Writer.Error!void {
7618 const w = t.writer;
7619
7620 t.setColor(.dim) catch {};
7621 try w.writeAll("=== Rendered Source ===\n");
7622 t.setColor(.reset) catch {};
7623
7624 for (0.., source) |i, c| switch (c) {
7625 ' '...0x7e => try w.writeByte(c),
7626 '\n' => {
7627 if (i != 0 and source[i - 1] == ' ') {
7628 try w.writeAll("⏎");
7629 }
7630 try w.writeByte('\n');
7631 },
7632 else => {
7633 t.setColor(.cyan) catch {};
7634 try w.print("\\x{x:0>2}", .{c});
7635 t.setColor(.reset) catch {};
7636 },
7637 };
7638 try w.writeAll("␃\n");
75157639}
lib/std/zig/tokenizer.zig+2-1
......@@ -313,7 +313,8 @@ pub const Token = struct {
313313 return tag.lexeme() orelse switch (tag) {
314314 .invalid => "invalid token",
315315 .identifier => "an identifier",
316 .string_literal, .multiline_string_literal_line => "a string literal",
316 .string_literal => "a string literal",
317 .multiline_string_literal_line => "a multiline string literal",
317318 .char_literal => "a character literal",
318319 .eof => "EOF",
319320 .builtin => "a builtin function",
src/codegen/x86_64/CodeGen.zig+1
......@@ -170801,6 +170801,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
170801170801 else => |e| return e,
170802170802 };
170803170803 try ops[0].toSlicePtr(cg);
170804 try ops[1].toSlicePtr(cg);
170804170805 cg.select(&.{}, &.{}, &ops, switch (air_tag) {
170805170806 else => unreachable,
170806170807 inline .memcpy, .memmove => |symbol| comptime &.{.{
test/behavior/array.zig+12
......@@ -45,6 +45,18 @@ fn getArrayLen(a: []const u32) usize {
4545 return a.len;
4646}
4747
48test "runtime array concat with comptime slice" {
49 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
50 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
51 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
52
53 var a: [1]u8 = .{1};
54 const b = (comptime @as([]const u8, &.{0})) ++ &a;
55 const c = &a ++ (comptime @as([]const u8, &.{0}));
56 try std.testing.expectEqualSlices(u8, &.{ 0, 1 }, b);
57 try std.testing.expectEqualSlices(u8, &.{ 1, 0 }, c);
58}
59
4860test "array concat with undefined" {
4961 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
5062 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;