authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-02 15:22:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-03 20:05:32-07:00
log5f1f14519949ea8c9fc136e14b01b419fe59b751
tree6c150486533ccdca20409c0b367d4cf6528acce9
parent3263d6e6aba197656ba6b0e35330ce4d756eaf60

add a transformation for gutting the body of a function


2 files changed, 123 insertions(+), 46 deletions(-)

lib/std/zig/render.zig+106-45
......@@ -18,6 +18,11 @@ pub const Fixups = struct {
1818 /// The key is the mut token (`var`/`const`) of the variable declaration
1919 /// that should have a `_ = foo;` inserted afterwards.
2020 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .{},
21 /// The functions in this unordered set of indices will render with a
22 /// function body of `@trap()` instead, with all parameters discarded.
23 /// The indexes correspond to the order in which the functions appear in
24 /// the file.
25 gut_functions: std.AutoHashMapUnmanaged(u32, void) = .{},
2126
2227 pub fn count(f: Fixups) usize {
2328 return f.unused_var_decls.count();
......@@ -34,6 +39,9 @@ const Render = struct {
3439 ais: *Ais,
3540 tree: Ast,
3641 fixups: Fixups,
42 /// Keeps track of how many function declarations we have seen so far. Used
43 /// by Fixups.
44 function_index: u32,
3745};
3846
3947pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void {
......@@ -47,24 +55,25 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!v
4755 .ais = &auto_indenting_stream,
4856 .tree = tree,
4957 .fixups = fixups,
58 .function_index = 0,
5059 };
5160
5261 // Render all the line comments at the beginning of the file.
5362 const comment_end_loc = tree.tokens.items(.start)[0];
54 _ = try renderComments(r, 0, comment_end_loc);
63 _ = try renderComments(&r, 0, comment_end_loc);
5564
5665 if (tree.tokens.items(.tag)[0] == .container_doc_comment) {
57 try renderContainerDocComments(r, 0);
66 try renderContainerDocComments(&r, 0);
5867 }
5968
6069 if (tree.mode == .zon) {
6170 try renderExpression(
62 r,
71 &r,
6372 tree.nodes.items(.data)[0].lhs,
6473 .newline,
6574 );
6675 } else {
67 try renderMembers(r, tree.rootDecls());
76 try renderMembers(&r, tree.rootDecls());
6877 }
6978
7079 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
......@@ -73,7 +82,7 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!v
7382}
7483
7584/// Render all members in the given slice, keeping empty lines where appropriate
76fn renderMembers(r: Render, members: []const Ast.Node.Index) Error!void {
85fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
7786 const tree = r.tree;
7887 if (members.len == 0) return;
7988 const container: Container = for (members) |member| {
......@@ -93,7 +102,7 @@ const Container = enum {
93102};
94103
95104fn renderMember(
96 r: Render,
105 r: *Render,
97106 container: Container,
98107 decl: Ast.Node.Index,
99108 space: Space,
......@@ -106,6 +115,8 @@ fn renderMember(
106115 try renderDocComments(r, tree.firstToken(decl));
107116 switch (tree.nodes.items(.tag)[decl]) {
108117 .fn_decl => {
118 const this_index = r.function_index;
119 r.function_index += 1;
109120 // Some examples:
110121 // pub extern "foo" fn ...
111122 // export fn ...
......@@ -150,7 +161,19 @@ fn renderMember(
150161 }
151162 assert(datas[decl].rhs != 0);
152163 try renderExpression(r, fn_proto, .space);
153 return renderExpression(r, datas[decl].rhs, space);
164 const body_node = datas[decl].rhs;
165 if (r.fixups.gut_functions.contains(this_index)) {
166 ais.pushIndent();
167 const lbrace = tree.nodes.items(.main_token)[body_node];
168 try renderToken(r, lbrace, .newline);
169 try discardAllParams(r, fn_proto);
170 try ais.writer().writeAll("@trap();");
171 ais.popIndent();
172 try ais.insertNewline();
173 try renderToken(r, tree.lastToken(body_node), space); // rbrace
174 } else {
175 return renderExpression(r, body_node, space);
176 }
154177 },
155178 .fn_proto_simple,
156179 .fn_proto_multi,
......@@ -227,7 +250,7 @@ fn renderMember(
227250}
228251
229252/// Render all expressions in the slice, keeping empty lines where appropriate
230fn renderExpressions(r: Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
253fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
231254 if (expressions.len == 0) return;
232255 try renderExpression(r, expressions[0], space);
233256 for (expressions[1..]) |expression| {
......@@ -236,7 +259,7 @@ fn renderExpressions(r: Render, expressions: []const Ast.Node.Index, space: Spac
236259 }
237260}
238261
239fn renderExpression(r: Render, node: Ast.Node.Index, space: Space) Error!void {
262fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
240263 const tree = r.tree;
241264 const ais = r.ais;
242265 const token_tags = tree.tokens.items(.tag);
......@@ -815,7 +838,7 @@ fn renderExpression(r: Render, node: Ast.Node.Index, space: Space) Error!void {
815838}
816839
817840fn renderArrayType(
818 r: Render,
841 r: *Render,
819842 array_type: Ast.full.ArrayType,
820843 space: Space,
821844) Error!void {
......@@ -836,7 +859,7 @@ fn renderArrayType(
836859 return renderExpression(r, array_type.ast.elem_type, space);
837860}
838861
839fn renderPtrType(r: Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
862fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
840863 const tree = r.tree;
841864 switch (ptr_type.size) {
842865 .One => {
......@@ -925,7 +948,7 @@ fn renderPtrType(r: Render, ptr_type: Ast.full.PtrType, space: Space) Error!void
925948}
926949
927950fn renderSlice(
928 r: Render,
951 r: *Render,
929952 slice_node: Ast.Node.Index,
930953 slice: Ast.full.Slice,
931954 space: Space,
......@@ -960,7 +983,7 @@ fn renderSlice(
960983}
961984
962985fn renderAsmOutput(
963 r: Render,
986 r: *Render,
964987 asm_output: Ast.Node.Index,
965988 space: Space,
966989) Error!void {
......@@ -989,7 +1012,7 @@ fn renderAsmOutput(
9891012}
9901013
9911014fn renderAsmInput(
992 r: Render,
1015 r: *Render,
9931016 asm_input: Ast.Node.Index,
9941017 space: Space,
9951018) Error!void {
......@@ -1010,7 +1033,25 @@ fn renderAsmInput(
10101033}
10111034
10121035fn renderVarDecl(
1013 r: Render,
1036 r: *Render,
1037 var_decl: Ast.full.VarDecl,
1038 /// Destructures intentionally ignore leading `comptime` tokens.
1039 ignore_comptime_token: bool,
1040 /// `comma_space` and `space` are used for destructure LHS decls.
1041 space: Space,
1042) Error!void {
1043 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1044 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token)) {
1045 // Discard the variable like this: `_ = foo;`
1046 const w = r.ais.writer();
1047 try w.writeAll("_ = ");
1048 try w.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1049 try w.writeAll(";\n");
1050 }
1051}
1052
1053fn renderVarDeclWithoutFixups(
1054 r: *Render,
10141055 var_decl: Ast.full.VarDecl,
10151056 /// Destructures intentionally ignore leading `comptime` tokens.
10161057 ignore_comptime_token: bool,
......@@ -1131,7 +1172,7 @@ fn renderVarDecl(
11311172 return renderExpression(r, var_decl.ast.init_node, space); // ;
11321173}
11331174
1134fn renderIf(r: Render, if_node: Ast.full.If, space: Space) Error!void {
1175fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
11351176 return renderWhile(r, .{
11361177 .ast = .{
11371178 .while_token = if_node.ast.if_token,
......@@ -1150,7 +1191,7 @@ fn renderIf(r: Render, if_node: Ast.full.If, space: Space) Error!void {
11501191
11511192/// Note that this function is additionally used to render if expressions, with
11521193/// respective values set to null.
1153fn renderWhile(r: Render, while_node: Ast.full.While, space: Space) Error!void {
1194fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
11541195 const tree = r.tree;
11551196 const token_tags = tree.tokens.items(.tag);
11561197
......@@ -1214,7 +1255,7 @@ fn renderWhile(r: Render, while_node: Ast.full.While, space: Space) Error!void {
12141255}
12151256
12161257fn renderThenElse(
1217 r: Render,
1258 r: *Render,
12181259 last_prefix_token: Ast.TokenIndex,
12191260 then_expr: Ast.Node.Index,
12201261 else_token: Ast.TokenIndex,
......@@ -1275,7 +1316,7 @@ fn renderThenElse(
12751316 }
12761317}
12771318
1278fn renderFor(r: Render, for_node: Ast.full.For, space: Space) Error!void {
1319fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
12791320 const tree = r.tree;
12801321 const ais = r.ais;
12811322 const token_tags = tree.tokens.items(.tag);
......@@ -1346,7 +1387,7 @@ fn renderFor(r: Render, for_node: Ast.full.For, space: Space) Error!void {
13461387}
13471388
13481389fn renderContainerField(
1349 r: Render,
1390 r: *Render,
13501391 container: Container,
13511392 field_param: Ast.full.ContainerField,
13521393 space: Space,
......@@ -1450,7 +1491,7 @@ fn renderContainerField(
14501491}
14511492
14521493fn renderBuiltinCall(
1453 r: Render,
1494 r: *Render,
14541495 builtin_token: Ast.TokenIndex,
14551496 params: []const Ast.Node.Index,
14561497 space: Space,
......@@ -1588,7 +1629,7 @@ fn renderBuiltinCall(
15881629 }
15891630}
15901631
1591fn renderFnProto(r: Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1632fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
15921633 const tree = r.tree;
15931634 const ais = r.ais;
15941635 const token_tags = tree.tokens.items(.tag);
......@@ -1806,7 +1847,7 @@ fn renderFnProto(r: Render, fn_proto: Ast.full.FnProto, space: Space) Error!void
18061847}
18071848
18081849fn renderSwitchCase(
1809 r: Render,
1850 r: *Render,
18101851 switch_case: Ast.full.SwitchCase,
18111852 space: Space,
18121853) Error!void {
......@@ -1869,7 +1910,7 @@ fn renderSwitchCase(
18691910}
18701911
18711912fn renderBlock(
1872 r: Render,
1913 r: *Render,
18731914 block_node: Ast.Node.Index,
18741915 statements: []const Ast.Node.Index,
18751916 space: Space,
......@@ -1910,7 +1951,7 @@ fn renderBlock(
19101951}
19111952
19121953fn renderStructInit(
1913 r: Render,
1954 r: *Render,
19141955 struct_node: Ast.Node.Index,
19151956 struct_init: Ast.full.StructInit,
19161957 space: Space,
......@@ -1975,7 +2016,7 @@ fn renderStructInit(
19752016}
19762017
19772018fn renderArrayInit(
1978 r: Render,
2019 r: *Render,
19792020 array_init: Ast.full.ArrayInit,
19802021 space: Space,
19812022) Error!void {
......@@ -2095,6 +2136,7 @@ fn renderArrayInit(
20952136 .ais = &auto_indenting_stream,
20962137 .tree = r.tree,
20972138 .fixups = r.fixups,
2139 .function_index = r.function_index,
20982140 };
20992141
21002142 // Calculate size of columns in current section
......@@ -2106,7 +2148,7 @@ fn renderArrayInit(
21062148 sub_expr_buffer_starts[i] = start;
21072149
21082150 if (i + 1 < section_exprs.len) {
2109 try renderExpression(sub_render, expr, .none);
2151 try renderExpression(&sub_render, expr, .none);
21102152 const width = sub_expr_buffer.items.len - start;
21112153 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start..], '\n') != null;
21122154 contains_newline = contains_newline or this_contains_newline;
......@@ -2126,7 +2168,7 @@ fn renderArrayInit(
21262168 column_counter = 0;
21272169 }
21282170 } else {
2129 try renderExpression(sub_render, expr, .comma);
2171 try renderExpression(&sub_render, expr, .comma);
21302172 const width = sub_expr_buffer.items.len - start - 2;
21312173 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start .. sub_expr_buffer.items.len - 1], '\n') != null;
21322174 contains_newline = contains_newline or this_contains_newline;
......@@ -2201,7 +2243,7 @@ fn renderArrayInit(
22012243}
22022244
22032245fn renderContainerDecl(
2204 r: Render,
2246 r: *Render,
22052247 container_decl_node: Ast.Node.Index,
22062248 container_decl: Ast.full.ContainerDecl,
22072249 space: Space,
......@@ -2317,7 +2359,7 @@ fn renderContainerDecl(
23172359}
23182360
23192361fn renderAsm(
2320 r: Render,
2362 r: *Render,
23212363 asm_node: Ast.full.Asm,
23222364 space: Space,
23232365) Error!void {
......@@ -2476,7 +2518,7 @@ fn renderAsm(
24762518}
24772519
24782520fn renderCall(
2479 r: Render,
2521 r: *Render,
24802522 call: Ast.full.Call,
24812523 space: Space,
24822524) Error!void {
......@@ -2488,7 +2530,7 @@ fn renderCall(
24882530}
24892531
24902532fn renderParamList(
2491 r: Render,
2533 r: *Render,
24922534 lparen: Ast.TokenIndex,
24932535 params: []const Ast.Node.Index,
24942536 space: Space,
......@@ -2557,7 +2599,7 @@ fn renderParamList(
25572599
25582600/// Renders the given expression indented, popping the indent before rendering
25592601/// any following line comments
2560fn renderExpressionIndented(r: Render, node: Ast.Node.Index, space: Space) Error!void {
2602fn renderExpressionIndented(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
25612603 const tree = r.tree;
25622604 const ais = r.ais;
25632605 const token_starts = tree.tokens.items(.start);
......@@ -2617,7 +2659,7 @@ fn renderExpressionIndented(r: Render, node: Ast.Node.Index, space: Space) Error
26172659
26182660/// Render an expression, and the comma that follows it, if it is present in the source.
26192661/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2620fn renderExpressionComma(r: Render, node: Ast.Node.Index, space: Space) Error!void {
2662fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
26212663 const tree = r.tree;
26222664 const token_tags = tree.tokens.items(.tag);
26232665 const maybe_comma = tree.lastToken(node) + 1;
......@@ -2631,7 +2673,7 @@ fn renderExpressionComma(r: Render, node: Ast.Node.Index, space: Space) Error!vo
26312673
26322674/// Render a token, and the comma that follows it, if it is present in the source.
26332675/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2634fn renderTokenComma(r: Render, token: Ast.TokenIndex, space: Space) Error!void {
2676fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
26352677 const tree = r.tree;
26362678 const token_tags = tree.tokens.items(.tag);
26372679 const maybe_comma = token + 1;
......@@ -2645,7 +2687,7 @@ fn renderTokenComma(r: Render, token: Ast.TokenIndex, space: Space) Error!void {
26452687
26462688/// Render an identifier, and the comma that follows it, if it is present in the source.
26472689/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2648fn renderIdentifierComma(r: Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2690fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
26492691 const tree = r.tree;
26502692 const token_tags = tree.tokens.items(.tag);
26512693 const maybe_comma = token + 1;
......@@ -2678,7 +2720,7 @@ const Space = enum {
26782720 skip,
26792721};
26802722
2681fn renderToken(r: Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2723fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
26822724 const tree = r.tree;
26832725 const ais = r.ais;
26842726 const lexeme = tokenSliceForRender(tree, token_index);
......@@ -2686,7 +2728,7 @@ fn renderToken(r: Render, token_index: Ast.TokenIndex, space: Space) Error!void
26862728 try renderSpace(r, token_index, lexeme.len, space);
26872729}
26882730
2689fn renderSpace(r: Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2731fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
26902732 const tree = r.tree;
26912733 const ais = r.ais;
26922734 const token_tags = tree.tokens.items(.tag);
......@@ -2734,7 +2776,7 @@ const QuoteBehavior = enum {
27342776 eagerly_unquote_except_underscore,
27352777};
27362778
2737fn renderIdentifier(r: Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2779fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
27382780 const tree = r.tree;
27392781 const token_tags = tree.tokens.items(.tag);
27402782 assert(token_tags[token_index] == .identifier);
......@@ -2836,7 +2878,7 @@ fn renderIdentifier(r: Render, token_index: Ast.TokenIndex, space: Space, quote:
28362878// Renders a @"" quoted identifier, normalizing escapes.
28372879// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
28382880// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2839fn renderQuotedIdentifier(r: Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2881fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
28402882 const tree = r.tree;
28412883 const ais = r.ais;
28422884 const token_tags = tree.tokens.items(.tag);
......@@ -2922,7 +2964,7 @@ fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.Tok
29222964
29232965/// Assumes that start is the first byte past the previous token and
29242966/// that end is the last byte before the next token.
2925fn renderComments(r: Render, start: usize, end: usize) Error!bool {
2967fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
29262968 const tree = r.tree;
29272969 const ais = r.ais;
29282970
......@@ -2985,12 +3027,12 @@ fn renderComments(r: Render, start: usize, end: usize) Error!bool {
29853027 return index != start;
29863028}
29873029
2988fn renderExtraNewline(r: Render, node: Ast.Node.Index) Error!void {
3030fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
29893031 return renderExtraNewlineToken(r, r.tree.firstToken(node));
29903032}
29913033
29923034/// Check if there is an empty line immediately before the given token. If so, render it.
2993fn renderExtraNewlineToken(r: Render, token_index: Ast.TokenIndex) Error!void {
3035fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
29943036 const tree = r.tree;
29953037 const ais = r.ais;
29963038 const token_starts = tree.tokens.items(.start);
......@@ -3017,7 +3059,7 @@ fn renderExtraNewlineToken(r: Render, token_index: Ast.TokenIndex) Error!void {
30173059
30183060/// end_token is the token one past the last doc comment token. This function
30193061/// searches backwards from there.
3020fn renderDocComments(r: Render, end_token: Ast.TokenIndex) Error!void {
3062fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
30213063 const tree = r.tree;
30223064 // Search backwards for the first doc comment.
30233065 const token_tags = tree.tokens.items(.tag);
......@@ -3049,7 +3091,7 @@ fn renderDocComments(r: Render, end_token: Ast.TokenIndex) Error!void {
30493091}
30503092
30513093/// start_token is first container doc comment token.
3052fn renderContainerDocComments(r: Render, start_token: Ast.TokenIndex) Error!void {
3094fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
30533095 const tree = r.tree;
30543096 const token_tags = tree.tokens.items(.tag);
30553097 var tok = start_token;
......@@ -3064,6 +3106,25 @@ fn renderContainerDocComments(r: Render, start_token: Ast.TokenIndex) Error!void
30643106 }
30653107}
30663108
3109fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3110 const tree = r.tree;
3111 const ais = r.ais;
3112 var buf: [1]Ast.Node.Index = undefined;
3113 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3114 const token_tags = tree.tokens.items(.tag);
3115 const main_tokens = tree.nodes.items(.main_token);
3116 for (fn_proto.ast.params) |param_node| {
3117 const type_ident = main_tokens[param_node];
3118 assert(token_tags[type_ident] == .identifier);
3119 const name_ident = type_ident - 2;
3120 assert(token_tags[name_ident] == .identifier);
3121 const w = ais.writer();
3122 try w.writeAll("_ = ");
3123 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
3124 try w.writeAll(";\n");
3125 }
3126}
3127
30673128fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
30683129 var ret = tree.tokenSlice(token_index);
30693130 switch (tree.tokens.items(.tag)[token_index]) {
src/reduce.zig+17-1
......@@ -11,6 +11,15 @@ const Transformation = enum {
1111 none,
1212};
1313
14// Roadmap:
15// - add the main loop that checks for interestingness
16// - add transformations
17// - gut function
18// - replace body with `@trap();`
19// - discard the parameters
20// - add thread pool
21// - add support for `@import` detection and other files
22
1423pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
1524 const file_path = args[2];
1625 const transformation = std.meta.stringToEnum(Transformation, args[3]);
......@@ -35,7 +44,14 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
3544 var rendered = std.ArrayList(u8).init(gpa);
3645 defer rendered.deinit();
3746 rendered.clearRetainingCapacity();
38 try tree.renderToArrayList(&rendered);
47
48 var gut_functions: std.AutoHashMapUnmanaged(u32, void) = .{};
49 try gut_functions.put(arena, 1, {});
50 try gut_functions.put(arena, 3, {});
51
52 try tree.renderToArrayList(&rendered, .{
53 .gut_functions = gut_functions,
54 });
3955
4056 const stdout = std.io.getStdOut();
4157 try stdout.writeAll(rendered.items);