authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-15 21:29:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-16 17:20:02-07:00
log0389b4c7b9b83434daab05e8b94da315c61166ce
treeb4049618b835199fc8be2af3c46c384ca794a033
parent6c4a104822679e92e094b166cc11b56b43f84a33

move a file without changing it


2 files changed, 3621 insertions(+), 3621 deletions(-)

lib/std/zig/Ast/Render.zig created+3621
...@@ -0,0 +1,3621 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const meta = std.meta;
6const Ast = std.zig.Ast;
7const Token = std.zig.Token;
8const primitives = std.zig.primitives;
9
10const indent_delta = 4;
11const asm_indent_delta = 2;
12
13pub const Error = Ast.RenderError;
14
15const Ais = AutoIndentingStream(std.ArrayList(u8).Writer);
16
17pub const Fixups = struct {
18 /// The key is the mut token (`var`/`const`) of the variable declaration
19 /// that should have a `_ = foo;` inserted afterwards.
20 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .empty,
21 /// The functions in this unordered set of AST fn decl nodes will render
22 /// with a function body of `@trap()` instead, with all parameters
23 /// discarded.
24 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
25 /// These global declarations will be omitted.
26 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
27 /// These expressions will be replaced with the string value.
28 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
29 /// The string value will be inserted directly after the node.
30 append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
31 /// These nodes will be replaced with a different node.
32 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
33 /// Change all identifier names matching the key to be value instead.
34 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .empty,
35
36 /// All `@import` builtin calls which refer to a file path will be prefixed
37 /// with this path.
38 rebase_imported_paths: ?[]const u8 = null,
39
40 pub fn count(f: Fixups) usize {
41 return f.unused_var_decls.count() +
42 f.gut_functions.count() +
43 f.omit_nodes.count() +
44 f.replace_nodes_with_string.count() +
45 f.append_string_after_node.count() +
46 f.replace_nodes_with_node.count() +
47 f.rename_identifiers.count() +
48 @intFromBool(f.rebase_imported_paths != null);
49 }
50
51 pub fn clearRetainingCapacity(f: *Fixups) void {
52 f.unused_var_decls.clearRetainingCapacity();
53 f.gut_functions.clearRetainingCapacity();
54 f.omit_nodes.clearRetainingCapacity();
55 f.replace_nodes_with_string.clearRetainingCapacity();
56 f.append_string_after_node.clearRetainingCapacity();
57 f.replace_nodes_with_node.clearRetainingCapacity();
58 f.rename_identifiers.clearRetainingCapacity();
59
60 f.rebase_imported_paths = null;
61 }
62
63 pub fn deinit(f: *Fixups, gpa: Allocator) void {
64 f.unused_var_decls.deinit(gpa);
65 f.gut_functions.deinit(gpa);
66 f.omit_nodes.deinit(gpa);
67 f.replace_nodes_with_string.deinit(gpa);
68 f.append_string_after_node.deinit(gpa);
69 f.replace_nodes_with_node.deinit(gpa);
70 f.rename_identifiers.deinit(gpa);
71 f.* = undefined;
72 }
73};
74
75const Render = struct {
76 gpa: Allocator,
77 ais: *Ais,
78 tree: Ast,
79 fixups: Fixups,
80};
81
82pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void {
83 assert(tree.errors.len == 0); // Cannot render an invalid tree.
84 var auto_indenting_stream = Ais.init(buffer, indent_delta);
85 defer auto_indenting_stream.deinit();
86 var r: Render = .{
87 .gpa = buffer.allocator,
88 .ais = &auto_indenting_stream,
89 .tree = tree,
90 .fixups = fixups,
91 };
92
93 // Render all the line comments at the beginning of the file.
94 const comment_end_loc = tree.tokenStart(0);
95 _ = try renderComments(&r, 0, comment_end_loc);
96
97 if (tree.tokenTag(0) == .container_doc_comment) {
98 try renderContainerDocComments(&r, 0);
99 }
100
101 switch (tree.mode) {
102 .zig => try renderMembers(&r, tree.rootDecls()),
103 .zon => {
104 try renderExpression(
105 &r,
106 tree.rootDecls()[0],
107 .newline,
108 );
109 },
110 }
111
112 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
113 try writeFixingWhitespace(auto_indenting_stream.underlying_writer, tree.source[disabled_offset..]);
114 }
115}
116
117/// Render all members in the given slice, keeping empty lines where appropriate
118fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
119 const tree = r.tree;
120 if (members.len == 0) return;
121 const container: Container = for (members) |member| {
122 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
123 } else .tuple;
124 try renderMember(r, container, members[0], .newline);
125 for (members[1..]) |member| {
126 try renderExtraNewline(r, member);
127 try renderMember(r, container, member, .newline);
128 }
129}
130
131const Container = enum {
132 @"enum",
133 tuple,
134 other,
135};
136
137fn renderMember(
138 r: *Render,
139 container: Container,
140 decl: Ast.Node.Index,
141 space: Space,
142) Error!void {
143 const tree = r.tree;
144 const ais = r.ais;
145 if (r.fixups.omit_nodes.contains(decl)) return;
146 try renderDocComments(r, tree.firstToken(decl));
147 switch (tree.nodeTag(decl)) {
148 .fn_decl => {
149 // Some examples:
150 // pub extern "foo" fn ...
151 // export fn ...
152 const fn_proto, const body_node = tree.nodeData(decl).node_and_node;
153 const fn_token = tree.nodeMainToken(fn_proto);
154 // Go back to the first token we should render here.
155 var i = fn_token;
156 while (i > 0) {
157 i -= 1;
158 switch (tree.tokenTag(i)) {
159 .keyword_extern,
160 .keyword_export,
161 .keyword_pub,
162 .string_literal,
163 .keyword_inline,
164 .keyword_noinline,
165 => continue,
166
167 else => {
168 i += 1;
169 break;
170 },
171 }
172 }
173
174 while (i < fn_token) : (i += 1) {
175 try renderToken(r, i, .space);
176 }
177 switch (tree.nodeTag(fn_proto)) {
178 .fn_proto_one, .fn_proto => {
179 var buf: [1]Ast.Node.Index = undefined;
180 const opt_callconv_expr = if (tree.nodeTag(fn_proto) == .fn_proto_one)
181 tree.fnProtoOne(&buf, fn_proto).ast.callconv_expr
182 else
183 tree.fnProto(fn_proto).ast.callconv_expr;
184
185 // Keep in sync with logic in `renderFnProto`. Search this file for the marker PROMOTE_CALLCONV_INLINE
186 if (opt_callconv_expr.unwrap()) |callconv_expr| {
187 if (tree.nodeTag(callconv_expr) == .enum_literal) {
188 if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) {
189 try ais.writer().writeAll("inline ");
190 }
191 }
192 }
193 },
194 .fn_proto_simple, .fn_proto_multi => {},
195 else => unreachable,
196 }
197 try renderExpression(r, fn_proto, .space);
198 if (r.fixups.gut_functions.contains(decl)) {
199 try ais.pushIndent(.normal);
200 const lbrace = tree.nodeMainToken(body_node);
201 try renderToken(r, lbrace, .newline);
202 try discardAllParams(r, fn_proto);
203 try ais.writer().writeAll("@trap();");
204 ais.popIndent();
205 try ais.insertNewline();
206 try renderToken(r, tree.lastToken(body_node), space); // rbrace
207 } else if (r.fixups.unused_var_decls.count() != 0) {
208 try ais.pushIndent(.normal);
209 const lbrace = tree.nodeMainToken(body_node);
210 try renderToken(r, lbrace, .newline);
211
212 var fn_proto_buf: [1]Ast.Node.Index = undefined;
213 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
214 var it = full_fn_proto.iterate(&tree);
215 while (it.next()) |param| {
216 const name_ident = param.name_token.?;
217 assert(tree.tokenTag(name_ident) == .identifier);
218 if (r.fixups.unused_var_decls.contains(name_ident)) {
219 const w = ais.writer();
220 try w.writeAll("_ = ");
221 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
222 try w.writeAll(";\n");
223 }
224 }
225 var statements_buf: [2]Ast.Node.Index = undefined;
226 const statements = tree.blockStatements(&statements_buf, body_node).?;
227 return finishRenderBlock(r, body_node, statements, space);
228 } else {
229 return renderExpression(r, body_node, space);
230 }
231 },
232 .fn_proto_simple,
233 .fn_proto_multi,
234 .fn_proto_one,
235 .fn_proto,
236 => {
237 // Extern function prototypes are parsed as these tags.
238 // Go back to the first token we should render here.
239 const fn_token = tree.nodeMainToken(decl);
240 var i = fn_token;
241 while (i > 0) {
242 i -= 1;
243 switch (tree.tokenTag(i)) {
244 .keyword_extern,
245 .keyword_export,
246 .keyword_pub,
247 .string_literal,
248 .keyword_inline,
249 .keyword_noinline,
250 => continue,
251
252 else => {
253 i += 1;
254 break;
255 },
256 }
257 }
258 while (i < fn_token) : (i += 1) {
259 try renderToken(r, i, .space);
260 }
261 try renderExpression(r, decl, .none);
262 return renderToken(r, tree.lastToken(decl) + 1, space); // semicolon
263 },
264
265 .global_var_decl,
266 .local_var_decl,
267 .simple_var_decl,
268 .aligned_var_decl,
269 => {
270 try ais.pushSpace(.semicolon);
271 try renderVarDecl(r, tree.fullVarDecl(decl).?, false, .semicolon);
272 ais.popSpace();
273 },
274
275 .test_decl => {
276 const test_token = tree.nodeMainToken(decl);
277 const opt_name_token, const block_node = tree.nodeData(decl).opt_token_and_node;
278 try renderToken(r, test_token, .space);
279 if (opt_name_token.unwrap()) |name_token| {
280 switch (tree.tokenTag(name_token)) {
281 .string_literal => try renderToken(r, name_token, .space),
282 .identifier => try renderIdentifier(r, name_token, .space, .preserve_when_shadowing),
283 else => unreachable,
284 }
285 }
286 try renderExpression(r, block_node, space);
287 },
288
289 .container_field_init,
290 .container_field_align,
291 .container_field,
292 => return renderContainerField(r, container, tree.fullContainerField(decl).?, space),
293
294 .@"comptime" => return renderExpression(r, decl, space),
295
296 .root => unreachable,
297 else => unreachable,
298 }
299}
300
301/// Render all expressions in the slice, keeping empty lines where appropriate
302fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
303 if (expressions.len == 0) return;
304 try renderExpression(r, expressions[0], space);
305 for (expressions[1..]) |expression| {
306 try renderExtraNewline(r, expression);
307 try renderExpression(r, expression, space);
308 }
309}
310
311fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
312 const tree = r.tree;
313 const ais = r.ais;
314 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
315 try ais.writer().writeAll(replacement);
316 try renderOnlySpace(r, space);
317 return;
318 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
319 return renderExpression(r, replacement, space);
320 }
321 switch (tree.nodeTag(node)) {
322 .identifier => {
323 const token_index = tree.nodeMainToken(node);
324 return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
325 },
326
327 .number_literal,
328 .char_literal,
329 .unreachable_literal,
330 .anyframe_literal,
331 .string_literal,
332 => return renderToken(r, tree.nodeMainToken(node), space),
333
334 .multiline_string_literal => {
335 try ais.maybeInsertNewline();
336
337 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
338 for (first_tok..last_tok + 1) |i| {
339 try renderToken(r, @intCast(i), .newline);
340 }
341
342 const next_token = last_tok + 1;
343 const next_token_tag = tree.tokenTag(next_token);
344
345 // dedent the next thing that comes after a multiline string literal
346 if (!ais.indentStackEmpty() and
347 next_token_tag != .colon and
348 ((next_token_tag != .semicolon and next_token_tag != .comma) or
349 ais.lastSpaceModeIndent() < ais.currentIndent()))
350 {
351 ais.popIndent();
352 try ais.pushIndent(.normal);
353 }
354
355 switch (space) {
356 .none, .space, .newline, .skip => {},
357 .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
358 .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
359 .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space),
360 }
361 },
362
363 .error_value => {
364 const main_token = tree.nodeMainToken(node);
365 try renderToken(r, main_token, .none);
366 try renderToken(r, main_token + 1, .none);
367 return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
368 },
369
370 .block_two,
371 .block_two_semicolon,
372 .block,
373 .block_semicolon,
374 => {
375 var buf: [2]Ast.Node.Index = undefined;
376 const statements = tree.blockStatements(&buf, node).?;
377 return renderBlock(r, node, statements, space);
378 },
379
380 .@"errdefer" => {
381 const defer_token = tree.nodeMainToken(node);
382 const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
383
384 try renderToken(r, defer_token, .space);
385 if (maybe_payload_token.unwrap()) |payload_token| {
386 try renderToken(r, payload_token - 1, .none); // |
387 try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
388 try renderToken(r, payload_token + 1, .space); // |
389 }
390 return renderExpression(r, expr, space);
391 },
392
393 .@"defer",
394 .@"comptime",
395 .@"nosuspend",
396 .@"suspend",
397 => {
398 const main_token = tree.nodeMainToken(node);
399 const item = tree.nodeData(node).node;
400 try renderToken(r, main_token, .space);
401 return renderExpression(r, item, space);
402 },
403
404 .@"catch" => {
405 const main_token = tree.nodeMainToken(node);
406 const lhs, const rhs = tree.nodeData(node).node_and_node;
407 const fallback_first = tree.firstToken(rhs);
408
409 const same_line = tree.tokensOnSameLine(main_token, fallback_first);
410 const after_op_space = if (same_line) Space.space else Space.newline;
411
412 try renderExpression(r, lhs, .space); // target
413
414 try ais.pushIndent(.normal);
415 if (tree.tokenTag(fallback_first - 1) == .pipe) {
416 try renderToken(r, main_token, .space); // catch keyword
417 try renderToken(r, main_token + 1, .none); // pipe
418 try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
419 try renderToken(r, main_token + 3, after_op_space); // pipe
420 } else {
421 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
422 try renderToken(r, main_token, after_op_space); // catch keyword
423 }
424 try renderExpression(r, rhs, space); // fallback
425 ais.popIndent();
426 },
427
428 .field_access => {
429 const lhs, const name_token = tree.nodeData(node).node_and_token;
430 const dot_token = name_token - 1;
431
432 try ais.pushIndent(.field_access);
433 try renderExpression(r, lhs, .none);
434
435 // Allow a line break between the lhs and the dot if the lhs and rhs
436 // are on different lines.
437 const lhs_last_token = tree.lastToken(lhs);
438 const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
439 if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline();
440
441 try renderToken(r, dot_token, .none);
442
443 try renderIdentifier(r, name_token, space, .eagerly_unquote); // field
444 ais.popIndent();
445 },
446
447 .error_union,
448 .switch_range,
449 => {
450 const lhs, const rhs = tree.nodeData(node).node_and_node;
451 try renderExpression(r, lhs, .none);
452 try renderToken(r, tree.nodeMainToken(node), .none);
453 return renderExpression(r, rhs, space);
454 },
455 .for_range => {
456 const start, const opt_end = tree.nodeData(node).node_and_opt_node;
457 try renderExpression(r, start, .none);
458 if (opt_end.unwrap()) |end| {
459 try renderToken(r, tree.nodeMainToken(node), .none);
460 return renderExpression(r, end, space);
461 } else {
462 return renderToken(r, tree.nodeMainToken(node), space);
463 }
464 },
465
466 .assign,
467 .assign_bit_and,
468 .assign_bit_or,
469 .assign_shl,
470 .assign_shl_sat,
471 .assign_shr,
472 .assign_bit_xor,
473 .assign_div,
474 .assign_sub,
475 .assign_sub_wrap,
476 .assign_sub_sat,
477 .assign_mod,
478 .assign_add,
479 .assign_add_wrap,
480 .assign_add_sat,
481 .assign_mul,
482 .assign_mul_wrap,
483 .assign_mul_sat,
484 => {
485 const lhs, const rhs = tree.nodeData(node).node_and_node;
486 try renderExpression(r, lhs, .space);
487 const op_token = tree.nodeMainToken(node);
488 try ais.pushIndent(.after_equals);
489 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
490 try renderToken(r, op_token, .space);
491 } else {
492 try renderToken(r, op_token, .newline);
493 }
494 try renderExpression(r, rhs, space);
495 ais.popIndent();
496 },
497
498 .add,
499 .add_wrap,
500 .add_sat,
501 .array_cat,
502 .array_mult,
503 .bang_equal,
504 .bit_and,
505 .bit_or,
506 .shl,
507 .shl_sat,
508 .shr,
509 .bit_xor,
510 .bool_and,
511 .bool_or,
512 .div,
513 .equal_equal,
514 .greater_or_equal,
515 .greater_than,
516 .less_or_equal,
517 .less_than,
518 .merge_error_sets,
519 .mod,
520 .mul,
521 .mul_wrap,
522 .mul_sat,
523 .sub,
524 .sub_wrap,
525 .sub_sat,
526 .@"orelse",
527 => {
528 const lhs, const rhs = tree.nodeData(node).node_and_node;
529 try renderExpression(r, lhs, .space);
530 const op_token = tree.nodeMainToken(node);
531 try ais.pushIndent(.binop);
532 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
533 try renderToken(r, op_token, .space);
534 } else {
535 try renderToken(r, op_token, .newline);
536 }
537 try renderExpression(r, rhs, space);
538 ais.popIndent();
539 },
540
541 .assign_destructure => {
542 const full = tree.assignDestructure(node);
543 if (full.comptime_token) |comptime_token| {
544 try renderToken(r, comptime_token, .space);
545 }
546
547 for (full.ast.variables, 0..) |variable_node, i| {
548 const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space;
549 switch (tree.nodeTag(variable_node)) {
550 .global_var_decl,
551 .local_var_decl,
552 .simple_var_decl,
553 .aligned_var_decl,
554 => {
555 try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
556 },
557 else => try renderExpression(r, variable_node, variable_space),
558 }
559 }
560 try ais.pushIndent(.after_equals);
561 if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
562 try renderToken(r, full.ast.equal_token, .space);
563 } else {
564 try renderToken(r, full.ast.equal_token, .newline);
565 }
566 try renderExpression(r, full.ast.value_expr, space);
567 ais.popIndent();
568 },
569
570 .bit_not,
571 .bool_not,
572 .negation,
573 .negation_wrap,
574 .optional_type,
575 .address_of,
576 => {
577 try renderToken(r, tree.nodeMainToken(node), .none);
578 return renderExpression(r, tree.nodeData(node).node, space);
579 },
580
581 .@"try",
582 .@"resume",
583 => {
584 try renderToken(r, tree.nodeMainToken(node), .space);
585 return renderExpression(r, tree.nodeData(node).node, space);
586 },
587
588 .array_type,
589 .array_type_sentinel,
590 => return renderArrayType(r, tree.fullArrayType(node).?, space),
591
592 .ptr_type_aligned,
593 .ptr_type_sentinel,
594 .ptr_type,
595 .ptr_type_bit_range,
596 => return renderPtrType(r, tree.fullPtrType(node).?, space),
597
598 .array_init_one,
599 .array_init_one_comma,
600 .array_init_dot_two,
601 .array_init_dot_two_comma,
602 .array_init_dot,
603 .array_init_dot_comma,
604 .array_init,
605 .array_init_comma,
606 => {
607 var elements: [2]Ast.Node.Index = undefined;
608 return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
609 },
610
611 .struct_init_one,
612 .struct_init_one_comma,
613 .struct_init_dot_two,
614 .struct_init_dot_two_comma,
615 .struct_init_dot,
616 .struct_init_dot_comma,
617 .struct_init,
618 .struct_init_comma,
619 => {
620 var buf: [2]Ast.Node.Index = undefined;
621 return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
622 },
623
624 .call_one,
625 .call_one_comma,
626 .call,
627 .call_comma,
628 => {
629 var buf: [1]Ast.Node.Index = undefined;
630 return renderCall(r, tree.fullCall(&buf, node).?, space);
631 },
632
633 .array_access => {
634 const lhs, const rhs = tree.nodeData(node).node_and_node;
635 const lbracket = tree.firstToken(rhs) - 1;
636 const rbracket = tree.lastToken(rhs) + 1;
637 const one_line = tree.tokensOnSameLine(lbracket, rbracket);
638 const inner_space = if (one_line) Space.none else Space.newline;
639 try renderExpression(r, lhs, .none);
640 try ais.pushIndent(.normal);
641 try renderToken(r, lbracket, inner_space); // [
642 try renderExpression(r, rhs, inner_space);
643 ais.popIndent();
644 return renderToken(r, rbracket, space); // ]
645 },
646
647 .slice_open,
648 .slice,
649 .slice_sentinel,
650 => return renderSlice(r, node, tree.fullSlice(node).?, space),
651
652 .deref => {
653 try renderExpression(r, tree.nodeData(node).node, .none);
654 return renderToken(r, tree.nodeMainToken(node), space);
655 },
656
657 .unwrap_optional => {
658 const lhs, const question_mark = tree.nodeData(node).node_and_token;
659 const dot_token = question_mark - 1;
660 try renderExpression(r, lhs, .none);
661 try renderToken(r, dot_token, .none);
662 return renderToken(r, question_mark, space);
663 },
664
665 .@"break", .@"continue" => {
666 const main_token = tree.nodeMainToken(node);
667 const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
668 if (opt_label_token == .none and opt_target == .none) {
669 try renderToken(r, main_token, space); // break/continue
670 } else if (opt_label_token == .none and opt_target != .none) {
671 const target = opt_target.unwrap().?;
672 try renderToken(r, main_token, .space); // break/continue
673 try renderExpression(r, target, space);
674 } else if (opt_label_token != .none and opt_target == .none) {
675 const label_token = opt_label_token.unwrap().?;
676 try renderToken(r, main_token, .space); // break/continue
677 try renderToken(r, label_token - 1, .none); // :
678 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
679 } else if (opt_label_token != .none and opt_target != .none) {
680 const label_token = opt_label_token.unwrap().?;
681 const target = opt_target.unwrap().?;
682 try renderToken(r, main_token, .space); // break/continue
683 try renderToken(r, label_token - 1, .none); // :
684 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
685 try renderExpression(r, target, space);
686 } else unreachable;
687 },
688
689 .@"return" => {
690 if (tree.nodeData(node).opt_node.unwrap()) |expr| {
691 try renderToken(r, tree.nodeMainToken(node), .space);
692 try renderExpression(r, expr, space);
693 } else {
694 try renderToken(r, tree.nodeMainToken(node), space);
695 }
696 },
697
698 .grouped_expression => {
699 const expr, const rparen = tree.nodeData(node).node_and_token;
700 try ais.pushIndent(.normal);
701 try renderToken(r, tree.nodeMainToken(node), .none); // lparen
702 try renderExpression(r, expr, .none);
703 ais.popIndent();
704 return renderToken(r, rparen, space);
705 },
706
707 .container_decl,
708 .container_decl_trailing,
709 .container_decl_arg,
710 .container_decl_arg_trailing,
711 .container_decl_two,
712 .container_decl_two_trailing,
713 .tagged_union,
714 .tagged_union_trailing,
715 .tagged_union_enum_tag,
716 .tagged_union_enum_tag_trailing,
717 .tagged_union_two,
718 .tagged_union_two_trailing,
719 => {
720 var buf: [2]Ast.Node.Index = undefined;
721 return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
722 },
723
724 .error_set_decl => {
725 const error_token = tree.nodeMainToken(node);
726 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
727
728 try renderToken(r, error_token, .none);
729
730 if (lbrace + 1 == rbrace) {
731 // There is nothing between the braces so render condensed: `error{}`
732 try renderToken(r, lbrace, .none);
733 return renderToken(r, rbrace, space);
734 } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
735 // There is exactly one member and no trailing comma or
736 // comments, so render without surrounding spaces: `error{Foo}`
737 try renderToken(r, lbrace, .none);
738 try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
739 return renderToken(r, rbrace, space);
740 } else if (tree.tokenTag(rbrace - 1) == .comma) {
741 // There is a trailing comma so render each member on a new line.
742 try ais.pushIndent(.normal);
743 try renderToken(r, lbrace, .newline);
744 var i = lbrace + 1;
745 while (i < rbrace) : (i += 1) {
746 if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
747 switch (tree.tokenTag(i)) {
748 .doc_comment => try renderToken(r, i, .newline),
749 .identifier => {
750 try ais.pushSpace(.comma);
751 try renderIdentifier(r, i, .comma, .eagerly_unquote);
752 ais.popSpace();
753 },
754 .comma => {},
755 else => unreachable,
756 }
757 }
758 ais.popIndent();
759 return renderToken(r, rbrace, space);
760 } else {
761 // There is no trailing comma so render everything on one line.
762 try renderToken(r, lbrace, .space);
763 var i = lbrace + 1;
764 while (i < rbrace) : (i += 1) {
765 switch (tree.tokenTag(i)) {
766 .doc_comment => unreachable, // TODO
767 .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
768 .comma => {},
769 else => unreachable,
770 }
771 }
772 return renderToken(r, rbrace, space);
773 }
774 },
775
776 .builtin_call_two,
777 .builtin_call_two_comma,
778 .builtin_call,
779 .builtin_call_comma,
780 => {
781 var buf: [2]Ast.Node.Index = undefined;
782 const params = tree.builtinCallParams(&buf, node).?;
783 return renderBuiltinCall(r, tree.nodeMainToken(node), params, space);
784 },
785
786 .fn_proto_simple,
787 .fn_proto_multi,
788 .fn_proto_one,
789 .fn_proto,
790 => {
791 var buf: [1]Ast.Node.Index = undefined;
792 return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
793 },
794
795 .anyframe_type => {
796 const main_token = tree.nodeMainToken(node);
797 try renderToken(r, main_token, .none); // anyframe
798 try renderToken(r, main_token + 1, .none); // ->
799 return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
800 },
801
802 .@"switch",
803 .switch_comma,
804 => {
805 const full = tree.switchFull(node);
806
807 if (full.label_token) |label_token| {
808 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
809 try renderToken(r, label_token + 1, .space); // :
810 }
811
812 const rparen = tree.lastToken(full.ast.condition) + 1;
813
814 try renderToken(r, full.ast.switch_token, .space); // switch
815 try renderToken(r, full.ast.switch_token + 1, .none); // (
816 try renderExpression(r, full.ast.condition, .none); // condition expression
817 try renderToken(r, rparen, .space); // )
818
819 try ais.pushIndent(.normal);
820 if (full.ast.cases.len == 0) {
821 try renderToken(r, rparen + 1, .none); // {
822 } else {
823 try renderToken(r, rparen + 1, .newline); // {
824 try ais.pushSpace(.comma);
825 try renderExpressions(r, full.ast.cases, .comma);
826 ais.popSpace();
827 }
828 ais.popIndent();
829 return renderToken(r, tree.lastToken(node), space); // }
830 },
831
832 .switch_case_one,
833 .switch_case_inline_one,
834 .switch_case,
835 .switch_case_inline,
836 => return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
837
838 .while_simple,
839 .while_cont,
840 .@"while",
841 => return renderWhile(r, tree.fullWhile(node).?, space),
842
843 .for_simple,
844 .@"for",
845 => return renderFor(r, tree.fullFor(node).?, space),
846
847 .if_simple,
848 .@"if",
849 => return renderIf(r, tree.fullIf(node).?, space),
850
851 .asm_simple,
852 .@"asm",
853 => return renderAsm(r, tree.fullAsm(node).?, space),
854
855 // To be removed after 0.15.0 is tagged
856 .asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),
857
858 .enum_literal => {
859 try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
860 return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
861 },
862
863 .fn_decl => unreachable,
864 .container_field => unreachable,
865 .container_field_init => unreachable,
866 .container_field_align => unreachable,
867 .root => unreachable,
868 .global_var_decl => unreachable,
869 .local_var_decl => unreachable,
870 .simple_var_decl => unreachable,
871 .aligned_var_decl => unreachable,
872 .test_decl => unreachable,
873 .asm_output => unreachable,
874 .asm_input => unreachable,
875 }
876}
877
878/// Same as `renderExpression`, but afterwards looks for any
879/// append_string_after_node fixups to apply
880fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
881 const ais = r.ais;
882 try renderExpression(r, node, space);
883 if (r.fixups.append_string_after_node.get(node)) |bytes| {
884 try ais.writer().writeAll(bytes);
885 }
886}
887
888fn renderArrayType(
889 r: *Render,
890 array_type: Ast.full.ArrayType,
891 space: Space,
892) Error!void {
893 const tree = r.tree;
894 const ais = r.ais;
895 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
896 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket);
897 const inner_space = if (one_line) Space.none else Space.newline;
898 try ais.pushIndent(.normal);
899 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
900 try renderExpression(r, array_type.ast.elem_count, inner_space);
901 if (array_type.ast.sentinel.unwrap()) |sentinel| {
902 try renderToken(r, tree.firstToken(sentinel) - 1, inner_space); // colon
903 try renderExpression(r, sentinel, inner_space);
904 }
905 ais.popIndent();
906 try renderToken(r, rbracket, .none); // rbracket
907 return renderExpression(r, array_type.ast.elem_type, space);
908}
909
910fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
911 const tree = r.tree;
912 const main_token = ptr_type.ast.main_token;
913 switch (ptr_type.size) {
914 .one => {
915 // Since ** tokens exist and the same token is shared by two
916 // nested pointer types, we check to see if we are the parent
917 // in such a relationship. If so, skip rendering anything for
918 // this pointer type and rely on the child to render our asterisk
919 // as well when it renders the ** token.
920 if (tree.tokenTag(main_token) == .asterisk_asterisk and
921 main_token == tree.nodeMainToken(ptr_type.ast.child_type))
922 {
923 return renderExpression(r, ptr_type.ast.child_type, space);
924 }
925 try renderToken(r, main_token, .none); // asterisk
926 },
927 .many => {
928 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
929 try renderToken(r, main_token, .none); // lbracket
930 try renderToken(r, main_token + 1, .none); // asterisk
931 try renderToken(r, main_token + 2, .none); // colon
932 try renderExpression(r, sentinel, .none);
933 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
934 } else {
935 try renderToken(r, main_token, .none); // lbracket
936 try renderToken(r, main_token + 1, .none); // asterisk
937 try renderToken(r, main_token + 2, .none); // rbracket
938 }
939 },
940 .c => {
941 try renderToken(r, main_token, .none); // lbracket
942 try renderToken(r, main_token + 1, .none); // asterisk
943 try renderToken(r, main_token + 2, .none); // c
944 try renderToken(r, main_token + 3, .none); // rbracket
945 },
946 .slice => {
947 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
948 try renderToken(r, main_token, .none); // lbracket
949 try renderToken(r, main_token + 1, .none); // colon
950 try renderExpression(r, sentinel, .none);
951 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
952 } else {
953 try renderToken(r, main_token, .none); // lbracket
954 try renderToken(r, main_token + 1, .none); // rbracket
955 }
956 },
957 }
958
959 if (ptr_type.allowzero_token) |allowzero_token| {
960 try renderToken(r, allowzero_token, .space);
961 }
962
963 if (ptr_type.ast.align_node.unwrap()) |align_node| {
964 const align_first = tree.firstToken(align_node);
965 try renderToken(r, align_first - 2, .none); // align
966 try renderToken(r, align_first - 1, .none); // lparen
967 try renderExpression(r, align_node, .none);
968 if (ptr_type.ast.bit_range_start.unwrap()) |bit_range_start| {
969 const bit_range_end = ptr_type.ast.bit_range_end.unwrap().?;
970 try renderToken(r, tree.firstToken(bit_range_start) - 1, .none); // colon
971 try renderExpression(r, bit_range_start, .none);
972 try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon
973 try renderExpression(r, bit_range_end, .none);
974 try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen
975 } else {
976 try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen
977 }
978 }
979
980 if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| {
981 const addrspace_first = tree.firstToken(addrspace_node);
982 try renderToken(r, addrspace_first - 2, .none); // addrspace
983 try renderToken(r, addrspace_first - 1, .none); // lparen
984 try renderExpression(r, addrspace_node, .none);
985 try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen
986 }
987
988 if (ptr_type.const_token) |const_token| {
989 try renderToken(r, const_token, .space);
990 }
991
992 if (ptr_type.volatile_token) |volatile_token| {
993 try renderToken(r, volatile_token, .space);
994 }
995
996 try renderExpression(r, ptr_type.ast.child_type, space);
997}
998
999fn renderSlice(
1000 r: *Render,
1001 slice_node: Ast.Node.Index,
1002 slice: Ast.full.Slice,
1003 space: Space,
1004) Error!void {
1005 const tree = r.tree;
1006 const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or
1007 if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false;
1008 const after_start_space = if (after_start_space_bool) Space.space else Space.none;
1009 const after_dots_space = if (slice.ast.end != .none)
1010 after_start_space
1011 else if (slice.ast.sentinel != .none) Space.space else Space.none;
1012
1013 try renderExpression(r, slice.ast.sliced, .none);
1014 try renderToken(r, slice.ast.lbracket, .none); // lbracket
1015
1016 const start_last = tree.lastToken(slice.ast.start);
1017 try renderExpression(r, slice.ast.start, after_start_space);
1018 try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..")
1019
1020 if (slice.ast.end.unwrap()) |end| {
1021 const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none;
1022 try renderExpression(r, end, after_end_space);
1023 }
1024
1025 if (slice.ast.sentinel.unwrap()) |sentinel| {
1026 try renderToken(r, tree.firstToken(sentinel) - 1, .none); // colon
1027 try renderExpression(r, sentinel, .none);
1028 }
1029
1030 try renderToken(r, tree.lastToken(slice_node), space); // rbracket
1031}
1032
1033fn renderAsmOutput(
1034 r: *Render,
1035 asm_output: Ast.Node.Index,
1036 space: Space,
1037) Error!void {
1038 const tree = r.tree;
1039 assert(tree.nodeTag(asm_output) == .asm_output);
1040 const symbolic_name = tree.nodeMainToken(asm_output);
1041
1042 try renderToken(r, symbolic_name - 1, .none); // lbracket
1043 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1044 try renderToken(r, symbolic_name + 1, .space); // rbracket
1045 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1046 try renderToken(r, symbolic_name + 3, .none); // lparen
1047
1048 if (tree.tokenTag(symbolic_name + 4) == .arrow) {
1049 const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token;
1050 try renderToken(r, symbolic_name + 4, .space); // ->
1051 try renderExpression(r, type_expr.unwrap().?, Space.none);
1052 return renderToken(r, rparen, space);
1053 } else {
1054 try renderIdentifier(r, symbolic_name + 4, .none, .eagerly_unquote); // ident
1055 return renderToken(r, symbolic_name + 5, space); // rparen
1056 }
1057}
1058
1059fn renderAsmInput(
1060 r: *Render,
1061 asm_input: Ast.Node.Index,
1062 space: Space,
1063) Error!void {
1064 const tree = r.tree;
1065 assert(tree.nodeTag(asm_input) == .asm_input);
1066 const symbolic_name = tree.nodeMainToken(asm_input);
1067 const expr, const rparen = tree.nodeData(asm_input).node_and_token;
1068
1069 try renderToken(r, symbolic_name - 1, .none); // lbracket
1070 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1071 try renderToken(r, symbolic_name + 1, .space); // rbracket
1072 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1073 try renderToken(r, symbolic_name + 3, .none); // lparen
1074 try renderExpression(r, expr, Space.none);
1075 return renderToken(r, rparen, space);
1076}
1077
1078fn renderVarDecl(
1079 r: *Render,
1080 var_decl: Ast.full.VarDecl,
1081 /// Destructures intentionally ignore leading `comptime` tokens.
1082 ignore_comptime_token: bool,
1083 /// `comma_space` and `space` are used for destructure LHS decls.
1084 space: Space,
1085) Error!void {
1086 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1087 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
1088 // Discard the variable like this: `_ = foo;`
1089 const w = r.ais.writer();
1090 try w.writeAll("_ = ");
1091 try w.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1092 try w.writeAll(";\n");
1093 }
1094}
1095
1096fn renderVarDeclWithoutFixups(
1097 r: *Render,
1098 var_decl: Ast.full.VarDecl,
1099 /// Destructures intentionally ignore leading `comptime` tokens.
1100 ignore_comptime_token: bool,
1101 /// `comma_space` and `space` are used for destructure LHS decls.
1102 space: Space,
1103) Error!void {
1104 const tree = r.tree;
1105 const ais = r.ais;
1106
1107 if (var_decl.visib_token) |visib_token| {
1108 try renderToken(r, visib_token, Space.space); // pub
1109 }
1110
1111 if (var_decl.extern_export_token) |extern_export_token| {
1112 try renderToken(r, extern_export_token, Space.space); // extern
1113
1114 if (var_decl.lib_name) |lib_name| {
1115 try renderToken(r, lib_name, Space.space); // "lib"
1116 }
1117 }
1118
1119 if (var_decl.threadlocal_token) |thread_local_token| {
1120 try renderToken(r, thread_local_token, Space.space); // threadlocal
1121 }
1122
1123 if (!ignore_comptime_token) {
1124 if (var_decl.comptime_token) |comptime_token| {
1125 try renderToken(r, comptime_token, Space.space); // comptime
1126 }
1127 }
1128
1129 try renderToken(r, var_decl.ast.mut_token, .space); // var
1130
1131 if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or
1132 var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1133 var_decl.ast.init_node != .none)
1134 {
1135 const name_space = if (var_decl.ast.type_node == .none and
1136 (var_decl.ast.align_node != .none or
1137 var_decl.ast.addrspace_node != .none or
1138 var_decl.ast.section_node != .none or
1139 var_decl.ast.init_node != .none))
1140 Space.space
1141 else
1142 Space.none;
1143
1144 try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name
1145 } else {
1146 return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name
1147 }
1148
1149 if (var_decl.ast.type_node.unwrap()) |type_node| {
1150 try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // :
1151 if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or
1152 var_decl.ast.section_node != .none or var_decl.ast.init_node != .none)
1153 {
1154 try renderExpression(r, type_node, .space);
1155 } else {
1156 return renderExpression(r, type_node, space);
1157 }
1158 }
1159
1160 if (var_decl.ast.align_node.unwrap()) |align_node| {
1161 const lparen = tree.firstToken(align_node) - 1;
1162 const align_kw = lparen - 1;
1163 const rparen = tree.lastToken(align_node) + 1;
1164 try renderToken(r, align_kw, Space.none); // align
1165 try renderToken(r, lparen, Space.none); // (
1166 try renderExpression(r, align_node, Space.none);
1167 if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1168 var_decl.ast.init_node != .none)
1169 {
1170 try renderToken(r, rparen, .space); // )
1171 } else {
1172 return renderToken(r, rparen, space); // )
1173 }
1174 }
1175
1176 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
1177 const lparen = tree.firstToken(addrspace_node) - 1;
1178 const addrspace_kw = lparen - 1;
1179 const rparen = tree.lastToken(addrspace_node) + 1;
1180 try renderToken(r, addrspace_kw, Space.none); // addrspace
1181 try renderToken(r, lparen, Space.none); // (
1182 try renderExpression(r, addrspace_node, Space.none);
1183 if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) {
1184 try renderToken(r, rparen, .space); // )
1185 } else {
1186 try renderToken(r, rparen, .none); // )
1187 return renderToken(r, rparen + 1, Space.newline); // ;
1188 }
1189 }
1190
1191 if (var_decl.ast.section_node.unwrap()) |section_node| {
1192 const lparen = tree.firstToken(section_node) - 1;
1193 const section_kw = lparen - 1;
1194 const rparen = tree.lastToken(section_node) + 1;
1195 try renderToken(r, section_kw, Space.none); // linksection
1196 try renderToken(r, lparen, Space.none); // (
1197 try renderExpression(r, section_node, Space.none);
1198 if (var_decl.ast.init_node != .none) {
1199 try renderToken(r, rparen, .space); // )
1200 } else {
1201 return renderToken(r, rparen, space); // )
1202 }
1203 }
1204
1205 const init_node = var_decl.ast.init_node.unwrap().?;
1206
1207 const eq_token = tree.firstToken(init_node) - 1;
1208 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1209 try ais.pushIndent(.after_equals);
1210 try renderToken(r, eq_token, eq_space); // =
1211 try renderExpression(r, init_node, space); // ;
1212 ais.popIndent();
1213}
1214
1215fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
1216 return renderWhile(r, .{
1217 .ast = .{
1218 .while_token = if_node.ast.if_token,
1219 .cond_expr = if_node.ast.cond_expr,
1220 .cont_expr = .none,
1221 .then_expr = if_node.ast.then_expr,
1222 .else_expr = if_node.ast.else_expr,
1223 },
1224 .inline_token = null,
1225 .label_token = null,
1226 .payload_token = if_node.payload_token,
1227 .else_token = if_node.else_token,
1228 .error_token = if_node.error_token,
1229 }, space);
1230}
1231
1232/// Note that this function is additionally used to render if expressions, with
1233/// respective values set to null.
1234fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
1235 const tree = r.tree;
1236
1237 if (while_node.label_token) |label| {
1238 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1239 try renderToken(r, label + 1, .space); // :
1240 }
1241
1242 if (while_node.inline_token) |inline_token| {
1243 try renderToken(r, inline_token, .space); // inline
1244 }
1245
1246 try renderToken(r, while_node.ast.while_token, .space); // if/for/while
1247 try renderToken(r, while_node.ast.while_token + 1, .none); // lparen
1248 try renderExpression(r, while_node.ast.cond_expr, .none); // condition
1249
1250 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
1251
1252 if (while_node.payload_token) |payload_token| {
1253 try renderToken(r, last_prefix_token, .space);
1254 try renderToken(r, payload_token - 1, .none); // |
1255 const ident = blk: {
1256 if (tree.tokenTag(payload_token) == .asterisk) {
1257 try renderToken(r, payload_token, .none); // *
1258 break :blk payload_token + 1;
1259 } else {
1260 break :blk payload_token;
1261 }
1262 };
1263 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1264 const pipe = blk: {
1265 if (tree.tokenTag(ident + 1) == .comma) {
1266 try renderToken(r, ident + 1, .space); // ,
1267 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // index
1268 break :blk ident + 3;
1269 } else {
1270 break :blk ident + 1;
1271 }
1272 };
1273 last_prefix_token = pipe;
1274 }
1275
1276 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
1277 try renderToken(r, last_prefix_token, .space);
1278 const lparen = tree.firstToken(cont_expr) - 1;
1279 try renderToken(r, lparen - 1, .space); // :
1280 try renderToken(r, lparen, .none); // lparen
1281 try renderExpression(r, cont_expr, .none);
1282 last_prefix_token = tree.lastToken(cont_expr) + 1; // rparen
1283 }
1284
1285 try renderThenElse(
1286 r,
1287 last_prefix_token,
1288 while_node.ast.then_expr,
1289 while_node.else_token,
1290 while_node.error_token,
1291 while_node.ast.else_expr,
1292 space,
1293 );
1294}
1295
1296fn renderThenElse(
1297 r: *Render,
1298 last_prefix_token: Ast.TokenIndex,
1299 then_expr: Ast.Node.Index,
1300 else_token: ?Ast.TokenIndex,
1301 maybe_error_token: ?Ast.TokenIndex,
1302 opt_else_expr: Ast.Node.OptionalIndex,
1303 space: Space,
1304) Error!void {
1305 const tree = r.tree;
1306 const ais = r.ais;
1307 const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr));
1308 const indent_then_expr = !then_expr_is_block and
1309 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr));
1310
1311 if (indent_then_expr) try ais.pushIndent(.normal);
1312
1313 if (then_expr_is_block and ais.isLineOverIndented()) {
1314 ais.disableIndentCommitting();
1315 try renderToken(r, last_prefix_token, .newline);
1316 ais.enableIndentCommitting();
1317 } else if (indent_then_expr) {
1318 try renderToken(r, last_prefix_token, .newline);
1319 } else {
1320 try renderToken(r, last_prefix_token, .space);
1321 }
1322
1323 if (opt_else_expr.unwrap()) |else_expr| {
1324 if (indent_then_expr) {
1325 try renderExpression(r, then_expr, .newline);
1326 } else {
1327 try renderExpression(r, then_expr, .space);
1328 }
1329
1330 if (indent_then_expr) ais.popIndent();
1331
1332 var last_else_token = else_token.?;
1333
1334 if (maybe_error_token) |error_token| {
1335 try renderToken(r, last_else_token, .space); // else
1336 try renderToken(r, error_token - 1, .none); // |
1337 try renderIdentifier(r, error_token, .none, .preserve_when_shadowing); // identifier
1338 last_else_token = error_token + 1; // |
1339 }
1340
1341 const indent_else_expr = indent_then_expr and
1342 !nodeIsBlock(tree.nodeTag(else_expr)) and
1343 !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr));
1344 if (indent_else_expr) {
1345 try ais.pushIndent(.normal);
1346 try renderToken(r, last_else_token, .newline);
1347 try renderExpression(r, else_expr, space);
1348 ais.popIndent();
1349 } else {
1350 try renderToken(r, last_else_token, .space);
1351 try renderExpression(r, else_expr, space);
1352 }
1353 } else {
1354 try renderExpression(r, then_expr, space);
1355 if (indent_then_expr) ais.popIndent();
1356 }
1357}
1358
1359fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
1360 const tree = r.tree;
1361 const ais = r.ais;
1362 const token_tags = tree.tokens.items(.tag);
1363
1364 if (for_node.label_token) |label| {
1365 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1366 try renderToken(r, label + 1, .space); // :
1367 }
1368
1369 if (for_node.inline_token) |inline_token| {
1370 try renderToken(r, inline_token, .space); // inline
1371 }
1372
1373 try renderToken(r, for_node.ast.for_token, .space); // if/for/while
1374
1375 const lparen = for_node.ast.for_token + 1;
1376 try renderParamList(r, lparen, for_node.ast.inputs, .space);
1377
1378 var cur = for_node.payload_token;
1379 const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?;
1380 if (tree.tokenTag(@intCast(pipe - 1)) == .comma) {
1381 try ais.pushIndent(.normal);
1382 try renderToken(r, cur - 1, .newline); // |
1383 while (true) {
1384 if (tree.tokenTag(cur) == .asterisk) {
1385 try renderToken(r, cur, .none); // *
1386 cur += 1;
1387 }
1388 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1389 cur += 1;
1390 if (tree.tokenTag(cur) == .comma) {
1391 try renderToken(r, cur, .newline); // ,
1392 cur += 1;
1393 }
1394 if (tree.tokenTag(cur) == .pipe) {
1395 break;
1396 }
1397 }
1398 ais.popIndent();
1399 } else {
1400 try renderToken(r, cur - 1, .none); // |
1401 while (true) {
1402 if (tree.tokenTag(cur) == .asterisk) {
1403 try renderToken(r, cur, .none); // *
1404 cur += 1;
1405 }
1406 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1407 cur += 1;
1408 if (tree.tokenTag(cur) == .comma) {
1409 try renderToken(r, cur, .space); // ,
1410 cur += 1;
1411 }
1412 if (tree.tokenTag(cur) == .pipe) {
1413 break;
1414 }
1415 }
1416 }
1417
1418 try renderThenElse(
1419 r,
1420 cur,
1421 for_node.ast.then_expr,
1422 for_node.else_token,
1423 null,
1424 for_node.ast.else_expr,
1425 space,
1426 );
1427}
1428
1429fn renderContainerField(
1430 r: *Render,
1431 container: Container,
1432 field_param: Ast.full.ContainerField,
1433 space: Space,
1434) Error!void {
1435 const tree = r.tree;
1436 const ais = r.ais;
1437 var field = field_param;
1438 if (container != .tuple) field.convertToNonTupleLike(&tree);
1439 const quote: QuoteBehavior = switch (container) {
1440 .@"enum" => .eagerly_unquote_except_underscore,
1441 .tuple, .other => .eagerly_unquote,
1442 };
1443
1444 if (field.comptime_token) |t| {
1445 try renderToken(r, t, .space); // comptime
1446 }
1447 if (field.ast.type_expr == .none and field.ast.value_expr == .none) {
1448 if (field.ast.align_expr.unwrap()) |align_expr| {
1449 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1450 const lparen_token = tree.firstToken(align_expr) - 1;
1451 const align_kw = lparen_token - 1;
1452 const rparen_token = tree.lastToken(align_expr) + 1;
1453 try renderToken(r, align_kw, .none); // align
1454 try renderToken(r, lparen_token, .none); // (
1455 try renderExpression(r, align_expr, .none); // alignment
1456 return renderToken(r, rparen_token, .space); // )
1457 }
1458 return renderIdentifierComma(r, field.ast.main_token, space, quote); // name
1459 }
1460 if (field.ast.type_expr != .none and field.ast.value_expr == .none) {
1461 const type_expr = field.ast.type_expr.unwrap().?;
1462 if (!field.ast.tuple_like) {
1463 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1464 try renderToken(r, field.ast.main_token + 1, .space); // :
1465 }
1466
1467 if (field.ast.align_expr.unwrap()) |align_expr| {
1468 try renderExpression(r, type_expr, .space); // type
1469 const align_token = tree.firstToken(align_expr) - 2;
1470 try renderToken(r, align_token, .none); // align
1471 try renderToken(r, align_token + 1, .none); // (
1472 try renderExpression(r, align_expr, .none); // alignment
1473 const rparen = tree.lastToken(align_expr) + 1;
1474 return renderTokenComma(r, rparen, space); // )
1475 } else {
1476 return renderExpressionComma(r, type_expr, space); // type
1477 }
1478 }
1479 if (field.ast.type_expr == .none and field.ast.value_expr != .none) {
1480 const value_expr = field.ast.value_expr.unwrap().?;
1481
1482 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1483 if (field.ast.align_expr.unwrap()) |align_expr| {
1484 const lparen_token = tree.firstToken(align_expr) - 1;
1485 const align_kw = lparen_token - 1;
1486 const rparen_token = tree.lastToken(align_expr) + 1;
1487 try renderToken(r, align_kw, .none); // align
1488 try renderToken(r, lparen_token, .none); // (
1489 try renderExpression(r, align_expr, .none); // alignment
1490 try renderToken(r, rparen_token, .space); // )
1491 }
1492 try renderToken(r, field.ast.main_token + 1, .space); // =
1493 return renderExpressionComma(r, value_expr, space); // value
1494 }
1495 if (!field.ast.tuple_like) {
1496 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1497 try renderToken(r, field.ast.main_token + 1, .space); // :
1498 }
1499
1500 const type_expr = field.ast.type_expr.unwrap().?;
1501 const value_expr = field.ast.value_expr.unwrap().?;
1502
1503 try renderExpression(r, type_expr, .space); // type
1504
1505 if (field.ast.align_expr.unwrap()) |align_expr| {
1506 const lparen_token = tree.firstToken(align_expr) - 1;
1507 const align_kw = lparen_token - 1;
1508 const rparen_token = tree.lastToken(align_expr) + 1;
1509 try renderToken(r, align_kw, .none); // align
1510 try renderToken(r, lparen_token, .none); // (
1511 try renderExpression(r, align_expr, .none); // alignment
1512 try renderToken(r, rparen_token, .space); // )
1513 }
1514 const eq_token = tree.firstToken(value_expr) - 1;
1515 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1516
1517 try ais.pushIndent(.after_equals);
1518 try renderToken(r, eq_token, eq_space); // =
1519
1520 if (eq_space == .space) {
1521 ais.popIndent();
1522 try renderExpressionComma(r, value_expr, space); // value
1523 return;
1524 }
1525
1526 const maybe_comma = tree.lastToken(value_expr) + 1;
1527
1528 if (tree.tokenTag(maybe_comma) == .comma) {
1529 try renderExpression(r, value_expr, .none); // value
1530 ais.popIndent();
1531 try renderToken(r, maybe_comma, .newline);
1532 } else {
1533 try renderExpression(r, value_expr, space); // value
1534 ais.popIndent();
1535 }
1536}
1537
1538fn renderBuiltinCall(
1539 r: *Render,
1540 builtin_token: Ast.TokenIndex,
1541 params: []const Ast.Node.Index,
1542 space: Space,
1543) Error!void {
1544 const tree = r.tree;
1545 const ais = r.ais;
1546
1547 try renderToken(r, builtin_token, .none); // @name
1548
1549 if (params.len == 0) {
1550 try renderToken(r, builtin_token + 1, .none); // (
1551 return renderToken(r, builtin_token + 2, space); // )
1552 }
1553
1554 if (r.fixups.rebase_imported_paths) |prefix| {
1555 const slice = tree.tokenSlice(builtin_token);
1556 if (mem.eql(u8, slice, "@import")) f: {
1557 const param = params[0];
1558 const str_lit_token = tree.nodeMainToken(param);
1559 assert(tree.tokenTag(str_lit_token) == .string_literal);
1560 const token_bytes = tree.tokenSlice(str_lit_token);
1561 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1562 error.OutOfMemory => return error.OutOfMemory,
1563 error.InvalidLiteral => break :f,
1564 };
1565 defer r.gpa.free(imported_string);
1566 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1567 defer r.gpa.free(new_string);
1568
1569 try renderToken(r, builtin_token + 1, .none); // (
1570 try ais.writer().print("\"{f}\"", .{std.zig.fmtString(new_string)});
1571 return renderToken(r, str_lit_token + 1, space); // )
1572 }
1573 }
1574
1575 const last_param = params[params.len - 1];
1576 const after_last_param_token = tree.lastToken(last_param) + 1;
1577
1578 if (tree.tokenTag(after_last_param_token) != .comma) {
1579 // Render all on one line, no trailing comma.
1580 try renderToken(r, builtin_token + 1, .none); // (
1581
1582 for (params, 0..) |param_node, i| {
1583 const first_param_token = tree.firstToken(param_node);
1584 if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or
1585 hasSameLineComment(tree, first_param_token - 1))
1586 {
1587 try ais.pushIndent(.normal);
1588 try renderExpression(r, param_node, .none);
1589 ais.popIndent();
1590 } else {
1591 try renderExpression(r, param_node, .none);
1592 }
1593
1594 if (i + 1 < params.len) {
1595 const comma_token = tree.lastToken(param_node) + 1;
1596 try renderToken(r, comma_token, .space); // ,
1597 }
1598 }
1599 return renderToken(r, after_last_param_token, space); // )
1600 } else {
1601 // Render one param per line.
1602 try ais.pushIndent(.normal);
1603 try renderToken(r, builtin_token + 1, Space.newline); // (
1604
1605 for (params) |param_node| {
1606 try ais.pushSpace(.comma);
1607 try renderExpression(r, param_node, .comma);
1608 ais.popSpace();
1609 }
1610 ais.popIndent();
1611
1612 return renderToken(r, after_last_param_token + 1, space); // )
1613 }
1614}
1615
1616fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1617 const tree = r.tree;
1618 const ais = r.ais;
1619
1620 const after_fn_token = fn_proto.ast.fn_token + 1;
1621 const lparen = if (tree.tokenTag(after_fn_token) == .identifier) blk: {
1622 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1623 try renderIdentifier(r, after_fn_token, .none, .preserve_when_shadowing); // name
1624 break :blk after_fn_token + 1;
1625 } else blk: {
1626 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1627 break :blk fn_proto.ast.fn_token + 1;
1628 };
1629 assert(tree.tokenTag(lparen) == .l_paren);
1630
1631 const return_type = fn_proto.ast.return_type.unwrap().?;
1632 const maybe_bang = tree.firstToken(return_type) - 1;
1633 const rparen = blk: {
1634 // These may appear in any order, so we have to check the token_starts array
1635 // to find out which is first.
1636 var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang;
1637 var smallest_start = tree.tokenStart(maybe_bang);
1638 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1639 const tok = tree.firstToken(align_expr) - 3;
1640 const start = tree.tokenStart(tok);
1641 if (start < smallest_start) {
1642 rparen = tok;
1643 smallest_start = start;
1644 }
1645 }
1646 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1647 const tok = tree.firstToken(addrspace_expr) - 3;
1648 const start = tree.tokenStart(tok);
1649 if (start < smallest_start) {
1650 rparen = tok;
1651 smallest_start = start;
1652 }
1653 }
1654 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1655 const tok = tree.firstToken(section_expr) - 3;
1656 const start = tree.tokenStart(tok);
1657 if (start < smallest_start) {
1658 rparen = tok;
1659 smallest_start = start;
1660 }
1661 }
1662 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1663 const tok = tree.firstToken(callconv_expr) - 3;
1664 const start = tree.tokenStart(tok);
1665 if (start < smallest_start) {
1666 rparen = tok;
1667 smallest_start = start;
1668 }
1669 }
1670 break :blk rparen;
1671 };
1672 assert(tree.tokenTag(rparen) == .r_paren);
1673
1674 // The params list is a sparse set that does *not* include anytype or ... parameters.
1675
1676 const trailing_comma = tree.tokenTag(rparen - 1) == .comma;
1677 if (!trailing_comma and !hasComment(tree, lparen, rparen)) {
1678 // Render all on one line, no trailing comma.
1679 try renderToken(r, lparen, .none); // (
1680
1681 var param_i: usize = 0;
1682 var last_param_token = lparen;
1683 while (true) {
1684 last_param_token += 1;
1685 switch (tree.tokenTag(last_param_token)) {
1686 .doc_comment => {
1687 try renderToken(r, last_param_token, .newline);
1688 continue;
1689 },
1690 .ellipsis3 => {
1691 try renderToken(r, last_param_token, .none); // ...
1692 break;
1693 },
1694 .keyword_noalias, .keyword_comptime => {
1695 try renderToken(r, last_param_token, .space);
1696 last_param_token += 1;
1697 },
1698 .identifier => {},
1699 .keyword_anytype => {
1700 try renderToken(r, last_param_token, .none); // anytype
1701 continue;
1702 },
1703 .r_paren => break,
1704 .comma => {
1705 try renderToken(r, last_param_token, .space); // ,
1706 continue;
1707 },
1708 else => {}, // Parameter type without a name.
1709 }
1710 if (tree.tokenTag(last_param_token) == .identifier and
1711 tree.tokenTag(last_param_token + 1) == .colon)
1712 {
1713 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1714 last_param_token = last_param_token + 1;
1715 try renderToken(r, last_param_token, .space); // :
1716 last_param_token += 1;
1717 }
1718 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1719 try renderToken(r, last_param_token, .none); // anytype
1720 continue;
1721 }
1722 const param = fn_proto.ast.params[param_i];
1723 param_i += 1;
1724 try renderExpression(r, param, .none);
1725 last_param_token = tree.lastToken(param);
1726 }
1727 } else {
1728 // One param per line.
1729 try ais.pushIndent(.normal);
1730 try renderToken(r, lparen, .newline); // (
1731
1732 var param_i: usize = 0;
1733 var last_param_token = lparen;
1734 while (true) {
1735 last_param_token += 1;
1736 switch (tree.tokenTag(last_param_token)) {
1737 .doc_comment => {
1738 try renderToken(r, last_param_token, .newline);
1739 continue;
1740 },
1741 .ellipsis3 => {
1742 try renderToken(r, last_param_token, .comma); // ...
1743 break;
1744 },
1745 .keyword_noalias, .keyword_comptime => {
1746 try renderToken(r, last_param_token, .space);
1747 last_param_token += 1;
1748 },
1749 .identifier => {},
1750 .keyword_anytype => {
1751 try renderToken(r, last_param_token, .comma); // anytype
1752 if (tree.tokenTag(last_param_token + 1) == .comma)
1753 last_param_token += 1;
1754 continue;
1755 },
1756 .r_paren => break,
1757 else => {}, // Parameter type without a name.
1758 }
1759 if (tree.tokenTag(last_param_token) == .identifier and
1760 tree.tokenTag(last_param_token + 1) == .colon)
1761 {
1762 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1763 last_param_token += 1;
1764 try renderToken(r, last_param_token, .space); // :
1765 last_param_token += 1;
1766 }
1767 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1768 try renderToken(r, last_param_token, .comma); // anytype
1769 if (tree.tokenTag(last_param_token + 1) == .comma)
1770 last_param_token += 1;
1771 continue;
1772 }
1773 const param = fn_proto.ast.params[param_i];
1774 param_i += 1;
1775 try ais.pushSpace(.comma);
1776 try renderExpression(r, param, .comma);
1777 ais.popSpace();
1778 last_param_token = tree.lastToken(param);
1779 if (tree.tokenTag(last_param_token + 1) == .comma) last_param_token += 1;
1780 }
1781 ais.popIndent();
1782 }
1783
1784 try renderToken(r, rparen, .space); // )
1785
1786 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1787 const align_lparen = tree.firstToken(align_expr) - 1;
1788 const align_rparen = tree.lastToken(align_expr) + 1;
1789
1790 try renderToken(r, align_lparen - 1, .none); // align
1791 try renderToken(r, align_lparen, .none); // (
1792 try renderExpression(r, align_expr, .none);
1793 try renderToken(r, align_rparen, .space); // )
1794 }
1795
1796 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1797 const align_lparen = tree.firstToken(addrspace_expr) - 1;
1798 const align_rparen = tree.lastToken(addrspace_expr) + 1;
1799
1800 try renderToken(r, align_lparen - 1, .none); // addrspace
1801 try renderToken(r, align_lparen, .none); // (
1802 try renderExpression(r, addrspace_expr, .none);
1803 try renderToken(r, align_rparen, .space); // )
1804 }
1805
1806 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1807 const section_lparen = tree.firstToken(section_expr) - 1;
1808 const section_rparen = tree.lastToken(section_expr) + 1;
1809
1810 try renderToken(r, section_lparen - 1, .none); // section
1811 try renderToken(r, section_lparen, .none); // (
1812 try renderExpression(r, section_expr, .none);
1813 try renderToken(r, section_rparen, .space); // )
1814 }
1815
1816 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1817 // Keep in sync with logic in `renderMember`. Search this file for the marker PROMOTE_CALLCONV_INLINE
1818 const is_callconv_inline = mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)));
1819 const is_declaration = fn_proto.name_token != null;
1820 if (!(is_declaration and is_callconv_inline)) {
1821 const callconv_lparen = tree.firstToken(callconv_expr) - 1;
1822 const callconv_rparen = tree.lastToken(callconv_expr) + 1;
1823
1824 try renderToken(r, callconv_lparen - 1, .none); // callconv
1825 try renderToken(r, callconv_lparen, .none); // (
1826 try renderExpression(r, callconv_expr, .none);
1827 try renderToken(r, callconv_rparen, .space); // )
1828 }
1829 }
1830
1831 if (tree.tokenTag(maybe_bang) == .bang) {
1832 try renderToken(r, maybe_bang, .none); // !
1833 }
1834 return renderExpression(r, return_type, space);
1835}
1836
1837fn renderSwitchCase(
1838 r: *Render,
1839 switch_case: Ast.full.SwitchCase,
1840 space: Space,
1841) Error!void {
1842 const ais = r.ais;
1843 const tree = r.tree;
1844 const trailing_comma = tree.tokenTag(switch_case.ast.arrow_token - 1) == .comma;
1845 const has_comment_before_arrow = blk: {
1846 if (switch_case.ast.values.len == 0) break :blk false;
1847 break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
1848 };
1849
1850 // render inline keyword
1851 if (switch_case.inline_token) |some| {
1852 try renderToken(r, some, .space);
1853 }
1854
1855 // Render everything before the arrow
1856 if (switch_case.ast.values.len == 0) {
1857 try renderToken(r, switch_case.ast.arrow_token - 1, .space); // else keyword
1858 } else if (trailing_comma or has_comment_before_arrow) {
1859 // Render each value on a new line
1860 try ais.pushSpace(.comma);
1861 try renderExpressions(r, switch_case.ast.values, .comma);
1862 ais.popSpace();
1863 } else {
1864 // Render on one line
1865 for (switch_case.ast.values) |value_expr| {
1866 try renderExpression(r, value_expr, .comma_space);
1867 }
1868 }
1869
1870 // Render the arrow and everything after it
1871 const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal)
1872 // Newline gets inserted when rendering the target expr.
1873 Space.none
1874 else
1875 Space.space;
1876 const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space;
1877 try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // =>
1878
1879 if (switch_case.payload_token) |payload_token| {
1880 try renderToken(r, payload_token - 1, .none); // pipe
1881 const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk);
1882 if (tree.tokenTag(payload_token) == .asterisk) {
1883 try renderToken(r, payload_token, .none); // asterisk
1884 }
1885 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1886 if (tree.tokenTag(ident + 1) == .comma) {
1887 try renderToken(r, ident + 1, .space); // ,
1888 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier
1889 try renderToken(r, ident + 3, pre_target_space); // pipe
1890 } else {
1891 try renderToken(r, ident + 1, pre_target_space); // pipe
1892 }
1893 }
1894
1895 try renderExpression(r, switch_case.ast.target_expr, space);
1896}
1897
1898fn renderBlock(
1899 r: *Render,
1900 block_node: Ast.Node.Index,
1901 statements: []const Ast.Node.Index,
1902 space: Space,
1903) Error!void {
1904 const tree = r.tree;
1905 const ais = r.ais;
1906 const lbrace = tree.nodeMainToken(block_node);
1907
1908 if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
1909 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
1910 try renderToken(r, lbrace - 1, .space); // :
1911 }
1912 try ais.pushIndent(.normal);
1913 if (statements.len == 0) {
1914 try renderToken(r, lbrace, .none);
1915 ais.popIndent();
1916 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1917 return;
1918 }
1919 try renderToken(r, lbrace, .newline);
1920 return finishRenderBlock(r, block_node, statements, space);
1921}
1922
1923fn finishRenderBlock(
1924 r: *Render,
1925 block_node: Ast.Node.Index,
1926 statements: []const Ast.Node.Index,
1927 space: Space,
1928) Error!void {
1929 const tree = r.tree;
1930 const ais = r.ais;
1931 for (statements, 0..) |stmt, i| {
1932 if (i != 0) try renderExtraNewline(r, stmt);
1933 if (r.fixups.omit_nodes.contains(stmt)) continue;
1934 try ais.pushSpace(.semicolon);
1935 switch (tree.nodeTag(stmt)) {
1936 .global_var_decl,
1937 .local_var_decl,
1938 .simple_var_decl,
1939 .aligned_var_decl,
1940 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1941
1942 else => try renderExpression(r, stmt, .semicolon),
1943 }
1944 ais.popSpace();
1945 }
1946 ais.popIndent();
1947
1948 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1949}
1950
1951fn renderStructInit(
1952 r: *Render,
1953 struct_node: Ast.Node.Index,
1954 struct_init: Ast.full.StructInit,
1955 space: Space,
1956) Error!void {
1957 const tree = r.tree;
1958 const ais = r.ais;
1959
1960 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
1961 try renderExpression(r, type_expr, .none); // T
1962 } else {
1963 try renderToken(r, struct_init.ast.lbrace - 1, .none); // .
1964 }
1965
1966 if (struct_init.ast.fields.len == 0) {
1967 try ais.pushIndent(.normal);
1968 try renderToken(r, struct_init.ast.lbrace, .none); // lbrace
1969 ais.popIndent();
1970 return renderToken(r, struct_init.ast.lbrace + 1, space); // rbrace
1971 }
1972
1973 const rbrace = tree.lastToken(struct_node);
1974 const trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
1975 if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) {
1976 // Render one field init per line.
1977 try ais.pushIndent(.normal);
1978 try renderToken(r, struct_init.ast.lbrace, .newline);
1979
1980 try renderToken(r, struct_init.ast.lbrace + 1, .none); // .
1981 try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name
1982 // Don't output a space after the = if expression is a multiline string,
1983 // since then it will start on the next line.
1984 const field_node = struct_init.ast.fields[0];
1985 const expr = tree.nodeTag(field_node);
1986 var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
1987 try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
1988
1989 try ais.pushSpace(.comma);
1990 try renderExpressionFixup(r, field_node, .comma);
1991 ais.popSpace();
1992
1993 for (struct_init.ast.fields[1..]) |field_init| {
1994 const init_token = tree.firstToken(field_init);
1995 try renderExtraNewlineToken(r, init_token - 3);
1996 try renderToken(r, init_token - 3, .none); // .
1997 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
1998 space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space;
1999 try renderToken(r, init_token - 1, space_after_equal); // =
2000
2001 try ais.pushSpace(.comma);
2002 try renderExpressionFixup(r, field_init, .comma);
2003 ais.popSpace();
2004 }
2005
2006 ais.popIndent();
2007 } else {
2008 // Render all on one line, no trailing comma.
2009 try renderToken(r, struct_init.ast.lbrace, .space);
2010
2011 for (struct_init.ast.fields) |field_init| {
2012 const init_token = tree.firstToken(field_init);
2013 try renderToken(r, init_token - 3, .none); // .
2014 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2015 try renderToken(r, init_token - 1, .space); // =
2016 try renderExpressionFixup(r, field_init, .comma_space);
2017 }
2018 }
2019
2020 return renderToken(r, rbrace, space);
2021}
2022
2023fn renderArrayInit(
2024 r: *Render,
2025 array_init: Ast.full.ArrayInit,
2026 space: Space,
2027) Error!void {
2028 const tree = r.tree;
2029 const ais = r.ais;
2030 const gpa = r.gpa;
2031
2032 if (array_init.ast.type_expr.unwrap()) |type_expr| {
2033 try renderExpression(r, type_expr, .none); // T
2034 } else {
2035 try renderToken(r, array_init.ast.lbrace - 1, .none); // .
2036 }
2037
2038 if (array_init.ast.elements.len == 0) {
2039 try ais.pushIndent(.normal);
2040 try renderToken(r, array_init.ast.lbrace, .none); // lbrace
2041 ais.popIndent();
2042 return renderToken(r, array_init.ast.lbrace + 1, space); // rbrace
2043 }
2044
2045 const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
2046 const last_elem_token = tree.lastToken(last_elem);
2047 const trailing_comma = tree.tokenTag(last_elem_token + 1) == .comma;
2048 const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1;
2049 assert(tree.tokenTag(rbrace) == .r_brace);
2050
2051 if (array_init.ast.elements.len == 1) {
2052 const only_elem = array_init.ast.elements[0];
2053 const first_token = tree.firstToken(only_elem);
2054 if (tree.tokenTag(first_token) != .multiline_string_literal_line and
2055 !anythingBetween(tree, last_elem_token, rbrace))
2056 {
2057 try renderToken(r, array_init.ast.lbrace, .none);
2058 try renderExpression(r, only_elem, .none);
2059 return renderToken(r, rbrace, space);
2060 }
2061 }
2062
2063 const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
2064 const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
2065
2066 if (!trailing_comma and !contains_comment and !contains_multiline_string) {
2067 // Render all on one line, no trailing comma.
2068 if (array_init.ast.elements.len == 1) {
2069 // If there is only one element, we don't use spaces
2070 try renderToken(r, array_init.ast.lbrace, .none);
2071 try renderExpression(r, array_init.ast.elements[0], .none);
2072 } else {
2073 try renderToken(r, array_init.ast.lbrace, .space);
2074 for (array_init.ast.elements) |elem| {
2075 try renderExpression(r, elem, .comma_space);
2076 }
2077 }
2078 return renderToken(r, last_elem_token + 1, space); // rbrace
2079 }
2080
2081 try ais.pushIndent(.normal);
2082 try renderToken(r, array_init.ast.lbrace, .newline);
2083
2084 var expr_index: usize = 0;
2085 while (true) {
2086 const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2087 const row_exprs = array_init.ast.elements[expr_index..];
2088 // A place to store the width of each expression and its column's maximum
2089 const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2090 defer gpa.free(widths);
2091 @memset(widths, 0);
2092
2093 const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2094 defer gpa.free(expr_newlines);
2095 @memset(expr_newlines, false);
2096
2097 const expr_widths = widths[0..row_exprs.len];
2098 const column_widths = widths[row_exprs.len..];
2099
2100 // Find next row with trailing comment (if any) to end the current section.
2101 const section_end = sec_end: {
2102 var this_line_first_expr: usize = 0;
2103 var this_line_size = rowSize(tree, row_exprs, rbrace);
2104 for (row_exprs, 0..) |expr, i| {
2105 // Ignore comment on first line of this section.
2106 if (i == 0) continue;
2107 const expr_last_token = tree.lastToken(expr);
2108 if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2109 continue;
2110 // Track start of line containing comment.
2111 if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2112 this_line_first_expr = i;
2113 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2114 }
2115
2116 const maybe_comma = expr_last_token + 1;
2117 if (tree.tokenTag(maybe_comma) == .comma) {
2118 if (hasSameLineComment(tree, maybe_comma))
2119 break :sec_end i - this_line_size + 1;
2120 }
2121 }
2122 break :sec_end row_exprs.len;
2123 };
2124 expr_index += section_end;
2125
2126 const section_exprs = row_exprs[0..section_end];
2127
2128 var sub_expr_buffer = std.ArrayList(u8).init(gpa);
2129 defer sub_expr_buffer.deinit();
2130
2131 const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2132 defer gpa.free(sub_expr_buffer_starts);
2133
2134 var auto_indenting_stream = Ais.init(&sub_expr_buffer, indent_delta);
2135 defer auto_indenting_stream.deinit();
2136 var sub_render: Render = .{
2137 .gpa = r.gpa,
2138 .ais = &auto_indenting_stream,
2139 .tree = r.tree,
2140 .fixups = r.fixups,
2141 };
2142
2143 // Calculate size of columns in current section
2144 var column_counter: usize = 0;
2145 var single_line = true;
2146 var contains_newline = false;
2147 for (section_exprs, 0..) |expr, i| {
2148 const start = sub_expr_buffer.items.len;
2149 sub_expr_buffer_starts[i] = start;
2150
2151 if (i + 1 < section_exprs.len) {
2152 try renderExpression(&sub_render, expr, .none);
2153 const width = sub_expr_buffer.items.len - start;
2154 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start..], '\n') != null;
2155 contains_newline = contains_newline or this_contains_newline;
2156 expr_widths[i] = width;
2157 expr_newlines[i] = this_contains_newline;
2158
2159 if (!this_contains_newline) {
2160 const column = column_counter % row_size;
2161 column_widths[column] = @max(column_widths[column], width);
2162
2163 const expr_last_token = tree.lastToken(expr) + 1;
2164 const next_expr = section_exprs[i + 1];
2165 column_counter += 1;
2166 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2167 } else {
2168 single_line = false;
2169 column_counter = 0;
2170 }
2171 } else {
2172 try ais.pushSpace(.comma);
2173 try renderExpression(&sub_render, expr, .comma);
2174 ais.popSpace();
2175
2176 const width = sub_expr_buffer.items.len - start - 2;
2177 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start .. sub_expr_buffer.items.len - 1], '\n') != null;
2178 contains_newline = contains_newline or this_contains_newline;
2179 expr_widths[i] = width;
2180 expr_newlines[i] = contains_newline;
2181
2182 if (!contains_newline) {
2183 const column = column_counter % row_size;
2184 column_widths[column] = @max(column_widths[column], width);
2185 }
2186 }
2187 }
2188 sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.items.len;
2189
2190 // Render exprs in current section.
2191 column_counter = 0;
2192 for (section_exprs, 0..) |expr, i| {
2193 const start = sub_expr_buffer_starts[i];
2194 const end = sub_expr_buffer_starts[i + 1];
2195 const expr_text = sub_expr_buffer.items[start..end];
2196 if (!expr_newlines[i]) {
2197 try ais.writer().writeAll(expr_text);
2198 } else {
2199 var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2200 var last_line_was_empty = false;
2201 try ais.writer().writeAll(by_line.first());
2202 while (by_line.next()) |line| {
2203 if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2204 try ais.insertNewline();
2205 } else {
2206 try ais.maybeInsertNewline();
2207 }
2208 last_line_was_empty = (line.len == 0);
2209 try ais.writer().writeAll(line);
2210 }
2211 }
2212
2213 if (i + 1 < section_exprs.len) {
2214 const next_expr = section_exprs[i + 1];
2215 const comma = tree.lastToken(expr) + 1;
2216
2217 if (column_counter != row_size - 1) {
2218 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2219 // Neither the current or next expression is multiline
2220 try renderToken(r, comma, .space); // ,
2221 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2222 const padding = column_widths[column_counter % row_size] - expr_widths[i];
2223 try ais.writer().writeByteNTimes(' ', padding);
2224
2225 column_counter += 1;
2226 continue;
2227 }
2228 }
2229
2230 if (single_line and row_size != 1) {
2231 try renderToken(r, comma, .space); // ,
2232 continue;
2233 }
2234
2235 column_counter = 0;
2236 try renderToken(r, comma, .newline); // ,
2237 try renderExtraNewline(r, next_expr);
2238 }
2239 }
2240
2241 if (expr_index == array_init.ast.elements.len)
2242 break;
2243 }
2244
2245 ais.popIndent();
2246 return renderToken(r, rbrace, space); // rbrace
2247}
2248
2249fn renderContainerDecl(
2250 r: *Render,
2251 container_decl_node: Ast.Node.Index,
2252 container_decl: Ast.full.ContainerDecl,
2253 space: Space,
2254) Error!void {
2255 const tree = r.tree;
2256 const ais = r.ais;
2257
2258 if (container_decl.layout_token) |layout_token| {
2259 try renderToken(r, layout_token, .space);
2260 }
2261
2262 const container: Container = switch (tree.tokenTag(container_decl.ast.main_token)) {
2263 .keyword_enum => .@"enum",
2264 .keyword_struct => for (container_decl.ast.members) |member| {
2265 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
2266 } else .tuple,
2267 else => .other,
2268 };
2269
2270 var lbrace: Ast.TokenIndex = undefined;
2271 if (container_decl.ast.enum_token) |enum_token| {
2272 try renderToken(r, container_decl.ast.main_token, .none); // union
2273 try renderToken(r, enum_token - 1, .none); // lparen
2274 try renderToken(r, enum_token, .none); // enum
2275 if (container_decl.ast.arg.unwrap()) |arg| {
2276 try renderToken(r, enum_token + 1, .none); // lparen
2277 try renderExpression(r, arg, .none);
2278 const rparen = tree.lastToken(arg) + 1;
2279 try renderToken(r, rparen, .none); // rparen
2280 try renderToken(r, rparen + 1, .space); // rparen
2281 lbrace = rparen + 2;
2282 } else {
2283 try renderToken(r, enum_token + 1, .space); // rparen
2284 lbrace = enum_token + 2;
2285 }
2286 } else if (container_decl.ast.arg.unwrap()) |arg| {
2287 try renderToken(r, container_decl.ast.main_token, .none); // union
2288 try renderToken(r, container_decl.ast.main_token + 1, .none); // lparen
2289 try renderExpression(r, arg, .none);
2290 const rparen = tree.lastToken(arg) + 1;
2291 try renderToken(r, rparen, .space); // rparen
2292 lbrace = rparen + 1;
2293 } else {
2294 try renderToken(r, container_decl.ast.main_token, .space); // union
2295 lbrace = container_decl.ast.main_token + 1;
2296 }
2297
2298 const rbrace = tree.lastToken(container_decl_node);
2299
2300 if (container_decl.ast.members.len == 0) {
2301 try ais.pushIndent(.normal);
2302 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2303 try renderToken(r, lbrace, .newline); // lbrace
2304 try renderContainerDocComments(r, lbrace + 1);
2305 } else {
2306 try renderToken(r, lbrace, .none); // lbrace
2307 }
2308 ais.popIndent();
2309 return renderToken(r, rbrace, space); // rbrace
2310 }
2311
2312 const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
2313 if (!src_has_trailing_comma) one_line: {
2314 // We print all the members in-line unless one of the following conditions are true:
2315
2316 // 1. The container has comments or multiline strings.
2317 if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
2318 break :one_line;
2319 }
2320
2321 // 2. The container has a container comment.
2322 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line;
2323
2324 // 3. A member of the container has a doc comment.
2325 for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| {
2326 if (tag == .doc_comment) break :one_line;
2327 }
2328
2329 // 4. The container has non-field members.
2330 for (container_decl.ast.members) |member| {
2331 if (tree.fullContainerField(member) == null) break :one_line;
2332 }
2333
2334 // Print all the declarations on the same line.
2335 try renderToken(r, lbrace, .space); // lbrace
2336 for (container_decl.ast.members) |member| {
2337 try renderMember(r, container, member, .space);
2338 }
2339 return renderToken(r, rbrace, space); // rbrace
2340 }
2341
2342 // One member per line.
2343 try ais.pushIndent(.normal);
2344 try renderToken(r, lbrace, .newline); // lbrace
2345 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2346 try renderContainerDocComments(r, lbrace + 1);
2347 }
2348 for (container_decl.ast.members, 0..) |member, i| {
2349 if (i != 0) try renderExtraNewline(r, member);
2350 switch (tree.nodeTag(member)) {
2351 // For container fields, ensure a trailing comma is added if necessary.
2352 .container_field_init,
2353 .container_field_align,
2354 .container_field,
2355 => {
2356 try ais.pushSpace(.comma);
2357 try renderMember(r, container, member, .comma);
2358 ais.popSpace();
2359 },
2360
2361 else => try renderMember(r, container, member, .newline),
2362 }
2363 }
2364 ais.popIndent();
2365
2366 return renderToken(r, rbrace, space); // rbrace
2367}
2368
2369fn renderAsmLegacy(
2370 r: *Render,
2371 asm_node: Ast.full.AsmLegacy,
2372 space: Space,
2373) Error!void {
2374 const tree = r.tree;
2375 const ais = r.ais;
2376
2377 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2378
2379 if (asm_node.volatile_token) |volatile_token| {
2380 try renderToken(r, volatile_token, .space); // volatile
2381 try renderToken(r, volatile_token + 1, .none); // lparen
2382 } else {
2383 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2384 }
2385
2386 if (asm_node.ast.items.len == 0) {
2387 try ais.forcePushIndent(.normal);
2388 if (asm_node.first_clobber) |first_clobber| {
2389 // asm ("foo" ::: "a", "b")
2390 // asm ("foo" ::: "a", "b",)
2391 try renderExpression(r, asm_node.ast.template, .space);
2392 // Render the three colons.
2393 try renderToken(r, first_clobber - 3, .none);
2394 try renderToken(r, first_clobber - 2, .none);
2395 try renderToken(r, first_clobber - 1, .space);
2396
2397 try ais.writer().writeAll(".{ ");
2398
2399 var tok_i = first_clobber;
2400 while (true) : (tok_i += 1) {
2401 try ais.writer().writeByte('.');
2402 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2403 try ais.writer().writeAll(" = true");
2404
2405 tok_i += 1;
2406 switch (tree.tokenTag(tok_i)) {
2407 .r_paren => {
2408 try ais.writer().writeAll(" }");
2409 ais.popIndent();
2410 return renderToken(r, tok_i, space);
2411 },
2412 .comma => {
2413 if (tree.tokenTag(tok_i + 1) == .r_paren) {
2414 try ais.writer().writeAll(" }");
2415 ais.popIndent();
2416 return renderToken(r, tok_i + 1, space);
2417 } else {
2418 try renderToken(r, tok_i, .space);
2419 }
2420 },
2421 else => unreachable,
2422 }
2423 }
2424 } else {
2425 unreachable;
2426 }
2427 }
2428
2429 try ais.forcePushIndent(.normal);
2430 try renderExpression(r, asm_node.ast.template, .newline);
2431 ais.setIndentDelta(asm_indent_delta);
2432 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2433
2434 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2435 try renderToken(r, colon1, .newline); // :
2436 break :colon2 colon1 + 1;
2437 } else colon2: {
2438 try renderToken(r, colon1, .space); // :
2439
2440 try ais.forcePushIndent(.normal);
2441 for (asm_node.outputs, 0..) |asm_output, i| {
2442 if (i + 1 < asm_node.outputs.len) {
2443 const next_asm_output = asm_node.outputs[i + 1];
2444 try renderAsmOutput(r, asm_output, .none);
2445
2446 const comma = tree.firstToken(next_asm_output) - 1;
2447 try renderToken(r, comma, .newline); // ,
2448 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2449 } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
2450 try ais.pushSpace(.comma);
2451 try renderAsmOutput(r, asm_output, .comma);
2452 ais.popSpace();
2453 ais.popIndent();
2454 ais.setIndentDelta(indent_delta);
2455 ais.popIndent();
2456 return renderToken(r, asm_node.ast.rparen, space); // rparen
2457 } else {
2458 try ais.pushSpace(.comma);
2459 try renderAsmOutput(r, asm_output, .comma);
2460 ais.popSpace();
2461 const comma_or_colon = tree.lastToken(asm_output) + 1;
2462 ais.popIndent();
2463 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2464 .comma => comma_or_colon + 1,
2465 else => comma_or_colon,
2466 };
2467 }
2468 } else unreachable;
2469 };
2470
2471 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2472 try renderToken(r, colon2, .newline); // :
2473 break :colon3 colon2 + 1;
2474 } else colon3: {
2475 try renderToken(r, colon2, .space); // :
2476 try ais.forcePushIndent(.normal);
2477 for (asm_node.inputs, 0..) |asm_input, i| {
2478 if (i + 1 < asm_node.inputs.len) {
2479 const next_asm_input = asm_node.inputs[i + 1];
2480 try renderAsmInput(r, asm_input, .none);
2481
2482 const first_token = tree.firstToken(next_asm_input);
2483 try renderToken(r, first_token - 1, .newline); // ,
2484 try renderExtraNewlineToken(r, first_token);
2485 } else if (asm_node.first_clobber == null) {
2486 try ais.pushSpace(.comma);
2487 try renderAsmInput(r, asm_input, .comma);
2488 ais.popSpace();
2489 ais.popIndent();
2490 ais.setIndentDelta(indent_delta);
2491 ais.popIndent();
2492 return renderToken(r, asm_node.ast.rparen, space); // rparen
2493 } else {
2494 try ais.pushSpace(.comma);
2495 try renderAsmInput(r, asm_input, .comma);
2496 ais.popSpace();
2497 const comma_or_colon = tree.lastToken(asm_input) + 1;
2498 ais.popIndent();
2499 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2500 .comma => comma_or_colon + 1,
2501 else => comma_or_colon,
2502 };
2503 }
2504 }
2505 unreachable;
2506 };
2507
2508 try renderToken(r, colon3, .space); // :
2509 try ais.writer().writeAll(".{ ");
2510 const first_clobber = asm_node.first_clobber.?;
2511 var tok_i = first_clobber;
2512 while (true) {
2513 switch (tree.tokenTag(tok_i + 1)) {
2514 .r_paren => {
2515 ais.setIndentDelta(indent_delta);
2516 try ais.writer().writeByte('.');
2517 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2518 try ais.writer().writeAll(" = true }");
2519 try renderSpace(r, tok_i, lexeme_len, .newline);
2520 ais.popIndent();
2521 return renderToken(r, tok_i + 1, space);
2522 },
2523 .comma => {
2524 switch (tree.tokenTag(tok_i + 2)) {
2525 .r_paren => {
2526 ais.setIndentDelta(indent_delta);
2527 try ais.writer().writeByte('.');
2528 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2529 try ais.writer().writeAll(" = true }");
2530 try renderSpace(r, tok_i, lexeme_len, .newline);
2531 ais.popIndent();
2532 return renderToken(r, tok_i + 2, space);
2533 },
2534 else => {
2535 try ais.writer().writeByte('.');
2536 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2537 try ais.writer().writeAll(" = true");
2538 try renderToken(r, tok_i + 1, .space);
2539 tok_i += 2;
2540 },
2541 }
2542 },
2543 else => unreachable,
2544 }
2545 }
2546}
2547
2548fn renderAsm(
2549 r: *Render,
2550 asm_node: Ast.full.Asm,
2551 space: Space,
2552) Error!void {
2553 const tree = r.tree;
2554 const ais = r.ais;
2555
2556 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2557
2558 if (asm_node.volatile_token) |volatile_token| {
2559 try renderToken(r, volatile_token, .space); // volatile
2560 try renderToken(r, volatile_token + 1, .none); // lparen
2561 } else {
2562 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2563 }
2564
2565 if (asm_node.ast.items.len == 0) {
2566 try ais.forcePushIndent(.normal);
2567 if (asm_node.ast.clobbers.unwrap()) |clobbers| {
2568 // asm ("foo" ::: clobbers)
2569 try renderExpression(r, asm_node.ast.template, .space);
2570 // Render the three colons.
2571 const first_clobber = tree.firstToken(clobbers);
2572 try renderToken(r, first_clobber - 3, .none);
2573 try renderToken(r, first_clobber - 2, .none);
2574 try renderToken(r, first_clobber - 1, .space);
2575 try renderExpression(r, clobbers, .none);
2576 ais.popIndent();
2577 return renderToken(r, asm_node.ast.rparen, space); // rparen
2578 }
2579
2580 // asm ("foo")
2581 try renderExpression(r, asm_node.ast.template, .none);
2582 ais.popIndent();
2583 return renderToken(r, asm_node.ast.rparen, space); // rparen
2584 }
2585
2586 try ais.forcePushIndent(.normal);
2587 try renderExpression(r, asm_node.ast.template, .newline);
2588 ais.setIndentDelta(asm_indent_delta);
2589 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2590
2591 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2592 try renderToken(r, colon1, .newline); // :
2593 break :colon2 colon1 + 1;
2594 } else colon2: {
2595 try renderToken(r, colon1, .space); // :
2596
2597 try ais.forcePushIndent(.normal);
2598 for (asm_node.outputs, 0..) |asm_output, i| {
2599 if (i + 1 < asm_node.outputs.len) {
2600 const next_asm_output = asm_node.outputs[i + 1];
2601 try renderAsmOutput(r, asm_output, .none);
2602
2603 const comma = tree.firstToken(next_asm_output) - 1;
2604 try renderToken(r, comma, .newline); // ,
2605 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2606 } else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
2607 try ais.pushSpace(.comma);
2608 try renderAsmOutput(r, asm_output, .comma);
2609 ais.popSpace();
2610 ais.popIndent();
2611 ais.setIndentDelta(indent_delta);
2612 ais.popIndent();
2613 return renderToken(r, asm_node.ast.rparen, space); // rparen
2614 } else {
2615 try ais.pushSpace(.comma);
2616 try renderAsmOutput(r, asm_output, .comma);
2617 ais.popSpace();
2618 const comma_or_colon = tree.lastToken(asm_output) + 1;
2619 ais.popIndent();
2620 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2621 .comma => comma_or_colon + 1,
2622 else => comma_or_colon,
2623 };
2624 }
2625 } else unreachable;
2626 };
2627
2628 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2629 try renderToken(r, colon2, .newline); // :
2630 break :colon3 colon2 + 1;
2631 } else colon3: {
2632 try renderToken(r, colon2, .space); // :
2633 try ais.forcePushIndent(.normal);
2634 for (asm_node.inputs, 0..) |asm_input, i| {
2635 if (i + 1 < asm_node.inputs.len) {
2636 const next_asm_input = asm_node.inputs[i + 1];
2637 try renderAsmInput(r, asm_input, .none);
2638
2639 const first_token = tree.firstToken(next_asm_input);
2640 try renderToken(r, first_token - 1, .newline); // ,
2641 try renderExtraNewlineToken(r, first_token);
2642 } else if (asm_node.ast.clobbers == .none) {
2643 try ais.pushSpace(.comma);
2644 try renderAsmInput(r, asm_input, .comma);
2645 ais.popSpace();
2646 ais.popIndent();
2647 ais.setIndentDelta(indent_delta);
2648 ais.popIndent();
2649 return renderToken(r, asm_node.ast.rparen, space); // rparen
2650 } else {
2651 try ais.pushSpace(.comma);
2652 try renderAsmInput(r, asm_input, .comma);
2653 ais.popSpace();
2654 const comma_or_colon = tree.lastToken(asm_input) + 1;
2655 ais.popIndent();
2656 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2657 .comma => comma_or_colon + 1,
2658 else => comma_or_colon,
2659 };
2660 }
2661 }
2662 unreachable;
2663 };
2664
2665 try renderToken(r, colon3, .space); // :
2666 const clobbers = asm_node.ast.clobbers.unwrap().?;
2667 try renderExpression(r, clobbers, .none);
2668 ais.setIndentDelta(indent_delta);
2669 ais.popIndent();
2670 return renderToken(r, asm_node.ast.rparen, space); // rparen
2671}
2672
2673fn renderCall(
2674 r: *Render,
2675 call: Ast.full.Call,
2676 space: Space,
2677) Error!void {
2678 try renderExpression(r, call.ast.fn_expr, .none);
2679 try renderParamList(r, call.ast.lparen, call.ast.params, space);
2680}
2681
2682fn renderParamList(
2683 r: *Render,
2684 lparen: Ast.TokenIndex,
2685 params: []const Ast.Node.Index,
2686 space: Space,
2687) Error!void {
2688 const tree = r.tree;
2689 const ais = r.ais;
2690
2691 if (params.len == 0) {
2692 try ais.pushIndent(.normal);
2693 try renderToken(r, lparen, .none);
2694 ais.popIndent();
2695 return renderToken(r, lparen + 1, space); // )
2696 }
2697
2698 const last_param = params[params.len - 1];
2699 const after_last_param_tok = tree.lastToken(last_param) + 1;
2700 if (tree.tokenTag(after_last_param_tok) == .comma) {
2701 try ais.pushIndent(.normal);
2702 try renderToken(r, lparen, .newline); // (
2703 for (params, 0..) |param_node, i| {
2704 if (i + 1 < params.len) {
2705 try renderExpression(r, param_node, .none);
2706
2707 const comma = tree.lastToken(param_node) + 1;
2708 try renderToken(r, comma, .newline); // ,
2709
2710 try renderExtraNewline(r, params[i + 1]);
2711 } else {
2712 try ais.pushSpace(.comma);
2713 try renderExpression(r, param_node, .comma);
2714 ais.popSpace();
2715 }
2716 }
2717 ais.popIndent();
2718 return renderToken(r, after_last_param_tok + 1, space); // )
2719 }
2720
2721 try ais.pushIndent(.normal);
2722 try renderToken(r, lparen, .none); // (
2723 for (params, 0..) |param_node, i| {
2724 try renderExpression(r, param_node, .none);
2725
2726 if (i + 1 < params.len) {
2727 const comma = tree.lastToken(param_node) + 1;
2728 const next_multiline_string =
2729 tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line;
2730 const comma_space: Space = if (next_multiline_string) .none else .space;
2731 try renderToken(r, comma, comma_space);
2732 }
2733 }
2734 ais.popIndent();
2735 return renderToken(r, after_last_param_tok, space); // )
2736}
2737
2738/// Render an expression, and the comma that follows it, if it is present in the source.
2739/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2740fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
2741 const tree = r.tree;
2742 const maybe_comma = tree.lastToken(node) + 1;
2743 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2744 try renderExpression(r, node, .none);
2745 return renderToken(r, maybe_comma, space);
2746 } else {
2747 return renderExpression(r, node, space);
2748 }
2749}
2750
2751/// Render a token, and the comma that follows it, if it is present in the source.
2752/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2753fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
2754 const tree = r.tree;
2755 const maybe_comma = token + 1;
2756 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2757 try renderToken(r, token, .none);
2758 return renderToken(r, maybe_comma, space);
2759 } else {
2760 return renderToken(r, token, space);
2761 }
2762}
2763
2764/// Render an identifier, and the comma that follows it, if it is present in the source.
2765/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2766fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2767 const tree = r.tree;
2768 const maybe_comma = token + 1;
2769 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2770 try renderIdentifier(r, token, .none, quote);
2771 return renderToken(r, maybe_comma, space);
2772 } else {
2773 return renderIdentifier(r, token, space, quote);
2774 }
2775}
2776
2777const Space = enum {
2778 /// Output the token lexeme only.
2779 none,
2780 /// Output the token lexeme followed by a single space.
2781 space,
2782 /// Output the token lexeme followed by a newline.
2783 newline,
2784 /// If the next token is a comma, render it as well. If not, insert one.
2785 /// In either case, a newline will be inserted afterwards.
2786 comma,
2787 /// Additionally consume the next token if it is a comma.
2788 /// In either case, a space will be inserted afterwards.
2789 comma_space,
2790 /// Additionally consume the next token if it is a semicolon.
2791 /// In either case, a newline will be inserted afterwards.
2792 semicolon,
2793 /// Skip rendering whitespace and comments. If this is used, the caller
2794 /// *must* handle whitespace and comments manually.
2795 skip,
2796};
2797
2798fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2799 const tree = r.tree;
2800 const ais = r.ais;
2801 const lexeme = tokenSliceForRender(tree, token_index);
2802 try ais.writer().writeAll(lexeme);
2803 try renderSpace(r, token_index, lexeme.len, space);
2804}
2805
2806fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: Space, override_space: Space) Error!void {
2807 const tree = r.tree;
2808 const ais = r.ais;
2809 const lexeme = tokenSliceForRender(tree, token_index);
2810 try ais.writer().writeAll(lexeme);
2811 ais.enableSpaceMode(override_space);
2812 defer ais.disableSpaceMode();
2813 try renderSpace(r, token_index, lexeme.len, space);
2814}
2815
2816fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2817 const tree = r.tree;
2818 const ais = r.ais;
2819
2820 const next_token_tag = tree.tokenTag(token_index + 1);
2821
2822 if (space == .skip) return;
2823
2824 if (space == .comma and next_token_tag != .comma) {
2825 try ais.writer().writeByte(',');
2826 }
2827 if (space == .semicolon or space == .comma) ais.enableSpaceMode(space);
2828 defer ais.disableSpaceMode();
2829 const comment = try renderComments(
2830 r,
2831 tree.tokenStart(token_index) + lexeme_len,
2832 tree.tokenStart(token_index + 1),
2833 );
2834 switch (space) {
2835 .none => {},
2836 .space => if (!comment) try ais.writer().writeByte(' '),
2837 .newline => if (!comment) try ais.insertNewline(),
2838
2839 .comma => if (next_token_tag == .comma) {
2840 try renderToken(r, token_index + 1, .newline);
2841 } else if (!comment) {
2842 try ais.insertNewline();
2843 },
2844
2845 .comma_space => if (next_token_tag == .comma) {
2846 try renderToken(r, token_index + 1, .space);
2847 } else if (!comment) {
2848 try ais.writer().writeByte(' ');
2849 },
2850
2851 .semicolon => if (next_token_tag == .semicolon) {
2852 try renderToken(r, token_index + 1, .newline);
2853 } else if (!comment) {
2854 try ais.insertNewline();
2855 },
2856
2857 .skip => unreachable,
2858 }
2859}
2860
2861fn renderOnlySpace(r: *Render, space: Space) Error!void {
2862 const ais = r.ais;
2863 switch (space) {
2864 .none => {},
2865 .space => try ais.writer().writeByte(' '),
2866 .newline => try ais.insertNewline(),
2867 .comma => try ais.writer().writeAll(",\n"),
2868 .comma_space => try ais.writer().writeAll(", "),
2869 .semicolon => try ais.writer().writeAll(";\n"),
2870 .skip => unreachable,
2871 }
2872}
2873
2874const QuoteBehavior = enum {
2875 preserve_when_shadowing,
2876 eagerly_unquote,
2877 eagerly_unquote_except_underscore,
2878};
2879
2880fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2881 const tree = r.tree;
2882 assert(tree.tokenTag(token_index) == .identifier);
2883 const lexeme = tokenSliceForRender(tree, token_index);
2884
2885 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2886 try r.ais.writer().writeAll(mangled);
2887 try renderSpace(r, token_index, lexeme.len, space);
2888 return;
2889 }
2890
2891 if (lexeme[0] != '@') {
2892 return renderToken(r, token_index, space);
2893 }
2894
2895 assert(lexeme.len >= 3);
2896 assert(lexeme[0] == '@');
2897 assert(lexeme[1] == '\"');
2898 assert(lexeme[lexeme.len - 1] == '\"');
2899 const contents = lexeme[2 .. lexeme.len - 1]; // inside the @"" quotation
2900
2901 // Empty name can't be unquoted.
2902 if (contents.len == 0) {
2903 return renderQuotedIdentifier(r, token_index, space, false);
2904 }
2905
2906 // Special case for _.
2907 if (std.zig.isUnderscore(contents)) switch (quote) {
2908 .eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
2909 .eagerly_unquote_except_underscore,
2910 .preserve_when_shadowing,
2911 => return renderQuotedIdentifier(r, token_index, space, false),
2912 };
2913
2914 // Scan the entire name for characters that would (after un-escaping) be illegal in a symbol,
2915 // i.e. contents don't match: [A-Za-z_][A-Za-z0-9_]*
2916 var contents_i: usize = 0;
2917 while (contents_i < contents.len) {
2918 switch (contents[contents_i]) {
2919 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2920 'A'...'Z', 'a'...'z', '_' => {},
2921 '\\' => {
2922 var esc_offset = contents_i;
2923 const res = std.zig.string_literal.parseEscapeSequence(contents, &esc_offset);
2924 switch (res) {
2925 .success => |char| switch (char) {
2926 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2927 'A'...'Z', 'a'...'z', '_' => {},
2928 else => return renderQuotedIdentifier(r, token_index, space, false),
2929 },
2930 .failure => return renderQuotedIdentifier(r, token_index, space, false),
2931 }
2932 contents_i += esc_offset;
2933 continue;
2934 },
2935 else => return renderQuotedIdentifier(r, token_index, space, false),
2936 }
2937 contents_i += 1;
2938 }
2939
2940 // Read enough of the name (while un-escaping) to determine if it's a keyword or primitive.
2941 // If it's too long to fit in this buffer, we know it's neither and quoting is unnecessary.
2942 // If we read the whole thing, we have to do further checks.
2943 const longest_keyword_or_primitive_len = comptime blk: {
2944 var longest = 0;
2945 for (primitives.names.keys()) |key| {
2946 if (key.len > longest) longest = key.len;
2947 }
2948 for (std.zig.Token.keywords.keys()) |key| {
2949 if (key.len > longest) longest = key.len;
2950 }
2951 break :blk longest;
2952 };
2953 var buf: [longest_keyword_or_primitive_len]u8 = undefined;
2954
2955 contents_i = 0;
2956 var buf_i: usize = 0;
2957 while (contents_i < contents.len and buf_i < longest_keyword_or_primitive_len) {
2958 if (contents[contents_i] == '\\') {
2959 const res = std.zig.string_literal.parseEscapeSequence(contents, &contents_i).success;
2960 buf[buf_i] = @as(u8, @intCast(res));
2961 buf_i += 1;
2962 } else {
2963 buf[buf_i] = contents[contents_i];
2964 contents_i += 1;
2965 buf_i += 1;
2966 }
2967 }
2968
2969 // We read the whole thing, so it could be a keyword or primitive.
2970 if (contents_i == contents.len) {
2971 if (!std.zig.isValidId(buf[0..buf_i])) {
2972 return renderQuotedIdentifier(r, token_index, space, false);
2973 }
2974 if (primitives.isPrimitive(buf[0..buf_i])) switch (quote) {
2975 .eagerly_unquote,
2976 .eagerly_unquote_except_underscore,
2977 => return renderQuotedIdentifier(r, token_index, space, true),
2978 .preserve_when_shadowing => return renderQuotedIdentifier(r, token_index, space, false),
2979 };
2980 }
2981
2982 try renderQuotedIdentifier(r, token_index, space, true);
2983}
2984
2985// Renders a @"" quoted identifier, normalizing escapes.
2986// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
2987// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2988fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2989 const tree = r.tree;
2990 const ais = r.ais;
2991 assert(tree.tokenTag(token_index) == .identifier);
2992 const lexeme = tokenSliceForRender(tree, token_index);
2993 assert(lexeme.len >= 3 and lexeme[0] == '@');
2994
2995 if (!unquote) try ais.writer().writeAll("@\"");
2996 const contents = lexeme[2 .. lexeme.len - 1];
2997 try renderIdentifierContents(ais.writer(), contents);
2998 if (!unquote) try ais.writer().writeByte('\"');
2999
3000 try renderSpace(r, token_index, lexeme.len, space);
3001}
3002
3003fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void {
3004 var pos: usize = 0;
3005 while (pos < bytes.len) {
3006 const byte = bytes[pos];
3007 switch (byte) {
3008 '\\' => {
3009 const old_pos = pos;
3010 const res = std.zig.string_literal.parseEscapeSequence(bytes, &pos);
3011 const escape_sequence = bytes[old_pos..pos];
3012 switch (res) {
3013 .success => |codepoint| {
3014 if (codepoint <= 0x7f) {
3015 const buf = [1]u8{@as(u8, @intCast(codepoint))};
3016 try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
3017 } else {
3018 try writer.writeAll(escape_sequence);
3019 }
3020 },
3021 .failure => {
3022 try writer.writeAll(escape_sequence);
3023 },
3024 }
3025 },
3026 0x00...('\\' - 1), ('\\' + 1)...0x7f => {
3027 const buf = [1]u8{byte};
3028 try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
3029 pos += 1;
3030 },
3031 0x80...0xff => {
3032 try writer.writeByte(byte);
3033 pos += 1;
3034 },
3035 }
3036 }
3037}
3038
3039/// Returns true if there exists a line comment between any of the tokens from
3040/// `start_token` to `end_token`. This is used to determine if e.g. a
3041/// fn_proto should be wrapped and have a trailing comma inserted even if
3042/// there is none in the source.
3043fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3044 for (start_token..end_token) |i| {
3045 const token: Ast.TokenIndex = @intCast(i);
3046 const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
3047 const end = tree.tokenStart(token + 1);
3048 if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
3049 }
3050
3051 return false;
3052}
3053
3054/// Returns true if there exists a multiline string literal between the start
3055/// of token `start_token` and the start of token `end_token`.
3056fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3057 return std.mem.indexOfScalar(
3058 Token.Tag,
3059 tree.tokens.items(.tag)[start_token..end_token],
3060 .multiline_string_literal_line,
3061 ) != null;
3062}
3063
3064/// Assumes that start is the first byte past the previous token and
3065/// that end is the last byte before the next token.
3066fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
3067 const tree = r.tree;
3068 const ais = r.ais;
3069
3070 var index: usize = start;
3071 while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| {
3072 const comment_start = index + offset;
3073
3074 // If there is no newline, the comment ends with EOF
3075 const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n');
3076 const newline = if (newline_index) |i| comment_start + i else null;
3077
3078 const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len];
3079 const trimmed_comment = mem.trimEnd(u8, untrimmed_comment, &std.ascii.whitespace);
3080
3081 // Don't leave any whitespace at the start of the file
3082 if (index != 0) {
3083 if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) {
3084 // Leave up to one empty line before the first comment
3085 try ais.insertNewline();
3086 try ais.insertNewline();
3087 } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) {
3088 // Respect the newline directly before the comment.
3089 // Note: This allows an empty line between comments
3090 try ais.insertNewline();
3091 } else if (index == start) {
3092 // Otherwise if the first comment is on the same line as
3093 // the token before it, prefix it with a single space.
3094 try ais.writer().writeByte(' ');
3095 }
3096 }
3097
3098 index = 1 + (newline orelse end - 1);
3099
3100 const comment_content = mem.trimStart(u8, trimmed_comment["//".len..], &std.ascii.whitespace);
3101 if (ais.disabled_offset != null and mem.eql(u8, comment_content, "zig fmt: on")) {
3102 // Write the source for which formatting was disabled directly
3103 // to the underlying writer, fixing up invalid whitespace.
3104 const disabled_source = tree.source[ais.disabled_offset.?..comment_start];
3105 try writeFixingWhitespace(ais.underlying_writer, disabled_source);
3106 // Write with the canonical single space.
3107 try ais.underlying_writer.writeAll("// zig fmt: on\n");
3108 ais.disabled_offset = null;
3109 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
3110 // Write with the canonical single space.
3111 try ais.writer().writeAll("// zig fmt: off\n");
3112 ais.disabled_offset = index;
3113 } else {
3114 // Write the comment minus trailing whitespace.
3115 try ais.writer().print("{s}\n", .{trimmed_comment});
3116 }
3117 }
3118
3119 if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
3120 // Don't leave any whitespace at the end of the file
3121 if (end != tree.source.len) {
3122 try ais.insertNewline();
3123 }
3124 }
3125
3126 return index != start;
3127}
3128
3129fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
3130 return renderExtraNewlineToken(r, r.tree.firstToken(node));
3131}
3132
3133/// Check if there is an empty line immediately before the given token. If so, render it.
3134fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
3135 const tree = r.tree;
3136 const ais = r.ais;
3137 const token_start = tree.tokenStart(token_index);
3138 if (token_start == 0) return;
3139 const prev_token_end = if (token_index == 0)
3140 0
3141 else
3142 tree.tokenStart(token_index - 1) + tokenSliceForRender(tree, token_index - 1).len;
3143
3144 // If there is a immediately preceding comment or doc_comment,
3145 // skip it because required extra newline has already been rendered.
3146 if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return;
3147 if (tree.isTokenPrecededByTags(token_index, &.{.doc_comment})) return;
3148
3149 // Iterate backwards to the end of the previous token, stopping if a
3150 // non-whitespace character is encountered or two newlines have been found.
3151 var i = token_start - 1;
3152 var newlines: u2 = 0;
3153 while (std.ascii.isWhitespace(tree.source[i])) : (i -= 1) {
3154 if (tree.source[i] == '\n') newlines += 1;
3155 if (newlines == 2) return ais.insertNewline();
3156 if (i == prev_token_end) break;
3157 }
3158}
3159
3160/// end_token is the token one past the last doc comment token. This function
3161/// searches backwards from there.
3162fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
3163 const tree = r.tree;
3164 // Search backwards for the first doc comment.
3165 if (end_token == 0) return;
3166 var tok = end_token - 1;
3167 while (tree.tokenTag(tok) == .doc_comment) {
3168 if (tok == 0) break;
3169 tok -= 1;
3170 } else {
3171 tok += 1;
3172 }
3173 const first_tok = tok;
3174 if (first_tok == end_token) return;
3175
3176 if (first_tok != 0) {
3177 const prev_token_tag = tree.tokenTag(first_tok - 1);
3178
3179 // Prevent accidental use of `renderDocComments` for a function argument doc comment
3180 assert(prev_token_tag != .l_paren);
3181
3182 if (prev_token_tag != .l_brace) {
3183 try renderExtraNewlineToken(r, first_tok);
3184 }
3185 }
3186
3187 while (tree.tokenTag(tok) == .doc_comment) : (tok += 1) {
3188 try renderToken(r, tok, .newline);
3189 }
3190}
3191
3192/// start_token is first container doc comment token.
3193fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
3194 const tree = r.tree;
3195 var tok = start_token;
3196 while (tree.tokenTag(tok) == .container_doc_comment) : (tok += 1) {
3197 try renderToken(r, tok, .newline);
3198 }
3199 // Render extra newline if there is one between final container doc comment and
3200 // the next token. If the next token is a doc comment, that code path
3201 // will have its own logic to insert a newline.
3202 if (tree.tokenTag(tok) != .doc_comment) {
3203 try renderExtraNewlineToken(r, tok);
3204 }
3205}
3206
3207fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3208 const tree = &r.tree;
3209 const ais = r.ais;
3210 var buf: [1]Ast.Node.Index = undefined;
3211 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3212 var it = fn_proto.iterate(tree);
3213 while (it.next()) |param| {
3214 const name_ident = param.name_token.?;
3215 assert(tree.tokenTag(name_ident) == .identifier);
3216 const w = ais.writer();
3217 try w.writeAll("_ = ");
3218 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
3219 try w.writeAll(";\n");
3220 }
3221}
3222
3223fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
3224 var ret = tree.tokenSlice(token_index);
3225 switch (tree.tokenTag(token_index)) {
3226 .container_doc_comment, .doc_comment => {
3227 ret = mem.trimEnd(u8, ret, &std.ascii.whitespace);
3228 },
3229 else => {},
3230 }
3231 return ret;
3232}
3233
3234fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usize {
3235 const tree = r.tree;
3236 const ais = r.ais;
3237 assert(tree.tokenTag(token_index) == .string_literal);
3238 const lexeme = tokenSliceForRender(tree, token_index);
3239 const unquoted = lexeme[1..][0 .. lexeme.len - 2];
3240 if (std.zig.isValidId(unquoted)) {
3241 try ais.writer().writeAll(unquoted);
3242 return unquoted.len;
3243 } else {
3244 try ais.writer().writeByte('@');
3245 try ais.writer().writeAll(lexeme);
3246 return lexeme.len + 1;
3247 }
3248}
3249
3250fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3251 const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3252 for (between_source) |byte| switch (byte) {
3253 '\n' => return false,
3254 '/' => return true,
3255 else => continue,
3256 };
3257 return false;
3258}
3259
3260/// Returns `true` if and only if there are any tokens or line comments between
3261/// start_token and end_token.
3262fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3263 if (start_token + 1 != end_token) return true;
3264 const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3265 for (between_source) |byte| switch (byte) {
3266 '/' => return true,
3267 else => continue,
3268 };
3269 return false;
3270}
3271
3272fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void {
3273 for (slice) |byte| switch (byte) {
3274 '\t' => try writer.writeAll(" " ** indent_delta),
3275 '\r' => {},
3276 else => try writer.writeByte(byte),
3277 };
3278}
3279
3280fn nodeIsBlock(tag: Ast.Node.Tag) bool {
3281 return switch (tag) {
3282 .block,
3283 .block_semicolon,
3284 .block_two,
3285 .block_two_semicolon,
3286 => true,
3287 else => false,
3288 };
3289}
3290
3291fn nodeIsIfForWhileSwitch(tag: Ast.Node.Tag) bool {
3292 return switch (tag) {
3293 .@"if",
3294 .if_simple,
3295 .@"for",
3296 .for_simple,
3297 .@"while",
3298 .while_simple,
3299 .while_cont,
3300 .@"switch",
3301 .switch_comma,
3302 => true,
3303 else => false,
3304 };
3305}
3306
3307fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
3308 return switch (tag) {
3309 .@"catch",
3310 .add,
3311 .add_wrap,
3312 .array_cat,
3313 .array_mult,
3314 .assign,
3315 .assign_bit_and,
3316 .assign_bit_or,
3317 .assign_shl,
3318 .assign_shr,
3319 .assign_bit_xor,
3320 .assign_div,
3321 .assign_sub,
3322 .assign_sub_wrap,
3323 .assign_mod,
3324 .assign_add,
3325 .assign_add_wrap,
3326 .assign_mul,
3327 .assign_mul_wrap,
3328 .bang_equal,
3329 .bit_and,
3330 .bit_or,
3331 .shl,
3332 .shr,
3333 .bit_xor,
3334 .bool_and,
3335 .bool_or,
3336 .div,
3337 .equal_equal,
3338 .error_union,
3339 .greater_or_equal,
3340 .greater_than,
3341 .less_or_equal,
3342 .less_than,
3343 .merge_error_sets,
3344 .mod,
3345 .mul,
3346 .mul_wrap,
3347 .sub,
3348 .sub_wrap,
3349 .@"orelse",
3350 => true,
3351
3352 else => false,
3353 };
3354}
3355
3356// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3357fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3358 const first_token = tree.firstToken(exprs[0]);
3359 if (tree.tokensOnSameLine(first_token, rtoken)) {
3360 const maybe_comma = rtoken - 1;
3361 if (tree.tokenTag(maybe_comma) == .comma)
3362 return 1;
3363 return exprs.len; // no newlines
3364 }
3365
3366 var count: usize = 1;
3367 for (exprs, 0..) |expr, i| {
3368 if (i + 1 < exprs.len) {
3369 const expr_last_token = tree.lastToken(expr) + 1;
3370 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3371 count += 1;
3372 } else {
3373 return count;
3374 }
3375 }
3376 unreachable;
3377}
3378
3379/// Automatically inserts indentation of written data by keeping
3380/// track of the current indentation level
3381///
3382/// We introduce a new indentation scope with pushIndent/popIndent whenever
3383/// we potentially want to introduce an indent after the next newline.
3384///
3385/// Indentation should only ever increment by one from one line to the next,
3386/// no matter how many new indentation scopes are introduced. This is done by
3387/// only realizing the indentation from the most recent scope. As an example:
3388///
3389/// while (foo) if (bar)
3390/// f(x);
3391///
3392/// The body of `while` introduces a new indentation scope and the body of
3393/// `if` also introduces a new indentation scope. When the newline is seen,
3394/// only the indentation scope of the `if` is realized, and the `while` is
3395/// not.
3396///
3397/// As comments are rendered during space rendering, we need to keep track
3398/// of the appropriate indentation level for them with pushSpace/popSpace.
3399/// This should be done whenever a scope that ends in a .semicolon or a
3400/// .comma is introduced.
3401fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
3402 return struct {
3403 const Self = @This();
3404 pub const WriteError = UnderlyingWriter.Error;
3405 pub const Writer = std.io.GenericWriter(*Self, WriteError, write);
3406
3407 pub const IndentType = enum {
3408 normal,
3409 after_equals,
3410 binop,
3411 field_access,
3412 };
3413 const StackElem = struct {
3414 indent_type: IndentType,
3415 realized: bool,
3416 };
3417 const SpaceElem = struct {
3418 space: Space,
3419 indent_count: usize,
3420 };
3421
3422 underlying_writer: UnderlyingWriter,
3423
3424 /// Offset into the source at which formatting has been disabled with
3425 /// a `zig fmt: off` comment.
3426 ///
3427 /// If non-null, the AutoIndentingStream will not write any bytes
3428 /// to the underlying writer. It will however continue to track the
3429 /// indentation level.
3430 disabled_offset: ?usize = null,
3431
3432 indent_count: usize = 0,
3433 indent_delta: usize,
3434 indent_stack: std.ArrayList(StackElem),
3435 space_stack: std.ArrayList(SpaceElem),
3436 space_mode: ?usize = null,
3437 disable_indent_committing: usize = 0,
3438 current_line_empty: bool = true,
3439 /// the most recently applied indent
3440 applied_indent: usize = 0,
3441
3442 pub fn init(buffer: *std.ArrayList(u8), indent_delta_: usize) Self {
3443 return .{
3444 .underlying_writer = buffer.writer(),
3445 .indent_delta = indent_delta_,
3446 .indent_stack = std.ArrayList(StackElem).init(buffer.allocator),
3447 .space_stack = std.ArrayList(SpaceElem).init(buffer.allocator),
3448 };
3449 }
3450
3451 pub fn deinit(self: *Self) void {
3452 self.indent_stack.deinit();
3453 self.space_stack.deinit();
3454 }
3455
3456 pub fn writer(self: *Self) Writer {
3457 return .{ .context = self };
3458 }
3459
3460 pub fn write(self: *Self, bytes: []const u8) WriteError!usize {
3461 if (bytes.len == 0)
3462 return @as(usize, 0);
3463
3464 try self.applyIndent();
3465 return self.writeNoIndent(bytes);
3466 }
3467
3468 // Change the indent delta without changing the final indentation level
3469 pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void {
3470 if (self.indent_delta == new_indent_delta) {
3471 return;
3472 } else if (self.indent_delta > new_indent_delta) {
3473 assert(self.indent_delta % new_indent_delta == 0);
3474 self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta);
3475 } else {
3476 // assert that the current indentation (in spaces) in a multiple of the new delta
3477 assert((self.indent_count * self.indent_delta) % new_indent_delta == 0);
3478 self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta);
3479 }
3480 self.indent_delta = new_indent_delta;
3481 }
3482
3483 fn writeNoIndent(self: *Self, bytes: []const u8) WriteError!usize {
3484 if (bytes.len == 0)
3485 return @as(usize, 0);
3486
3487 if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes);
3488 if (bytes[bytes.len - 1] == '\n')
3489 self.resetLine();
3490 return bytes.len;
3491 }
3492
3493 pub fn insertNewline(self: *Self) WriteError!void {
3494 _ = try self.writeNoIndent("\n");
3495 }
3496
3497 fn resetLine(self: *Self) void {
3498 self.current_line_empty = true;
3499
3500 if (self.disable_indent_committing > 0) return;
3501
3502 if (self.indent_stack.items.len > 0) {
3503 // By default, we realize the most recent indentation scope.
3504 var to_realize = self.indent_stack.items.len - 1;
3505
3506 if (self.indent_stack.items.len >= 2 and
3507 self.indent_stack.items[to_realize - 1].indent_type == .after_equals and
3508 self.indent_stack.items[to_realize - 1].realized and
3509 self.indent_stack.items[to_realize].indent_type == .binop)
3510 {
3511 // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
3512 // This ensures correct indentation in the below example:
3513 //
3514 // const foo =
3515 // (x >= 'a' and x <= 'z') or //<-- we are here
3516 // (x >= 'A' and x <= 'Z');
3517 //
3518 return;
3519 }
3520
3521 if (self.indent_stack.items[to_realize].indent_type == .field_access) {
3522 // Only realize the top-most field_access in a chain.
3523 while (to_realize > 0 and self.indent_stack.items[to_realize - 1].indent_type == .field_access)
3524 to_realize -= 1;
3525 }
3526
3527 if (self.indent_stack.items[to_realize].realized) return;
3528 self.indent_stack.items[to_realize].realized = true;
3529 self.indent_count += 1;
3530 }
3531 }
3532
3533 /// Disables indentation level changes during the next newlines until re-enabled.
3534 pub fn disableIndentCommitting(self: *Self) void {
3535 self.disable_indent_committing += 1;
3536 }
3537
3538 pub fn enableIndentCommitting(self: *Self) void {
3539 assert(self.disable_indent_committing > 0);
3540 self.disable_indent_committing -= 1;
3541 }
3542
3543 pub fn pushSpace(self: *Self, space: Space) !void {
3544 try self.space_stack.append(.{ .space = space, .indent_count = self.indent_count });
3545 }
3546
3547 pub fn popSpace(self: *Self) void {
3548 _ = self.space_stack.pop();
3549 }
3550
3551 /// Sets current indentation level to be the same as that of the last pushSpace.
3552 pub fn enableSpaceMode(self: *Self, space: Space) void {
3553 if (self.space_stack.items.len == 0) return;
3554 const curr = self.space_stack.getLast();
3555 if (curr.space != space) return;
3556 self.space_mode = curr.indent_count;
3557 }
3558
3559 pub fn disableSpaceMode(self: *Self) void {
3560 self.space_mode = null;
3561 }
3562
3563 pub fn lastSpaceModeIndent(self: *Self) usize {
3564 if (self.space_stack.items.len == 0) return 0;
3565 return self.space_stack.getLast().indent_count * self.indent_delta;
3566 }
3567
3568 /// Insert a newline unless the current line is blank
3569 pub fn maybeInsertNewline(self: *Self) WriteError!void {
3570 if (!self.current_line_empty)
3571 try self.insertNewline();
3572 }
3573
3574 /// Push default indentation
3575 /// Doesn't actually write any indentation.
3576 /// Just primes the stream to be able to write the correct indentation if it needs to.
3577 pub fn pushIndent(self: *Self, indent_type: IndentType) !void {
3578 try self.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
3579 }
3580
3581 /// Forces an indentation level to be realized.
3582 pub fn forcePushIndent(self: *Self, indent_type: IndentType) !void {
3583 try self.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
3584 self.indent_count += 1;
3585 }
3586
3587 pub fn popIndent(self: *Self) void {
3588 if (self.indent_stack.pop().?.realized) {
3589 assert(self.indent_count > 0);
3590 self.indent_count -= 1;
3591 }
3592 }
3593
3594 pub fn indentStackEmpty(self: *Self) bool {
3595 return self.indent_stack.items.len == 0;
3596 }
3597
3598 /// Writes ' ' bytes if the current line is empty
3599 fn applyIndent(self: *Self) WriteError!void {
3600 const current_indent = self.currentIndent();
3601 if (self.current_line_empty and current_indent > 0) {
3602 if (self.disabled_offset == null) {
3603 try self.underlying_writer.writeByteNTimes(' ', current_indent);
3604 }
3605 self.applied_indent = current_indent;
3606 }
3607 self.current_line_empty = false;
3608 }
3609
3610 /// Checks to see if the most recent indentation exceeds the currently pushed indents
3611 pub fn isLineOverIndented(self: *Self) bool {
3612 if (self.current_line_empty) return false;
3613 return self.applied_indent > self.currentIndent();
3614 }
3615
3616 fn currentIndent(self: *Self) usize {
3617 const indent_count = self.space_mode orelse self.indent_count;
3618 return indent_count * self.indent_delta;
3619 }
3620 };
3621}
lib/std/zig/render.zig deleted-3621
...@@ -1,3621 +0,0 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const meta = std.meta;
6const Ast = std.zig.Ast;
7const Token = std.zig.Token;
8const primitives = std.zig.primitives;
9
10const indent_delta = 4;
11const asm_indent_delta = 2;
12
13pub const Error = Ast.RenderError;
14
15const Ais = AutoIndentingStream(std.ArrayList(u8).Writer);
16
17pub const Fixups = struct {
18 /// The key is the mut token (`var`/`const`) of the variable declaration
19 /// that should have a `_ = foo;` inserted afterwards.
20 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .empty,
21 /// The functions in this unordered set of AST fn decl nodes will render
22 /// with a function body of `@trap()` instead, with all parameters
23 /// discarded.
24 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
25 /// These global declarations will be omitted.
26 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
27 /// These expressions will be replaced with the string value.
28 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
29 /// The string value will be inserted directly after the node.
30 append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
31 /// These nodes will be replaced with a different node.
32 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
33 /// Change all identifier names matching the key to be value instead.
34 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .empty,
35
36 /// All `@import` builtin calls which refer to a file path will be prefixed
37 /// with this path.
38 rebase_imported_paths: ?[]const u8 = null,
39
40 pub fn count(f: Fixups) usize {
41 return f.unused_var_decls.count() +
42 f.gut_functions.count() +
43 f.omit_nodes.count() +
44 f.replace_nodes_with_string.count() +
45 f.append_string_after_node.count() +
46 f.replace_nodes_with_node.count() +
47 f.rename_identifiers.count() +
48 @intFromBool(f.rebase_imported_paths != null);
49 }
50
51 pub fn clearRetainingCapacity(f: *Fixups) void {
52 f.unused_var_decls.clearRetainingCapacity();
53 f.gut_functions.clearRetainingCapacity();
54 f.omit_nodes.clearRetainingCapacity();
55 f.replace_nodes_with_string.clearRetainingCapacity();
56 f.append_string_after_node.clearRetainingCapacity();
57 f.replace_nodes_with_node.clearRetainingCapacity();
58 f.rename_identifiers.clearRetainingCapacity();
59
60 f.rebase_imported_paths = null;
61 }
62
63 pub fn deinit(f: *Fixups, gpa: Allocator) void {
64 f.unused_var_decls.deinit(gpa);
65 f.gut_functions.deinit(gpa);
66 f.omit_nodes.deinit(gpa);
67 f.replace_nodes_with_string.deinit(gpa);
68 f.append_string_after_node.deinit(gpa);
69 f.replace_nodes_with_node.deinit(gpa);
70 f.rename_identifiers.deinit(gpa);
71 f.* = undefined;
72 }
73};
74
75const Render = struct {
76 gpa: Allocator,
77 ais: *Ais,
78 tree: Ast,
79 fixups: Fixups,
80};
81
82pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void {
83 assert(tree.errors.len == 0); // Cannot render an invalid tree.
84 var auto_indenting_stream = Ais.init(buffer, indent_delta);
85 defer auto_indenting_stream.deinit();
86 var r: Render = .{
87 .gpa = buffer.allocator,
88 .ais = &auto_indenting_stream,
89 .tree = tree,
90 .fixups = fixups,
91 };
92
93 // Render all the line comments at the beginning of the file.
94 const comment_end_loc = tree.tokenStart(0);
95 _ = try renderComments(&r, 0, comment_end_loc);
96
97 if (tree.tokenTag(0) == .container_doc_comment) {
98 try renderContainerDocComments(&r, 0);
99 }
100
101 switch (tree.mode) {
102 .zig => try renderMembers(&r, tree.rootDecls()),
103 .zon => {
104 try renderExpression(
105 &r,
106 tree.rootDecls()[0],
107 .newline,
108 );
109 },
110 }
111
112 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
113 try writeFixingWhitespace(auto_indenting_stream.underlying_writer, tree.source[disabled_offset..]);
114 }
115}
116
117/// Render all members in the given slice, keeping empty lines where appropriate
118fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
119 const tree = r.tree;
120 if (members.len == 0) return;
121 const container: Container = for (members) |member| {
122 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
123 } else .tuple;
124 try renderMember(r, container, members[0], .newline);
125 for (members[1..]) |member| {
126 try renderExtraNewline(r, member);
127 try renderMember(r, container, member, .newline);
128 }
129}
130
131const Container = enum {
132 @"enum",
133 tuple,
134 other,
135};
136
137fn renderMember(
138 r: *Render,
139 container: Container,
140 decl: Ast.Node.Index,
141 space: Space,
142) Error!void {
143 const tree = r.tree;
144 const ais = r.ais;
145 if (r.fixups.omit_nodes.contains(decl)) return;
146 try renderDocComments(r, tree.firstToken(decl));
147 switch (tree.nodeTag(decl)) {
148 .fn_decl => {
149 // Some examples:
150 // pub extern "foo" fn ...
151 // export fn ...
152 const fn_proto, const body_node = tree.nodeData(decl).node_and_node;
153 const fn_token = tree.nodeMainToken(fn_proto);
154 // Go back to the first token we should render here.
155 var i = fn_token;
156 while (i > 0) {
157 i -= 1;
158 switch (tree.tokenTag(i)) {
159 .keyword_extern,
160 .keyword_export,
161 .keyword_pub,
162 .string_literal,
163 .keyword_inline,
164 .keyword_noinline,
165 => continue,
166
167 else => {
168 i += 1;
169 break;
170 },
171 }
172 }
173
174 while (i < fn_token) : (i += 1) {
175 try renderToken(r, i, .space);
176 }
177 switch (tree.nodeTag(fn_proto)) {
178 .fn_proto_one, .fn_proto => {
179 var buf: [1]Ast.Node.Index = undefined;
180 const opt_callconv_expr = if (tree.nodeTag(fn_proto) == .fn_proto_one)
181 tree.fnProtoOne(&buf, fn_proto).ast.callconv_expr
182 else
183 tree.fnProto(fn_proto).ast.callconv_expr;
184
185 // Keep in sync with logic in `renderFnProto`. Search this file for the marker PROMOTE_CALLCONV_INLINE
186 if (opt_callconv_expr.unwrap()) |callconv_expr| {
187 if (tree.nodeTag(callconv_expr) == .enum_literal) {
188 if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) {
189 try ais.writer().writeAll("inline ");
190 }
191 }
192 }
193 },
194 .fn_proto_simple, .fn_proto_multi => {},
195 else => unreachable,
196 }
197 try renderExpression(r, fn_proto, .space);
198 if (r.fixups.gut_functions.contains(decl)) {
199 try ais.pushIndent(.normal);
200 const lbrace = tree.nodeMainToken(body_node);
201 try renderToken(r, lbrace, .newline);
202 try discardAllParams(r, fn_proto);
203 try ais.writer().writeAll("@trap();");
204 ais.popIndent();
205 try ais.insertNewline();
206 try renderToken(r, tree.lastToken(body_node), space); // rbrace
207 } else if (r.fixups.unused_var_decls.count() != 0) {
208 try ais.pushIndent(.normal);
209 const lbrace = tree.nodeMainToken(body_node);
210 try renderToken(r, lbrace, .newline);
211
212 var fn_proto_buf: [1]Ast.Node.Index = undefined;
213 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
214 var it = full_fn_proto.iterate(&tree);
215 while (it.next()) |param| {
216 const name_ident = param.name_token.?;
217 assert(tree.tokenTag(name_ident) == .identifier);
218 if (r.fixups.unused_var_decls.contains(name_ident)) {
219 const w = ais.writer();
220 try w.writeAll("_ = ");
221 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
222 try w.writeAll(";\n");
223 }
224 }
225 var statements_buf: [2]Ast.Node.Index = undefined;
226 const statements = tree.blockStatements(&statements_buf, body_node).?;
227 return finishRenderBlock(r, body_node, statements, space);
228 } else {
229 return renderExpression(r, body_node, space);
230 }
231 },
232 .fn_proto_simple,
233 .fn_proto_multi,
234 .fn_proto_one,
235 .fn_proto,
236 => {
237 // Extern function prototypes are parsed as these tags.
238 // Go back to the first token we should render here.
239 const fn_token = tree.nodeMainToken(decl);
240 var i = fn_token;
241 while (i > 0) {
242 i -= 1;
243 switch (tree.tokenTag(i)) {
244 .keyword_extern,
245 .keyword_export,
246 .keyword_pub,
247 .string_literal,
248 .keyword_inline,
249 .keyword_noinline,
250 => continue,
251
252 else => {
253 i += 1;
254 break;
255 },
256 }
257 }
258 while (i < fn_token) : (i += 1) {
259 try renderToken(r, i, .space);
260 }
261 try renderExpression(r, decl, .none);
262 return renderToken(r, tree.lastToken(decl) + 1, space); // semicolon
263 },
264
265 .global_var_decl,
266 .local_var_decl,
267 .simple_var_decl,
268 .aligned_var_decl,
269 => {
270 try ais.pushSpace(.semicolon);
271 try renderVarDecl(r, tree.fullVarDecl(decl).?, false, .semicolon);
272 ais.popSpace();
273 },
274
275 .test_decl => {
276 const test_token = tree.nodeMainToken(decl);
277 const opt_name_token, const block_node = tree.nodeData(decl).opt_token_and_node;
278 try renderToken(r, test_token, .space);
279 if (opt_name_token.unwrap()) |name_token| {
280 switch (tree.tokenTag(name_token)) {
281 .string_literal => try renderToken(r, name_token, .space),
282 .identifier => try renderIdentifier(r, name_token, .space, .preserve_when_shadowing),
283 else => unreachable,
284 }
285 }
286 try renderExpression(r, block_node, space);
287 },
288
289 .container_field_init,
290 .container_field_align,
291 .container_field,
292 => return renderContainerField(r, container, tree.fullContainerField(decl).?, space),
293
294 .@"comptime" => return renderExpression(r, decl, space),
295
296 .root => unreachable,
297 else => unreachable,
298 }
299}
300
301/// Render all expressions in the slice, keeping empty lines where appropriate
302fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
303 if (expressions.len == 0) return;
304 try renderExpression(r, expressions[0], space);
305 for (expressions[1..]) |expression| {
306 try renderExtraNewline(r, expression);
307 try renderExpression(r, expression, space);
308 }
309}
310
311fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
312 const tree = r.tree;
313 const ais = r.ais;
314 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
315 try ais.writer().writeAll(replacement);
316 try renderOnlySpace(r, space);
317 return;
318 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
319 return renderExpression(r, replacement, space);
320 }
321 switch (tree.nodeTag(node)) {
322 .identifier => {
323 const token_index = tree.nodeMainToken(node);
324 return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
325 },
326
327 .number_literal,
328 .char_literal,
329 .unreachable_literal,
330 .anyframe_literal,
331 .string_literal,
332 => return renderToken(r, tree.nodeMainToken(node), space),
333
334 .multiline_string_literal => {
335 try ais.maybeInsertNewline();
336
337 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
338 for (first_tok..last_tok + 1) |i| {
339 try renderToken(r, @intCast(i), .newline);
340 }
341
342 const next_token = last_tok + 1;
343 const next_token_tag = tree.tokenTag(next_token);
344
345 // dedent the next thing that comes after a multiline string literal
346 if (!ais.indentStackEmpty() and
347 next_token_tag != .colon and
348 ((next_token_tag != .semicolon and next_token_tag != .comma) or
349 ais.lastSpaceModeIndent() < ais.currentIndent()))
350 {
351 ais.popIndent();
352 try ais.pushIndent(.normal);
353 }
354
355 switch (space) {
356 .none, .space, .newline, .skip => {},
357 .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
358 .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
359 .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space),
360 }
361 },
362
363 .error_value => {
364 const main_token = tree.nodeMainToken(node);
365 try renderToken(r, main_token, .none);
366 try renderToken(r, main_token + 1, .none);
367 return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
368 },
369
370 .block_two,
371 .block_two_semicolon,
372 .block,
373 .block_semicolon,
374 => {
375 var buf: [2]Ast.Node.Index = undefined;
376 const statements = tree.blockStatements(&buf, node).?;
377 return renderBlock(r, node, statements, space);
378 },
379
380 .@"errdefer" => {
381 const defer_token = tree.nodeMainToken(node);
382 const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
383
384 try renderToken(r, defer_token, .space);
385 if (maybe_payload_token.unwrap()) |payload_token| {
386 try renderToken(r, payload_token - 1, .none); // |
387 try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
388 try renderToken(r, payload_token + 1, .space); // |
389 }
390 return renderExpression(r, expr, space);
391 },
392
393 .@"defer",
394 .@"comptime",
395 .@"nosuspend",
396 .@"suspend",
397 => {
398 const main_token = tree.nodeMainToken(node);
399 const item = tree.nodeData(node).node;
400 try renderToken(r, main_token, .space);
401 return renderExpression(r, item, space);
402 },
403
404 .@"catch" => {
405 const main_token = tree.nodeMainToken(node);
406 const lhs, const rhs = tree.nodeData(node).node_and_node;
407 const fallback_first = tree.firstToken(rhs);
408
409 const same_line = tree.tokensOnSameLine(main_token, fallback_first);
410 const after_op_space = if (same_line) Space.space else Space.newline;
411
412 try renderExpression(r, lhs, .space); // target
413
414 try ais.pushIndent(.normal);
415 if (tree.tokenTag(fallback_first - 1) == .pipe) {
416 try renderToken(r, main_token, .space); // catch keyword
417 try renderToken(r, main_token + 1, .none); // pipe
418 try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
419 try renderToken(r, main_token + 3, after_op_space); // pipe
420 } else {
421 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
422 try renderToken(r, main_token, after_op_space); // catch keyword
423 }
424 try renderExpression(r, rhs, space); // fallback
425 ais.popIndent();
426 },
427
428 .field_access => {
429 const lhs, const name_token = tree.nodeData(node).node_and_token;
430 const dot_token = name_token - 1;
431
432 try ais.pushIndent(.field_access);
433 try renderExpression(r, lhs, .none);
434
435 // Allow a line break between the lhs and the dot if the lhs and rhs
436 // are on different lines.
437 const lhs_last_token = tree.lastToken(lhs);
438 const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
439 if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline();
440
441 try renderToken(r, dot_token, .none);
442
443 try renderIdentifier(r, name_token, space, .eagerly_unquote); // field
444 ais.popIndent();
445 },
446
447 .error_union,
448 .switch_range,
449 => {
450 const lhs, const rhs = tree.nodeData(node).node_and_node;
451 try renderExpression(r, lhs, .none);
452 try renderToken(r, tree.nodeMainToken(node), .none);
453 return renderExpression(r, rhs, space);
454 },
455 .for_range => {
456 const start, const opt_end = tree.nodeData(node).node_and_opt_node;
457 try renderExpression(r, start, .none);
458 if (opt_end.unwrap()) |end| {
459 try renderToken(r, tree.nodeMainToken(node), .none);
460 return renderExpression(r, end, space);
461 } else {
462 return renderToken(r, tree.nodeMainToken(node), space);
463 }
464 },
465
466 .assign,
467 .assign_bit_and,
468 .assign_bit_or,
469 .assign_shl,
470 .assign_shl_sat,
471 .assign_shr,
472 .assign_bit_xor,
473 .assign_div,
474 .assign_sub,
475 .assign_sub_wrap,
476 .assign_sub_sat,
477 .assign_mod,
478 .assign_add,
479 .assign_add_wrap,
480 .assign_add_sat,
481 .assign_mul,
482 .assign_mul_wrap,
483 .assign_mul_sat,
484 => {
485 const lhs, const rhs = tree.nodeData(node).node_and_node;
486 try renderExpression(r, lhs, .space);
487 const op_token = tree.nodeMainToken(node);
488 try ais.pushIndent(.after_equals);
489 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
490 try renderToken(r, op_token, .space);
491 } else {
492 try renderToken(r, op_token, .newline);
493 }
494 try renderExpression(r, rhs, space);
495 ais.popIndent();
496 },
497
498 .add,
499 .add_wrap,
500 .add_sat,
501 .array_cat,
502 .array_mult,
503 .bang_equal,
504 .bit_and,
505 .bit_or,
506 .shl,
507 .shl_sat,
508 .shr,
509 .bit_xor,
510 .bool_and,
511 .bool_or,
512 .div,
513 .equal_equal,
514 .greater_or_equal,
515 .greater_than,
516 .less_or_equal,
517 .less_than,
518 .merge_error_sets,
519 .mod,
520 .mul,
521 .mul_wrap,
522 .mul_sat,
523 .sub,
524 .sub_wrap,
525 .sub_sat,
526 .@"orelse",
527 => {
528 const lhs, const rhs = tree.nodeData(node).node_and_node;
529 try renderExpression(r, lhs, .space);
530 const op_token = tree.nodeMainToken(node);
531 try ais.pushIndent(.binop);
532 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
533 try renderToken(r, op_token, .space);
534 } else {
535 try renderToken(r, op_token, .newline);
536 }
537 try renderExpression(r, rhs, space);
538 ais.popIndent();
539 },
540
541 .assign_destructure => {
542 const full = tree.assignDestructure(node);
543 if (full.comptime_token) |comptime_token| {
544 try renderToken(r, comptime_token, .space);
545 }
546
547 for (full.ast.variables, 0..) |variable_node, i| {
548 const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space;
549 switch (tree.nodeTag(variable_node)) {
550 .global_var_decl,
551 .local_var_decl,
552 .simple_var_decl,
553 .aligned_var_decl,
554 => {
555 try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
556 },
557 else => try renderExpression(r, variable_node, variable_space),
558 }
559 }
560 try ais.pushIndent(.after_equals);
561 if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
562 try renderToken(r, full.ast.equal_token, .space);
563 } else {
564 try renderToken(r, full.ast.equal_token, .newline);
565 }
566 try renderExpression(r, full.ast.value_expr, space);
567 ais.popIndent();
568 },
569
570 .bit_not,
571 .bool_not,
572 .negation,
573 .negation_wrap,
574 .optional_type,
575 .address_of,
576 => {
577 try renderToken(r, tree.nodeMainToken(node), .none);
578 return renderExpression(r, tree.nodeData(node).node, space);
579 },
580
581 .@"try",
582 .@"resume",
583 => {
584 try renderToken(r, tree.nodeMainToken(node), .space);
585 return renderExpression(r, tree.nodeData(node).node, space);
586 },
587
588 .array_type,
589 .array_type_sentinel,
590 => return renderArrayType(r, tree.fullArrayType(node).?, space),
591
592 .ptr_type_aligned,
593 .ptr_type_sentinel,
594 .ptr_type,
595 .ptr_type_bit_range,
596 => return renderPtrType(r, tree.fullPtrType(node).?, space),
597
598 .array_init_one,
599 .array_init_one_comma,
600 .array_init_dot_two,
601 .array_init_dot_two_comma,
602 .array_init_dot,
603 .array_init_dot_comma,
604 .array_init,
605 .array_init_comma,
606 => {
607 var elements: [2]Ast.Node.Index = undefined;
608 return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
609 },
610
611 .struct_init_one,
612 .struct_init_one_comma,
613 .struct_init_dot_two,
614 .struct_init_dot_two_comma,
615 .struct_init_dot,
616 .struct_init_dot_comma,
617 .struct_init,
618 .struct_init_comma,
619 => {
620 var buf: [2]Ast.Node.Index = undefined;
621 return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
622 },
623
624 .call_one,
625 .call_one_comma,
626 .call,
627 .call_comma,
628 => {
629 var buf: [1]Ast.Node.Index = undefined;
630 return renderCall(r, tree.fullCall(&buf, node).?, space);
631 },
632
633 .array_access => {
634 const lhs, const rhs = tree.nodeData(node).node_and_node;
635 const lbracket = tree.firstToken(rhs) - 1;
636 const rbracket = tree.lastToken(rhs) + 1;
637 const one_line = tree.tokensOnSameLine(lbracket, rbracket);
638 const inner_space = if (one_line) Space.none else Space.newline;
639 try renderExpression(r, lhs, .none);
640 try ais.pushIndent(.normal);
641 try renderToken(r, lbracket, inner_space); // [
642 try renderExpression(r, rhs, inner_space);
643 ais.popIndent();
644 return renderToken(r, rbracket, space); // ]
645 },
646
647 .slice_open,
648 .slice,
649 .slice_sentinel,
650 => return renderSlice(r, node, tree.fullSlice(node).?, space),
651
652 .deref => {
653 try renderExpression(r, tree.nodeData(node).node, .none);
654 return renderToken(r, tree.nodeMainToken(node), space);
655 },
656
657 .unwrap_optional => {
658 const lhs, const question_mark = tree.nodeData(node).node_and_token;
659 const dot_token = question_mark - 1;
660 try renderExpression(r, lhs, .none);
661 try renderToken(r, dot_token, .none);
662 return renderToken(r, question_mark, space);
663 },
664
665 .@"break", .@"continue" => {
666 const main_token = tree.nodeMainToken(node);
667 const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
668 if (opt_label_token == .none and opt_target == .none) {
669 try renderToken(r, main_token, space); // break/continue
670 } else if (opt_label_token == .none and opt_target != .none) {
671 const target = opt_target.unwrap().?;
672 try renderToken(r, main_token, .space); // break/continue
673 try renderExpression(r, target, space);
674 } else if (opt_label_token != .none and opt_target == .none) {
675 const label_token = opt_label_token.unwrap().?;
676 try renderToken(r, main_token, .space); // break/continue
677 try renderToken(r, label_token - 1, .none); // :
678 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
679 } else if (opt_label_token != .none and opt_target != .none) {
680 const label_token = opt_label_token.unwrap().?;
681 const target = opt_target.unwrap().?;
682 try renderToken(r, main_token, .space); // break/continue
683 try renderToken(r, label_token - 1, .none); // :
684 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
685 try renderExpression(r, target, space);
686 } else unreachable;
687 },
688
689 .@"return" => {
690 if (tree.nodeData(node).opt_node.unwrap()) |expr| {
691 try renderToken(r, tree.nodeMainToken(node), .space);
692 try renderExpression(r, expr, space);
693 } else {
694 try renderToken(r, tree.nodeMainToken(node), space);
695 }
696 },
697
698 .grouped_expression => {
699 const expr, const rparen = tree.nodeData(node).node_and_token;
700 try ais.pushIndent(.normal);
701 try renderToken(r, tree.nodeMainToken(node), .none); // lparen
702 try renderExpression(r, expr, .none);
703 ais.popIndent();
704 return renderToken(r, rparen, space);
705 },
706
707 .container_decl,
708 .container_decl_trailing,
709 .container_decl_arg,
710 .container_decl_arg_trailing,
711 .container_decl_two,
712 .container_decl_two_trailing,
713 .tagged_union,
714 .tagged_union_trailing,
715 .tagged_union_enum_tag,
716 .tagged_union_enum_tag_trailing,
717 .tagged_union_two,
718 .tagged_union_two_trailing,
719 => {
720 var buf: [2]Ast.Node.Index = undefined;
721 return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
722 },
723
724 .error_set_decl => {
725 const error_token = tree.nodeMainToken(node);
726 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
727
728 try renderToken(r, error_token, .none);
729
730 if (lbrace + 1 == rbrace) {
731 // There is nothing between the braces so render condensed: `error{}`
732 try renderToken(r, lbrace, .none);
733 return renderToken(r, rbrace, space);
734 } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
735 // There is exactly one member and no trailing comma or
736 // comments, so render without surrounding spaces: `error{Foo}`
737 try renderToken(r, lbrace, .none);
738 try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
739 return renderToken(r, rbrace, space);
740 } else if (tree.tokenTag(rbrace - 1) == .comma) {
741 // There is a trailing comma so render each member on a new line.
742 try ais.pushIndent(.normal);
743 try renderToken(r, lbrace, .newline);
744 var i = lbrace + 1;
745 while (i < rbrace) : (i += 1) {
746 if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
747 switch (tree.tokenTag(i)) {
748 .doc_comment => try renderToken(r, i, .newline),
749 .identifier => {
750 try ais.pushSpace(.comma);
751 try renderIdentifier(r, i, .comma, .eagerly_unquote);
752 ais.popSpace();
753 },
754 .comma => {},
755 else => unreachable,
756 }
757 }
758 ais.popIndent();
759 return renderToken(r, rbrace, space);
760 } else {
761 // There is no trailing comma so render everything on one line.
762 try renderToken(r, lbrace, .space);
763 var i = lbrace + 1;
764 while (i < rbrace) : (i += 1) {
765 switch (tree.tokenTag(i)) {
766 .doc_comment => unreachable, // TODO
767 .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
768 .comma => {},
769 else => unreachable,
770 }
771 }
772 return renderToken(r, rbrace, space);
773 }
774 },
775
776 .builtin_call_two,
777 .builtin_call_two_comma,
778 .builtin_call,
779 .builtin_call_comma,
780 => {
781 var buf: [2]Ast.Node.Index = undefined;
782 const params = tree.builtinCallParams(&buf, node).?;
783 return renderBuiltinCall(r, tree.nodeMainToken(node), params, space);
784 },
785
786 .fn_proto_simple,
787 .fn_proto_multi,
788 .fn_proto_one,
789 .fn_proto,
790 => {
791 var buf: [1]Ast.Node.Index = undefined;
792 return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
793 },
794
795 .anyframe_type => {
796 const main_token = tree.nodeMainToken(node);
797 try renderToken(r, main_token, .none); // anyframe
798 try renderToken(r, main_token + 1, .none); // ->
799 return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
800 },
801
802 .@"switch",
803 .switch_comma,
804 => {
805 const full = tree.switchFull(node);
806
807 if (full.label_token) |label_token| {
808 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
809 try renderToken(r, label_token + 1, .space); // :
810 }
811
812 const rparen = tree.lastToken(full.ast.condition) + 1;
813
814 try renderToken(r, full.ast.switch_token, .space); // switch
815 try renderToken(r, full.ast.switch_token + 1, .none); // (
816 try renderExpression(r, full.ast.condition, .none); // condition expression
817 try renderToken(r, rparen, .space); // )
818
819 try ais.pushIndent(.normal);
820 if (full.ast.cases.len == 0) {
821 try renderToken(r, rparen + 1, .none); // {
822 } else {
823 try renderToken(r, rparen + 1, .newline); // {
824 try ais.pushSpace(.comma);
825 try renderExpressions(r, full.ast.cases, .comma);
826 ais.popSpace();
827 }
828 ais.popIndent();
829 return renderToken(r, tree.lastToken(node), space); // }
830 },
831
832 .switch_case_one,
833 .switch_case_inline_one,
834 .switch_case,
835 .switch_case_inline,
836 => return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
837
838 .while_simple,
839 .while_cont,
840 .@"while",
841 => return renderWhile(r, tree.fullWhile(node).?, space),
842
843 .for_simple,
844 .@"for",
845 => return renderFor(r, tree.fullFor(node).?, space),
846
847 .if_simple,
848 .@"if",
849 => return renderIf(r, tree.fullIf(node).?, space),
850
851 .asm_simple,
852 .@"asm",
853 => return renderAsm(r, tree.fullAsm(node).?, space),
854
855 // To be removed after 0.15.0 is tagged
856 .asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),
857
858 .enum_literal => {
859 try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
860 return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
861 },
862
863 .fn_decl => unreachable,
864 .container_field => unreachable,
865 .container_field_init => unreachable,
866 .container_field_align => unreachable,
867 .root => unreachable,
868 .global_var_decl => unreachable,
869 .local_var_decl => unreachable,
870 .simple_var_decl => unreachable,
871 .aligned_var_decl => unreachable,
872 .test_decl => unreachable,
873 .asm_output => unreachable,
874 .asm_input => unreachable,
875 }
876}
877
878/// Same as `renderExpression`, but afterwards looks for any
879/// append_string_after_node fixups to apply
880fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
881 const ais = r.ais;
882 try renderExpression(r, node, space);
883 if (r.fixups.append_string_after_node.get(node)) |bytes| {
884 try ais.writer().writeAll(bytes);
885 }
886}
887
888fn renderArrayType(
889 r: *Render,
890 array_type: Ast.full.ArrayType,
891 space: Space,
892) Error!void {
893 const tree = r.tree;
894 const ais = r.ais;
895 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
896 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket);
897 const inner_space = if (one_line) Space.none else Space.newline;
898 try ais.pushIndent(.normal);
899 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
900 try renderExpression(r, array_type.ast.elem_count, inner_space);
901 if (array_type.ast.sentinel.unwrap()) |sentinel| {
902 try renderToken(r, tree.firstToken(sentinel) - 1, inner_space); // colon
903 try renderExpression(r, sentinel, inner_space);
904 }
905 ais.popIndent();
906 try renderToken(r, rbracket, .none); // rbracket
907 return renderExpression(r, array_type.ast.elem_type, space);
908}
909
910fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
911 const tree = r.tree;
912 const main_token = ptr_type.ast.main_token;
913 switch (ptr_type.size) {
914 .one => {
915 // Since ** tokens exist and the same token is shared by two
916 // nested pointer types, we check to see if we are the parent
917 // in such a relationship. If so, skip rendering anything for
918 // this pointer type and rely on the child to render our asterisk
919 // as well when it renders the ** token.
920 if (tree.tokenTag(main_token) == .asterisk_asterisk and
921 main_token == tree.nodeMainToken(ptr_type.ast.child_type))
922 {
923 return renderExpression(r, ptr_type.ast.child_type, space);
924 }
925 try renderToken(r, main_token, .none); // asterisk
926 },
927 .many => {
928 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
929 try renderToken(r, main_token, .none); // lbracket
930 try renderToken(r, main_token + 1, .none); // asterisk
931 try renderToken(r, main_token + 2, .none); // colon
932 try renderExpression(r, sentinel, .none);
933 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
934 } else {
935 try renderToken(r, main_token, .none); // lbracket
936 try renderToken(r, main_token + 1, .none); // asterisk
937 try renderToken(r, main_token + 2, .none); // rbracket
938 }
939 },
940 .c => {
941 try renderToken(r, main_token, .none); // lbracket
942 try renderToken(r, main_token + 1, .none); // asterisk
943 try renderToken(r, main_token + 2, .none); // c
944 try renderToken(r, main_token + 3, .none); // rbracket
945 },
946 .slice => {
947 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
948 try renderToken(r, main_token, .none); // lbracket
949 try renderToken(r, main_token + 1, .none); // colon
950 try renderExpression(r, sentinel, .none);
951 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
952 } else {
953 try renderToken(r, main_token, .none); // lbracket
954 try renderToken(r, main_token + 1, .none); // rbracket
955 }
956 },
957 }
958
959 if (ptr_type.allowzero_token) |allowzero_token| {
960 try renderToken(r, allowzero_token, .space);
961 }
962
963 if (ptr_type.ast.align_node.unwrap()) |align_node| {
964 const align_first = tree.firstToken(align_node);
965 try renderToken(r, align_first - 2, .none); // align
966 try renderToken(r, align_first - 1, .none); // lparen
967 try renderExpression(r, align_node, .none);
968 if (ptr_type.ast.bit_range_start.unwrap()) |bit_range_start| {
969 const bit_range_end = ptr_type.ast.bit_range_end.unwrap().?;
970 try renderToken(r, tree.firstToken(bit_range_start) - 1, .none); // colon
971 try renderExpression(r, bit_range_start, .none);
972 try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon
973 try renderExpression(r, bit_range_end, .none);
974 try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen
975 } else {
976 try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen
977 }
978 }
979
980 if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| {
981 const addrspace_first = tree.firstToken(addrspace_node);
982 try renderToken(r, addrspace_first - 2, .none); // addrspace
983 try renderToken(r, addrspace_first - 1, .none); // lparen
984 try renderExpression(r, addrspace_node, .none);
985 try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen
986 }
987
988 if (ptr_type.const_token) |const_token| {
989 try renderToken(r, const_token, .space);
990 }
991
992 if (ptr_type.volatile_token) |volatile_token| {
993 try renderToken(r, volatile_token, .space);
994 }
995
996 try renderExpression(r, ptr_type.ast.child_type, space);
997}
998
999fn renderSlice(
1000 r: *Render,
1001 slice_node: Ast.Node.Index,
1002 slice: Ast.full.Slice,
1003 space: Space,
1004) Error!void {
1005 const tree = r.tree;
1006 const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or
1007 if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false;
1008 const after_start_space = if (after_start_space_bool) Space.space else Space.none;
1009 const after_dots_space = if (slice.ast.end != .none)
1010 after_start_space
1011 else if (slice.ast.sentinel != .none) Space.space else Space.none;
1012
1013 try renderExpression(r, slice.ast.sliced, .none);
1014 try renderToken(r, slice.ast.lbracket, .none); // lbracket
1015
1016 const start_last = tree.lastToken(slice.ast.start);
1017 try renderExpression(r, slice.ast.start, after_start_space);
1018 try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..")
1019
1020 if (slice.ast.end.unwrap()) |end| {
1021 const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none;
1022 try renderExpression(r, end, after_end_space);
1023 }
1024
1025 if (slice.ast.sentinel.unwrap()) |sentinel| {
1026 try renderToken(r, tree.firstToken(sentinel) - 1, .none); // colon
1027 try renderExpression(r, sentinel, .none);
1028 }
1029
1030 try renderToken(r, tree.lastToken(slice_node), space); // rbracket
1031}
1032
1033fn renderAsmOutput(
1034 r: *Render,
1035 asm_output: Ast.Node.Index,
1036 space: Space,
1037) Error!void {
1038 const tree = r.tree;
1039 assert(tree.nodeTag(asm_output) == .asm_output);
1040 const symbolic_name = tree.nodeMainToken(asm_output);
1041
1042 try renderToken(r, symbolic_name - 1, .none); // lbracket
1043 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1044 try renderToken(r, symbolic_name + 1, .space); // rbracket
1045 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1046 try renderToken(r, symbolic_name + 3, .none); // lparen
1047
1048 if (tree.tokenTag(symbolic_name + 4) == .arrow) {
1049 const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token;
1050 try renderToken(r, symbolic_name + 4, .space); // ->
1051 try renderExpression(r, type_expr.unwrap().?, Space.none);
1052 return renderToken(r, rparen, space);
1053 } else {
1054 try renderIdentifier(r, symbolic_name + 4, .none, .eagerly_unquote); // ident
1055 return renderToken(r, symbolic_name + 5, space); // rparen
1056 }
1057}
1058
1059fn renderAsmInput(
1060 r: *Render,
1061 asm_input: Ast.Node.Index,
1062 space: Space,
1063) Error!void {
1064 const tree = r.tree;
1065 assert(tree.nodeTag(asm_input) == .asm_input);
1066 const symbolic_name = tree.nodeMainToken(asm_input);
1067 const expr, const rparen = tree.nodeData(asm_input).node_and_token;
1068
1069 try renderToken(r, symbolic_name - 1, .none); // lbracket
1070 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1071 try renderToken(r, symbolic_name + 1, .space); // rbracket
1072 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1073 try renderToken(r, symbolic_name + 3, .none); // lparen
1074 try renderExpression(r, expr, Space.none);
1075 return renderToken(r, rparen, space);
1076}
1077
1078fn renderVarDecl(
1079 r: *Render,
1080 var_decl: Ast.full.VarDecl,
1081 /// Destructures intentionally ignore leading `comptime` tokens.
1082 ignore_comptime_token: bool,
1083 /// `comma_space` and `space` are used for destructure LHS decls.
1084 space: Space,
1085) Error!void {
1086 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1087 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
1088 // Discard the variable like this: `_ = foo;`
1089 const w = r.ais.writer();
1090 try w.writeAll("_ = ");
1091 try w.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1092 try w.writeAll(";\n");
1093 }
1094}
1095
1096fn renderVarDeclWithoutFixups(
1097 r: *Render,
1098 var_decl: Ast.full.VarDecl,
1099 /// Destructures intentionally ignore leading `comptime` tokens.
1100 ignore_comptime_token: bool,
1101 /// `comma_space` and `space` are used for destructure LHS decls.
1102 space: Space,
1103) Error!void {
1104 const tree = r.tree;
1105 const ais = r.ais;
1106
1107 if (var_decl.visib_token) |visib_token| {
1108 try renderToken(r, visib_token, Space.space); // pub
1109 }
1110
1111 if (var_decl.extern_export_token) |extern_export_token| {
1112 try renderToken(r, extern_export_token, Space.space); // extern
1113
1114 if (var_decl.lib_name) |lib_name| {
1115 try renderToken(r, lib_name, Space.space); // "lib"
1116 }
1117 }
1118
1119 if (var_decl.threadlocal_token) |thread_local_token| {
1120 try renderToken(r, thread_local_token, Space.space); // threadlocal
1121 }
1122
1123 if (!ignore_comptime_token) {
1124 if (var_decl.comptime_token) |comptime_token| {
1125 try renderToken(r, comptime_token, Space.space); // comptime
1126 }
1127 }
1128
1129 try renderToken(r, var_decl.ast.mut_token, .space); // var
1130
1131 if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or
1132 var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1133 var_decl.ast.init_node != .none)
1134 {
1135 const name_space = if (var_decl.ast.type_node == .none and
1136 (var_decl.ast.align_node != .none or
1137 var_decl.ast.addrspace_node != .none or
1138 var_decl.ast.section_node != .none or
1139 var_decl.ast.init_node != .none))
1140 Space.space
1141 else
1142 Space.none;
1143
1144 try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name
1145 } else {
1146 return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name
1147 }
1148
1149 if (var_decl.ast.type_node.unwrap()) |type_node| {
1150 try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // :
1151 if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or
1152 var_decl.ast.section_node != .none or var_decl.ast.init_node != .none)
1153 {
1154 try renderExpression(r, type_node, .space);
1155 } else {
1156 return renderExpression(r, type_node, space);
1157 }
1158 }
1159
1160 if (var_decl.ast.align_node.unwrap()) |align_node| {
1161 const lparen = tree.firstToken(align_node) - 1;
1162 const align_kw = lparen - 1;
1163 const rparen = tree.lastToken(align_node) + 1;
1164 try renderToken(r, align_kw, Space.none); // align
1165 try renderToken(r, lparen, Space.none); // (
1166 try renderExpression(r, align_node, Space.none);
1167 if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1168 var_decl.ast.init_node != .none)
1169 {
1170 try renderToken(r, rparen, .space); // )
1171 } else {
1172 return renderToken(r, rparen, space); // )
1173 }
1174 }
1175
1176 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
1177 const lparen = tree.firstToken(addrspace_node) - 1;
1178 const addrspace_kw = lparen - 1;
1179 const rparen = tree.lastToken(addrspace_node) + 1;
1180 try renderToken(r, addrspace_kw, Space.none); // addrspace
1181 try renderToken(r, lparen, Space.none); // (
1182 try renderExpression(r, addrspace_node, Space.none);
1183 if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) {
1184 try renderToken(r, rparen, .space); // )
1185 } else {
1186 try renderToken(r, rparen, .none); // )
1187 return renderToken(r, rparen + 1, Space.newline); // ;
1188 }
1189 }
1190
1191 if (var_decl.ast.section_node.unwrap()) |section_node| {
1192 const lparen = tree.firstToken(section_node) - 1;
1193 const section_kw = lparen - 1;
1194 const rparen = tree.lastToken(section_node) + 1;
1195 try renderToken(r, section_kw, Space.none); // linksection
1196 try renderToken(r, lparen, Space.none); // (
1197 try renderExpression(r, section_node, Space.none);
1198 if (var_decl.ast.init_node != .none) {
1199 try renderToken(r, rparen, .space); // )
1200 } else {
1201 return renderToken(r, rparen, space); // )
1202 }
1203 }
1204
1205 const init_node = var_decl.ast.init_node.unwrap().?;
1206
1207 const eq_token = tree.firstToken(init_node) - 1;
1208 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1209 try ais.pushIndent(.after_equals);
1210 try renderToken(r, eq_token, eq_space); // =
1211 try renderExpression(r, init_node, space); // ;
1212 ais.popIndent();
1213}
1214
1215fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
1216 return renderWhile(r, .{
1217 .ast = .{
1218 .while_token = if_node.ast.if_token,
1219 .cond_expr = if_node.ast.cond_expr,
1220 .cont_expr = .none,
1221 .then_expr = if_node.ast.then_expr,
1222 .else_expr = if_node.ast.else_expr,
1223 },
1224 .inline_token = null,
1225 .label_token = null,
1226 .payload_token = if_node.payload_token,
1227 .else_token = if_node.else_token,
1228 .error_token = if_node.error_token,
1229 }, space);
1230}
1231
1232/// Note that this function is additionally used to render if expressions, with
1233/// respective values set to null.
1234fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
1235 const tree = r.tree;
1236
1237 if (while_node.label_token) |label| {
1238 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1239 try renderToken(r, label + 1, .space); // :
1240 }
1241
1242 if (while_node.inline_token) |inline_token| {
1243 try renderToken(r, inline_token, .space); // inline
1244 }
1245
1246 try renderToken(r, while_node.ast.while_token, .space); // if/for/while
1247 try renderToken(r, while_node.ast.while_token + 1, .none); // lparen
1248 try renderExpression(r, while_node.ast.cond_expr, .none); // condition
1249
1250 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
1251
1252 if (while_node.payload_token) |payload_token| {
1253 try renderToken(r, last_prefix_token, .space);
1254 try renderToken(r, payload_token - 1, .none); // |
1255 const ident = blk: {
1256 if (tree.tokenTag(payload_token) == .asterisk) {
1257 try renderToken(r, payload_token, .none); // *
1258 break :blk payload_token + 1;
1259 } else {
1260 break :blk payload_token;
1261 }
1262 };
1263 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1264 const pipe = blk: {
1265 if (tree.tokenTag(ident + 1) == .comma) {
1266 try renderToken(r, ident + 1, .space); // ,
1267 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // index
1268 break :blk ident + 3;
1269 } else {
1270 break :blk ident + 1;
1271 }
1272 };
1273 last_prefix_token = pipe;
1274 }
1275
1276 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
1277 try renderToken(r, last_prefix_token, .space);
1278 const lparen = tree.firstToken(cont_expr) - 1;
1279 try renderToken(r, lparen - 1, .space); // :
1280 try renderToken(r, lparen, .none); // lparen
1281 try renderExpression(r, cont_expr, .none);
1282 last_prefix_token = tree.lastToken(cont_expr) + 1; // rparen
1283 }
1284
1285 try renderThenElse(
1286 r,
1287 last_prefix_token,
1288 while_node.ast.then_expr,
1289 while_node.else_token,
1290 while_node.error_token,
1291 while_node.ast.else_expr,
1292 space,
1293 );
1294}
1295
1296fn renderThenElse(
1297 r: *Render,
1298 last_prefix_token: Ast.TokenIndex,
1299 then_expr: Ast.Node.Index,
1300 else_token: ?Ast.TokenIndex,
1301 maybe_error_token: ?Ast.TokenIndex,
1302 opt_else_expr: Ast.Node.OptionalIndex,
1303 space: Space,
1304) Error!void {
1305 const tree = r.tree;
1306 const ais = r.ais;
1307 const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr));
1308 const indent_then_expr = !then_expr_is_block and
1309 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr));
1310
1311 if (indent_then_expr) try ais.pushIndent(.normal);
1312
1313 if (then_expr_is_block and ais.isLineOverIndented()) {
1314 ais.disableIndentCommitting();
1315 try renderToken(r, last_prefix_token, .newline);
1316 ais.enableIndentCommitting();
1317 } else if (indent_then_expr) {
1318 try renderToken(r, last_prefix_token, .newline);
1319 } else {
1320 try renderToken(r, last_prefix_token, .space);
1321 }
1322
1323 if (opt_else_expr.unwrap()) |else_expr| {
1324 if (indent_then_expr) {
1325 try renderExpression(r, then_expr, .newline);
1326 } else {
1327 try renderExpression(r, then_expr, .space);
1328 }
1329
1330 if (indent_then_expr) ais.popIndent();
1331
1332 var last_else_token = else_token.?;
1333
1334 if (maybe_error_token) |error_token| {
1335 try renderToken(r, last_else_token, .space); // else
1336 try renderToken(r, error_token - 1, .none); // |
1337 try renderIdentifier(r, error_token, .none, .preserve_when_shadowing); // identifier
1338 last_else_token = error_token + 1; // |
1339 }
1340
1341 const indent_else_expr = indent_then_expr and
1342 !nodeIsBlock(tree.nodeTag(else_expr)) and
1343 !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr));
1344 if (indent_else_expr) {
1345 try ais.pushIndent(.normal);
1346 try renderToken(r, last_else_token, .newline);
1347 try renderExpression(r, else_expr, space);
1348 ais.popIndent();
1349 } else {
1350 try renderToken(r, last_else_token, .space);
1351 try renderExpression(r, else_expr, space);
1352 }
1353 } else {
1354 try renderExpression(r, then_expr, space);
1355 if (indent_then_expr) ais.popIndent();
1356 }
1357}
1358
1359fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
1360 const tree = r.tree;
1361 const ais = r.ais;
1362 const token_tags = tree.tokens.items(.tag);
1363
1364 if (for_node.label_token) |label| {
1365 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1366 try renderToken(r, label + 1, .space); // :
1367 }
1368
1369 if (for_node.inline_token) |inline_token| {
1370 try renderToken(r, inline_token, .space); // inline
1371 }
1372
1373 try renderToken(r, for_node.ast.for_token, .space); // if/for/while
1374
1375 const lparen = for_node.ast.for_token + 1;
1376 try renderParamList(r, lparen, for_node.ast.inputs, .space);
1377
1378 var cur = for_node.payload_token;
1379 const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?;
1380 if (tree.tokenTag(@intCast(pipe - 1)) == .comma) {
1381 try ais.pushIndent(.normal);
1382 try renderToken(r, cur - 1, .newline); // |
1383 while (true) {
1384 if (tree.tokenTag(cur) == .asterisk) {
1385 try renderToken(r, cur, .none); // *
1386 cur += 1;
1387 }
1388 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1389 cur += 1;
1390 if (tree.tokenTag(cur) == .comma) {
1391 try renderToken(r, cur, .newline); // ,
1392 cur += 1;
1393 }
1394 if (tree.tokenTag(cur) == .pipe) {
1395 break;
1396 }
1397 }
1398 ais.popIndent();
1399 } else {
1400 try renderToken(r, cur - 1, .none); // |
1401 while (true) {
1402 if (tree.tokenTag(cur) == .asterisk) {
1403 try renderToken(r, cur, .none); // *
1404 cur += 1;
1405 }
1406 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1407 cur += 1;
1408 if (tree.tokenTag(cur) == .comma) {
1409 try renderToken(r, cur, .space); // ,
1410 cur += 1;
1411 }
1412 if (tree.tokenTag(cur) == .pipe) {
1413 break;
1414 }
1415 }
1416 }
1417
1418 try renderThenElse(
1419 r,
1420 cur,
1421 for_node.ast.then_expr,
1422 for_node.else_token,
1423 null,
1424 for_node.ast.else_expr,
1425 space,
1426 );
1427}
1428
1429fn renderContainerField(
1430 r: *Render,
1431 container: Container,
1432 field_param: Ast.full.ContainerField,
1433 space: Space,
1434) Error!void {
1435 const tree = r.tree;
1436 const ais = r.ais;
1437 var field = field_param;
1438 if (container != .tuple) field.convertToNonTupleLike(&tree);
1439 const quote: QuoteBehavior = switch (container) {
1440 .@"enum" => .eagerly_unquote_except_underscore,
1441 .tuple, .other => .eagerly_unquote,
1442 };
1443
1444 if (field.comptime_token) |t| {
1445 try renderToken(r, t, .space); // comptime
1446 }
1447 if (field.ast.type_expr == .none and field.ast.value_expr == .none) {
1448 if (field.ast.align_expr.unwrap()) |align_expr| {
1449 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1450 const lparen_token = tree.firstToken(align_expr) - 1;
1451 const align_kw = lparen_token - 1;
1452 const rparen_token = tree.lastToken(align_expr) + 1;
1453 try renderToken(r, align_kw, .none); // align
1454 try renderToken(r, lparen_token, .none); // (
1455 try renderExpression(r, align_expr, .none); // alignment
1456 return renderToken(r, rparen_token, .space); // )
1457 }
1458 return renderIdentifierComma(r, field.ast.main_token, space, quote); // name
1459 }
1460 if (field.ast.type_expr != .none and field.ast.value_expr == .none) {
1461 const type_expr = field.ast.type_expr.unwrap().?;
1462 if (!field.ast.tuple_like) {
1463 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1464 try renderToken(r, field.ast.main_token + 1, .space); // :
1465 }
1466
1467 if (field.ast.align_expr.unwrap()) |align_expr| {
1468 try renderExpression(r, type_expr, .space); // type
1469 const align_token = tree.firstToken(align_expr) - 2;
1470 try renderToken(r, align_token, .none); // align
1471 try renderToken(r, align_token + 1, .none); // (
1472 try renderExpression(r, align_expr, .none); // alignment
1473 const rparen = tree.lastToken(align_expr) + 1;
1474 return renderTokenComma(r, rparen, space); // )
1475 } else {
1476 return renderExpressionComma(r, type_expr, space); // type
1477 }
1478 }
1479 if (field.ast.type_expr == .none and field.ast.value_expr != .none) {
1480 const value_expr = field.ast.value_expr.unwrap().?;
1481
1482 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1483 if (field.ast.align_expr.unwrap()) |align_expr| {
1484 const lparen_token = tree.firstToken(align_expr) - 1;
1485 const align_kw = lparen_token - 1;
1486 const rparen_token = tree.lastToken(align_expr) + 1;
1487 try renderToken(r, align_kw, .none); // align
1488 try renderToken(r, lparen_token, .none); // (
1489 try renderExpression(r, align_expr, .none); // alignment
1490 try renderToken(r, rparen_token, .space); // )
1491 }
1492 try renderToken(r, field.ast.main_token + 1, .space); // =
1493 return renderExpressionComma(r, value_expr, space); // value
1494 }
1495 if (!field.ast.tuple_like) {
1496 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1497 try renderToken(r, field.ast.main_token + 1, .space); // :
1498 }
1499
1500 const type_expr = field.ast.type_expr.unwrap().?;
1501 const value_expr = field.ast.value_expr.unwrap().?;
1502
1503 try renderExpression(r, type_expr, .space); // type
1504
1505 if (field.ast.align_expr.unwrap()) |align_expr| {
1506 const lparen_token = tree.firstToken(align_expr) - 1;
1507 const align_kw = lparen_token - 1;
1508 const rparen_token = tree.lastToken(align_expr) + 1;
1509 try renderToken(r, align_kw, .none); // align
1510 try renderToken(r, lparen_token, .none); // (
1511 try renderExpression(r, align_expr, .none); // alignment
1512 try renderToken(r, rparen_token, .space); // )
1513 }
1514 const eq_token = tree.firstToken(value_expr) - 1;
1515 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1516
1517 try ais.pushIndent(.after_equals);
1518 try renderToken(r, eq_token, eq_space); // =
1519
1520 if (eq_space == .space) {
1521 ais.popIndent();
1522 try renderExpressionComma(r, value_expr, space); // value
1523 return;
1524 }
1525
1526 const maybe_comma = tree.lastToken(value_expr) + 1;
1527
1528 if (tree.tokenTag(maybe_comma) == .comma) {
1529 try renderExpression(r, value_expr, .none); // value
1530 ais.popIndent();
1531 try renderToken(r, maybe_comma, .newline);
1532 } else {
1533 try renderExpression(r, value_expr, space); // value
1534 ais.popIndent();
1535 }
1536}
1537
1538fn renderBuiltinCall(
1539 r: *Render,
1540 builtin_token: Ast.TokenIndex,
1541 params: []const Ast.Node.Index,
1542 space: Space,
1543) Error!void {
1544 const tree = r.tree;
1545 const ais = r.ais;
1546
1547 try renderToken(r, builtin_token, .none); // @name
1548
1549 if (params.len == 0) {
1550 try renderToken(r, builtin_token + 1, .none); // (
1551 return renderToken(r, builtin_token + 2, space); // )
1552 }
1553
1554 if (r.fixups.rebase_imported_paths) |prefix| {
1555 const slice = tree.tokenSlice(builtin_token);
1556 if (mem.eql(u8, slice, "@import")) f: {
1557 const param = params[0];
1558 const str_lit_token = tree.nodeMainToken(param);
1559 assert(tree.tokenTag(str_lit_token) == .string_literal);
1560 const token_bytes = tree.tokenSlice(str_lit_token);
1561 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1562 error.OutOfMemory => return error.OutOfMemory,
1563 error.InvalidLiteral => break :f,
1564 };
1565 defer r.gpa.free(imported_string);
1566 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1567 defer r.gpa.free(new_string);
1568
1569 try renderToken(r, builtin_token + 1, .none); // (
1570 try ais.writer().print("\"{f}\"", .{std.zig.fmtString(new_string)});
1571 return renderToken(r, str_lit_token + 1, space); // )
1572 }
1573 }
1574
1575 const last_param = params[params.len - 1];
1576 const after_last_param_token = tree.lastToken(last_param) + 1;
1577
1578 if (tree.tokenTag(after_last_param_token) != .comma) {
1579 // Render all on one line, no trailing comma.
1580 try renderToken(r, builtin_token + 1, .none); // (
1581
1582 for (params, 0..) |param_node, i| {
1583 const first_param_token = tree.firstToken(param_node);
1584 if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or
1585 hasSameLineComment(tree, first_param_token - 1))
1586 {
1587 try ais.pushIndent(.normal);
1588 try renderExpression(r, param_node, .none);
1589 ais.popIndent();
1590 } else {
1591 try renderExpression(r, param_node, .none);
1592 }
1593
1594 if (i + 1 < params.len) {
1595 const comma_token = tree.lastToken(param_node) + 1;
1596 try renderToken(r, comma_token, .space); // ,
1597 }
1598 }
1599 return renderToken(r, after_last_param_token, space); // )
1600 } else {
1601 // Render one param per line.
1602 try ais.pushIndent(.normal);
1603 try renderToken(r, builtin_token + 1, Space.newline); // (
1604
1605 for (params) |param_node| {
1606 try ais.pushSpace(.comma);
1607 try renderExpression(r, param_node, .comma);
1608 ais.popSpace();
1609 }
1610 ais.popIndent();
1611
1612 return renderToken(r, after_last_param_token + 1, space); // )
1613 }
1614}
1615
1616fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1617 const tree = r.tree;
1618 const ais = r.ais;
1619
1620 const after_fn_token = fn_proto.ast.fn_token + 1;
1621 const lparen = if (tree.tokenTag(after_fn_token) == .identifier) blk: {
1622 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1623 try renderIdentifier(r, after_fn_token, .none, .preserve_when_shadowing); // name
1624 break :blk after_fn_token + 1;
1625 } else blk: {
1626 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1627 break :blk fn_proto.ast.fn_token + 1;
1628 };
1629 assert(tree.tokenTag(lparen) == .l_paren);
1630
1631 const return_type = fn_proto.ast.return_type.unwrap().?;
1632 const maybe_bang = tree.firstToken(return_type) - 1;
1633 const rparen = blk: {
1634 // These may appear in any order, so we have to check the token_starts array
1635 // to find out which is first.
1636 var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang;
1637 var smallest_start = tree.tokenStart(maybe_bang);
1638 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1639 const tok = tree.firstToken(align_expr) - 3;
1640 const start = tree.tokenStart(tok);
1641 if (start < smallest_start) {
1642 rparen = tok;
1643 smallest_start = start;
1644 }
1645 }
1646 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1647 const tok = tree.firstToken(addrspace_expr) - 3;
1648 const start = tree.tokenStart(tok);
1649 if (start < smallest_start) {
1650 rparen = tok;
1651 smallest_start = start;
1652 }
1653 }
1654 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1655 const tok = tree.firstToken(section_expr) - 3;
1656 const start = tree.tokenStart(tok);
1657 if (start < smallest_start) {
1658 rparen = tok;
1659 smallest_start = start;
1660 }
1661 }
1662 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1663 const tok = tree.firstToken(callconv_expr) - 3;
1664 const start = tree.tokenStart(tok);
1665 if (start < smallest_start) {
1666 rparen = tok;
1667 smallest_start = start;
1668 }
1669 }
1670 break :blk rparen;
1671 };
1672 assert(tree.tokenTag(rparen) == .r_paren);
1673
1674 // The params list is a sparse set that does *not* include anytype or ... parameters.
1675
1676 const trailing_comma = tree.tokenTag(rparen - 1) == .comma;
1677 if (!trailing_comma and !hasComment(tree, lparen, rparen)) {
1678 // Render all on one line, no trailing comma.
1679 try renderToken(r, lparen, .none); // (
1680
1681 var param_i: usize = 0;
1682 var last_param_token = lparen;
1683 while (true) {
1684 last_param_token += 1;
1685 switch (tree.tokenTag(last_param_token)) {
1686 .doc_comment => {
1687 try renderToken(r, last_param_token, .newline);
1688 continue;
1689 },
1690 .ellipsis3 => {
1691 try renderToken(r, last_param_token, .none); // ...
1692 break;
1693 },
1694 .keyword_noalias, .keyword_comptime => {
1695 try renderToken(r, last_param_token, .space);
1696 last_param_token += 1;
1697 },
1698 .identifier => {},
1699 .keyword_anytype => {
1700 try renderToken(r, last_param_token, .none); // anytype
1701 continue;
1702 },
1703 .r_paren => break,
1704 .comma => {
1705 try renderToken(r, last_param_token, .space); // ,
1706 continue;
1707 },
1708 else => {}, // Parameter type without a name.
1709 }
1710 if (tree.tokenTag(last_param_token) == .identifier and
1711 tree.tokenTag(last_param_token + 1) == .colon)
1712 {
1713 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1714 last_param_token = last_param_token + 1;
1715 try renderToken(r, last_param_token, .space); // :
1716 last_param_token += 1;
1717 }
1718 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1719 try renderToken(r, last_param_token, .none); // anytype
1720 continue;
1721 }
1722 const param = fn_proto.ast.params[param_i];
1723 param_i += 1;
1724 try renderExpression(r, param, .none);
1725 last_param_token = tree.lastToken(param);
1726 }
1727 } else {
1728 // One param per line.
1729 try ais.pushIndent(.normal);
1730 try renderToken(r, lparen, .newline); // (
1731
1732 var param_i: usize = 0;
1733 var last_param_token = lparen;
1734 while (true) {
1735 last_param_token += 1;
1736 switch (tree.tokenTag(last_param_token)) {
1737 .doc_comment => {
1738 try renderToken(r, last_param_token, .newline);
1739 continue;
1740 },
1741 .ellipsis3 => {
1742 try renderToken(r, last_param_token, .comma); // ...
1743 break;
1744 },
1745 .keyword_noalias, .keyword_comptime => {
1746 try renderToken(r, last_param_token, .space);
1747 last_param_token += 1;
1748 },
1749 .identifier => {},
1750 .keyword_anytype => {
1751 try renderToken(r, last_param_token, .comma); // anytype
1752 if (tree.tokenTag(last_param_token + 1) == .comma)
1753 last_param_token += 1;
1754 continue;
1755 },
1756 .r_paren => break,
1757 else => {}, // Parameter type without a name.
1758 }
1759 if (tree.tokenTag(last_param_token) == .identifier and
1760 tree.tokenTag(last_param_token + 1) == .colon)
1761 {
1762 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1763 last_param_token += 1;
1764 try renderToken(r, last_param_token, .space); // :
1765 last_param_token += 1;
1766 }
1767 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1768 try renderToken(r, last_param_token, .comma); // anytype
1769 if (tree.tokenTag(last_param_token + 1) == .comma)
1770 last_param_token += 1;
1771 continue;
1772 }
1773 const param = fn_proto.ast.params[param_i];
1774 param_i += 1;
1775 try ais.pushSpace(.comma);
1776 try renderExpression(r, param, .comma);
1777 ais.popSpace();
1778 last_param_token = tree.lastToken(param);
1779 if (tree.tokenTag(last_param_token + 1) == .comma) last_param_token += 1;
1780 }
1781 ais.popIndent();
1782 }
1783
1784 try renderToken(r, rparen, .space); // )
1785
1786 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1787 const align_lparen = tree.firstToken(align_expr) - 1;
1788 const align_rparen = tree.lastToken(align_expr) + 1;
1789
1790 try renderToken(r, align_lparen - 1, .none); // align
1791 try renderToken(r, align_lparen, .none); // (
1792 try renderExpression(r, align_expr, .none);
1793 try renderToken(r, align_rparen, .space); // )
1794 }
1795
1796 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1797 const align_lparen = tree.firstToken(addrspace_expr) - 1;
1798 const align_rparen = tree.lastToken(addrspace_expr) + 1;
1799
1800 try renderToken(r, align_lparen - 1, .none); // addrspace
1801 try renderToken(r, align_lparen, .none); // (
1802 try renderExpression(r, addrspace_expr, .none);
1803 try renderToken(r, align_rparen, .space); // )
1804 }
1805
1806 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1807 const section_lparen = tree.firstToken(section_expr) - 1;
1808 const section_rparen = tree.lastToken(section_expr) + 1;
1809
1810 try renderToken(r, section_lparen - 1, .none); // section
1811 try renderToken(r, section_lparen, .none); // (
1812 try renderExpression(r, section_expr, .none);
1813 try renderToken(r, section_rparen, .space); // )
1814 }
1815
1816 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1817 // Keep in sync with logic in `renderMember`. Search this file for the marker PROMOTE_CALLCONV_INLINE
1818 const is_callconv_inline = mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)));
1819 const is_declaration = fn_proto.name_token != null;
1820 if (!(is_declaration and is_callconv_inline)) {
1821 const callconv_lparen = tree.firstToken(callconv_expr) - 1;
1822 const callconv_rparen = tree.lastToken(callconv_expr) + 1;
1823
1824 try renderToken(r, callconv_lparen - 1, .none); // callconv
1825 try renderToken(r, callconv_lparen, .none); // (
1826 try renderExpression(r, callconv_expr, .none);
1827 try renderToken(r, callconv_rparen, .space); // )
1828 }
1829 }
1830
1831 if (tree.tokenTag(maybe_bang) == .bang) {
1832 try renderToken(r, maybe_bang, .none); // !
1833 }
1834 return renderExpression(r, return_type, space);
1835}
1836
1837fn renderSwitchCase(
1838 r: *Render,
1839 switch_case: Ast.full.SwitchCase,
1840 space: Space,
1841) Error!void {
1842 const ais = r.ais;
1843 const tree = r.tree;
1844 const trailing_comma = tree.tokenTag(switch_case.ast.arrow_token - 1) == .comma;
1845 const has_comment_before_arrow = blk: {
1846 if (switch_case.ast.values.len == 0) break :blk false;
1847 break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
1848 };
1849
1850 // render inline keyword
1851 if (switch_case.inline_token) |some| {
1852 try renderToken(r, some, .space);
1853 }
1854
1855 // Render everything before the arrow
1856 if (switch_case.ast.values.len == 0) {
1857 try renderToken(r, switch_case.ast.arrow_token - 1, .space); // else keyword
1858 } else if (trailing_comma or has_comment_before_arrow) {
1859 // Render each value on a new line
1860 try ais.pushSpace(.comma);
1861 try renderExpressions(r, switch_case.ast.values, .comma);
1862 ais.popSpace();
1863 } else {
1864 // Render on one line
1865 for (switch_case.ast.values) |value_expr| {
1866 try renderExpression(r, value_expr, .comma_space);
1867 }
1868 }
1869
1870 // Render the arrow and everything after it
1871 const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal)
1872 // Newline gets inserted when rendering the target expr.
1873 Space.none
1874 else
1875 Space.space;
1876 const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space;
1877 try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // =>
1878
1879 if (switch_case.payload_token) |payload_token| {
1880 try renderToken(r, payload_token - 1, .none); // pipe
1881 const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk);
1882 if (tree.tokenTag(payload_token) == .asterisk) {
1883 try renderToken(r, payload_token, .none); // asterisk
1884 }
1885 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1886 if (tree.tokenTag(ident + 1) == .comma) {
1887 try renderToken(r, ident + 1, .space); // ,
1888 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier
1889 try renderToken(r, ident + 3, pre_target_space); // pipe
1890 } else {
1891 try renderToken(r, ident + 1, pre_target_space); // pipe
1892 }
1893 }
1894
1895 try renderExpression(r, switch_case.ast.target_expr, space);
1896}
1897
1898fn renderBlock(
1899 r: *Render,
1900 block_node: Ast.Node.Index,
1901 statements: []const Ast.Node.Index,
1902 space: Space,
1903) Error!void {
1904 const tree = r.tree;
1905 const ais = r.ais;
1906 const lbrace = tree.nodeMainToken(block_node);
1907
1908 if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
1909 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
1910 try renderToken(r, lbrace - 1, .space); // :
1911 }
1912 try ais.pushIndent(.normal);
1913 if (statements.len == 0) {
1914 try renderToken(r, lbrace, .none);
1915 ais.popIndent();
1916 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1917 return;
1918 }
1919 try renderToken(r, lbrace, .newline);
1920 return finishRenderBlock(r, block_node, statements, space);
1921}
1922
1923fn finishRenderBlock(
1924 r: *Render,
1925 block_node: Ast.Node.Index,
1926 statements: []const Ast.Node.Index,
1927 space: Space,
1928) Error!void {
1929 const tree = r.tree;
1930 const ais = r.ais;
1931 for (statements, 0..) |stmt, i| {
1932 if (i != 0) try renderExtraNewline(r, stmt);
1933 if (r.fixups.omit_nodes.contains(stmt)) continue;
1934 try ais.pushSpace(.semicolon);
1935 switch (tree.nodeTag(stmt)) {
1936 .global_var_decl,
1937 .local_var_decl,
1938 .simple_var_decl,
1939 .aligned_var_decl,
1940 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1941
1942 else => try renderExpression(r, stmt, .semicolon),
1943 }
1944 ais.popSpace();
1945 }
1946 ais.popIndent();
1947
1948 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1949}
1950
1951fn renderStructInit(
1952 r: *Render,
1953 struct_node: Ast.Node.Index,
1954 struct_init: Ast.full.StructInit,
1955 space: Space,
1956) Error!void {
1957 const tree = r.tree;
1958 const ais = r.ais;
1959
1960 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
1961 try renderExpression(r, type_expr, .none); // T
1962 } else {
1963 try renderToken(r, struct_init.ast.lbrace - 1, .none); // .
1964 }
1965
1966 if (struct_init.ast.fields.len == 0) {
1967 try ais.pushIndent(.normal);
1968 try renderToken(r, struct_init.ast.lbrace, .none); // lbrace
1969 ais.popIndent();
1970 return renderToken(r, struct_init.ast.lbrace + 1, space); // rbrace
1971 }
1972
1973 const rbrace = tree.lastToken(struct_node);
1974 const trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
1975 if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) {
1976 // Render one field init per line.
1977 try ais.pushIndent(.normal);
1978 try renderToken(r, struct_init.ast.lbrace, .newline);
1979
1980 try renderToken(r, struct_init.ast.lbrace + 1, .none); // .
1981 try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name
1982 // Don't output a space after the = if expression is a multiline string,
1983 // since then it will start on the next line.
1984 const field_node = struct_init.ast.fields[0];
1985 const expr = tree.nodeTag(field_node);
1986 var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
1987 try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
1988
1989 try ais.pushSpace(.comma);
1990 try renderExpressionFixup(r, field_node, .comma);
1991 ais.popSpace();
1992
1993 for (struct_init.ast.fields[1..]) |field_init| {
1994 const init_token = tree.firstToken(field_init);
1995 try renderExtraNewlineToken(r, init_token - 3);
1996 try renderToken(r, init_token - 3, .none); // .
1997 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
1998 space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space;
1999 try renderToken(r, init_token - 1, space_after_equal); // =
2000
2001 try ais.pushSpace(.comma);
2002 try renderExpressionFixup(r, field_init, .comma);
2003 ais.popSpace();
2004 }
2005
2006 ais.popIndent();
2007 } else {
2008 // Render all on one line, no trailing comma.
2009 try renderToken(r, struct_init.ast.lbrace, .space);
2010
2011 for (struct_init.ast.fields) |field_init| {
2012 const init_token = tree.firstToken(field_init);
2013 try renderToken(r, init_token - 3, .none); // .
2014 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2015 try renderToken(r, init_token - 1, .space); // =
2016 try renderExpressionFixup(r, field_init, .comma_space);
2017 }
2018 }
2019
2020 return renderToken(r, rbrace, space);
2021}
2022
2023fn renderArrayInit(
2024 r: *Render,
2025 array_init: Ast.full.ArrayInit,
2026 space: Space,
2027) Error!void {
2028 const tree = r.tree;
2029 const ais = r.ais;
2030 const gpa = r.gpa;
2031
2032 if (array_init.ast.type_expr.unwrap()) |type_expr| {
2033 try renderExpression(r, type_expr, .none); // T
2034 } else {
2035 try renderToken(r, array_init.ast.lbrace - 1, .none); // .
2036 }
2037
2038 if (array_init.ast.elements.len == 0) {
2039 try ais.pushIndent(.normal);
2040 try renderToken(r, array_init.ast.lbrace, .none); // lbrace
2041 ais.popIndent();
2042 return renderToken(r, array_init.ast.lbrace + 1, space); // rbrace
2043 }
2044
2045 const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
2046 const last_elem_token = tree.lastToken(last_elem);
2047 const trailing_comma = tree.tokenTag(last_elem_token + 1) == .comma;
2048 const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1;
2049 assert(tree.tokenTag(rbrace) == .r_brace);
2050
2051 if (array_init.ast.elements.len == 1) {
2052 const only_elem = array_init.ast.elements[0];
2053 const first_token = tree.firstToken(only_elem);
2054 if (tree.tokenTag(first_token) != .multiline_string_literal_line and
2055 !anythingBetween(tree, last_elem_token, rbrace))
2056 {
2057 try renderToken(r, array_init.ast.lbrace, .none);
2058 try renderExpression(r, only_elem, .none);
2059 return renderToken(r, rbrace, space);
2060 }
2061 }
2062
2063 const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
2064 const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
2065
2066 if (!trailing_comma and !contains_comment and !contains_multiline_string) {
2067 // Render all on one line, no trailing comma.
2068 if (array_init.ast.elements.len == 1) {
2069 // If there is only one element, we don't use spaces
2070 try renderToken(r, array_init.ast.lbrace, .none);
2071 try renderExpression(r, array_init.ast.elements[0], .none);
2072 } else {
2073 try renderToken(r, array_init.ast.lbrace, .space);
2074 for (array_init.ast.elements) |elem| {
2075 try renderExpression(r, elem, .comma_space);
2076 }
2077 }
2078 return renderToken(r, last_elem_token + 1, space); // rbrace
2079 }
2080
2081 try ais.pushIndent(.normal);
2082 try renderToken(r, array_init.ast.lbrace, .newline);
2083
2084 var expr_index: usize = 0;
2085 while (true) {
2086 const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2087 const row_exprs = array_init.ast.elements[expr_index..];
2088 // A place to store the width of each expression and its column's maximum
2089 const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2090 defer gpa.free(widths);
2091 @memset(widths, 0);
2092
2093 const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2094 defer gpa.free(expr_newlines);
2095 @memset(expr_newlines, false);
2096
2097 const expr_widths = widths[0..row_exprs.len];
2098 const column_widths = widths[row_exprs.len..];
2099
2100 // Find next row with trailing comment (if any) to end the current section.
2101 const section_end = sec_end: {
2102 var this_line_first_expr: usize = 0;
2103 var this_line_size = rowSize(tree, row_exprs, rbrace);
2104 for (row_exprs, 0..) |expr, i| {
2105 // Ignore comment on first line of this section.
2106 if (i == 0) continue;
2107 const expr_last_token = tree.lastToken(expr);
2108 if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2109 continue;
2110 // Track start of line containing comment.
2111 if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2112 this_line_first_expr = i;
2113 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2114 }
2115
2116 const maybe_comma = expr_last_token + 1;
2117 if (tree.tokenTag(maybe_comma) == .comma) {
2118 if (hasSameLineComment(tree, maybe_comma))
2119 break :sec_end i - this_line_size + 1;
2120 }
2121 }
2122 break :sec_end row_exprs.len;
2123 };
2124 expr_index += section_end;
2125
2126 const section_exprs = row_exprs[0..section_end];
2127
2128 var sub_expr_buffer = std.ArrayList(u8).init(gpa);
2129 defer sub_expr_buffer.deinit();
2130
2131 const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2132 defer gpa.free(sub_expr_buffer_starts);
2133
2134 var auto_indenting_stream = Ais.init(&sub_expr_buffer, indent_delta);
2135 defer auto_indenting_stream.deinit();
2136 var sub_render: Render = .{
2137 .gpa = r.gpa,
2138 .ais = &auto_indenting_stream,
2139 .tree = r.tree,
2140 .fixups = r.fixups,
2141 };
2142
2143 // Calculate size of columns in current section
2144 var column_counter: usize = 0;
2145 var single_line = true;
2146 var contains_newline = false;
2147 for (section_exprs, 0..) |expr, i| {
2148 const start = sub_expr_buffer.items.len;
2149 sub_expr_buffer_starts[i] = start;
2150
2151 if (i + 1 < section_exprs.len) {
2152 try renderExpression(&sub_render, expr, .none);
2153 const width = sub_expr_buffer.items.len - start;
2154 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start..], '\n') != null;
2155 contains_newline = contains_newline or this_contains_newline;
2156 expr_widths[i] = width;
2157 expr_newlines[i] = this_contains_newline;
2158
2159 if (!this_contains_newline) {
2160 const column = column_counter % row_size;
2161 column_widths[column] = @max(column_widths[column], width);
2162
2163 const expr_last_token = tree.lastToken(expr) + 1;
2164 const next_expr = section_exprs[i + 1];
2165 column_counter += 1;
2166 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2167 } else {
2168 single_line = false;
2169 column_counter = 0;
2170 }
2171 } else {
2172 try ais.pushSpace(.comma);
2173 try renderExpression(&sub_render, expr, .comma);
2174 ais.popSpace();
2175
2176 const width = sub_expr_buffer.items.len - start - 2;
2177 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start .. sub_expr_buffer.items.len - 1], '\n') != null;
2178 contains_newline = contains_newline or this_contains_newline;
2179 expr_widths[i] = width;
2180 expr_newlines[i] = contains_newline;
2181
2182 if (!contains_newline) {
2183 const column = column_counter % row_size;
2184 column_widths[column] = @max(column_widths[column], width);
2185 }
2186 }
2187 }
2188 sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.items.len;
2189
2190 // Render exprs in current section.
2191 column_counter = 0;
2192 for (section_exprs, 0..) |expr, i| {
2193 const start = sub_expr_buffer_starts[i];
2194 const end = sub_expr_buffer_starts[i + 1];
2195 const expr_text = sub_expr_buffer.items[start..end];
2196 if (!expr_newlines[i]) {
2197 try ais.writer().writeAll(expr_text);
2198 } else {
2199 var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2200 var last_line_was_empty = false;
2201 try ais.writer().writeAll(by_line.first());
2202 while (by_line.next()) |line| {
2203 if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2204 try ais.insertNewline();
2205 } else {
2206 try ais.maybeInsertNewline();
2207 }
2208 last_line_was_empty = (line.len == 0);
2209 try ais.writer().writeAll(line);
2210 }
2211 }
2212
2213 if (i + 1 < section_exprs.len) {
2214 const next_expr = section_exprs[i + 1];
2215 const comma = tree.lastToken(expr) + 1;
2216
2217 if (column_counter != row_size - 1) {
2218 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2219 // Neither the current or next expression is multiline
2220 try renderToken(r, comma, .space); // ,
2221 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2222 const padding = column_widths[column_counter % row_size] - expr_widths[i];
2223 try ais.writer().writeByteNTimes(' ', padding);
2224
2225 column_counter += 1;
2226 continue;
2227 }
2228 }
2229
2230 if (single_line and row_size != 1) {
2231 try renderToken(r, comma, .space); // ,
2232 continue;
2233 }
2234
2235 column_counter = 0;
2236 try renderToken(r, comma, .newline); // ,
2237 try renderExtraNewline(r, next_expr);
2238 }
2239 }
2240
2241 if (expr_index == array_init.ast.elements.len)
2242 break;
2243 }
2244
2245 ais.popIndent();
2246 return renderToken(r, rbrace, space); // rbrace
2247}
2248
2249fn renderContainerDecl(
2250 r: *Render,
2251 container_decl_node: Ast.Node.Index,
2252 container_decl: Ast.full.ContainerDecl,
2253 space: Space,
2254) Error!void {
2255 const tree = r.tree;
2256 const ais = r.ais;
2257
2258 if (container_decl.layout_token) |layout_token| {
2259 try renderToken(r, layout_token, .space);
2260 }
2261
2262 const container: Container = switch (tree.tokenTag(container_decl.ast.main_token)) {
2263 .keyword_enum => .@"enum",
2264 .keyword_struct => for (container_decl.ast.members) |member| {
2265 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
2266 } else .tuple,
2267 else => .other,
2268 };
2269
2270 var lbrace: Ast.TokenIndex = undefined;
2271 if (container_decl.ast.enum_token) |enum_token| {
2272 try renderToken(r, container_decl.ast.main_token, .none); // union
2273 try renderToken(r, enum_token - 1, .none); // lparen
2274 try renderToken(r, enum_token, .none); // enum
2275 if (container_decl.ast.arg.unwrap()) |arg| {
2276 try renderToken(r, enum_token + 1, .none); // lparen
2277 try renderExpression(r, arg, .none);
2278 const rparen = tree.lastToken(arg) + 1;
2279 try renderToken(r, rparen, .none); // rparen
2280 try renderToken(r, rparen + 1, .space); // rparen
2281 lbrace = rparen + 2;
2282 } else {
2283 try renderToken(r, enum_token + 1, .space); // rparen
2284 lbrace = enum_token + 2;
2285 }
2286 } else if (container_decl.ast.arg.unwrap()) |arg| {
2287 try renderToken(r, container_decl.ast.main_token, .none); // union
2288 try renderToken(r, container_decl.ast.main_token + 1, .none); // lparen
2289 try renderExpression(r, arg, .none);
2290 const rparen = tree.lastToken(arg) + 1;
2291 try renderToken(r, rparen, .space); // rparen
2292 lbrace = rparen + 1;
2293 } else {
2294 try renderToken(r, container_decl.ast.main_token, .space); // union
2295 lbrace = container_decl.ast.main_token + 1;
2296 }
2297
2298 const rbrace = tree.lastToken(container_decl_node);
2299
2300 if (container_decl.ast.members.len == 0) {
2301 try ais.pushIndent(.normal);
2302 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2303 try renderToken(r, lbrace, .newline); // lbrace
2304 try renderContainerDocComments(r, lbrace + 1);
2305 } else {
2306 try renderToken(r, lbrace, .none); // lbrace
2307 }
2308 ais.popIndent();
2309 return renderToken(r, rbrace, space); // rbrace
2310 }
2311
2312 const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
2313 if (!src_has_trailing_comma) one_line: {
2314 // We print all the members in-line unless one of the following conditions are true:
2315
2316 // 1. The container has comments or multiline strings.
2317 if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
2318 break :one_line;
2319 }
2320
2321 // 2. The container has a container comment.
2322 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line;
2323
2324 // 3. A member of the container has a doc comment.
2325 for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| {
2326 if (tag == .doc_comment) break :one_line;
2327 }
2328
2329 // 4. The container has non-field members.
2330 for (container_decl.ast.members) |member| {
2331 if (tree.fullContainerField(member) == null) break :one_line;
2332 }
2333
2334 // Print all the declarations on the same line.
2335 try renderToken(r, lbrace, .space); // lbrace
2336 for (container_decl.ast.members) |member| {
2337 try renderMember(r, container, member, .space);
2338 }
2339 return renderToken(r, rbrace, space); // rbrace
2340 }
2341
2342 // One member per line.
2343 try ais.pushIndent(.normal);
2344 try renderToken(r, lbrace, .newline); // lbrace
2345 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2346 try renderContainerDocComments(r, lbrace + 1);
2347 }
2348 for (container_decl.ast.members, 0..) |member, i| {
2349 if (i != 0) try renderExtraNewline(r, member);
2350 switch (tree.nodeTag(member)) {
2351 // For container fields, ensure a trailing comma is added if necessary.
2352 .container_field_init,
2353 .container_field_align,
2354 .container_field,
2355 => {
2356 try ais.pushSpace(.comma);
2357 try renderMember(r, container, member, .comma);
2358 ais.popSpace();
2359 },
2360
2361 else => try renderMember(r, container, member, .newline),
2362 }
2363 }
2364 ais.popIndent();
2365
2366 return renderToken(r, rbrace, space); // rbrace
2367}
2368
2369fn renderAsmLegacy(
2370 r: *Render,
2371 asm_node: Ast.full.AsmLegacy,
2372 space: Space,
2373) Error!void {
2374 const tree = r.tree;
2375 const ais = r.ais;
2376
2377 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2378
2379 if (asm_node.volatile_token) |volatile_token| {
2380 try renderToken(r, volatile_token, .space); // volatile
2381 try renderToken(r, volatile_token + 1, .none); // lparen
2382 } else {
2383 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2384 }
2385
2386 if (asm_node.ast.items.len == 0) {
2387 try ais.forcePushIndent(.normal);
2388 if (asm_node.first_clobber) |first_clobber| {
2389 // asm ("foo" ::: "a", "b")
2390 // asm ("foo" ::: "a", "b",)
2391 try renderExpression(r, asm_node.ast.template, .space);
2392 // Render the three colons.
2393 try renderToken(r, first_clobber - 3, .none);
2394 try renderToken(r, first_clobber - 2, .none);
2395 try renderToken(r, first_clobber - 1, .space);
2396
2397 try ais.writer().writeAll(".{ ");
2398
2399 var tok_i = first_clobber;
2400 while (true) : (tok_i += 1) {
2401 try ais.writer().writeByte('.');
2402 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2403 try ais.writer().writeAll(" = true");
2404
2405 tok_i += 1;
2406 switch (tree.tokenTag(tok_i)) {
2407 .r_paren => {
2408 try ais.writer().writeAll(" }");
2409 ais.popIndent();
2410 return renderToken(r, tok_i, space);
2411 },
2412 .comma => {
2413 if (tree.tokenTag(tok_i + 1) == .r_paren) {
2414 try ais.writer().writeAll(" }");
2415 ais.popIndent();
2416 return renderToken(r, tok_i + 1, space);
2417 } else {
2418 try renderToken(r, tok_i, .space);
2419 }
2420 },
2421 else => unreachable,
2422 }
2423 }
2424 } else {
2425 unreachable;
2426 }
2427 }
2428
2429 try ais.forcePushIndent(.normal);
2430 try renderExpression(r, asm_node.ast.template, .newline);
2431 ais.setIndentDelta(asm_indent_delta);
2432 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2433
2434 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2435 try renderToken(r, colon1, .newline); // :
2436 break :colon2 colon1 + 1;
2437 } else colon2: {
2438 try renderToken(r, colon1, .space); // :
2439
2440 try ais.forcePushIndent(.normal);
2441 for (asm_node.outputs, 0..) |asm_output, i| {
2442 if (i + 1 < asm_node.outputs.len) {
2443 const next_asm_output = asm_node.outputs[i + 1];
2444 try renderAsmOutput(r, asm_output, .none);
2445
2446 const comma = tree.firstToken(next_asm_output) - 1;
2447 try renderToken(r, comma, .newline); // ,
2448 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2449 } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
2450 try ais.pushSpace(.comma);
2451 try renderAsmOutput(r, asm_output, .comma);
2452 ais.popSpace();
2453 ais.popIndent();
2454 ais.setIndentDelta(indent_delta);
2455 ais.popIndent();
2456 return renderToken(r, asm_node.ast.rparen, space); // rparen
2457 } else {
2458 try ais.pushSpace(.comma);
2459 try renderAsmOutput(r, asm_output, .comma);
2460 ais.popSpace();
2461 const comma_or_colon = tree.lastToken(asm_output) + 1;
2462 ais.popIndent();
2463 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2464 .comma => comma_or_colon + 1,
2465 else => comma_or_colon,
2466 };
2467 }
2468 } else unreachable;
2469 };
2470
2471 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2472 try renderToken(r, colon2, .newline); // :
2473 break :colon3 colon2 + 1;
2474 } else colon3: {
2475 try renderToken(r, colon2, .space); // :
2476 try ais.forcePushIndent(.normal);
2477 for (asm_node.inputs, 0..) |asm_input, i| {
2478 if (i + 1 < asm_node.inputs.len) {
2479 const next_asm_input = asm_node.inputs[i + 1];
2480 try renderAsmInput(r, asm_input, .none);
2481
2482 const first_token = tree.firstToken(next_asm_input);
2483 try renderToken(r, first_token - 1, .newline); // ,
2484 try renderExtraNewlineToken(r, first_token);
2485 } else if (asm_node.first_clobber == null) {
2486 try ais.pushSpace(.comma);
2487 try renderAsmInput(r, asm_input, .comma);
2488 ais.popSpace();
2489 ais.popIndent();
2490 ais.setIndentDelta(indent_delta);
2491 ais.popIndent();
2492 return renderToken(r, asm_node.ast.rparen, space); // rparen
2493 } else {
2494 try ais.pushSpace(.comma);
2495 try renderAsmInput(r, asm_input, .comma);
2496 ais.popSpace();
2497 const comma_or_colon = tree.lastToken(asm_input) + 1;
2498 ais.popIndent();
2499 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2500 .comma => comma_or_colon + 1,
2501 else => comma_or_colon,
2502 };
2503 }
2504 }
2505 unreachable;
2506 };
2507
2508 try renderToken(r, colon3, .space); // :
2509 try ais.writer().writeAll(".{ ");
2510 const first_clobber = asm_node.first_clobber.?;
2511 var tok_i = first_clobber;
2512 while (true) {
2513 switch (tree.tokenTag(tok_i + 1)) {
2514 .r_paren => {
2515 ais.setIndentDelta(indent_delta);
2516 try ais.writer().writeByte('.');
2517 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2518 try ais.writer().writeAll(" = true }");
2519 try renderSpace(r, tok_i, lexeme_len, .newline);
2520 ais.popIndent();
2521 return renderToken(r, tok_i + 1, space);
2522 },
2523 .comma => {
2524 switch (tree.tokenTag(tok_i + 2)) {
2525 .r_paren => {
2526 ais.setIndentDelta(indent_delta);
2527 try ais.writer().writeByte('.');
2528 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2529 try ais.writer().writeAll(" = true }");
2530 try renderSpace(r, tok_i, lexeme_len, .newline);
2531 ais.popIndent();
2532 return renderToken(r, tok_i + 2, space);
2533 },
2534 else => {
2535 try ais.writer().writeByte('.');
2536 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2537 try ais.writer().writeAll(" = true");
2538 try renderToken(r, tok_i + 1, .space);
2539 tok_i += 2;
2540 },
2541 }
2542 },
2543 else => unreachable,
2544 }
2545 }
2546}
2547
2548fn renderAsm(
2549 r: *Render,
2550 asm_node: Ast.full.Asm,
2551 space: Space,
2552) Error!void {
2553 const tree = r.tree;
2554 const ais = r.ais;
2555
2556 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2557
2558 if (asm_node.volatile_token) |volatile_token| {
2559 try renderToken(r, volatile_token, .space); // volatile
2560 try renderToken(r, volatile_token + 1, .none); // lparen
2561 } else {
2562 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2563 }
2564
2565 if (asm_node.ast.items.len == 0) {
2566 try ais.forcePushIndent(.normal);
2567 if (asm_node.ast.clobbers.unwrap()) |clobbers| {
2568 // asm ("foo" ::: clobbers)
2569 try renderExpression(r, asm_node.ast.template, .space);
2570 // Render the three colons.
2571 const first_clobber = tree.firstToken(clobbers);
2572 try renderToken(r, first_clobber - 3, .none);
2573 try renderToken(r, first_clobber - 2, .none);
2574 try renderToken(r, first_clobber - 1, .space);
2575 try renderExpression(r, clobbers, .none);
2576 ais.popIndent();
2577 return renderToken(r, asm_node.ast.rparen, space); // rparen
2578 }
2579
2580 // asm ("foo")
2581 try renderExpression(r, asm_node.ast.template, .none);
2582 ais.popIndent();
2583 return renderToken(r, asm_node.ast.rparen, space); // rparen
2584 }
2585
2586 try ais.forcePushIndent(.normal);
2587 try renderExpression(r, asm_node.ast.template, .newline);
2588 ais.setIndentDelta(asm_indent_delta);
2589 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2590
2591 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2592 try renderToken(r, colon1, .newline); // :
2593 break :colon2 colon1 + 1;
2594 } else colon2: {
2595 try renderToken(r, colon1, .space); // :
2596
2597 try ais.forcePushIndent(.normal);
2598 for (asm_node.outputs, 0..) |asm_output, i| {
2599 if (i + 1 < asm_node.outputs.len) {
2600 const next_asm_output = asm_node.outputs[i + 1];
2601 try renderAsmOutput(r, asm_output, .none);
2602
2603 const comma = tree.firstToken(next_asm_output) - 1;
2604 try renderToken(r, comma, .newline); // ,
2605 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2606 } else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
2607 try ais.pushSpace(.comma);
2608 try renderAsmOutput(r, asm_output, .comma);
2609 ais.popSpace();
2610 ais.popIndent();
2611 ais.setIndentDelta(indent_delta);
2612 ais.popIndent();
2613 return renderToken(r, asm_node.ast.rparen, space); // rparen
2614 } else {
2615 try ais.pushSpace(.comma);
2616 try renderAsmOutput(r, asm_output, .comma);
2617 ais.popSpace();
2618 const comma_or_colon = tree.lastToken(asm_output) + 1;
2619 ais.popIndent();
2620 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2621 .comma => comma_or_colon + 1,
2622 else => comma_or_colon,
2623 };
2624 }
2625 } else unreachable;
2626 };
2627
2628 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2629 try renderToken(r, colon2, .newline); // :
2630 break :colon3 colon2 + 1;
2631 } else colon3: {
2632 try renderToken(r, colon2, .space); // :
2633 try ais.forcePushIndent(.normal);
2634 for (asm_node.inputs, 0..) |asm_input, i| {
2635 if (i + 1 < asm_node.inputs.len) {
2636 const next_asm_input = asm_node.inputs[i + 1];
2637 try renderAsmInput(r, asm_input, .none);
2638
2639 const first_token = tree.firstToken(next_asm_input);
2640 try renderToken(r, first_token - 1, .newline); // ,
2641 try renderExtraNewlineToken(r, first_token);
2642 } else if (asm_node.ast.clobbers == .none) {
2643 try ais.pushSpace(.comma);
2644 try renderAsmInput(r, asm_input, .comma);
2645 ais.popSpace();
2646 ais.popIndent();
2647 ais.setIndentDelta(indent_delta);
2648 ais.popIndent();
2649 return renderToken(r, asm_node.ast.rparen, space); // rparen
2650 } else {
2651 try ais.pushSpace(.comma);
2652 try renderAsmInput(r, asm_input, .comma);
2653 ais.popSpace();
2654 const comma_or_colon = tree.lastToken(asm_input) + 1;
2655 ais.popIndent();
2656 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2657 .comma => comma_or_colon + 1,
2658 else => comma_or_colon,
2659 };
2660 }
2661 }
2662 unreachable;
2663 };
2664
2665 try renderToken(r, colon3, .space); // :
2666 const clobbers = asm_node.ast.clobbers.unwrap().?;
2667 try renderExpression(r, clobbers, .none);
2668 ais.setIndentDelta(indent_delta);
2669 ais.popIndent();
2670 return renderToken(r, asm_node.ast.rparen, space); // rparen
2671}
2672
2673fn renderCall(
2674 r: *Render,
2675 call: Ast.full.Call,
2676 space: Space,
2677) Error!void {
2678 try renderExpression(r, call.ast.fn_expr, .none);
2679 try renderParamList(r, call.ast.lparen, call.ast.params, space);
2680}
2681
2682fn renderParamList(
2683 r: *Render,
2684 lparen: Ast.TokenIndex,
2685 params: []const Ast.Node.Index,
2686 space: Space,
2687) Error!void {
2688 const tree = r.tree;
2689 const ais = r.ais;
2690
2691 if (params.len == 0) {
2692 try ais.pushIndent(.normal);
2693 try renderToken(r, lparen, .none);
2694 ais.popIndent();
2695 return renderToken(r, lparen + 1, space); // )
2696 }
2697
2698 const last_param = params[params.len - 1];
2699 const after_last_param_tok = tree.lastToken(last_param) + 1;
2700 if (tree.tokenTag(after_last_param_tok) == .comma) {
2701 try ais.pushIndent(.normal);
2702 try renderToken(r, lparen, .newline); // (
2703 for (params, 0..) |param_node, i| {
2704 if (i + 1 < params.len) {
2705 try renderExpression(r, param_node, .none);
2706
2707 const comma = tree.lastToken(param_node) + 1;
2708 try renderToken(r, comma, .newline); // ,
2709
2710 try renderExtraNewline(r, params[i + 1]);
2711 } else {
2712 try ais.pushSpace(.comma);
2713 try renderExpression(r, param_node, .comma);
2714 ais.popSpace();
2715 }
2716 }
2717 ais.popIndent();
2718 return renderToken(r, after_last_param_tok + 1, space); // )
2719 }
2720
2721 try ais.pushIndent(.normal);
2722 try renderToken(r, lparen, .none); // (
2723 for (params, 0..) |param_node, i| {
2724 try renderExpression(r, param_node, .none);
2725
2726 if (i + 1 < params.len) {
2727 const comma = tree.lastToken(param_node) + 1;
2728 const next_multiline_string =
2729 tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line;
2730 const comma_space: Space = if (next_multiline_string) .none else .space;
2731 try renderToken(r, comma, comma_space);
2732 }
2733 }
2734 ais.popIndent();
2735 return renderToken(r, after_last_param_tok, space); // )
2736}
2737
2738/// Render an expression, and the comma that follows it, if it is present in the source.
2739/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2740fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
2741 const tree = r.tree;
2742 const maybe_comma = tree.lastToken(node) + 1;
2743 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2744 try renderExpression(r, node, .none);
2745 return renderToken(r, maybe_comma, space);
2746 } else {
2747 return renderExpression(r, node, space);
2748 }
2749}
2750
2751/// Render a token, and the comma that follows it, if it is present in the source.
2752/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2753fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
2754 const tree = r.tree;
2755 const maybe_comma = token + 1;
2756 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2757 try renderToken(r, token, .none);
2758 return renderToken(r, maybe_comma, space);
2759 } else {
2760 return renderToken(r, token, space);
2761 }
2762}
2763
2764/// Render an identifier, and the comma that follows it, if it is present in the source.
2765/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2766fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2767 const tree = r.tree;
2768 const maybe_comma = token + 1;
2769 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2770 try renderIdentifier(r, token, .none, quote);
2771 return renderToken(r, maybe_comma, space);
2772 } else {
2773 return renderIdentifier(r, token, space, quote);
2774 }
2775}
2776
2777const Space = enum {
2778 /// Output the token lexeme only.
2779 none,
2780 /// Output the token lexeme followed by a single space.
2781 space,
2782 /// Output the token lexeme followed by a newline.
2783 newline,
2784 /// If the next token is a comma, render it as well. If not, insert one.
2785 /// In either case, a newline will be inserted afterwards.
2786 comma,
2787 /// Additionally consume the next token if it is a comma.
2788 /// In either case, a space will be inserted afterwards.
2789 comma_space,
2790 /// Additionally consume the next token if it is a semicolon.
2791 /// In either case, a newline will be inserted afterwards.
2792 semicolon,
2793 /// Skip rendering whitespace and comments. If this is used, the caller
2794 /// *must* handle whitespace and comments manually.
2795 skip,
2796};
2797
2798fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2799 const tree = r.tree;
2800 const ais = r.ais;
2801 const lexeme = tokenSliceForRender(tree, token_index);
2802 try ais.writer().writeAll(lexeme);
2803 try renderSpace(r, token_index, lexeme.len, space);
2804}
2805
2806fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: Space, override_space: Space) Error!void {
2807 const tree = r.tree;
2808 const ais = r.ais;
2809 const lexeme = tokenSliceForRender(tree, token_index);
2810 try ais.writer().writeAll(lexeme);
2811 ais.enableSpaceMode(override_space);
2812 defer ais.disableSpaceMode();
2813 try renderSpace(r, token_index, lexeme.len, space);
2814}
2815
2816fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2817 const tree = r.tree;
2818 const ais = r.ais;
2819
2820 const next_token_tag = tree.tokenTag(token_index + 1);
2821
2822 if (space == .skip) return;
2823
2824 if (space == .comma and next_token_tag != .comma) {
2825 try ais.writer().writeByte(',');
2826 }
2827 if (space == .semicolon or space == .comma) ais.enableSpaceMode(space);
2828 defer ais.disableSpaceMode();
2829 const comment = try renderComments(
2830 r,
2831 tree.tokenStart(token_index) + lexeme_len,
2832 tree.tokenStart(token_index + 1),
2833 );
2834 switch (space) {
2835 .none => {},
2836 .space => if (!comment) try ais.writer().writeByte(' '),
2837 .newline => if (!comment) try ais.insertNewline(),
2838
2839 .comma => if (next_token_tag == .comma) {
2840 try renderToken(r, token_index + 1, .newline);
2841 } else if (!comment) {
2842 try ais.insertNewline();
2843 },
2844
2845 .comma_space => if (next_token_tag == .comma) {
2846 try renderToken(r, token_index + 1, .space);
2847 } else if (!comment) {
2848 try ais.writer().writeByte(' ');
2849 },
2850
2851 .semicolon => if (next_token_tag == .semicolon) {
2852 try renderToken(r, token_index + 1, .newline);
2853 } else if (!comment) {
2854 try ais.insertNewline();
2855 },
2856
2857 .skip => unreachable,
2858 }
2859}
2860
2861fn renderOnlySpace(r: *Render, space: Space) Error!void {
2862 const ais = r.ais;
2863 switch (space) {
2864 .none => {},
2865 .space => try ais.writer().writeByte(' '),
2866 .newline => try ais.insertNewline(),
2867 .comma => try ais.writer().writeAll(",\n"),
2868 .comma_space => try ais.writer().writeAll(", "),
2869 .semicolon => try ais.writer().writeAll(";\n"),
2870 .skip => unreachable,
2871 }
2872}
2873
2874const QuoteBehavior = enum {
2875 preserve_when_shadowing,
2876 eagerly_unquote,
2877 eagerly_unquote_except_underscore,
2878};
2879
2880fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2881 const tree = r.tree;
2882 assert(tree.tokenTag(token_index) == .identifier);
2883 const lexeme = tokenSliceForRender(tree, token_index);
2884
2885 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2886 try r.ais.writer().writeAll(mangled);
2887 try renderSpace(r, token_index, lexeme.len, space);
2888 return;
2889 }
2890
2891 if (lexeme[0] != '@') {
2892 return renderToken(r, token_index, space);
2893 }
2894
2895 assert(lexeme.len >= 3);
2896 assert(lexeme[0] == '@');
2897 assert(lexeme[1] == '\"');
2898 assert(lexeme[lexeme.len - 1] == '\"');
2899 const contents = lexeme[2 .. lexeme.len - 1]; // inside the @"" quotation
2900
2901 // Empty name can't be unquoted.
2902 if (contents.len == 0) {
2903 return renderQuotedIdentifier(r, token_index, space, false);
2904 }
2905
2906 // Special case for _.
2907 if (std.zig.isUnderscore(contents)) switch (quote) {
2908 .eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
2909 .eagerly_unquote_except_underscore,
2910 .preserve_when_shadowing,
2911 => return renderQuotedIdentifier(r, token_index, space, false),
2912 };
2913
2914 // Scan the entire name for characters that would (after un-escaping) be illegal in a symbol,
2915 // i.e. contents don't match: [A-Za-z_][A-Za-z0-9_]*
2916 var contents_i: usize = 0;
2917 while (contents_i < contents.len) {
2918 switch (contents[contents_i]) {
2919 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2920 'A'...'Z', 'a'...'z', '_' => {},
2921 '\\' => {
2922 var esc_offset = contents_i;
2923 const res = std.zig.string_literal.parseEscapeSequence(contents, &esc_offset);
2924 switch (res) {
2925 .success => |char| switch (char) {
2926 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2927 'A'...'Z', 'a'...'z', '_' => {},
2928 else => return renderQuotedIdentifier(r, token_index, space, false),
2929 },
2930 .failure => return renderQuotedIdentifier(r, token_index, space, false),
2931 }
2932 contents_i += esc_offset;
2933 continue;
2934 },
2935 else => return renderQuotedIdentifier(r, token_index, space, false),
2936 }
2937 contents_i += 1;
2938 }
2939
2940 // Read enough of the name (while un-escaping) to determine if it's a keyword or primitive.
2941 // If it's too long to fit in this buffer, we know it's neither and quoting is unnecessary.
2942 // If we read the whole thing, we have to do further checks.
2943 const longest_keyword_or_primitive_len = comptime blk: {
2944 var longest = 0;
2945 for (primitives.names.keys()) |key| {
2946 if (key.len > longest) longest = key.len;
2947 }
2948 for (std.zig.Token.keywords.keys()) |key| {
2949 if (key.len > longest) longest = key.len;
2950 }
2951 break :blk longest;
2952 };
2953 var buf: [longest_keyword_or_primitive_len]u8 = undefined;
2954
2955 contents_i = 0;
2956 var buf_i: usize = 0;
2957 while (contents_i < contents.len and buf_i < longest_keyword_or_primitive_len) {
2958 if (contents[contents_i] == '\\') {
2959 const res = std.zig.string_literal.parseEscapeSequence(contents, &contents_i).success;
2960 buf[buf_i] = @as(u8, @intCast(res));
2961 buf_i += 1;
2962 } else {
2963 buf[buf_i] = contents[contents_i];
2964 contents_i += 1;
2965 buf_i += 1;
2966 }
2967 }
2968
2969 // We read the whole thing, so it could be a keyword or primitive.
2970 if (contents_i == contents.len) {
2971 if (!std.zig.isValidId(buf[0..buf_i])) {
2972 return renderQuotedIdentifier(r, token_index, space, false);
2973 }
2974 if (primitives.isPrimitive(buf[0..buf_i])) switch (quote) {
2975 .eagerly_unquote,
2976 .eagerly_unquote_except_underscore,
2977 => return renderQuotedIdentifier(r, token_index, space, true),
2978 .preserve_when_shadowing => return renderQuotedIdentifier(r, token_index, space, false),
2979 };
2980 }
2981
2982 try renderQuotedIdentifier(r, token_index, space, true);
2983}
2984
2985// Renders a @"" quoted identifier, normalizing escapes.
2986// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
2987// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2988fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2989 const tree = r.tree;
2990 const ais = r.ais;
2991 assert(tree.tokenTag(token_index) == .identifier);
2992 const lexeme = tokenSliceForRender(tree, token_index);
2993 assert(lexeme.len >= 3 and lexeme[0] == '@');
2994
2995 if (!unquote) try ais.writer().writeAll("@\"");
2996 const contents = lexeme[2 .. lexeme.len - 1];
2997 try renderIdentifierContents(ais.writer(), contents);
2998 if (!unquote) try ais.writer().writeByte('\"');
2999
3000 try renderSpace(r, token_index, lexeme.len, space);
3001}
3002
3003fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void {
3004 var pos: usize = 0;
3005 while (pos < bytes.len) {
3006 const byte = bytes[pos];
3007 switch (byte) {
3008 '\\' => {
3009 const old_pos = pos;
3010 const res = std.zig.string_literal.parseEscapeSequence(bytes, &pos);
3011 const escape_sequence = bytes[old_pos..pos];
3012 switch (res) {
3013 .success => |codepoint| {
3014 if (codepoint <= 0x7f) {
3015 const buf = [1]u8{@as(u8, @intCast(codepoint))};
3016 try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
3017 } else {
3018 try writer.writeAll(escape_sequence);
3019 }
3020 },
3021 .failure => {
3022 try writer.writeAll(escape_sequence);
3023 },
3024 }
3025 },
3026 0x00...('\\' - 1), ('\\' + 1)...0x7f => {
3027 const buf = [1]u8{byte};
3028 try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
3029 pos += 1;
3030 },
3031 0x80...0xff => {
3032 try writer.writeByte(byte);
3033 pos += 1;
3034 },
3035 }
3036 }
3037}
3038
3039/// Returns true if there exists a line comment between any of the tokens from
3040/// `start_token` to `end_token`. This is used to determine if e.g. a
3041/// fn_proto should be wrapped and have a trailing comma inserted even if
3042/// there is none in the source.
3043fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3044 for (start_token..end_token) |i| {
3045 const token: Ast.TokenIndex = @intCast(i);
3046 const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
3047 const end = tree.tokenStart(token + 1);
3048 if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
3049 }
3050
3051 return false;
3052}
3053
3054/// Returns true if there exists a multiline string literal between the start
3055/// of token `start_token` and the start of token `end_token`.
3056fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3057 return std.mem.indexOfScalar(
3058 Token.Tag,
3059 tree.tokens.items(.tag)[start_token..end_token],
3060 .multiline_string_literal_line,
3061 ) != null;
3062}
3063
3064/// Assumes that start is the first byte past the previous token and
3065/// that end is the last byte before the next token.
3066fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
3067 const tree = r.tree;
3068 const ais = r.ais;
3069
3070 var index: usize = start;
3071 while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| {
3072 const comment_start = index + offset;
3073
3074 // If there is no newline, the comment ends with EOF
3075 const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n');
3076 const newline = if (newline_index) |i| comment_start + i else null;
3077
3078 const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len];
3079 const trimmed_comment = mem.trimEnd(u8, untrimmed_comment, &std.ascii.whitespace);
3080
3081 // Don't leave any whitespace at the start of the file
3082 if (index != 0) {
3083 if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) {
3084 // Leave up to one empty line before the first comment
3085 try ais.insertNewline();
3086 try ais.insertNewline();
3087 } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) {
3088 // Respect the newline directly before the comment.
3089 // Note: This allows an empty line between comments
3090 try ais.insertNewline();
3091 } else if (index == start) {
3092 // Otherwise if the first comment is on the same line as
3093 // the token before it, prefix it with a single space.
3094 try ais.writer().writeByte(' ');
3095 }
3096 }
3097
3098 index = 1 + (newline orelse end - 1);
3099
3100 const comment_content = mem.trimStart(u8, trimmed_comment["//".len..], &std.ascii.whitespace);
3101 if (ais.disabled_offset != null and mem.eql(u8, comment_content, "zig fmt: on")) {
3102 // Write the source for which formatting was disabled directly
3103 // to the underlying writer, fixing up invalid whitespace.
3104 const disabled_source = tree.source[ais.disabled_offset.?..comment_start];
3105 try writeFixingWhitespace(ais.underlying_writer, disabled_source);
3106 // Write with the canonical single space.
3107 try ais.underlying_writer.writeAll("// zig fmt: on\n");
3108 ais.disabled_offset = null;
3109 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
3110 // Write with the canonical single space.
3111 try ais.writer().writeAll("// zig fmt: off\n");
3112 ais.disabled_offset = index;
3113 } else {
3114 // Write the comment minus trailing whitespace.
3115 try ais.writer().print("{s}\n", .{trimmed_comment});
3116 }
3117 }
3118
3119 if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
3120 // Don't leave any whitespace at the end of the file
3121 if (end != tree.source.len) {
3122 try ais.insertNewline();
3123 }
3124 }
3125
3126 return index != start;
3127}
3128
3129fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
3130 return renderExtraNewlineToken(r, r.tree.firstToken(node));
3131}
3132
3133/// Check if there is an empty line immediately before the given token. If so, render it.
3134fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
3135 const tree = r.tree;
3136 const ais = r.ais;
3137 const token_start = tree.tokenStart(token_index);
3138 if (token_start == 0) return;
3139 const prev_token_end = if (token_index == 0)
3140 0
3141 else
3142 tree.tokenStart(token_index - 1) + tokenSliceForRender(tree, token_index - 1).len;
3143
3144 // If there is a immediately preceding comment or doc_comment,
3145 // skip it because required extra newline has already been rendered.
3146 if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return;
3147 if (tree.isTokenPrecededByTags(token_index, &.{.doc_comment})) return;
3148
3149 // Iterate backwards to the end of the previous token, stopping if a
3150 // non-whitespace character is encountered or two newlines have been found.
3151 var i = token_start - 1;
3152 var newlines: u2 = 0;
3153 while (std.ascii.isWhitespace(tree.source[i])) : (i -= 1) {
3154 if (tree.source[i] == '\n') newlines += 1;
3155 if (newlines == 2) return ais.insertNewline();
3156 if (i == prev_token_end) break;
3157 }
3158}
3159
3160/// end_token is the token one past the last doc comment token. This function
3161/// searches backwards from there.
3162fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
3163 const tree = r.tree;
3164 // Search backwards for the first doc comment.
3165 if (end_token == 0) return;
3166 var tok = end_token - 1;
3167 while (tree.tokenTag(tok) == .doc_comment) {
3168 if (tok == 0) break;
3169 tok -= 1;
3170 } else {
3171 tok += 1;
3172 }
3173 const first_tok = tok;
3174 if (first_tok == end_token) return;
3175
3176 if (first_tok != 0) {
3177 const prev_token_tag = tree.tokenTag(first_tok - 1);
3178
3179 // Prevent accidental use of `renderDocComments` for a function argument doc comment
3180 assert(prev_token_tag != .l_paren);
3181
3182 if (prev_token_tag != .l_brace) {
3183 try renderExtraNewlineToken(r, first_tok);
3184 }
3185 }
3186
3187 while (tree.tokenTag(tok) == .doc_comment) : (tok += 1) {
3188 try renderToken(r, tok, .newline);
3189 }
3190}
3191
3192/// start_token is first container doc comment token.
3193fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
3194 const tree = r.tree;
3195 var tok = start_token;
3196 while (tree.tokenTag(tok) == .container_doc_comment) : (tok += 1) {
3197 try renderToken(r, tok, .newline);
3198 }
3199 // Render extra newline if there is one between final container doc comment and
3200 // the next token. If the next token is a doc comment, that code path
3201 // will have its own logic to insert a newline.
3202 if (tree.tokenTag(tok) != .doc_comment) {
3203 try renderExtraNewlineToken(r, tok);
3204 }
3205}
3206
3207fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3208 const tree = &r.tree;
3209 const ais = r.ais;
3210 var buf: [1]Ast.Node.Index = undefined;
3211 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3212 var it = fn_proto.iterate(tree);
3213 while (it.next()) |param| {
3214 const name_ident = param.name_token.?;
3215 assert(tree.tokenTag(name_ident) == .identifier);
3216 const w = ais.writer();
3217 try w.writeAll("_ = ");
3218 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
3219 try w.writeAll(";\n");
3220 }
3221}
3222
3223fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
3224 var ret = tree.tokenSlice(token_index);
3225 switch (tree.tokenTag(token_index)) {
3226 .container_doc_comment, .doc_comment => {
3227 ret = mem.trimEnd(u8, ret, &std.ascii.whitespace);
3228 },
3229 else => {},
3230 }
3231 return ret;
3232}
3233
3234fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usize {
3235 const tree = r.tree;
3236 const ais = r.ais;
3237 assert(tree.tokenTag(token_index) == .string_literal);
3238 const lexeme = tokenSliceForRender(tree, token_index);
3239 const unquoted = lexeme[1..][0 .. lexeme.len - 2];
3240 if (std.zig.isValidId(unquoted)) {
3241 try ais.writer().writeAll(unquoted);
3242 return unquoted.len;
3243 } else {
3244 try ais.writer().writeByte('@');
3245 try ais.writer().writeAll(lexeme);
3246 return lexeme.len + 1;
3247 }
3248}
3249
3250fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3251 const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3252 for (between_source) |byte| switch (byte) {
3253 '\n' => return false,
3254 '/' => return true,
3255 else => continue,
3256 };
3257 return false;
3258}
3259
3260/// Returns `true` if and only if there are any tokens or line comments between
3261/// start_token and end_token.
3262fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3263 if (start_token + 1 != end_token) return true;
3264 const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3265 for (between_source) |byte| switch (byte) {
3266 '/' => return true,
3267 else => continue,
3268 };
3269 return false;
3270}
3271
3272fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void {
3273 for (slice) |byte| switch (byte) {
3274 '\t' => try writer.writeAll(" " ** indent_delta),
3275 '\r' => {},
3276 else => try writer.writeByte(byte),
3277 };
3278}
3279
3280fn nodeIsBlock(tag: Ast.Node.Tag) bool {
3281 return switch (tag) {
3282 .block,
3283 .block_semicolon,
3284 .block_two,
3285 .block_two_semicolon,
3286 => true,
3287 else => false,
3288 };
3289}
3290
3291fn nodeIsIfForWhileSwitch(tag: Ast.Node.Tag) bool {
3292 return switch (tag) {
3293 .@"if",
3294 .if_simple,
3295 .@"for",
3296 .for_simple,
3297 .@"while",
3298 .while_simple,
3299 .while_cont,
3300 .@"switch",
3301 .switch_comma,
3302 => true,
3303 else => false,
3304 };
3305}
3306
3307fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
3308 return switch (tag) {
3309 .@"catch",
3310 .add,
3311 .add_wrap,
3312 .array_cat,
3313 .array_mult,
3314 .assign,
3315 .assign_bit_and,
3316 .assign_bit_or,
3317 .assign_shl,
3318 .assign_shr,
3319 .assign_bit_xor,
3320 .assign_div,
3321 .assign_sub,
3322 .assign_sub_wrap,
3323 .assign_mod,
3324 .assign_add,
3325 .assign_add_wrap,
3326 .assign_mul,
3327 .assign_mul_wrap,
3328 .bang_equal,
3329 .bit_and,
3330 .bit_or,
3331 .shl,
3332 .shr,
3333 .bit_xor,
3334 .bool_and,
3335 .bool_or,
3336 .div,
3337 .equal_equal,
3338 .error_union,
3339 .greater_or_equal,
3340 .greater_than,
3341 .less_or_equal,
3342 .less_than,
3343 .merge_error_sets,
3344 .mod,
3345 .mul,
3346 .mul_wrap,
3347 .sub,
3348 .sub_wrap,
3349 .@"orelse",
3350 => true,
3351
3352 else => false,
3353 };
3354}
3355
3356// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3357fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3358 const first_token = tree.firstToken(exprs[0]);
3359 if (tree.tokensOnSameLine(first_token, rtoken)) {
3360 const maybe_comma = rtoken - 1;
3361 if (tree.tokenTag(maybe_comma) == .comma)
3362 return 1;
3363 return exprs.len; // no newlines
3364 }
3365
3366 var count: usize = 1;
3367 for (exprs, 0..) |expr, i| {
3368 if (i + 1 < exprs.len) {
3369 const expr_last_token = tree.lastToken(expr) + 1;
3370 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3371 count += 1;
3372 } else {
3373 return count;
3374 }
3375 }
3376 unreachable;
3377}
3378
3379/// Automatically inserts indentation of written data by keeping
3380/// track of the current indentation level
3381///
3382/// We introduce a new indentation scope with pushIndent/popIndent whenever
3383/// we potentially want to introduce an indent after the next newline.
3384///
3385/// Indentation should only ever increment by one from one line to the next,
3386/// no matter how many new indentation scopes are introduced. This is done by
3387/// only realizing the indentation from the most recent scope. As an example:
3388///
3389/// while (foo) if (bar)
3390/// f(x);
3391///
3392/// The body of `while` introduces a new indentation scope and the body of
3393/// `if` also introduces a new indentation scope. When the newline is seen,
3394/// only the indentation scope of the `if` is realized, and the `while` is
3395/// not.
3396///
3397/// As comments are rendered during space rendering, we need to keep track
3398/// of the appropriate indentation level for them with pushSpace/popSpace.
3399/// This should be done whenever a scope that ends in a .semicolon or a
3400/// .comma is introduced.
3401fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
3402 return struct {
3403 const Self = @This();
3404 pub const WriteError = UnderlyingWriter.Error;
3405 pub const Writer = std.io.GenericWriter(*Self, WriteError, write);
3406
3407 pub const IndentType = enum {
3408 normal,
3409 after_equals,
3410 binop,
3411 field_access,
3412 };
3413 const StackElem = struct {
3414 indent_type: IndentType,
3415 realized: bool,
3416 };
3417 const SpaceElem = struct {
3418 space: Space,
3419 indent_count: usize,
3420 };
3421
3422 underlying_writer: UnderlyingWriter,
3423
3424 /// Offset into the source at which formatting has been disabled with
3425 /// a `zig fmt: off` comment.
3426 ///
3427 /// If non-null, the AutoIndentingStream will not write any bytes
3428 /// to the underlying writer. It will however continue to track the
3429 /// indentation level.
3430 disabled_offset: ?usize = null,
3431
3432 indent_count: usize = 0,
3433 indent_delta: usize,
3434 indent_stack: std.ArrayList(StackElem),
3435 space_stack: std.ArrayList(SpaceElem),
3436 space_mode: ?usize = null,
3437 disable_indent_committing: usize = 0,
3438 current_line_empty: bool = true,
3439 /// the most recently applied indent
3440 applied_indent: usize = 0,
3441
3442 pub fn init(buffer: *std.ArrayList(u8), indent_delta_: usize) Self {
3443 return .{
3444 .underlying_writer = buffer.writer(),
3445 .indent_delta = indent_delta_,
3446 .indent_stack = std.ArrayList(StackElem).init(buffer.allocator),
3447 .space_stack = std.ArrayList(SpaceElem).init(buffer.allocator),
3448 };
3449 }
3450
3451 pub fn deinit(self: *Self) void {
3452 self.indent_stack.deinit();
3453 self.space_stack.deinit();
3454 }
3455
3456 pub fn writer(self: *Self) Writer {
3457 return .{ .context = self };
3458 }
3459
3460 pub fn write(self: *Self, bytes: []const u8) WriteError!usize {
3461 if (bytes.len == 0)
3462 return @as(usize, 0);
3463
3464 try self.applyIndent();
3465 return self.writeNoIndent(bytes);
3466 }
3467
3468 // Change the indent delta without changing the final indentation level
3469 pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void {
3470 if (self.indent_delta == new_indent_delta) {
3471 return;
3472 } else if (self.indent_delta > new_indent_delta) {
3473 assert(self.indent_delta % new_indent_delta == 0);
3474 self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta);
3475 } else {
3476 // assert that the current indentation (in spaces) in a multiple of the new delta
3477 assert((self.indent_count * self.indent_delta) % new_indent_delta == 0);
3478 self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta);
3479 }
3480 self.indent_delta = new_indent_delta;
3481 }
3482
3483 fn writeNoIndent(self: *Self, bytes: []const u8) WriteError!usize {
3484 if (bytes.len == 0)
3485 return @as(usize, 0);
3486
3487 if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes);
3488 if (bytes[bytes.len - 1] == '\n')
3489 self.resetLine();
3490 return bytes.len;
3491 }
3492
3493 pub fn insertNewline(self: *Self) WriteError!void {
3494 _ = try self.writeNoIndent("\n");
3495 }
3496
3497 fn resetLine(self: *Self) void {
3498 self.current_line_empty = true;
3499
3500 if (self.disable_indent_committing > 0) return;
3501
3502 if (self.indent_stack.items.len > 0) {
3503 // By default, we realize the most recent indentation scope.
3504 var to_realize = self.indent_stack.items.len - 1;
3505
3506 if (self.indent_stack.items.len >= 2 and
3507 self.indent_stack.items[to_realize - 1].indent_type == .after_equals and
3508 self.indent_stack.items[to_realize - 1].realized and
3509 self.indent_stack.items[to_realize].indent_type == .binop)
3510 {
3511 // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
3512 // This ensures correct indentation in the below example:
3513 //
3514 // const foo =
3515 // (x >= 'a' and x <= 'z') or //<-- we are here
3516 // (x >= 'A' and x <= 'Z');
3517 //
3518 return;
3519 }
3520
3521 if (self.indent_stack.items[to_realize].indent_type == .field_access) {
3522 // Only realize the top-most field_access in a chain.
3523 while (to_realize > 0 and self.indent_stack.items[to_realize - 1].indent_type == .field_access)
3524 to_realize -= 1;
3525 }
3526
3527 if (self.indent_stack.items[to_realize].realized) return;
3528 self.indent_stack.items[to_realize].realized = true;
3529 self.indent_count += 1;
3530 }
3531 }
3532
3533 /// Disables indentation level changes during the next newlines until re-enabled.
3534 pub fn disableIndentCommitting(self: *Self) void {
3535 self.disable_indent_committing += 1;
3536 }
3537
3538 pub fn enableIndentCommitting(self: *Self) void {
3539 assert(self.disable_indent_committing > 0);
3540 self.disable_indent_committing -= 1;
3541 }
3542
3543 pub fn pushSpace(self: *Self, space: Space) !void {
3544 try self.space_stack.append(.{ .space = space, .indent_count = self.indent_count });
3545 }
3546
3547 pub fn popSpace(self: *Self) void {
3548 _ = self.space_stack.pop();
3549 }
3550
3551 /// Sets current indentation level to be the same as that of the last pushSpace.
3552 pub fn enableSpaceMode(self: *Self, space: Space) void {
3553 if (self.space_stack.items.len == 0) return;
3554 const curr = self.space_stack.getLast();
3555 if (curr.space != space) return;
3556 self.space_mode = curr.indent_count;
3557 }
3558
3559 pub fn disableSpaceMode(self: *Self) void {
3560 self.space_mode = null;
3561 }
3562
3563 pub fn lastSpaceModeIndent(self: *Self) usize {
3564 if (self.space_stack.items.len == 0) return 0;
3565 return self.space_stack.getLast().indent_count * self.indent_delta;
3566 }
3567
3568 /// Insert a newline unless the current line is blank
3569 pub fn maybeInsertNewline(self: *Self) WriteError!void {
3570 if (!self.current_line_empty)
3571 try self.insertNewline();
3572 }
3573
3574 /// Push default indentation
3575 /// Doesn't actually write any indentation.
3576 /// Just primes the stream to be able to write the correct indentation if it needs to.
3577 pub fn pushIndent(self: *Self, indent_type: IndentType) !void {
3578 try self.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
3579 }
3580
3581 /// Forces an indentation level to be realized.
3582 pub fn forcePushIndent(self: *Self, indent_type: IndentType) !void {
3583 try self.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
3584 self.indent_count += 1;
3585 }
3586
3587 pub fn popIndent(self: *Self) void {
3588 if (self.indent_stack.pop().?.realized) {
3589 assert(self.indent_count > 0);
3590 self.indent_count -= 1;
3591 }
3592 }
3593
3594 pub fn indentStackEmpty(self: *Self) bool {
3595 return self.indent_stack.items.len == 0;
3596 }
3597
3598 /// Writes ' ' bytes if the current line is empty
3599 fn applyIndent(self: *Self) WriteError!void {
3600 const current_indent = self.currentIndent();
3601 if (self.current_line_empty and current_indent > 0) {
3602 if (self.disabled_offset == null) {
3603 try self.underlying_writer.writeByteNTimes(' ', current_indent);
3604 }
3605 self.applied_indent = current_indent;
3606 }
3607 self.current_line_empty = false;
3608 }
3609
3610 /// Checks to see if the most recent indentation exceeds the currently pushed indents
3611 pub fn isLineOverIndented(self: *Self) bool {
3612 if (self.current_line_empty) return false;
3613 return self.applied_indent > self.currentIndent();
3614 }
3615
3616 fn currentIndent(self: *Self) usize {
3617 const indent_count = self.space_mode orelse self.indent_count;
3618 return indent_count * self.indent_delta;
3619 }
3620 };
3621}