zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 2f94d1cf (84829B) - Raw


      1 const std = @import("../index.zig");
      2 const builtin = @import("builtin");
      3 const assert = std.debug.assert;
      4 const mem = std.mem;
      5 const ast = std.zig.ast;
      6 const Token = std.zig.Token;
      7 
      8 const indent_delta = 4;
      9 
     10 pub const Error = error{
     11     /// Ran out of memory allocating call stack frames to complete rendering.
     12     OutOfMemory,
     13 };
     14 
     15 pub fn render(allocator: &mem.Allocator, stream: var, tree: &ast.Tree) (@typeOf(stream).Child.Error || Error)!void {
     16     comptime assert(@typeId(@typeOf(stream)) == builtin.TypeId.Pointer);
     17 
     18     // render all the line comments at the beginning of the file
     19     var tok_it = tree.tokens.iterator(0);
     20     while (tok_it.next()) |token| {
     21         if (token.id != Token.Id.LineComment) break;
     22         try stream.print("{}\n", mem.trimRight(u8, tree.tokenSlicePtr(token), " "));
     23         if (tok_it.peek()) |next_token| {
     24             const loc = tree.tokenLocationPtr(token.end, next_token);
     25             if (loc.line >= 2) {
     26                 try stream.writeByte('\n');
     27             }
     28         }
     29     }
     30 
     31 
     32     var it = tree.root_node.decls.iterator(0);
     33     while (it.next()) |decl| {
     34         try renderTopLevelDecl(allocator, stream, tree, 0, decl.*);
     35         if (it.peek()) |next_decl| {
     36             try renderExtraNewline(tree, stream, next_decl.*);
     37         }
     38     }
     39 }
     40 
     41 fn renderExtraNewline(tree: &ast.Tree, stream: var, node: &ast.Node) !void {
     42     var first_token = node.firstToken();
     43     while (tree.tokens.at(first_token - 1).id == Token.Id.DocComment) {
     44         first_token -= 1;
     45     }
     46     const prev_token_end = tree.tokens.at(first_token - 1).end;
     47     const loc = tree.tokenLocation(prev_token_end, first_token);
     48     if (loc.line >= 2) {
     49         try stream.writeByte('\n');
     50     }
     51 }
     52 
     53 fn renderTopLevelDecl(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize, decl: &ast.Node) (@typeOf(stream).Child.Error || Error)!void {
     54     switch (decl.id) {
     55         ast.Node.Id.FnProto => {
     56             const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
     57 
     58             try renderDocComments(tree, stream, fn_proto, indent);
     59 
     60             if (fn_proto.body_node) |body_node| {
     61                 try renderExpression(allocator, stream, tree, indent, decl, Space.Space);
     62                 try renderExpression(allocator, stream, tree, indent, body_node, Space.Newline);
     63             } else {
     64                 try renderExpression(allocator, stream, tree, indent, decl, Space.None);
     65                 try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, Space.Newline);
     66             }
     67         },
     68 
     69         ast.Node.Id.Use => {
     70             const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl);
     71 
     72             if (use_decl.visib_token) |visib_token| {
     73                 try renderToken(tree, stream, visib_token, indent, Space.Space); // pub
     74             }
     75             try renderToken(tree, stream, use_decl.use_token, indent, Space.Space); // use
     76             try renderExpression(allocator, stream, tree, indent, use_decl.expr, Space.None);
     77             try renderToken(tree, stream, use_decl.semicolon_token, indent, Space.Newline); // ;
     78         },
     79 
     80         ast.Node.Id.VarDecl => {
     81             const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl);
     82 
     83             try renderDocComments(tree, stream, var_decl, indent);
     84             try renderVarDecl(allocator, stream, tree, indent, var_decl);
     85         },
     86 
     87         ast.Node.Id.TestDecl => {
     88             const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);
     89 
     90             try renderDocComments(tree, stream, test_decl, indent);
     91             try renderToken(tree, stream, test_decl.test_token, indent, Space.Space);
     92             try renderExpression(allocator, stream, tree, indent, test_decl.name, Space.Space);
     93             try renderExpression(allocator, stream, tree, indent, test_decl.body_node, Space.Newline);
     94         },
     95 
     96         ast.Node.Id.StructField => {
     97             const field = @fieldParentPtr(ast.Node.StructField, "base", decl);
     98 
     99             try renderDocComments(tree, stream, field, indent);
    100             if (field.visib_token) |visib_token| {
    101                 try renderToken(tree, stream, visib_token, indent, Space.Space); // pub
    102             }
    103             try renderToken(tree, stream, field.name_token, indent, Space.None); // name
    104             try renderToken(tree, stream, tree.nextToken(field.name_token), indent, Space.Space); // :
    105             try renderTrailingComma(allocator, stream, tree, indent, field.type_expr, Space.Newline); // type,
    106         },
    107 
    108         ast.Node.Id.UnionTag => {
    109             const tag = @fieldParentPtr(ast.Node.UnionTag, "base", decl);
    110 
    111             try renderDocComments(tree, stream, tag, indent);
    112 
    113             const name_space = if (tag.type_expr == null and tag.value_expr != null) Space.Space else Space.None;
    114             try renderToken(tree, stream, tag.name_token, indent, name_space); // name
    115 
    116             if (tag.type_expr) |type_expr| {
    117                 try renderToken(tree, stream, tree.nextToken(tag.name_token), indent, Space.Space); // :
    118 
    119                 const after_type_space = if (tag.value_expr == null) Space.None else Space.Space;
    120                 try renderExpression(allocator, stream, tree, indent, type_expr, after_type_space);
    121             }
    122 
    123             if (tag.value_expr) |value_expr| {
    124                 try renderToken(tree, stream, tree.prevToken(value_expr.firstToken()), indent, Space.Space); // =
    125                 try renderExpression(allocator, stream, tree, indent, value_expr, Space.None);
    126             }
    127 
    128             try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, Space.Newline); // ,
    129         },
    130 
    131         ast.Node.Id.EnumTag => {
    132             const tag = @fieldParentPtr(ast.Node.EnumTag, "base", decl);
    133 
    134             try renderDocComments(tree, stream, tag, indent);
    135 
    136             const after_name_space = if (tag.value == null) Space.None else Space.Space;
    137             try renderToken(tree, stream, tag.name_token, indent, after_name_space); // name
    138 
    139             if (tag.value) |value| {
    140                 try renderToken(tree, stream, tree.nextToken(tag.name_token), indent, Space.Space); // =
    141                 try renderExpression(allocator, stream, tree, indent, value, Space.None);
    142             }
    143 
    144             try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, Space.Newline); // ,
    145         },
    146 
    147         ast.Node.Id.Comptime => {
    148             assert(!decl.requireSemiColon());
    149             try renderExpression(allocator, stream, tree, indent, decl, Space.Newline);
    150         },
    151         else => unreachable,
    152     }
    153 }
    154 
    155 fn renderExpression(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize, base: &ast.Node, space: Space) (@typeOf(stream).Child.Error || Error)!void {
    156     switch (base.id) {
    157         ast.Node.Id.Identifier => {
    158             const identifier = @fieldParentPtr(ast.Node.Identifier, "base", base);
    159             try renderToken(tree, stream, identifier.token, indent, space);
    160         },
    161         ast.Node.Id.Block => {
    162             const block = @fieldParentPtr(ast.Node.Block, "base", base);
    163 
    164             if (block.label) |label| {
    165                 try renderToken(tree, stream, label, indent, Space.None);
    166                 try renderToken(tree, stream, tree.nextToken(label), indent, Space.Space);
    167             }
    168 
    169             if (block.statements.len == 0) {
    170                 try renderToken(tree, stream, block.lbrace, indent + indent_delta, Space.None);
    171                 try renderToken(tree, stream, block.rbrace, indent, space);
    172             } else {
    173                 const block_indent = indent + indent_delta;
    174                 try renderToken(tree, stream, block.lbrace, block_indent, Space.Newline);
    175 
    176                 var it = block.statements.iterator(0);
    177                 while (it.next()) |statement| {
    178                     try stream.writeByteNTimes(' ', block_indent);
    179                     try renderStatement(allocator, stream, tree, block_indent, statement.*);
    180 
    181                     if (it.peek()) |next_statement| {
    182                         try renderExtraNewline(tree, stream, next_statement.*);
    183                     }
    184                 }
    185 
    186                 try stream.writeByteNTimes(' ', indent);
    187                 try renderToken(tree, stream, block.rbrace, indent, space);
    188             }
    189         },
    190         ast.Node.Id.Defer => {
    191             const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base);
    192 
    193             try renderToken(tree, stream, defer_node.defer_token, indent, Space.Space);
    194             try renderExpression(allocator, stream, tree, indent, defer_node.expr, space);
    195         },
    196         ast.Node.Id.Comptime => {
    197             const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base);
    198 
    199             try renderToken(tree, stream, comptime_node.comptime_token, indent, Space.Space);
    200             try renderExpression(allocator, stream, tree, indent, comptime_node.expr, space);
    201         },
    202 
    203         ast.Node.Id.AsyncAttribute => {
    204             const async_attr = @fieldParentPtr(ast.Node.AsyncAttribute, "base", base);
    205 
    206             if (async_attr.allocator_type) |allocator_type| {
    207                 try renderToken(tree, stream, async_attr.async_token, indent, Space.None);
    208 
    209                 try renderToken(tree, stream, tree.nextToken(async_attr.async_token), indent, Space.None);
    210                 try renderExpression(allocator, stream, tree, indent, allocator_type, Space.None);
    211                 try renderToken(tree, stream, tree.nextToken(allocator_type.lastToken()), indent, space);
    212             } else {
    213                 try renderToken(tree, stream, async_attr.async_token, indent, space);
    214             }
    215         },
    216 
    217         ast.Node.Id.Suspend => {
    218             const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base);
    219 
    220             if (suspend_node.label) |label| {
    221                 try renderToken(tree, stream, label, indent, Space.None);
    222                 try renderToken(tree, stream, tree.nextToken(label), indent, Space.Space);
    223             }
    224 
    225             if (suspend_node.payload) |payload| {
    226                 if (suspend_node.body) |body| {
    227                     try renderToken(tree, stream, suspend_node.suspend_token, indent, Space.Space);
    228                     try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
    229                     try renderExpression(allocator, stream, tree, indent, body, space);
    230                 } else {
    231                     try renderToken(tree, stream, suspend_node.suspend_token, indent, Space.Space);
    232                     try renderExpression(allocator, stream, tree, indent, payload, space);
    233                 }
    234             } else if (suspend_node.body) |body| {
    235                 try renderToken(tree, stream, suspend_node.suspend_token, indent, Space.Space);
    236                 try renderExpression(allocator, stream, tree, indent, body, space);
    237             } else {
    238                 try renderToken(tree, stream, suspend_node.suspend_token, indent, space);
    239             }
    240         },
    241 
    242         ast.Node.Id.InfixOp => {
    243             const infix_op_node = @fieldParentPtr(ast.Node.InfixOp, "base", base);
    244 
    245             const op_token = tree.tokens.at(infix_op_node.op_token);
    246             const op_space = switch (infix_op_node.op) {
    247                 ast.Node.InfixOp.Op.Period, ast.Node.InfixOp.Op.ErrorUnion, ast.Node.InfixOp.Op.Range => Space.None,
    248                 else => Space.Space,
    249             };
    250             try renderExpression(allocator, stream, tree, indent, infix_op_node.lhs, op_space);
    251             try renderToken(tree, stream, infix_op_node.op_token, indent, op_space);
    252 
    253             switch (infix_op_node.op) {
    254                 ast.Node.InfixOp.Op.Catch => |maybe_payload| if (maybe_payload) |payload| {
    255                     try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
    256                 },
    257                 else => {},
    258             }
    259 
    260             try renderExpression(allocator, stream, tree, indent, infix_op_node.rhs, space);
    261         },
    262 
    263         ast.Node.Id.PrefixOp => {
    264             const prefix_op_node = @fieldParentPtr(ast.Node.PrefixOp, "base", base);
    265 
    266             switch (prefix_op_node.op) {
    267                 ast.Node.PrefixOp.Op.AddrOf => |addr_of_info| {
    268                     try renderToken(tree, stream, prefix_op_node.op_token, indent, Space.None); // &
    269                     if (addr_of_info.align_info) |align_info| {
    270                         const lparen_token = tree.prevToken(align_info.node.firstToken());
    271                         const align_token = tree.prevToken(lparen_token);
    272 
    273                         try renderToken(tree, stream, align_token, indent, Space.None); // align
    274                         try renderToken(tree, stream, lparen_token, indent, Space.None); // (
    275 
    276                         try renderExpression(allocator, stream, tree, indent, align_info.node, Space.None);
    277 
    278                         if (align_info.bit_range) |bit_range| {
    279                             const colon1 = tree.prevToken(bit_range.start.firstToken());
    280                             const colon2 = tree.prevToken(bit_range.end.firstToken());
    281 
    282                             try renderToken(tree, stream, colon1, indent, Space.None); // :
    283                             try renderExpression(allocator, stream, tree, indent, bit_range.start, Space.None);
    284                             try renderToken(tree, stream, colon2, indent, Space.None); // :
    285                             try renderExpression(allocator, stream, tree, indent, bit_range.end, Space.None);
    286 
    287                             const rparen_token = tree.nextToken(bit_range.end.lastToken());
    288                             try renderToken(tree, stream, rparen_token, indent, Space.Space); // )
    289                         } else {
    290                             const rparen_token = tree.nextToken(align_info.node.lastToken());
    291                             try renderToken(tree, stream, rparen_token, indent, Space.Space); // )
    292                         }
    293                     }
    294                     if (addr_of_info.const_token) |const_token| {
    295                         try renderToken(tree, stream, const_token, indent, Space.Space); // const
    296                     }
    297                     if (addr_of_info.volatile_token) |volatile_token| {
    298                         try renderToken(tree, stream, volatile_token, indent, Space.Space); // volatile
    299                     }
    300                 },
    301 
    302                 ast.Node.PrefixOp.Op.SliceType => |addr_of_info| {
    303                     try renderToken(tree, stream, prefix_op_node.op_token, indent, Space.None); // [
    304                     try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, Space.None); // ]
    305 
    306                     if (addr_of_info.align_info) |align_info| {
    307                         const lparen_token = tree.prevToken(align_info.node.firstToken());
    308                         const align_token = tree.prevToken(lparen_token);
    309 
    310                         try renderToken(tree, stream, align_token, indent, Space.None); // align
    311                         try renderToken(tree, stream, lparen_token, indent, Space.None); // (
    312 
    313                         try renderExpression(allocator, stream, tree, indent, align_info.node, Space.None);
    314 
    315                         if (align_info.bit_range) |bit_range| {
    316                             const colon1 = tree.prevToken(bit_range.start.firstToken());
    317                             const colon2 = tree.prevToken(bit_range.end.firstToken());
    318 
    319                             try renderToken(tree, stream, colon1, indent, Space.None); // :
    320                             try renderExpression(allocator, stream, tree, indent, bit_range.start, Space.None);
    321                             try renderToken(tree, stream, colon2, indent, Space.None); // :
    322                             try renderExpression(allocator, stream, tree, indent, bit_range.end, Space.None);
    323 
    324                             const rparen_token = tree.nextToken(bit_range.end.lastToken());
    325                             try renderToken(tree, stream, rparen_token, indent, Space.Space); // )
    326                         } else {
    327                             const rparen_token = tree.nextToken(align_info.node.lastToken());
    328                             try renderToken(tree, stream, rparen_token, indent, Space.Space); // )
    329                         }
    330                     }
    331                     if (addr_of_info.const_token) |const_token| {
    332                         try renderToken(tree, stream, const_token, indent, Space.Space);
    333                     }
    334                     if (addr_of_info.volatile_token) |volatile_token| {
    335                         try renderToken(tree, stream, volatile_token, indent, Space.Space);
    336                     }
    337                 },
    338 
    339                 ast.Node.PrefixOp.Op.ArrayType => |array_index| {
    340                     try renderToken(tree, stream, prefix_op_node.op_token, indent, Space.None); // [
    341                     try renderExpression(allocator, stream, tree, indent, array_index, Space.None);
    342                     try renderToken(tree, stream, tree.nextToken(array_index.lastToken()), indent, Space.None); // ]
    343                 },
    344                 ast.Node.PrefixOp.Op.BitNot,
    345                 ast.Node.PrefixOp.Op.BoolNot,
    346                 ast.Node.PrefixOp.Op.Negation,
    347                 ast.Node.PrefixOp.Op.NegationWrap,
    348                 ast.Node.PrefixOp.Op.UnwrapMaybe,
    349                 ast.Node.PrefixOp.Op.MaybeType,
    350                 ast.Node.PrefixOp.Op.PointerType => {
    351                     try renderToken(tree, stream, prefix_op_node.op_token, indent, Space.None);
    352                 },
    353 
    354                 ast.Node.PrefixOp.Op.Try,
    355                 ast.Node.PrefixOp.Op.Await,
    356                 ast.Node.PrefixOp.Op.Cancel,
    357                 ast.Node.PrefixOp.Op.Resume => {
    358                     try renderToken(tree, stream, prefix_op_node.op_token, indent, Space.Space);
    359                 },
    360             }
    361 
    362             try renderExpression(allocator, stream, tree, indent, prefix_op_node.rhs, space);
    363         },
    364 
    365         ast.Node.Id.SuffixOp => {
    366             const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", base);
    367 
    368             switch (suffix_op.op) {
    369                 @TagType(ast.Node.SuffixOp.Op).Call => |*call_info| {
    370                     if (call_info.async_attr) |async_attr| {
    371                         try renderExpression(allocator, stream, tree, indent, &async_attr.base, Space.Space);
    372                     }
    373 
    374                     try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    375 
    376                     const lparen = tree.nextToken(suffix_op.lhs.lastToken());
    377 
    378                     if (call_info.params.len == 0) {
    379                         try renderToken(tree, stream, lparen, indent, Space.None);
    380                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    381                         return;
    382                     }
    383 
    384                     const src_has_trailing_comma = blk: {
    385                         const maybe_comma = tree.prevToken(suffix_op.rtoken);
    386                         break :blk tree.tokens.at(maybe_comma).id == Token.Id.Comma;
    387                     };
    388 
    389                     if (src_has_trailing_comma) {
    390                         const new_indent = indent + indent_delta;
    391                         try renderToken(tree, stream, lparen, new_indent, Space.Newline);
    392 
    393                         var it = call_info.params.iterator(0);
    394                         while (true) {
    395                             const param_node = ??it.next();
    396 
    397                             const param_node_new_indent = if (param_node.*.id == ast.Node.Id.MultilineStringLiteral) blk: {
    398                                 break :blk indent;
    399                             } else blk: {
    400                                 try stream.writeByteNTimes(' ', new_indent);
    401                                 break :blk new_indent;
    402                             };
    403 
    404                             if (it.peek()) |next_node| {
    405                                 try renderExpression(allocator, stream, tree, param_node_new_indent, param_node.*, Space.None);
    406                                 const comma = tree.nextToken(param_node.*.lastToken());
    407                                 try renderToken(tree, stream, comma, new_indent, Space.Newline); // ,
    408                                 try renderExtraNewline(tree, stream, next_node.*);
    409                             } else {
    410                                 try renderTrailingComma(allocator, stream, tree, param_node_new_indent, param_node.*, Space.Newline);
    411                                 try stream.writeByteNTimes(' ', indent);
    412                                 try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    413                                 return;
    414                             }
    415                         }
    416                     }
    417 
    418                     try renderToken(tree, stream, lparen, indent, Space.None); // (
    419 
    420                     var it = call_info.params.iterator(0);
    421                     while (it.next()) |param_node| {
    422                         try renderExpression(allocator, stream, tree, indent, param_node.*, Space.None);
    423 
    424                         if (it.peek() != null) {
    425                             const comma = tree.nextToken(param_node.*.lastToken());
    426                             try renderToken(tree, stream, comma, indent, Space.Space);
    427                         }
    428                     }
    429                     try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    430                 },
    431 
    432                 ast.Node.SuffixOp.Op.ArrayAccess => |index_expr| {
    433                     const lbracket = tree.prevToken(index_expr.firstToken());
    434                     const rbracket = tree.nextToken(index_expr.lastToken());
    435 
    436                     try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    437                     try renderToken(tree, stream, lbracket, indent, Space.None); // [
    438                     try renderExpression(allocator, stream, tree, indent, index_expr, Space.None);
    439                     try renderToken(tree, stream, rbracket, indent, space); // ]
    440                 },
    441 
    442                 ast.Node.SuffixOp.Op.Deref => {
    443                     try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    444                     try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, Space.None); // .
    445                     try renderToken(tree, stream, suffix_op.rtoken, indent, space); // *
    446                 },
    447 
    448                 @TagType(ast.Node.SuffixOp.Op).Slice => |range| {
    449                     try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    450 
    451                     const lbracket = tree.prevToken(range.start.firstToken());
    452                     const dotdot = tree.nextToken(range.start.lastToken());
    453 
    454                     try renderToken(tree, stream, lbracket, indent, Space.None); // [
    455                     try renderExpression(allocator, stream, tree, indent, range.start, Space.None);
    456                     try renderToken(tree, stream, dotdot, indent, Space.None); // ..
    457                     if (range.end) |end| {
    458                         try renderExpression(allocator, stream, tree, indent, end, Space.None);
    459                     }
    460                     try renderToken(tree, stream, suffix_op.rtoken, indent, space); // ]
    461                 },
    462 
    463                 ast.Node.SuffixOp.Op.StructInitializer => |*field_inits| {
    464                     const lbrace = tree.nextToken(suffix_op.lhs.lastToken());
    465 
    466                     if (field_inits.len == 0) {
    467                         try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    468                         try renderToken(tree, stream, lbrace, indent, Space.None);
    469                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    470                         return;
    471                     }
    472 
    473                     if (field_inits.len == 1) blk: {
    474                         const field_init = ??field_inits.at(0).*.cast(ast.Node.FieldInitializer);
    475 
    476                         if (field_init.expr.cast(ast.Node.SuffixOp)) |nested_suffix_op| {
    477                             if (nested_suffix_op.op == ast.Node.SuffixOp.Op.StructInitializer) {
    478                                 break :blk;
    479                             }
    480                         }
    481 
    482                         try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    483                         try renderToken(tree, stream, lbrace, indent, Space.Space);
    484                         try renderExpression(allocator, stream, tree, indent, &field_init.base, Space.Space);
    485                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    486                         return;
    487                     }
    488 
    489                     const src_has_trailing_comma = blk: {
    490                         const maybe_comma = tree.prevToken(suffix_op.rtoken);
    491                         break :blk tree.tokens.at(maybe_comma).id == Token.Id.Comma;
    492                     };
    493 
    494                     const src_same_line = blk: {
    495                         const loc = tree.tokenLocation(tree.tokens.at(lbrace).end, suffix_op.rtoken);
    496                         break :blk loc.line == 0;
    497                     };
    498 
    499                     if (!src_has_trailing_comma and src_same_line) {
    500                         // render all on one line, no trailing comma
    501                         try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    502                         try renderToken(tree, stream, lbrace, indent, Space.Space);
    503 
    504                         var it = field_inits.iterator(0);
    505                         while (it.next()) |field_init| {
    506                             if (it.peek() != null) {
    507                                 try renderExpression(allocator, stream, tree, indent, field_init.*, Space.None);
    508 
    509                                 const comma = tree.nextToken(field_init.*.lastToken());
    510                                 try renderToken(tree, stream, comma, indent, Space.Space);
    511                             } else {
    512                                 try renderExpression(allocator, stream, tree, indent, field_init.*, Space.Space);
    513                             }
    514                         }
    515 
    516                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    517                         return;
    518                     }
    519 
    520                     try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    521                     try renderToken(tree, stream, lbrace, indent, Space.Newline);
    522 
    523                     const new_indent = indent + indent_delta;
    524 
    525                     var it = field_inits.iterator(0);
    526                     while (it.next()) |field_init| {
    527                         try stream.writeByteNTimes(' ', new_indent);
    528 
    529                         if (it.peek()) |next_field_init| {
    530                             try renderExpression(allocator, stream, tree, new_indent, field_init.*, Space.None);
    531 
    532                             const comma = tree.nextToken(field_init.*.lastToken());
    533                             try renderToken(tree, stream, comma, new_indent, Space.Newline);
    534 
    535                             try renderExtraNewline(tree, stream, next_field_init.*);
    536                         } else {
    537                             try renderTrailingComma(allocator, stream, tree, new_indent, field_init.*, Space.Newline);
    538                         }
    539                     }
    540 
    541                     try stream.writeByteNTimes(' ', indent);
    542                     try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    543                 },
    544 
    545                 ast.Node.SuffixOp.Op.ArrayInitializer => |*exprs| {
    546                     const lbrace = tree.nextToken(suffix_op.lhs.lastToken());
    547 
    548                     if (exprs.len == 0) {
    549                         try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    550                         try renderToken(tree, stream, lbrace, indent, Space.None);
    551                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    552                         return;
    553                     }
    554                     if (exprs.len == 1) {
    555                         const expr = exprs.at(0).*;
    556 
    557                         try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    558                         try renderToken(tree, stream, lbrace, indent, Space.None);
    559                         try renderExpression(allocator, stream, tree, indent, expr, Space.None);
    560                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    561                         return;
    562                     }
    563 
    564                     try renderExpression(allocator, stream, tree, indent, suffix_op.lhs, Space.None);
    565 
    566                     // scan to find row size
    567                     const maybe_row_size: ?usize = blk: {
    568                         var count: usize = 1;
    569                         var it = exprs.iterator(0);
    570                         while (true) {
    571                             const expr = (??it.next()).*;
    572                             if (it.peek()) |next_expr| {
    573                                 const expr_last_token = expr.*.lastToken() + 1;
    574                                 const loc = tree.tokenLocation(tree.tokens.at(expr_last_token).end, next_expr.*.firstToken());
    575                                 if (loc.line != 0) break :blk count;
    576                                 count += 1;
    577                             } else {
    578                                 const expr_last_token = expr.*.lastToken();
    579                                 const loc = tree.tokenLocation(tree.tokens.at(expr_last_token).end, suffix_op.rtoken);
    580                                 if (loc.line == 0) {
    581                                     // all on one line
    582                                     const src_has_trailing_comma = trailblk: {
    583                                         const maybe_comma = tree.prevToken(suffix_op.rtoken);
    584                                         break :trailblk tree.tokens.at(maybe_comma).id == Token.Id.Comma;
    585                                     };
    586                                     if (src_has_trailing_comma) {
    587                                         break :blk 1; // force row size 1
    588                                     } else {
    589                                         break :blk null; // no newlines
    590                                     }
    591                                 }
    592                                 break :blk count;
    593                             }
    594                         }
    595                     };
    596 
    597                     if (maybe_row_size) |row_size| {
    598                         const new_indent = indent + indent_delta;
    599                         try renderToken(tree, stream, lbrace, new_indent, Space.Newline);
    600                         try stream.writeByteNTimes(' ', new_indent);
    601 
    602                         var it = exprs.iterator(0);
    603                         var i: usize = 1;
    604                         while (it.next()) |expr| {
    605                             if (it.peek()) |next_expr| {
    606                                 try renderExpression(allocator, stream, tree, new_indent, expr.*, Space.None);
    607 
    608                                 const comma = tree.nextToken(expr.*.lastToken());
    609 
    610                                 if (i != row_size) {
    611                                     try renderToken(tree, stream, comma, new_indent, Space.Space); // ,
    612                                     i += 1;
    613                                     continue;
    614                                 }
    615                                 i = 1;
    616 
    617                                 try renderToken(tree, stream, comma, new_indent, Space.Newline); // ,
    618 
    619                                 try renderExtraNewline(tree, stream, next_expr.*);
    620                                 try stream.writeByteNTimes(' ', new_indent);
    621                             } else {
    622                                 try renderTrailingComma(allocator, stream, tree, new_indent, expr.*, Space.Newline); // ,
    623                             }
    624                         }
    625                         try stream.writeByteNTimes(' ', indent);
    626                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    627                         return;
    628                     } else {
    629                         try renderToken(tree, stream, lbrace, indent, Space.Space);
    630                         var it = exprs.iterator(0);
    631                         while (it.next()) |expr| {
    632                             if (it.peek()) |next_expr| {
    633                                 try renderExpression(allocator, stream, tree, indent, expr.*, Space.None);
    634                                 const comma = tree.nextToken(expr.*.lastToken());
    635                                 try renderToken(tree, stream, comma, indent, Space.Space); // ,
    636                             } else {
    637                                 try renderExpression(allocator, stream, tree, indent, expr.*, Space.Space);
    638                             }
    639                         }
    640 
    641                         try renderToken(tree, stream, suffix_op.rtoken, indent, space);
    642                         return;
    643                     }
    644                 },
    645             }
    646         },
    647 
    648         ast.Node.Id.ControlFlowExpression => {
    649             const flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", base);
    650 
    651             switch (flow_expr.kind) {
    652                 ast.Node.ControlFlowExpression.Kind.Break => |maybe_label| {
    653                     const kw_space = if (maybe_label != null or flow_expr.rhs != null) Space.Space else space;
    654                     try renderToken(tree, stream, flow_expr.ltoken, indent, kw_space);
    655                     if (maybe_label) |label| {
    656                         const colon = tree.nextToken(flow_expr.ltoken);
    657                         try renderToken(tree, stream, colon, indent, Space.None);
    658 
    659                         const expr_space = if (flow_expr.rhs != null) Space.Space else space;
    660                         try renderExpression(allocator, stream, tree, indent, label, expr_space);
    661                     }
    662                 },
    663                 ast.Node.ControlFlowExpression.Kind.Continue => |maybe_label| {
    664                     const kw_space = if (maybe_label != null or flow_expr.rhs != null) Space.Space else space;
    665                     try renderToken(tree, stream, flow_expr.ltoken, indent, kw_space);
    666                     if (maybe_label) |label| {
    667                         const colon = tree.nextToken(flow_expr.ltoken);
    668                         try renderToken(tree, stream, colon, indent, Space.None);
    669 
    670                         const expr_space = if (flow_expr.rhs != null) Space.Space else space;
    671                         try renderExpression(allocator, stream, tree, indent, label, space);
    672                     }
    673                 },
    674                 ast.Node.ControlFlowExpression.Kind.Return => {
    675                     const kw_space = if (flow_expr.rhs != null) Space.Space else space;
    676                     try renderToken(tree, stream, flow_expr.ltoken, indent, kw_space);
    677                 },
    678             }
    679 
    680             if (flow_expr.rhs) |rhs| {
    681                 try renderExpression(allocator, stream, tree, indent, rhs, space);
    682             }
    683         },
    684 
    685         ast.Node.Id.Payload => {
    686             const payload = @fieldParentPtr(ast.Node.Payload, "base", base);
    687 
    688             try renderToken(tree, stream, payload.lpipe, indent, Space.None);
    689             try renderExpression(allocator, stream, tree, indent, payload.error_symbol, Space.None);
    690             try renderToken(tree, stream, payload.rpipe, indent, space);
    691         },
    692 
    693         ast.Node.Id.PointerPayload => {
    694             const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base);
    695 
    696             try renderToken(tree, stream, payload.lpipe, indent, Space.None);
    697             if (payload.ptr_token) |ptr_token| {
    698                 try renderToken(tree, stream, ptr_token, indent, Space.None);
    699             }
    700             try renderExpression(allocator, stream, tree, indent, payload.value_symbol, Space.None);
    701             try renderToken(tree, stream, payload.rpipe, indent, space);
    702         },
    703 
    704         ast.Node.Id.PointerIndexPayload => {
    705             const payload = @fieldParentPtr(ast.Node.PointerIndexPayload, "base", base);
    706 
    707             try renderToken(tree, stream, payload.lpipe, indent, Space.None);
    708             if (payload.ptr_token) |ptr_token| {
    709                 try renderToken(tree, stream, ptr_token, indent, Space.None);
    710             }
    711             try renderExpression(allocator, stream, tree, indent, payload.value_symbol, Space.None);
    712 
    713             if (payload.index_symbol) |index_symbol| {
    714                 const comma = tree.nextToken(payload.value_symbol.lastToken());
    715 
    716                 try renderToken(tree, stream, comma, indent, Space.Space);
    717                 try renderExpression(allocator, stream, tree, indent, index_symbol, Space.None);
    718             }
    719 
    720             try renderToken(tree, stream, payload.rpipe, indent, space);
    721         },
    722 
    723         ast.Node.Id.GroupedExpression => {
    724             const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base);
    725 
    726             try renderToken(tree, stream, grouped_expr.lparen, indent, Space.None);
    727             try renderExpression(allocator, stream, tree, indent, grouped_expr.expr, Space.None);
    728             try renderToken(tree, stream, grouped_expr.rparen, indent, space);
    729         },
    730 
    731         ast.Node.Id.FieldInitializer => {
    732             const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base);
    733 
    734             try renderToken(tree, stream, field_init.period_token, indent, Space.None); // .
    735             try renderToken(tree, stream, field_init.name_token, indent, Space.Space); // name
    736             try renderToken(tree, stream, tree.nextToken(field_init.name_token), indent, Space.Space); // =
    737             try renderExpression(allocator, stream, tree, indent, field_init.expr, space);
    738         },
    739 
    740         ast.Node.Id.IntegerLiteral => {
    741             const integer_literal = @fieldParentPtr(ast.Node.IntegerLiteral, "base", base);
    742             try renderToken(tree, stream, integer_literal.token, indent, space);
    743         },
    744         ast.Node.Id.FloatLiteral => {
    745             const float_literal = @fieldParentPtr(ast.Node.FloatLiteral, "base", base);
    746             try renderToken(tree, stream, float_literal.token, indent, space);
    747         },
    748         ast.Node.Id.StringLiteral => {
    749             const string_literal = @fieldParentPtr(ast.Node.StringLiteral, "base", base);
    750             try renderToken(tree, stream, string_literal.token, indent, space);
    751         },
    752         ast.Node.Id.CharLiteral => {
    753             const char_literal = @fieldParentPtr(ast.Node.CharLiteral, "base", base);
    754             try renderToken(tree, stream, char_literal.token, indent, space);
    755         },
    756         ast.Node.Id.BoolLiteral => {
    757             const bool_literal = @fieldParentPtr(ast.Node.CharLiteral, "base", base);
    758             try renderToken(tree, stream, bool_literal.token, indent, space);
    759         },
    760         ast.Node.Id.NullLiteral => {
    761             const null_literal = @fieldParentPtr(ast.Node.NullLiteral, "base", base);
    762             try renderToken(tree, stream, null_literal.token, indent, space);
    763         },
    764         ast.Node.Id.ThisLiteral => {
    765             const this_literal = @fieldParentPtr(ast.Node.ThisLiteral, "base", base);
    766             try renderToken(tree, stream, this_literal.token, indent, space);
    767         },
    768         ast.Node.Id.Unreachable => {
    769             const unreachable_node = @fieldParentPtr(ast.Node.Unreachable, "base", base);
    770             try renderToken(tree, stream, unreachable_node.token, indent, space);
    771         },
    772         ast.Node.Id.ErrorType => {
    773             const error_type = @fieldParentPtr(ast.Node.ErrorType, "base", base);
    774             try renderToken(tree, stream, error_type.token, indent, space);
    775         },
    776         ast.Node.Id.VarType => {
    777             const var_type = @fieldParentPtr(ast.Node.VarType, "base", base);
    778             try renderToken(tree, stream, var_type.token, indent, space);
    779         },
    780         ast.Node.Id.ContainerDecl => {
    781             const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base);
    782 
    783             if (container_decl.layout_token) |layout_token| {
    784                 try renderToken(tree, stream, layout_token, indent, Space.Space);
    785             }
    786 
    787             switch (container_decl.init_arg_expr) {
    788                 ast.Node.ContainerDecl.InitArg.None => {
    789                     try renderToken(tree, stream, container_decl.kind_token, indent, Space.Space); // union
    790                 },
    791                 ast.Node.ContainerDecl.InitArg.Enum => |enum_tag_type| {
    792                     try renderToken(tree, stream, container_decl.kind_token, indent, Space.None); // union
    793 
    794                     const lparen = tree.nextToken(container_decl.kind_token);
    795                     const enum_token = tree.nextToken(lparen);
    796 
    797                     try renderToken(tree, stream, lparen, indent, Space.None); // (
    798                     try renderToken(tree, stream, enum_token, indent, Space.None); // enum
    799 
    800                     if (enum_tag_type) |expr| {
    801                         try renderToken(tree, stream, tree.nextToken(enum_token), indent, Space.None); // (
    802                         try renderExpression(allocator, stream, tree, indent, expr, Space.None);
    803 
    804                         const rparen = tree.nextToken(expr.lastToken());
    805                         try renderToken(tree, stream, rparen, indent, Space.None); // )
    806                         try renderToken(tree, stream, tree.nextToken(rparen), indent, Space.Space); // )
    807                     } else {
    808                         try renderToken(tree, stream, tree.nextToken(enum_token), indent, Space.Space); // )
    809                     }
    810                 },
    811                 ast.Node.ContainerDecl.InitArg.Type => |type_expr| {
    812                     try renderToken(tree, stream, container_decl.kind_token, indent, Space.None); // union
    813 
    814                     const lparen = tree.nextToken(container_decl.kind_token);
    815                     const rparen = tree.nextToken(type_expr.lastToken());
    816 
    817                     try renderToken(tree, stream, lparen, indent, Space.None); // (
    818                     try renderExpression(allocator, stream, tree, indent, type_expr, Space.None);
    819                     try renderToken(tree, stream, rparen, indent, Space.Space); // )
    820                 },
    821             }
    822 
    823             if (container_decl.fields_and_decls.len == 0) {
    824                 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, Space.None); // {
    825                 try renderToken(tree, stream, container_decl.rbrace_token, indent, space); // }
    826             } else {
    827                 const new_indent = indent + indent_delta;
    828                 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, Space.Newline); // {
    829 
    830                 var it = container_decl.fields_and_decls.iterator(0);
    831                 while (it.next()) |decl| {
    832                     try stream.writeByteNTimes(' ', new_indent);
    833                     try renderTopLevelDecl(allocator, stream, tree, new_indent, decl.*);
    834 
    835                     if (it.peek()) |next_decl| {
    836                         try renderExtraNewline(tree, stream, next_decl.*);
    837                     }
    838                 }
    839 
    840                 try stream.writeByteNTimes(' ', indent);
    841                 try renderToken(tree, stream, container_decl.rbrace_token, indent, space); // }
    842             }
    843         },
    844 
    845         ast.Node.Id.ErrorSetDecl => {
    846             const err_set_decl = @fieldParentPtr(ast.Node.ErrorSetDecl, "base", base);
    847 
    848             const lbrace = tree.nextToken(err_set_decl.error_token);
    849 
    850             if (err_set_decl.decls.len == 0) {
    851                 try renderToken(tree, stream, err_set_decl.error_token, indent, Space.None);
    852                 try renderToken(tree, stream, lbrace, indent, Space.None);
    853                 try renderToken(tree, stream, err_set_decl.rbrace_token, indent, space);
    854                 return;
    855             }
    856 
    857             if (err_set_decl.decls.len == 1) blk: {
    858                 const node = err_set_decl.decls.at(0).*;
    859 
    860                 // if there are any doc comments or same line comments
    861                 // don't try to put it all on one line
    862                 if (node.cast(ast.Node.ErrorTag)) |tag| {
    863                     if (tag.doc_comments != null) break :blk;
    864                 } else {
    865                     break :blk;
    866                 }
    867 
    868                 try renderToken(tree, stream, err_set_decl.error_token, indent, Space.None); // error
    869                 try renderToken(tree, stream, lbrace, indent, Space.None); // {
    870                 try renderExpression(allocator, stream, tree, indent, node, Space.None);
    871                 try renderToken(tree, stream, err_set_decl.rbrace_token, indent, space); // }
    872                 return;
    873             }
    874 
    875             try renderToken(tree, stream, err_set_decl.error_token, indent, Space.None); // error
    876             try renderToken(tree, stream, lbrace, indent, Space.Newline); // {
    877             const new_indent = indent + indent_delta;
    878 
    879             var it = err_set_decl.decls.iterator(0);
    880             while (it.next()) |node| {
    881                 try stream.writeByteNTimes(' ', new_indent);
    882 
    883                 if (it.peek()) |next_node| {
    884                     try renderExpression(allocator, stream, tree, new_indent, node.*, Space.None);
    885                     try renderToken(tree, stream, tree.nextToken(node.*.lastToken()), new_indent, Space.Newline); // ,
    886 
    887                     try renderExtraNewline(tree, stream, next_node.*);
    888                 } else {
    889                     try renderTrailingComma(allocator, stream, tree, new_indent, node.*, Space.Newline);
    890                 }
    891             }
    892 
    893             try stream.writeByteNTimes(' ', indent);
    894             try renderToken(tree, stream, err_set_decl.rbrace_token, indent, space); // }
    895         },
    896 
    897         ast.Node.Id.ErrorTag => {
    898             const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base);
    899 
    900             try renderDocComments(tree, stream, tag, indent);
    901             try renderToken(tree, stream, tag.name_token, indent, space); // name
    902         },
    903 
    904         ast.Node.Id.MultilineStringLiteral => {
    905             const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base);
    906 
    907             var skip_first_indent = true;
    908             if (tree.tokens.at(multiline_str_literal.firstToken() - 1).id != Token.Id.LineComment) {
    909                 try stream.print("\n");
    910                 skip_first_indent = false;
    911             }
    912 
    913             var i: usize = 0;
    914             while (i < multiline_str_literal.lines.len) : (i += 1) {
    915                 const t = multiline_str_literal.lines.at(i).*;
    916                 if (!skip_first_indent) {
    917                     try stream.writeByteNTimes(' ', indent + indent_delta);
    918                 }
    919                 try renderToken(tree, stream, t, indent, Space.None);
    920                 skip_first_indent = false;
    921             }
    922             try stream.writeByteNTimes(' ', indent);
    923         },
    924         ast.Node.Id.UndefinedLiteral => {
    925             const undefined_literal = @fieldParentPtr(ast.Node.UndefinedLiteral, "base", base);
    926             try renderToken(tree, stream, undefined_literal.token, indent, space);
    927         },
    928 
    929         ast.Node.Id.BuiltinCall => {
    930             const builtin_call = @fieldParentPtr(ast.Node.BuiltinCall, "base", base);
    931 
    932             try renderToken(tree, stream, builtin_call.builtin_token, indent, Space.None); // @name
    933             try renderToken(tree, stream, tree.nextToken(builtin_call.builtin_token), indent, Space.None); // (
    934 
    935             var it = builtin_call.params.iterator(0);
    936             while (it.next()) |param_node| {
    937                 try renderExpression(allocator, stream, tree, indent, param_node.*, Space.None);
    938 
    939                 if (it.peek() != null) {
    940                     const comma_token = tree.nextToken(param_node.*.lastToken());
    941                     try renderToken(tree, stream, comma_token, indent, Space.Space); // ,
    942                 }
    943             }
    944             try renderToken(tree, stream, builtin_call.rparen_token, indent, space); // )
    945         },
    946 
    947         ast.Node.Id.FnProto => {
    948             const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", base);
    949 
    950             if (fn_proto.visib_token) |visib_token_index| {
    951                 const visib_token = tree.tokens.at(visib_token_index);
    952                 assert(visib_token.id == Token.Id.Keyword_pub or visib_token.id == Token.Id.Keyword_export);
    953 
    954                 try renderToken(tree, stream, visib_token_index, indent, Space.Space); // pub
    955             }
    956 
    957             if (fn_proto.extern_export_inline_token) |extern_export_inline_token| {
    958                 try renderToken(tree, stream, extern_export_inline_token, indent, Space.Space); // extern/export
    959             }
    960 
    961             if (fn_proto.lib_name) |lib_name| {
    962                 try renderExpression(allocator, stream, tree, indent, lib_name, Space.Space);
    963             }
    964 
    965             if (fn_proto.cc_token) |cc_token| {
    966                 try renderToken(tree, stream, cc_token, indent, Space.Space); // stdcallcc
    967             }
    968 
    969             if (fn_proto.async_attr) |async_attr| {
    970                 try renderExpression(allocator, stream, tree, indent, &async_attr.base, Space.Space);
    971             }
    972 
    973             if (fn_proto.name_token) |name_token| blk: {
    974                 try renderToken(tree, stream, fn_proto.fn_token, indent, Space.Space); // fn
    975                 try renderToken(tree, stream, name_token, indent, Space.None); // name
    976                 try renderToken(tree, stream, tree.nextToken(name_token), indent, Space.None); // (
    977             } else blk: {
    978                 try renderToken(tree, stream, fn_proto.fn_token, indent, Space.None); // fn
    979                 try renderToken(tree, stream, tree.nextToken(fn_proto.fn_token), indent, Space.None); // (
    980             }
    981 
    982             var it = fn_proto.params.iterator(0);
    983             while (it.next()) |param_decl_node| {
    984                 try renderParamDecl(allocator, stream, tree, indent, param_decl_node.*);
    985 
    986                 if (it.peek() != null) {
    987                     const comma = tree.nextToken(param_decl_node.*.lastToken());
    988                     try renderToken(tree, stream, comma, indent, Space.Space); // ,
    989                 }
    990             }
    991 
    992             const rparen = tree.prevToken(switch (fn_proto.return_type) {
    993                 ast.Node.FnProto.ReturnType.Explicit => |node| node.firstToken(),
    994                 ast.Node.FnProto.ReturnType.InferErrorSet => |node| tree.prevToken(node.firstToken()),
    995             });
    996             try renderToken(tree, stream, rparen, indent, Space.Space); // )
    997 
    998             if (fn_proto.align_expr) |align_expr| {
    999                 const align_rparen = tree.nextToken(align_expr.lastToken());
   1000                 const align_lparen = tree.prevToken(align_expr.firstToken());
   1001                 const align_kw = tree.prevToken(align_lparen);
   1002 
   1003                 try renderToken(tree, stream, align_kw, indent, Space.None); // align
   1004                 try renderToken(tree, stream, align_lparen, indent, Space.None); // (
   1005                 try renderExpression(allocator, stream, tree, indent, align_expr, Space.None);
   1006                 try renderToken(tree, stream, align_rparen, indent, Space.Space); // )
   1007             }
   1008 
   1009             switch (fn_proto.return_type) {
   1010                 ast.Node.FnProto.ReturnType.Explicit => |node| {
   1011                     try renderExpression(allocator, stream, tree, indent, node, space);
   1012                 },
   1013                 ast.Node.FnProto.ReturnType.InferErrorSet => |node| {
   1014                     try renderToken(tree, stream, tree.prevToken(node.firstToken()), indent, Space.None); // !
   1015                     try renderExpression(allocator, stream, tree, indent, node, space);
   1016                 },
   1017             }
   1018         },
   1019 
   1020         ast.Node.Id.PromiseType => {
   1021             const promise_type = @fieldParentPtr(ast.Node.PromiseType, "base", base);
   1022 
   1023             if (promise_type.result) |result| {
   1024                 try renderToken(tree, stream, promise_type.promise_token, indent, Space.None); // promise
   1025                 try renderToken(tree, stream, result.arrow_token, indent, Space.None); // ->
   1026                 try renderExpression(allocator, stream, tree, indent, result.return_type, space);
   1027             } else {
   1028                 try renderToken(tree, stream, promise_type.promise_token, indent, space); // promise
   1029             }
   1030         },
   1031 
   1032         ast.Node.Id.DocComment => unreachable, // doc comments are attached to nodes
   1033 
   1034         ast.Node.Id.Switch => {
   1035             const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base);
   1036 
   1037             try renderToken(tree, stream, switch_node.switch_token, indent, Space.Space); // switch
   1038             try renderToken(tree, stream, tree.nextToken(switch_node.switch_token), indent, Space.None); // (
   1039 
   1040             const rparen = tree.nextToken(switch_node.expr.lastToken());
   1041             const lbrace = tree.nextToken(rparen);
   1042 
   1043             if (switch_node.cases.len == 0) {
   1044                 try renderExpression(allocator, stream, tree, indent, switch_node.expr, Space.None);
   1045                 try renderToken(tree, stream, rparen, indent, Space.Space); // )
   1046                 try renderToken(tree, stream, lbrace, indent, Space.None); // {
   1047                 try renderToken(tree, stream, switch_node.rbrace, indent, space); // }
   1048                 return;
   1049             }
   1050 
   1051             try renderExpression(allocator, stream, tree, indent, switch_node.expr, Space.None);
   1052 
   1053             const new_indent = indent + indent_delta;
   1054 
   1055             try renderToken(tree, stream, rparen, indent, Space.Space); // )
   1056             try renderToken(tree, stream, lbrace, new_indent, Space.Newline); // {
   1057 
   1058             var it = switch_node.cases.iterator(0);
   1059             while (it.next()) |node| {
   1060                 try stream.writeByteNTimes(' ', new_indent);
   1061                 try renderExpression(allocator, stream, tree, new_indent, node.*, Space.Newline);
   1062 
   1063                 if (it.peek()) |next_node| {
   1064                     try renderExtraNewline(tree, stream, next_node.*);
   1065                 }
   1066             }
   1067 
   1068             try stream.writeByteNTimes(' ', indent);
   1069             try renderToken(tree, stream, switch_node.rbrace, indent, space); // }
   1070         },
   1071 
   1072         ast.Node.Id.SwitchCase => {
   1073             const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base);
   1074 
   1075             assert(switch_case.items.len != 0);
   1076             const src_has_trailing_comma = blk: {
   1077                 const last_node = switch_case.items.at(switch_case.items.len - 1).*;
   1078                 const maybe_comma = tree.nextToken(last_node.lastToken());
   1079                 break :blk tree.tokens.at(maybe_comma).id == Token.Id.Comma;
   1080             };
   1081 
   1082             if (switch_case.items.len == 1 or !src_has_trailing_comma) {
   1083                 var it = switch_case.items.iterator(0);
   1084                 while (it.next()) |node| {
   1085                     if (it.peek()) |next_node| {
   1086                         try renderExpression(allocator, stream, tree, indent, node.*, Space.None);
   1087 
   1088                         const comma_token = tree.nextToken(node.*.lastToken());
   1089                         try renderToken(tree, stream, comma_token, indent, Space.Space); // ,
   1090                         try renderExtraNewline(tree, stream, next_node.*);
   1091                     } else {
   1092                         try renderExpression(allocator, stream, tree, indent, node.*, Space.Space);
   1093                     }
   1094                 }
   1095             } else {
   1096                 var it = switch_case.items.iterator(0);
   1097                 while (true) {
   1098                     const node = ??it.next();
   1099                     if (it.peek()) |next_node| {
   1100                         try renderExpression(allocator, stream, tree, indent, node.*, Space.None);
   1101 
   1102                         const comma_token = tree.nextToken(node.*.lastToken());
   1103                         try renderToken(tree, stream, comma_token, indent, Space.Newline); // ,
   1104                         try renderExtraNewline(tree, stream, next_node.*);
   1105                         try stream.writeByteNTimes(' ', indent);
   1106                     } else {
   1107                         try renderTrailingComma(allocator, stream, tree, indent, node.*, Space.Space);
   1108                         break;
   1109                     }
   1110                 }
   1111             }
   1112 
   1113             try renderToken(tree, stream, switch_case.arrow_token, indent, Space.Space); // =>
   1114 
   1115             if (switch_case.payload) |payload| {
   1116                 try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
   1117             }
   1118 
   1119             try renderTrailingComma(allocator, stream, tree, indent, switch_case.expr, space);
   1120         },
   1121         ast.Node.Id.SwitchElse => {
   1122             const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base);
   1123             try renderToken(tree, stream, switch_else.token, indent, space);
   1124         },
   1125         ast.Node.Id.Else => {
   1126             const else_node = @fieldParentPtr(ast.Node.Else, "base", base);
   1127 
   1128             const block_body = switch (else_node.body.id) {
   1129                 ast.Node.Id.Block,
   1130                 ast.Node.Id.If,
   1131                 ast.Node.Id.For,
   1132                 ast.Node.Id.While,
   1133                 ast.Node.Id.Switch => true,
   1134                 else => false,
   1135             };
   1136 
   1137             const after_else_space = if (block_body or else_node.payload != null) Space.Space else Space.Newline;
   1138             try renderToken(tree, stream, else_node.else_token, indent, after_else_space);
   1139 
   1140             if (else_node.payload) |payload| {
   1141                 const payload_space = if (block_body) Space.Space else Space.Newline;
   1142                 try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
   1143             }
   1144 
   1145             if (block_body) {
   1146                 try renderExpression(allocator, stream, tree, indent, else_node.body, space);
   1147             } else {
   1148                 try stream.writeByteNTimes(' ', indent + indent_delta);
   1149                 try renderExpression(allocator, stream, tree, indent, else_node.body, space);
   1150             }
   1151         },
   1152 
   1153         ast.Node.Id.While => {
   1154             const while_node = @fieldParentPtr(ast.Node.While, "base", base);
   1155 
   1156             if (while_node.label) |label| {
   1157                 try renderToken(tree, stream, label, indent, Space.None); // label
   1158                 try renderToken(tree, stream, tree.nextToken(label), indent, Space.Space); // :
   1159             }
   1160 
   1161             if (while_node.inline_token) |inline_token| {
   1162                 try renderToken(tree, stream, inline_token, indent, Space.Space); // inline
   1163             }
   1164 
   1165             try renderToken(tree, stream, while_node.while_token, indent, Space.Space); // while
   1166             try renderToken(tree, stream, tree.nextToken(while_node.while_token), indent, Space.None); // (
   1167             try renderExpression(allocator, stream, tree, indent, while_node.condition, Space.None);
   1168 
   1169             {
   1170                 const rparen = tree.nextToken(while_node.condition.lastToken());
   1171                 const rparen_space = if (while_node.payload != null or while_node.continue_expr != null or
   1172                     while_node.body.id == ast.Node.Id.Block) Space.Space else Space.Newline;
   1173                 try renderToken(tree, stream, rparen, indent, rparen_space); // )
   1174             }
   1175 
   1176             if (while_node.payload) |payload| {
   1177                 try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
   1178             }
   1179 
   1180             if (while_node.continue_expr) |continue_expr| {
   1181                 const rparen = tree.nextToken(continue_expr.lastToken());
   1182                 const lparen = tree.prevToken(continue_expr.firstToken());
   1183                 const colon = tree.prevToken(lparen);
   1184 
   1185                 try renderToken(tree, stream, colon, indent, Space.Space); // :
   1186                 try renderToken(tree, stream, lparen, indent, Space.None); // (
   1187 
   1188                 try renderExpression(allocator, stream, tree, indent, continue_expr, Space.None);
   1189 
   1190                 const rparen_space = if (while_node.body.id == ast.Node.Id.Block) Space.Space else Space.Newline;
   1191                 try renderToken(tree, stream, rparen, indent, rparen_space); // )
   1192             }
   1193 
   1194             const body_space = blk: {
   1195                 if (while_node.@"else" != null) {
   1196                     break :blk if (while_node.body.id == ast.Node.Id.Block) Space.Space else Space.Newline;
   1197                 } else {
   1198                     break :blk space;
   1199                 }
   1200             };
   1201 
   1202             if (while_node.body.id == ast.Node.Id.Block) {
   1203                 try renderExpression(allocator, stream, tree, indent, while_node.body, body_space);
   1204             } else {
   1205                 try stream.writeByteNTimes(' ', indent + indent_delta);
   1206                 try renderExpression(allocator, stream, tree, indent, while_node.body, body_space);
   1207             }
   1208 
   1209             if (while_node.@"else") |@"else"| {
   1210                 if (while_node.body.id == ast.Node.Id.Block) {
   1211                 } else {
   1212                     try stream.writeByteNTimes(' ', indent);
   1213                 }
   1214 
   1215                 try renderExpression(allocator, stream, tree, indent, &@"else".base, space);
   1216             }
   1217         },
   1218 
   1219         ast.Node.Id.For => {
   1220             const for_node = @fieldParentPtr(ast.Node.For, "base", base);
   1221 
   1222             if (for_node.label) |label| {
   1223                 try renderToken(tree, stream, label, indent, Space.None); // label
   1224                 try renderToken(tree, stream, tree.nextToken(label), indent, Space.Space); // :
   1225             }
   1226 
   1227             if (for_node.inline_token) |inline_token| {
   1228                 try renderToken(tree, stream, inline_token, indent, Space.Space); // inline
   1229             }
   1230 
   1231             try renderToken(tree, stream, for_node.for_token, indent, Space.Space); // for
   1232             try renderToken(tree, stream, tree.nextToken(for_node.for_token), indent, Space.None); // (
   1233             try renderExpression(allocator, stream, tree, indent, for_node.array_expr, Space.None);
   1234 
   1235             const rparen = tree.nextToken(for_node.array_expr.lastToken());
   1236             const rparen_space = if (for_node.payload != null or
   1237                 for_node.body.id == ast.Node.Id.Block) Space.Space else Space.Newline;
   1238             try renderToken(tree, stream, rparen, indent, rparen_space); // )
   1239 
   1240             if (for_node.payload) |payload| {
   1241                 const payload_space = if (for_node.body.id == ast.Node.Id.Block) Space.Space else Space.Newline;
   1242                 try renderExpression(allocator, stream, tree, indent, payload, payload_space);
   1243             }
   1244 
   1245             const body_space = blk: {
   1246                 if (for_node.@"else" != null) {
   1247                     if (for_node.body.id == ast.Node.Id.Block) {
   1248                         break :blk Space.Space;
   1249                     } else {
   1250                         break :blk Space.Newline;
   1251                     }
   1252                 } else {
   1253                     break :blk space;
   1254                 }
   1255             };
   1256             if (for_node.body.id == ast.Node.Id.Block) {
   1257                 try renderExpression(allocator, stream, tree, indent, for_node.body, body_space);
   1258             } else {
   1259                 try stream.writeByteNTimes(' ', indent + indent_delta);
   1260                 try renderExpression(allocator, stream, tree, indent, for_node.body, body_space);
   1261             }
   1262 
   1263             if (for_node.@"else") |@"else"| {
   1264                 if (for_node.body.id != ast.Node.Id.Block) {
   1265                     try stream.writeByteNTimes(' ', indent);
   1266                 }
   1267 
   1268                 try renderExpression(allocator, stream, tree, indent, &@"else".base, space);
   1269             }
   1270         },
   1271 
   1272         ast.Node.Id.If => {
   1273             const if_node = @fieldParentPtr(ast.Node.If, "base", base);
   1274 
   1275             try renderToken(tree, stream, if_node.if_token, indent, Space.Space);
   1276             try renderToken(tree, stream, tree.prevToken(if_node.condition.firstToken()), indent, Space.None);
   1277 
   1278             try renderExpression(allocator, stream, tree, indent, if_node.condition, Space.None);
   1279             try renderToken(tree, stream, tree.nextToken(if_node.condition.lastToken()), indent, Space.Space);
   1280 
   1281             if (if_node.payload) |payload| {
   1282                 try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
   1283             }
   1284 
   1285             switch (if_node.body.id) {
   1286                 ast.Node.Id.Block,
   1287                 ast.Node.Id.If,
   1288                 ast.Node.Id.For,
   1289                 ast.Node.Id.While,
   1290                 ast.Node.Id.Switch => {
   1291                     if (if_node.@"else") |@"else"| {
   1292                         if (if_node.body.id == ast.Node.Id.Block) {
   1293                             try renderExpression(allocator, stream, tree, indent, if_node.body, Space.Space);
   1294                         } else {
   1295                             try renderExpression(allocator, stream, tree, indent, if_node.body, Space.Newline);
   1296                             try stream.writeByteNTimes(' ', indent);
   1297                         }
   1298 
   1299                         try renderExpression(allocator, stream, tree, indent, &@"else".base, space);
   1300                     } else {
   1301                         try renderExpression(allocator, stream, tree, indent, if_node.body, space);
   1302                     }
   1303                 },
   1304                 else => {
   1305                     if (if_node.@"else") |@"else"| {
   1306                         try renderExpression(allocator, stream, tree, indent, if_node.body, Space.Space);
   1307                         try renderToken(tree, stream, @"else".else_token, indent, Space.Space);
   1308 
   1309                         if (@"else".payload) |payload| {
   1310                             try renderExpression(allocator, stream, tree, indent, payload, Space.Space);
   1311                         }
   1312 
   1313                         try renderExpression(allocator, stream, tree, indent, @"else".body, space);
   1314                     } else {
   1315                         try renderExpression(allocator, stream, tree, indent, if_node.body, space);
   1316                     }
   1317                 },
   1318             }
   1319         },
   1320 
   1321         ast.Node.Id.Asm => {
   1322             const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base);
   1323 
   1324             try renderToken(tree, stream, asm_node.asm_token, indent, Space.Space); // asm
   1325 
   1326             if (asm_node.volatile_token) |volatile_token| {
   1327                 try renderToken(tree, stream, volatile_token, indent, Space.Space); // volatile
   1328                 try renderToken(tree, stream, tree.nextToken(volatile_token), indent, Space.None); // (
   1329             } else {
   1330                 try renderToken(tree, stream, tree.nextToken(asm_node.asm_token), indent, Space.None); // (
   1331             }
   1332 
   1333             if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
   1334                 try renderExpression(allocator, stream, tree, indent, asm_node.template, Space.None);
   1335                 try renderToken(tree, stream, asm_node.rparen, indent, space);
   1336                 return;
   1337             }
   1338 
   1339             try renderExpression(allocator, stream, tree, indent, asm_node.template, Space.Newline);
   1340 
   1341             const indent_once = indent + indent_delta;
   1342             try stream.writeByteNTimes(' ', indent_once);
   1343 
   1344             const colon1 = tree.nextToken(asm_node.template.lastToken());
   1345             const indent_extra = indent_once + 2;
   1346 
   1347             const colon2 = if (asm_node.outputs.len == 0) blk: {
   1348                 try renderToken(tree, stream, colon1, indent, Space.Newline); // :
   1349                 try stream.writeByteNTimes(' ', indent_once);
   1350 
   1351                 break :blk tree.nextToken(colon1);
   1352             } else blk: {
   1353                 try renderToken(tree, stream, colon1, indent, Space.Space); // :
   1354 
   1355                 var it = asm_node.outputs.iterator(0);
   1356                 while (true) {
   1357                     const asm_output = ??it.next();
   1358                     const node = &(asm_output.*).base;
   1359 
   1360                     if (it.peek()) |next_asm_output| {
   1361                         try renderExpression(allocator, stream, tree, indent_extra, node, Space.None);
   1362                         const next_node = &(next_asm_output.*).base;
   1363 
   1364                         const comma = tree.prevToken(next_asm_output.*.firstToken());
   1365                         try renderToken(tree, stream, comma, indent_extra, Space.Newline); // ,
   1366                         try renderExtraNewline(tree, stream, next_node);
   1367 
   1368                         try stream.writeByteNTimes(' ', indent_extra);
   1369                     } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
   1370                         try renderExpression(allocator, stream, tree, indent_extra, node, Space.Newline);
   1371                         try stream.writeByteNTimes(' ', indent);
   1372                         try renderToken(tree, stream, asm_node.rparen, indent, space);
   1373                         return;
   1374                     } else {
   1375                         try renderExpression(allocator, stream, tree, indent_extra, node, Space.Newline);
   1376                         try stream.writeByteNTimes(' ', indent_once);
   1377                         const comma_or_colon = tree.nextToken(node.lastToken());
   1378                         break :blk switch (tree.tokens.at(comma_or_colon).id) {
   1379                             Token.Id.Comma => tree.nextToken(comma_or_colon),
   1380                             else => comma_or_colon,
   1381                         };
   1382                     }
   1383                 }
   1384             };
   1385 
   1386             const colon3 = if (asm_node.inputs.len == 0) blk: {
   1387                 try renderToken(tree, stream, colon2, indent, Space.Newline); // :
   1388                 try stream.writeByteNTimes(' ', indent_once);
   1389 
   1390                 break :blk tree.nextToken(colon2);
   1391             } else blk: {
   1392                 try renderToken(tree, stream, colon2, indent, Space.Space); // :
   1393 
   1394                 var it = asm_node.inputs.iterator(0);
   1395                 while (true) {
   1396                     const asm_input = ??it.next();
   1397                     const node = &(asm_input.*).base;
   1398 
   1399                     if (it.peek()) |next_asm_input| {
   1400                         try renderExpression(allocator, stream, tree, indent_extra, node, Space.None);
   1401                         const next_node = &(next_asm_input.*).base;
   1402 
   1403                         const comma = tree.prevToken(next_asm_input.*.firstToken());
   1404                         try renderToken(tree, stream, comma, indent_extra, Space.Newline); // ,
   1405                         try renderExtraNewline(tree, stream, next_node);
   1406 
   1407                         try stream.writeByteNTimes(' ', indent_extra);
   1408                     } else if (asm_node.clobbers.len == 0) {
   1409                         try renderExpression(allocator, stream, tree, indent_extra, node, Space.Newline);
   1410                         try stream.writeByteNTimes(' ', indent);
   1411                         try renderToken(tree, stream, asm_node.rparen, indent, space); // )
   1412                         return;
   1413                     } else {
   1414                         try renderExpression(allocator, stream, tree, indent_extra, node, Space.Newline);
   1415                         try stream.writeByteNTimes(' ', indent_once);
   1416                         const comma_or_colon = tree.nextToken(node.lastToken());
   1417                         break :blk switch (tree.tokens.at(comma_or_colon).id) {
   1418                             Token.Id.Comma => tree.nextToken(comma_or_colon),
   1419                             else => comma_or_colon,
   1420                         };
   1421                     }
   1422                 }
   1423             };
   1424 
   1425             try renderToken(tree, stream, colon3, indent, Space.Space); // :
   1426 
   1427             var it = asm_node.clobbers.iterator(0);
   1428             while (true) {
   1429                 const clobber_token = ??it.next();
   1430 
   1431                 if (it.peek() == null) {
   1432                     try renderToken(tree, stream, clobber_token.*, indent_once, Space.Newline);
   1433                     try stream.writeByteNTimes(' ', indent);
   1434                     try renderToken(tree, stream, asm_node.rparen, indent, space);
   1435                     return;
   1436                 } else {
   1437                     try renderToken(tree, stream, clobber_token.*, indent_once, Space.None);
   1438                     const comma = tree.nextToken(clobber_token.*);
   1439                     try renderToken(tree, stream, comma, indent_once, Space.Space); // ,
   1440                 }
   1441             }
   1442         },
   1443 
   1444         ast.Node.Id.AsmInput => {
   1445             const asm_input = @fieldParentPtr(ast.Node.AsmInput, "base", base);
   1446 
   1447             try stream.write("[");
   1448             try renderExpression(allocator, stream, tree, indent, asm_input.symbolic_name, Space.None);
   1449             try stream.write("] ");
   1450             try renderExpression(allocator, stream, tree, indent, asm_input.constraint, Space.None);
   1451             try stream.write(" (");
   1452             try renderExpression(allocator, stream, tree, indent, asm_input.expr, Space.None);
   1453             try renderToken(tree, stream, asm_input.lastToken(), indent, space); // )
   1454         },
   1455 
   1456         ast.Node.Id.AsmOutput => {
   1457             const asm_output = @fieldParentPtr(ast.Node.AsmOutput, "base", base);
   1458 
   1459             try stream.write("[");
   1460             try renderExpression(allocator, stream, tree, indent, asm_output.symbolic_name, Space.None);
   1461             try stream.write("] ");
   1462             try renderExpression(allocator, stream, tree, indent, asm_output.constraint, Space.None);
   1463             try stream.write(" (");
   1464 
   1465             switch (asm_output.kind) {
   1466                 ast.Node.AsmOutput.Kind.Variable => |variable_name| {
   1467                     try renderExpression(allocator, stream, tree, indent, &variable_name.base, Space.None);
   1468                 },
   1469                 ast.Node.AsmOutput.Kind.Return => |return_type| {
   1470                     try stream.write("-> ");
   1471                     try renderExpression(allocator, stream, tree, indent, return_type, Space.None);
   1472                 },
   1473             }
   1474 
   1475             try renderToken(tree, stream, asm_output.lastToken(), indent, space); // )
   1476         },
   1477 
   1478         ast.Node.Id.StructField,
   1479         ast.Node.Id.UnionTag,
   1480         ast.Node.Id.EnumTag,
   1481         ast.Node.Id.Root,
   1482         ast.Node.Id.VarDecl,
   1483         ast.Node.Id.Use,
   1484         ast.Node.Id.TestDecl,
   1485         ast.Node.Id.ParamDecl => unreachable,
   1486     }
   1487 }
   1488 
   1489 fn renderVarDecl(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize,
   1490     var_decl: &ast.Node.VarDecl) (@typeOf(stream).Child.Error || Error)!void
   1491 {
   1492     if (var_decl.visib_token) |visib_token| {
   1493         try renderToken(tree, stream, visib_token, indent, Space.Space); // pub
   1494     }
   1495 
   1496     if (var_decl.extern_export_token) |extern_export_token| {
   1497         try renderToken(tree, stream, extern_export_token, indent, Space.Space); // extern
   1498 
   1499         if (var_decl.lib_name) |lib_name| {
   1500             try renderExpression(allocator, stream, tree, indent, lib_name, Space.Space); // "lib"
   1501         }
   1502     }
   1503 
   1504     if (var_decl.comptime_token) |comptime_token| {
   1505         try renderToken(tree, stream, comptime_token, indent, Space.Space); // comptime
   1506     }
   1507 
   1508     try renderToken(tree, stream, var_decl.mut_token, indent, Space.Space); // var
   1509 
   1510     const name_space = if (var_decl.type_node == null and (var_decl.align_node != null or
   1511         var_decl.init_node != null)) Space.Space else Space.None;
   1512     try renderToken(tree, stream, var_decl.name_token, indent, name_space);
   1513 
   1514     if (var_decl.type_node) |type_node| {
   1515         try renderToken(tree, stream, tree.nextToken(var_decl.name_token), indent, Space.Space);
   1516         const s = if (var_decl.align_node != null or var_decl.init_node != null) Space.Space else Space.None;
   1517         try renderExpression(allocator, stream, tree, indent, type_node, s);
   1518     }
   1519 
   1520     if (var_decl.align_node) |align_node| {
   1521         const lparen = tree.prevToken(align_node.firstToken());
   1522         const align_kw = tree.prevToken(lparen);
   1523         const rparen = tree.nextToken(align_node.lastToken());
   1524         try renderToken(tree, stream, align_kw, indent, Space.None); // align
   1525         try renderToken(tree, stream, lparen, indent, Space.None); // (
   1526         try renderExpression(allocator, stream, tree, indent, align_node, Space.None);
   1527         const s = if (var_decl.init_node != null) Space.Space else Space.None;
   1528         try renderToken(tree, stream, rparen, indent, s); // )
   1529     }
   1530 
   1531     if (var_decl.init_node) |init_node| {
   1532         const s = if (init_node.id == ast.Node.Id.MultilineStringLiteral) Space.None else Space.Space;
   1533         try renderToken(tree, stream, var_decl.eq_token, indent, s); // =
   1534         try renderExpression(allocator, stream, tree, indent, init_node, Space.None);
   1535     }
   1536 
   1537     try renderToken(tree, stream, var_decl.semicolon_token, indent, Space.Newline);
   1538 }
   1539 
   1540 fn renderParamDecl(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize, base: &ast.Node) (@typeOf(stream).Child.Error || Error)!void {
   1541     const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", base);
   1542 
   1543     if (param_decl.comptime_token) |comptime_token| {
   1544         try renderToken(tree, stream, comptime_token, indent, Space.Space);
   1545     }
   1546     if (param_decl.noalias_token) |noalias_token| {
   1547         try renderToken(tree, stream, noalias_token, indent, Space.Space);
   1548     }
   1549     if (param_decl.name_token) |name_token| {
   1550         try renderToken(tree, stream, name_token, indent, Space.None);
   1551         try renderToken(tree, stream, tree.nextToken(name_token), indent, Space.Space); // :
   1552     }
   1553     if (param_decl.var_args_token) |var_args_token| {
   1554         try renderToken(tree, stream, var_args_token, indent, Space.None);
   1555     } else {
   1556         try renderExpression(allocator, stream, tree, indent, param_decl.type_node, Space.None);
   1557     }
   1558 }
   1559 
   1560 fn renderStatement(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize, base: &ast.Node) (@typeOf(stream).Child.Error || Error)!void {
   1561     switch (base.id) {
   1562         ast.Node.Id.VarDecl => {
   1563             const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base);
   1564             try renderVarDecl(allocator, stream, tree, indent, var_decl);
   1565         },
   1566         else => {
   1567             if (base.requireSemiColon()) {
   1568                 try renderExpression(allocator, stream, tree, indent, base, Space.None);
   1569 
   1570                 const semicolon_index = tree.nextToken(base.lastToken());
   1571                 assert(tree.tokens.at(semicolon_index).id == Token.Id.Semicolon);
   1572                 try renderToken(tree, stream, semicolon_index, indent, Space.Newline);
   1573             } else {
   1574                 try renderExpression(allocator, stream, tree, indent, base, Space.Newline);
   1575             }
   1576         },
   1577     }
   1578 }
   1579 
   1580 const Space = enum {
   1581     None,
   1582     Newline,
   1583     Space,
   1584     NoNewline,
   1585     NoIndent,
   1586     NoComment,
   1587     IgnoreEmptyComment,
   1588 };
   1589 
   1590 fn renderToken(tree: &ast.Tree, stream: var, token_index: ast.TokenIndex, indent: usize, space: Space) (@typeOf(stream).Child.Error || Error)!void {
   1591     var token = tree.tokens.at(token_index);
   1592     try stream.write(mem.trimRight(u8, tree.tokenSlicePtr(token), " "));
   1593 
   1594     if (space == Space.NoComment) return;
   1595 
   1596     var next_token = tree.tokens.at(token_index + 1);
   1597     if (next_token.id != Token.Id.LineComment) {
   1598         switch (space) {
   1599             Space.None, Space.NoNewline, Space.NoIndent => return,
   1600             Space.Newline => {
   1601                 if (next_token.id == Token.Id.MultilineStringLiteralLine) {
   1602                     return;
   1603                 } else {
   1604                     return stream.write("\n");
   1605                 }
   1606             },
   1607             Space.Space, Space.IgnoreEmptyComment => return stream.writeByte(' '),
   1608             Space.NoComment => unreachable,
   1609         }
   1610     }
   1611 
   1612     const comment_is_empty = mem.trimRight(u8, tree.tokenSlicePtr(next_token), " ").len == 2;
   1613     if (comment_is_empty) {
   1614         switch (space) {
   1615             Space.IgnoreEmptyComment => return stream.writeByte(' '),
   1616             Space.Newline => return stream.writeByte('\n'),
   1617             else => {},
   1618         }
   1619     }
   1620 
   1621     var loc = tree.tokenLocationPtr(token.end, next_token);
   1622     var offset: usize = 1;
   1623     if (loc.line == 0) {
   1624         try stream.print(" {}", mem.trimRight(u8, tree.tokenSlicePtr(next_token), " "));
   1625         offset = 2;
   1626         token = next_token;
   1627         next_token = tree.tokens.at(token_index + offset);
   1628         if (next_token.id != Token.Id.LineComment) {
   1629             switch (space) {
   1630                 Space.None, Space.Space => {
   1631                     try stream.writeByte('\n');
   1632                     const after_comment_token = tree.tokens.at(token_index + offset);
   1633                     const next_line_indent = switch (after_comment_token.id) {
   1634                         Token.Id.RParen, Token.Id.RBrace, Token.Id.RBracket => indent,
   1635                         else => indent + indent_delta,
   1636                     };
   1637                     try stream.writeByteNTimes(' ', next_line_indent);
   1638                 },
   1639                 Space.Newline, Space.NoIndent => {
   1640                     if (next_token.id == Token.Id.MultilineStringLiteralLine) {
   1641                         return;
   1642                     } else {
   1643                         return stream.write("\n");
   1644                     }
   1645                 },
   1646                 Space.NoNewline => {},
   1647                 Space.NoComment, Space.IgnoreEmptyComment => unreachable,
   1648             }
   1649             return;
   1650         }
   1651         loc = tree.tokenLocationPtr(token.end, next_token);
   1652     }
   1653 
   1654     while (true) {
   1655         assert(loc.line != 0);
   1656         const newline_count = if (loc.line == 1) u8(1) else u8(2);
   1657         try stream.writeByteNTimes('\n', newline_count);
   1658         try stream.writeByteNTimes(' ', indent);
   1659         try stream.write(mem.trimRight(u8, tree.tokenSlicePtr(next_token), " "));
   1660 
   1661         offset += 1;
   1662         token = next_token;
   1663         next_token = tree.tokens.at(token_index + offset);
   1664         if (next_token.id != Token.Id.LineComment) {
   1665             switch (space) {
   1666                 Space.Newline, Space.NoIndent => {
   1667                     if (next_token.id == Token.Id.MultilineStringLiteralLine) {
   1668                         return;
   1669                     } else {
   1670                         return stream.write("\n");
   1671                     }
   1672                 },
   1673                 Space.None, Space.Space => {
   1674                     try stream.writeByte('\n');
   1675 
   1676                     const after_comment_token = tree.tokens.at(token_index + offset);
   1677                     const next_line_indent = switch (after_comment_token.id) {
   1678                         Token.Id.RParen, Token.Id.RBrace, Token.Id.RBracket => indent - indent_delta,
   1679                         else => indent,
   1680                     };
   1681                     try stream.writeByteNTimes(' ', next_line_indent);
   1682                 },
   1683                 Space.NoNewline => {},
   1684                 Space.NoComment, Space.IgnoreEmptyComment => unreachable,
   1685             }
   1686             return;
   1687         }
   1688         loc = tree.tokenLocationPtr(token.end, next_token);
   1689     }
   1690 }
   1691 
   1692 fn renderDocComments(tree: &ast.Tree, stream: var, node: var, indent: usize) (@typeOf(stream).Child.Error || Error)!void {
   1693     const comment = node.doc_comments ?? return;
   1694     var it = comment.lines.iterator(0);
   1695     while (it.next()) |line_token_index| {
   1696         try renderToken(tree, stream, line_token_index.*, indent, Space.Newline);
   1697         try stream.writeByteNTimes(' ', indent);
   1698     }
   1699 }
   1700 
   1701 fn renderTrailingComma(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize, base: &ast.Node,
   1702     space: Space) (@typeOf(stream).Child.Error || Error)!void
   1703 {
   1704     const end_token = base.lastToken() + 1;
   1705     switch (tree.tokens.at(end_token).id) {
   1706         Token.Id.Comma => {
   1707             try renderExpression(allocator, stream, tree, indent, base, Space.None);
   1708             try renderToken(tree, stream, end_token, indent, space); // ,
   1709         },
   1710         Token.Id.LineComment => {
   1711             try renderExpression(allocator, stream, tree, indent, base, Space.NoComment);
   1712             try stream.write(", ");
   1713             try renderToken(tree, stream, end_token, indent, space);
   1714         },
   1715         else => {
   1716             try renderExpression(allocator, stream, tree, indent, base, Space.None);
   1717             try stream.write(",\n");
   1718             assert(space == Space.Newline);
   1719         },
   1720     }
   1721 }
   1722 
   1723 fn renderTrailingCommaAndEmptyComment(allocator: &mem.Allocator, stream: var, tree: &ast.Tree, indent: usize, base: &ast.Node) (@typeOf(stream).Child.Error || Error)!void
   1724 {
   1725     const end_token = base.lastToken() + 1;
   1726     switch (tree.tokens.at(end_token).id) {
   1727         Token.Id.Comma => {
   1728             try renderExpression(allocator, stream, tree, indent, base, Space.None);
   1729             try renderToken(tree, stream, end_token, indent, Space.Space); // ,
   1730 
   1731             const next_token = tree.tokens.at(end_token + 1);
   1732             if (next_token.id != Token.Id.LineComment) {
   1733                 try stream.print("//\n");
   1734             }
   1735         },
   1736         Token.Id.LineComment => {
   1737             try renderExpression(allocator, stream, tree, indent, base, Space.NoComment);
   1738             try stream.write(", ");
   1739             try renderToken(tree, stream, end_token, indent, Space.Newline);
   1740         },
   1741         else => {
   1742             try renderExpression(allocator, stream, tree, indent, base, Space.None);
   1743             try stream.write(", //\n");
   1744         },
   1745     }
   1746 }