zig

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

ast.zig (104528B) - Raw


      1 const std = @import("std");
      2 const Allocator = std.mem.Allocator;
      3 
      4 pub const Node = extern union {
      5     /// If the tag value is less than Tag.no_payload_count, then no pointer
      6     /// dereference is needed.
      7     tag_if_small_enough: usize,
      8     ptr_otherwise: *Payload,
      9 
     10     pub const Tag = enum {
     11         /// Declarations add themselves to the correct scopes and should not be emitted as this tag.
     12         declaration,
     13         null_literal,
     14         undefined_literal,
     15         /// opaque {}
     16         opaque_literal,
     17         true_literal,
     18         false_literal,
     19         empty_block,
     20         return_void,
     21         zero_literal,
     22         one_literal,
     23         void_type,
     24         noreturn_type,
     25         @"anytype",
     26         @"continue",
     27         @"break",
     28         // After this, the tag requires a payload.
     29 
     30         integer_literal,
     31         float_literal,
     32         string_literal,
     33         char_literal,
     34         enum_literal,
     35         /// "string"[0..end]
     36         string_slice,
     37         identifier,
     38         fn_identifier,
     39         @"if",
     40         /// if (!operand) break;
     41         if_not_break,
     42         @"while",
     43         /// while (true) operand
     44         while_true,
     45         @"switch",
     46         /// else => operand,
     47         switch_else,
     48         /// items => body,
     49         switch_prong,
     50         break_val,
     51         @"return",
     52         field_access,
     53         array_access,
     54         call,
     55         var_decl,
     56         /// const name = struct { init }
     57         static_local_var,
     58         /// const ExternLocal_name = struct { init }
     59         extern_local_var,
     60         /// const ExternLocal_name = struct { init }
     61         extern_local_fn,
     62         /// var name = init.*
     63         mut_str,
     64         func,
     65         warning,
     66         @"struct",
     67         @"union",
     68         @"comptime",
     69         @"defer",
     70         array_init,
     71         tuple,
     72         container_init,
     73         container_init_dot,
     74         helpers_cast,
     75         /// _ = operand;
     76         discard,
     77 
     78         // a + b
     79         add,
     80         // a = b
     81         add_assign,
     82         // c = (a = b)
     83         add_wrap,
     84         add_wrap_assign,
     85         sub,
     86         sub_assign,
     87         sub_wrap,
     88         sub_wrap_assign,
     89         mul,
     90         mul_assign,
     91         mul_wrap,
     92         mul_wrap_assign,
     93         div,
     94         div_assign,
     95         shl,
     96         shl_assign,
     97         shr,
     98         shr_assign,
     99         mod,
    100         mod_assign,
    101         @"and",
    102         @"or",
    103         less_than,
    104         less_than_equal,
    105         greater_than,
    106         greater_than_equal,
    107         equal,
    108         not_equal,
    109         bit_and,
    110         bit_and_assign,
    111         bit_or,
    112         bit_or_assign,
    113         bit_xor,
    114         bit_xor_assign,
    115         array_cat,
    116         ellipsis3,
    117         assign,
    118 
    119         /// @import("std").zig.c_builtins.<name>
    120         import_c_builtin,
    121         /// @intCast(operand)
    122         int_cast,
    123         /// @constCast(operand)
    124         const_cast,
    125         /// @volatileCast(operand)
    126         volatile_cast,
    127         /// @import("std").zig.c_translation.promoteIntLiteral(value, type, base)
    128         helpers_promoteIntLiteral,
    129         /// @import("std").zig.c_translation.signedRemainder(lhs, rhs)
    130         signed_remainder,
    131         /// @divTrunc(lhs, rhs)
    132         div_trunc,
    133         /// @intFromBool(operand)
    134         int_from_bool,
    135         /// @as(lhs, rhs)
    136         as,
    137         /// @truncate(operand)
    138         truncate,
    139         /// @bitCast(operand)
    140         bit_cast,
    141         /// @floatCast(operand)
    142         float_cast,
    143         /// @intFromFloat(operand)
    144         int_from_float,
    145         /// @floatFromInt(operand)
    146         float_from_int,
    147         /// @ptrFromInt(operand)
    148         ptr_from_int,
    149         /// @intFromPtr(operand)
    150         int_from_ptr,
    151         /// @alignCast(operand)
    152         align_cast,
    153         /// @ptrCast(operand)
    154         ptr_cast,
    155         /// @divExact(lhs, rhs)
    156         div_exact,
    157         /// @offsetOf(lhs, rhs)
    158         offset_of,
    159         /// @splat(operand)
    160         vector_zero_init,
    161         /// @shuffle(type, a, b, mask)
    162         shuffle,
    163         /// @extern(ty, .{ .name = n })
    164         builtin_extern,
    165 
    166         /// @import("std").zig.c_translation.MacroArithmetic.<op>(lhs, rhs)
    167         macro_arithmetic,
    168 
    169         asm_simple,
    170 
    171         negate,
    172         negate_wrap,
    173         bit_not,
    174         not,
    175         address_of,
    176         /// .?
    177         unwrap,
    178         /// .*
    179         deref,
    180 
    181         block,
    182         /// { operand }
    183         block_single,
    184 
    185         sizeof,
    186         alignof,
    187         typeof,
    188         typeinfo,
    189         type,
    190 
    191         optional_type,
    192         c_pointer,
    193         single_pointer,
    194         array_type,
    195         null_sentinel_array_type,
    196 
    197         /// @import("std").zig.c_translation.sizeof(operand)
    198         helpers_sizeof,
    199         /// @import("std").zig.c_translation.FlexibleArrayType(lhs, rhs)
    200         helpers_flexible_array_type,
    201         /// @import("std").zig.c_translation.shuffleVectorIndex(lhs, rhs)
    202         helpers_shuffle_vector_index,
    203         /// @import("std").zig.c_translation.Macro.<operand>
    204         helpers_macro,
    205         /// @Vector(lhs, rhs)
    206         vector,
    207         /// @import("std").mem.zeroes(operand)
    208         std_mem_zeroes,
    209         /// @import("std").mem.zeroInit(lhs, rhs)
    210         std_mem_zeroinit,
    211         // pub const name = @compileError(msg);
    212         fail_decl,
    213         // var actual = mangled;
    214         arg_redecl,
    215         /// pub const alias = actual;
    216         alias,
    217         /// const name = init;
    218         var_simple,
    219         /// pub const name = init;
    220         pub_var_simple,
    221         /// pub? const name (: type)? = value
    222         enum_constant,
    223 
    224         /// pub inline fn name(params) return_type body
    225         pub_inline_fn,
    226 
    227         /// [0]type{}
    228         empty_array,
    229         /// [1]type{val} ** count
    230         array_filler,
    231 
    232         pub const last_no_payload_tag = Tag.@"break";
    233         pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1;
    234 
    235         pub fn Type(comptime t: Tag) type {
    236             return switch (t) {
    237                 .declaration,
    238                 .null_literal,
    239                 .undefined_literal,
    240                 .opaque_literal,
    241                 .true_literal,
    242                 .false_literal,
    243                 .empty_block,
    244                 .return_void,
    245                 .zero_literal,
    246                 .one_literal,
    247                 .void_type,
    248                 .noreturn_type,
    249                 .@"anytype",
    250                 .@"continue",
    251                 .@"break",
    252                 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
    253 
    254                 .std_mem_zeroes,
    255                 .@"return",
    256                 .@"comptime",
    257                 .@"defer",
    258                 .asm_simple,
    259                 .negate,
    260                 .negate_wrap,
    261                 .bit_not,
    262                 .not,
    263                 .optional_type,
    264                 .address_of,
    265                 .unwrap,
    266                 .deref,
    267                 .int_from_ptr,
    268                 .empty_array,
    269                 .while_true,
    270                 .if_not_break,
    271                 .switch_else,
    272                 .block_single,
    273                 .helpers_sizeof,
    274                 .int_from_bool,
    275                 .sizeof,
    276                 .alignof,
    277                 .typeof,
    278                 .typeinfo,
    279                 .align_cast,
    280                 .truncate,
    281                 .bit_cast,
    282                 .float_cast,
    283                 .int_from_float,
    284                 .float_from_int,
    285                 .ptr_from_int,
    286                 .ptr_cast,
    287                 .int_cast,
    288                 .const_cast,
    289                 .volatile_cast,
    290                 .vector_zero_init,
    291                 => Payload.UnOp,
    292 
    293                 .add,
    294                 .add_assign,
    295                 .add_wrap,
    296                 .add_wrap_assign,
    297                 .sub,
    298                 .sub_assign,
    299                 .sub_wrap,
    300                 .sub_wrap_assign,
    301                 .mul,
    302                 .mul_assign,
    303                 .mul_wrap,
    304                 .mul_wrap_assign,
    305                 .div,
    306                 .div_assign,
    307                 .shl,
    308                 .shl_assign,
    309                 .shr,
    310                 .shr_assign,
    311                 .mod,
    312                 .mod_assign,
    313                 .@"and",
    314                 .@"or",
    315                 .less_than,
    316                 .less_than_equal,
    317                 .greater_than,
    318                 .greater_than_equal,
    319                 .equal,
    320                 .not_equal,
    321                 .bit_and,
    322                 .bit_and_assign,
    323                 .bit_or,
    324                 .bit_or_assign,
    325                 .bit_xor,
    326                 .bit_xor_assign,
    327                 .div_trunc,
    328                 .signed_remainder,
    329                 .as,
    330                 .array_cat,
    331                 .ellipsis3,
    332                 .assign,
    333                 .array_access,
    334                 .std_mem_zeroinit,
    335                 .helpers_flexible_array_type,
    336                 .helpers_shuffle_vector_index,
    337                 .vector,
    338                 .div_exact,
    339                 .offset_of,
    340                 .helpers_cast,
    341                 => Payload.BinOp,
    342 
    343                 .integer_literal,
    344                 .float_literal,
    345                 .string_literal,
    346                 .char_literal,
    347                 .enum_literal,
    348                 .identifier,
    349                 .fn_identifier,
    350                 .warning,
    351                 .type,
    352                 .helpers_macro,
    353                 .import_c_builtin,
    354                 => Payload.Value,
    355                 .discard => Payload.Discard,
    356                 .@"if" => Payload.If,
    357                 .@"while" => Payload.While,
    358                 .@"switch", .array_init, .switch_prong => Payload.Switch,
    359                 .break_val => Payload.BreakVal,
    360                 .call => Payload.Call,
    361                 .var_decl => Payload.VarDecl,
    362                 .func => Payload.Func,
    363                 .@"struct", .@"union" => Payload.Record,
    364                 .tuple => Payload.TupleInit,
    365                 .container_init => Payload.ContainerInit,
    366                 .container_init_dot => Payload.ContainerInitDot,
    367                 .helpers_promoteIntLiteral => Payload.PromoteIntLiteral,
    368                 .block => Payload.Block,
    369                 .c_pointer, .single_pointer => Payload.Pointer,
    370                 .array_type, .null_sentinel_array_type => Payload.Array,
    371                 .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl,
    372                 .var_simple,
    373                 .pub_var_simple,
    374                 .static_local_var,
    375                 .extern_local_var,
    376                 .extern_local_fn,
    377                 .mut_str,
    378                 => Payload.SimpleVarDecl,
    379                 .enum_constant => Payload.EnumConstant,
    380                 .array_filler => Payload.ArrayFiller,
    381                 .pub_inline_fn => Payload.PubInlineFn,
    382                 .field_access => Payload.FieldAccess,
    383                 .string_slice => Payload.StringSlice,
    384                 .shuffle => Payload.Shuffle,
    385                 .builtin_extern => Payload.Extern,
    386                 .macro_arithmetic => Payload.MacroArithmetic,
    387             };
    388         }
    389 
    390         pub fn init(comptime t: Tag) Node {
    391             comptime std.debug.assert(@intFromEnum(t) < Tag.no_payload_count);
    392             return .{ .tag_if_small_enough = @intFromEnum(t) };
    393         }
    394 
    395         pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!Node {
    396             const ptr = try ally.create(t.Type());
    397             ptr.* = .{
    398                 .base = .{ .tag = t },
    399                 .data = data,
    400             };
    401             return Node{ .ptr_otherwise = &ptr.base };
    402         }
    403 
    404         pub fn Data(comptime t: Tag) type {
    405             return @FieldType(t.Type(), "data");
    406         }
    407     };
    408 
    409     pub fn tag(self: Node) Tag {
    410         if (self.tag_if_small_enough < Tag.no_payload_count) {
    411             return @as(Tag, @enumFromInt(@as(std.meta.Tag(Tag), @intCast(self.tag_if_small_enough))));
    412         } else {
    413             return self.ptr_otherwise.tag;
    414         }
    415     }
    416 
    417     pub fn castTag(self: Node, comptime t: Tag) ?*t.Type() {
    418         if (self.tag_if_small_enough < Tag.no_payload_count)
    419             return null;
    420 
    421         if (self.ptr_otherwise.tag == t)
    422             return @alignCast(@fieldParentPtr("base", self.ptr_otherwise));
    423 
    424         return null;
    425     }
    426 
    427     pub fn initPayload(payload: *Payload) Node {
    428         std.debug.assert(@intFromEnum(payload.tag) >= Tag.no_payload_count);
    429         return .{ .ptr_otherwise = payload };
    430     }
    431 
    432     pub fn isNoreturn(node: Node, break_counts: bool) bool {
    433         switch (node.tag()) {
    434             .block => {
    435                 const block_node = node.castTag(.block).?;
    436                 if (block_node.data.stmts.len == 0) return false;
    437 
    438                 const last = block_node.data.stmts[block_node.data.stmts.len - 1];
    439                 return last.isNoreturn(break_counts);
    440             },
    441             .@"switch" => {
    442                 const switch_node = node.castTag(.@"switch").?;
    443 
    444                 for (switch_node.data.cases) |case| {
    445                     const body = if (case.castTag(.switch_else)) |some|
    446                         some.data
    447                     else if (case.castTag(.switch_prong)) |some|
    448                         some.data.cond
    449                     else
    450                         unreachable;
    451 
    452                     if (!body.isNoreturn(break_counts)) return false;
    453                 }
    454                 return true;
    455             },
    456             .@"return", .return_void => return true,
    457             .@"break" => if (break_counts) return true,
    458             else => {},
    459         }
    460         return false;
    461     }
    462 };
    463 
    464 pub const Payload = struct {
    465     tag: Node.Tag,
    466 
    467     pub const Value = struct {
    468         base: Payload,
    469         data: []const u8,
    470     };
    471 
    472     pub const UnOp = struct {
    473         base: Payload,
    474         data: Node,
    475     };
    476 
    477     pub const BinOp = struct {
    478         base: Payload,
    479         data: struct {
    480             lhs: Node,
    481             rhs: Node,
    482         },
    483     };
    484 
    485     pub const Discard = struct {
    486         base: Payload,
    487         data: struct {
    488             should_skip: bool,
    489             value: Node,
    490         },
    491     };
    492 
    493     pub const If = struct {
    494         base: Payload,
    495         data: struct {
    496             cond: Node,
    497             then: Node,
    498             @"else": ?Node,
    499         },
    500     };
    501 
    502     pub const While = struct {
    503         base: Payload,
    504         data: struct {
    505             cond: Node,
    506             body: Node,
    507             cont_expr: ?Node,
    508         },
    509     };
    510 
    511     pub const Switch = struct {
    512         base: Payload,
    513         data: struct {
    514             cond: Node,
    515             cases: []Node,
    516         },
    517     };
    518 
    519     pub const BreakVal = struct {
    520         base: Payload,
    521         data: struct {
    522             label: ?[]const u8,
    523             val: Node,
    524         },
    525     };
    526 
    527     pub const Call = struct {
    528         base: Payload,
    529         data: struct {
    530             lhs: Node,
    531             args: []Node,
    532         },
    533     };
    534 
    535     pub const VarDecl = struct {
    536         base: Payload,
    537         data: struct {
    538             is_pub: bool,
    539             is_const: bool,
    540             is_extern: bool,
    541             is_export: bool,
    542             is_threadlocal: bool,
    543             alignment: ?c_uint,
    544             linksection_string: ?[]const u8,
    545             name: []const u8,
    546             type: Node,
    547             init: ?Node,
    548         },
    549     };
    550 
    551     pub const Func = struct {
    552         base: Payload,
    553         data: struct {
    554             is_pub: bool,
    555             is_extern: bool,
    556             is_export: bool,
    557             is_inline: bool,
    558             is_var_args: bool,
    559             name: ?[]const u8,
    560             linksection_string: ?[]const u8,
    561             explicit_callconv: ?CallingConvention,
    562             params: []Param,
    563             return_type: Node,
    564             body: ?Node,
    565             alignment: ?c_uint,
    566         },
    567 
    568         pub const CallingConvention = enum {
    569             c,
    570             x86_64_sysv,
    571             x86_64_win,
    572             x86_stdcall,
    573             x86_fastcall,
    574             x86_thiscall,
    575             x86_vectorcall,
    576             aarch64_vfabi,
    577             arm_aapcs,
    578             arm_aapcs_vfp,
    579             m68k_rtd,
    580         };
    581     };
    582 
    583     pub const Param = struct {
    584         is_noalias: bool,
    585         name: ?[]const u8,
    586         type: Node,
    587     };
    588 
    589     pub const Record = struct {
    590         base: Payload,
    591         data: struct {
    592             layout: enum { @"packed", @"extern", none },
    593             fields: []Field,
    594             functions: []Node,
    595             variables: []Node,
    596         },
    597 
    598         pub const Field = struct {
    599             name: []const u8,
    600             type: Node,
    601             alignment: ?c_uint,
    602             default_value: ?Node,
    603         };
    604     };
    605 
    606     pub const TupleInit = struct {
    607         base: Payload,
    608         data: []Node,
    609     };
    610 
    611     pub const ContainerInit = struct {
    612         base: Payload,
    613         data: struct {
    614             lhs: Node,
    615             inits: []Initializer,
    616         },
    617 
    618         pub const Initializer = struct {
    619             name: []const u8,
    620             value: Node,
    621         };
    622     };
    623 
    624     pub const ContainerInitDot = struct {
    625         base: Payload,
    626         data: []Initializer,
    627 
    628         pub const Initializer = struct {
    629             name: []const u8,
    630             value: Node,
    631         };
    632     };
    633 
    634     pub const Block = struct {
    635         base: Payload,
    636         data: struct {
    637             label: ?[]const u8,
    638             stmts: []Node,
    639         },
    640     };
    641 
    642     pub const Array = struct {
    643         base: Payload,
    644         data: ArrayTypeInfo,
    645 
    646         pub const ArrayTypeInfo = struct {
    647             elem_type: Node,
    648             len: usize,
    649         };
    650     };
    651 
    652     pub const Pointer = struct {
    653         base: Payload,
    654         data: struct {
    655             elem_type: Node,
    656             is_const: bool,
    657             is_volatile: bool,
    658         },
    659     };
    660 
    661     pub const ArgRedecl = struct {
    662         base: Payload,
    663         data: struct {
    664             actual: []const u8,
    665             mangled: []const u8,
    666         },
    667     };
    668 
    669     pub const SimpleVarDecl = struct {
    670         base: Payload,
    671         data: struct {
    672             name: []const u8,
    673             init: Node,
    674         },
    675     };
    676 
    677     pub const EnumConstant = struct {
    678         base: Payload,
    679         data: struct {
    680             name: []const u8,
    681             is_public: bool,
    682             type: ?Node,
    683             value: Node,
    684         },
    685     };
    686 
    687     pub const ArrayFiller = struct {
    688         base: Payload,
    689         data: struct {
    690             type: Node,
    691             filler: Node,
    692             count: usize,
    693         },
    694     };
    695 
    696     pub const PubInlineFn = struct {
    697         base: Payload,
    698         data: struct {
    699             name: []const u8,
    700             params: []Param,
    701             return_type: Node,
    702             body: Node,
    703         },
    704     };
    705 
    706     pub const FieldAccess = struct {
    707         base: Payload,
    708         data: struct {
    709             lhs: Node,
    710             field_name: []const u8,
    711         },
    712     };
    713 
    714     pub const PromoteIntLiteral = struct {
    715         base: Payload,
    716         data: struct {
    717             value: Node,
    718             type: Node,
    719             base: Node,
    720         },
    721     };
    722 
    723     pub const StringSlice = struct {
    724         base: Payload,
    725         data: struct {
    726             string: Node,
    727             end: usize,
    728         },
    729     };
    730 
    731     pub const Shuffle = struct {
    732         base: Payload,
    733         data: struct {
    734             element_type: Node,
    735             a: Node,
    736             b: Node,
    737             mask_vector: Node,
    738         },
    739     };
    740 
    741     pub const Extern = struct {
    742         base: Payload,
    743         data: struct {
    744             type: Node,
    745             name: Node,
    746         },
    747     };
    748 
    749     pub const MacroArithmetic = struct {
    750         base: Payload,
    751         data: struct {
    752             op: Operator,
    753             lhs: Node,
    754             rhs: Node,
    755         },
    756 
    757         pub const Operator = enum { div, rem };
    758     };
    759 };
    760 
    761 /// Converts the nodes into a Zig Ast.
    762 /// Caller must free the source slice.
    763 pub fn render(gpa: Allocator, nodes: []const Node) !std.zig.Ast {
    764     var ctx = Context{
    765         .gpa = gpa,
    766         .buf = std.array_list.Managed(u8).init(gpa),
    767     };
    768     defer ctx.buf.deinit();
    769     defer ctx.nodes.deinit(gpa);
    770     defer ctx.extra_data.deinit(gpa);
    771     defer ctx.tokens.deinit(gpa);
    772 
    773     // Estimate that each top level node has 10 child nodes.
    774     const estimated_node_count = nodes.len * 10;
    775     try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count);
    776     // Estimate that each each node has 2 tokens.
    777     const estimated_tokens_count = estimated_node_count * 2;
    778     try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count);
    779     // Estimate that each each token is 3 bytes long.
    780     const estimated_buf_len = estimated_tokens_count * 3;
    781     try ctx.buf.ensureTotalCapacity(estimated_buf_len);
    782 
    783     ctx.nodes.appendAssumeCapacity(.{
    784         .tag = .root,
    785         .main_token = 0,
    786         .data = undefined,
    787     });
    788 
    789     const root_members = blk: {
    790         var result = std.array_list.Managed(NodeIndex).init(gpa);
    791         defer result.deinit();
    792 
    793         for (nodes) |node| {
    794             const res = try renderNode(&ctx, node);
    795             if (node.tag() == .warning) continue;
    796             try result.append(res);
    797         }
    798         break :blk try ctx.listToSpan(result.items);
    799     };
    800 
    801     ctx.nodes.items(.data)[0] = .{ .extra_range = root_members };
    802 
    803     try ctx.tokens.append(gpa, .{
    804         .tag = .eof,
    805         .start = @as(u32, @intCast(ctx.buf.items.len)),
    806     });
    807 
    808     return std.zig.Ast{
    809         .source = try ctx.buf.toOwnedSliceSentinel(0),
    810         .tokens = ctx.tokens.toOwnedSlice(),
    811         .nodes = ctx.nodes.toOwnedSlice(),
    812         .extra_data = try ctx.extra_data.toOwnedSlice(gpa),
    813         .errors = &.{},
    814         .mode = .zig,
    815     };
    816 }
    817 
    818 const NodeIndex = std.zig.Ast.Node.Index;
    819 const NodeOptionalIndex = std.zig.Ast.Node.OptionalIndex;
    820 const NodeSubRange = std.zig.Ast.Node.SubRange;
    821 const TokenIndex = std.zig.Ast.TokenIndex;
    822 const TokenOptionalIndex = std.zig.Ast.OptionalTokenIndex;
    823 const TokenTag = std.zig.Token.Tag;
    824 const ExtraIndex = std.zig.Ast.ExtraIndex;
    825 
    826 const Context = struct {
    827     gpa: Allocator,
    828     buf: std.array_list.Managed(u8),
    829     nodes: std.zig.Ast.NodeList = .{},
    830     extra_data: std.ArrayListUnmanaged(u32) = .empty,
    831     tokens: std.zig.Ast.TokenList = .{},
    832 
    833     fn addTokenFmt(c: *Context, tag: TokenTag, comptime format: []const u8, args: anytype) Allocator.Error!TokenIndex {
    834         const start_index = c.buf.items.len;
    835         try c.buf.writer().print(format ++ " ", args);
    836 
    837         try c.tokens.append(c.gpa, .{
    838             .tag = tag,
    839             .start = @as(u32, @intCast(start_index)),
    840         });
    841 
    842         return @intCast(c.tokens.len - 1);
    843     }
    844 
    845     fn addToken(c: *Context, tag: TokenTag, bytes: []const u8) Allocator.Error!TokenIndex {
    846         return c.addTokenFmt(tag, "{s}", .{bytes});
    847     }
    848 
    849     fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex {
    850         if (std.zig.primitives.isPrimitive(bytes))
    851             return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes});
    852         return c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(bytes, .{ .allow_primitive = true })});
    853     }
    854 
    855     fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange {
    856         try c.extra_data.appendSlice(c.gpa, @ptrCast(list));
    857         return NodeSubRange{
    858             .start = @enumFromInt(c.extra_data.items.len - list.len),
    859             .end = @enumFromInt(c.extra_data.items.len),
    860         };
    861     }
    862 
    863     fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex {
    864         const result: NodeIndex = @enumFromInt(c.nodes.len);
    865         try c.nodes.append(c.gpa, elem);
    866         return result;
    867     }
    868 
    869     fn addExtra(c: *Context, extra: anytype) Allocator.Error!std.zig.Ast.ExtraIndex {
    870         const fields = std.meta.fields(@TypeOf(extra));
    871         try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len);
    872         const result: ExtraIndex = @enumFromInt(c.extra_data.items.len);
    873         inline for (fields) |field| {
    874             switch (field.type) {
    875                 NodeIndex,
    876                 NodeOptionalIndex,
    877                 TokenIndex,
    878                 TokenOptionalIndex,
    879                 ExtraIndex,
    880                 => c.extra_data.appendAssumeCapacity(@intFromEnum(@field(extra, field.name))),
    881                 else => @compileError("unexpected field type"),
    882             }
    883         }
    884         return result;
    885     }
    886 };
    887 
    888 fn renderNodes(c: *Context, nodes: []const Node) Allocator.Error!NodeSubRange {
    889     var result = std.array_list.Managed(NodeIndex).init(c.gpa);
    890     defer result.deinit();
    891 
    892     for (nodes) |node| {
    893         const res = try renderNode(c, node);
    894         if (node.tag() == .warning) continue;
    895         try result.append(res);
    896     }
    897 
    898     return try c.listToSpan(result.items);
    899 }
    900 
    901 fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
    902     switch (node.tag()) {
    903         .declaration => unreachable,
    904         .warning => {
    905             const payload = node.castTag(.warning).?.data;
    906             try c.buf.append('\n');
    907             try c.buf.appendSlice(payload);
    908             try c.buf.append('\n');
    909             return @enumFromInt(0);
    910         },
    911         .helpers_cast => {
    912             const payload = node.castTag(.helpers_cast).?.data;
    913             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "cast" });
    914             return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
    915         },
    916         .helpers_promoteIntLiteral => {
    917             const payload = node.castTag(.helpers_promoteIntLiteral).?.data;
    918             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "promoteIntLiteral" });
    919             return renderCall(c, import_node, &.{ payload.type, payload.value, payload.base });
    920         },
    921         .helpers_sizeof => {
    922             const payload = node.castTag(.helpers_sizeof).?.data;
    923             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "sizeof" });
    924             return renderCall(c, import_node, &.{payload});
    925         },
    926         .std_mem_zeroes => {
    927             const payload = node.castTag(.std_mem_zeroes).?.data;
    928             const import_node = try renderStdImport(c, &.{ "mem", "zeroes" });
    929             return renderCall(c, import_node, &.{payload});
    930         },
    931         .std_mem_zeroinit => {
    932             const payload = node.castTag(.std_mem_zeroinit).?.data;
    933             const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" });
    934             return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
    935         },
    936         .helpers_flexible_array_type => {
    937             const payload = node.castTag(.helpers_flexible_array_type).?.data;
    938             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "FlexibleArrayType" });
    939             return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
    940         },
    941         .helpers_shuffle_vector_index => {
    942             const payload = node.castTag(.helpers_shuffle_vector_index).?.data;
    943             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "shuffleVectorIndex" });
    944             return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
    945         },
    946         .vector => {
    947             const payload = node.castTag(.vector).?.data;
    948             return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs });
    949         },
    950         .call => {
    951             const payload = node.castTag(.call).?.data;
    952             // Cosmetic: avoids an unnecesary address_of on most function calls.
    953             const lhs = if (payload.lhs.tag() == .fn_identifier)
    954                 try c.addNode(.{
    955                     .tag = .identifier,
    956                     .main_token = try c.addIdentifier(payload.lhs.castTag(.fn_identifier).?.data),
    957                     .data = undefined,
    958                 })
    959             else
    960                 try renderNodeGrouped(c, payload.lhs);
    961             return renderCall(c, lhs, payload.args);
    962         },
    963         .null_literal => return c.addNode(.{
    964             .tag = .identifier,
    965             .main_token = try c.addToken(.identifier, "null"),
    966             .data = undefined,
    967         }),
    968         .undefined_literal => return c.addNode(.{
    969             .tag = .identifier,
    970             .main_token = try c.addToken(.identifier, "undefined"),
    971             .data = undefined,
    972         }),
    973         .true_literal => return c.addNode(.{
    974             .tag = .identifier,
    975             .main_token = try c.addToken(.identifier, "true"),
    976             .data = undefined,
    977         }),
    978         .false_literal => return c.addNode(.{
    979             .tag = .identifier,
    980             .main_token = try c.addToken(.identifier, "false"),
    981             .data = undefined,
    982         }),
    983         .zero_literal => return c.addNode(.{
    984             .tag = .number_literal,
    985             .main_token = try c.addToken(.number_literal, "0"),
    986             .data = undefined,
    987         }),
    988         .one_literal => return c.addNode(.{
    989             .tag = .number_literal,
    990             .main_token = try c.addToken(.number_literal, "1"),
    991             .data = undefined,
    992         }),
    993         .void_type => return c.addNode(.{
    994             .tag = .identifier,
    995             .main_token = try c.addToken(.identifier, "void"),
    996             .data = undefined,
    997         }),
    998         .noreturn_type => return c.addNode(.{
    999             .tag = .identifier,
   1000             .main_token = try c.addToken(.identifier, "noreturn"),
   1001             .data = undefined,
   1002         }),
   1003         .@"continue" => return c.addNode(.{
   1004             .tag = .@"continue",
   1005             .main_token = try c.addToken(.keyword_continue, "continue"),
   1006             .data = .{ .opt_token_and_opt_node = .{ .none, .none } },
   1007         }),
   1008         .return_void => return c.addNode(.{
   1009             .tag = .@"return",
   1010             .main_token = try c.addToken(.keyword_return, "return"),
   1011             .data = .{ .opt_node = .none },
   1012         }),
   1013         .@"break" => return c.addNode(.{
   1014             .tag = .@"break",
   1015             .main_token = try c.addToken(.keyword_break, "break"),
   1016             .data = .{ .opt_token_and_opt_node = .{ .none, .none } },
   1017         }),
   1018         .break_val => {
   1019             const payload = node.castTag(.break_val).?.data;
   1020             const tok = try c.addToken(.keyword_break, "break");
   1021             const break_label = if (payload.label) |some| blk: {
   1022                 _ = try c.addToken(.colon, ":");
   1023                 break :blk try c.addIdentifier(some);
   1024             } else null;
   1025             return c.addNode(.{
   1026                 .tag = .@"break",
   1027                 .main_token = tok,
   1028                 .data = .{ .opt_token_and_opt_node = .{
   1029                     .fromOptional(break_label),
   1030                     (try renderNode(c, payload.val)).toOptional(),
   1031                 } },
   1032             });
   1033         },
   1034         .@"return" => {
   1035             const payload = node.castTag(.@"return").?.data;
   1036             return c.addNode(.{
   1037                 .tag = .@"return",
   1038                 .main_token = try c.addToken(.keyword_return, "return"),
   1039                 .data = .{ .opt_node = (try renderNode(c, payload)).toOptional() },
   1040             });
   1041         },
   1042         .@"comptime" => {
   1043             const payload = node.castTag(.@"comptime").?.data;
   1044             return c.addNode(.{
   1045                 .tag = .@"comptime",
   1046                 .main_token = try c.addToken(.keyword_comptime, "comptime"),
   1047                 .data = .{ .node = try renderNode(c, payload) },
   1048             });
   1049         },
   1050         .@"defer" => {
   1051             const payload = node.castTag(.@"defer").?.data;
   1052             return c.addNode(.{
   1053                 .tag = .@"defer",
   1054                 .main_token = try c.addToken(.keyword_defer, "defer"),
   1055                 .data = .{ .node = try renderNode(c, payload) },
   1056             });
   1057         },
   1058         .asm_simple => {
   1059             const payload = node.castTag(.asm_simple).?.data;
   1060             const asm_token = try c.addToken(.keyword_asm, "asm");
   1061             _ = try c.addToken(.l_paren, "(");
   1062             return c.addNode(.{
   1063                 .tag = .asm_simple,
   1064                 .main_token = asm_token,
   1065                 .data = .{ .node_and_token = .{
   1066                     try renderNode(c, payload),
   1067                     try c.addToken(.r_paren, ")"),
   1068                 } },
   1069             });
   1070         },
   1071         .type => {
   1072             const payload = node.castTag(.type).?.data;
   1073             return c.addNode(.{
   1074                 .tag = .identifier,
   1075                 .main_token = try c.addToken(.identifier, payload),
   1076                 .data = undefined,
   1077             });
   1078         },
   1079         .identifier => {
   1080             const payload = node.castTag(.identifier).?.data;
   1081             return c.addNode(.{
   1082                 .tag = .identifier,
   1083                 .main_token = try c.addIdentifier(payload),
   1084                 .data = undefined,
   1085             });
   1086         },
   1087         .fn_identifier => {
   1088             // C semantics are that a function identifier has address
   1089             // value (implicit in stage1, explicit in stage2), except in
   1090             // the context of an address_of, which is handled there.
   1091             const payload = node.castTag(.fn_identifier).?.data;
   1092             const tok = try c.addToken(.ampersand, "&");
   1093             const arg = try c.addNode(.{
   1094                 .tag = .identifier,
   1095                 .main_token = try c.addIdentifier(payload),
   1096                 .data = undefined,
   1097             });
   1098             return c.addNode(.{
   1099                 .tag = .address_of,
   1100                 .main_token = tok,
   1101                 .data = .{ .node = arg },
   1102             });
   1103         },
   1104         .float_literal => {
   1105             const payload = node.castTag(.float_literal).?.data;
   1106             return c.addNode(.{
   1107                 .tag = .number_literal,
   1108                 .main_token = try c.addToken(.number_literal, payload),
   1109                 .data = undefined,
   1110             });
   1111         },
   1112         .integer_literal => {
   1113             const payload = node.castTag(.integer_literal).?.data;
   1114             return c.addNode(.{
   1115                 .tag = .number_literal,
   1116                 .main_token = try c.addToken(.number_literal, payload),
   1117                 .data = undefined,
   1118             });
   1119         },
   1120         .string_literal => {
   1121             const payload = node.castTag(.string_literal).?.data;
   1122             return c.addNode(.{
   1123                 .tag = .string_literal,
   1124                 .main_token = try c.addToken(.string_literal, payload),
   1125                 .data = undefined,
   1126             });
   1127         },
   1128         .char_literal => {
   1129             const payload = node.castTag(.char_literal).?.data;
   1130             return c.addNode(.{
   1131                 .tag = .char_literal,
   1132                 .main_token = try c.addToken(.char_literal, payload),
   1133                 .data = undefined,
   1134             });
   1135         },
   1136         .enum_literal => {
   1137             const payload = node.castTag(.enum_literal).?.data;
   1138             _ = try c.addToken(.period, ".");
   1139             return c.addNode(.{
   1140                 .tag = .enum_literal,
   1141                 .main_token = try c.addToken(.identifier, payload),
   1142                 .data = undefined,
   1143             });
   1144         },
   1145         .helpers_macro => {
   1146             const payload = node.castTag(.helpers_macro).?.data;
   1147             const chain = [_][]const u8{
   1148                 "zig",
   1149                 "c_translation",
   1150                 "Macros",
   1151                 payload,
   1152             };
   1153             return renderStdImport(c, &chain);
   1154         },
   1155         .import_c_builtin => {
   1156             const payload = node.castTag(.import_c_builtin).?.data;
   1157             const chain = [_][]const u8{
   1158                 "zig",
   1159                 "c_builtins",
   1160                 payload,
   1161             };
   1162             return renderStdImport(c, &chain);
   1163         },
   1164         .string_slice => {
   1165             const payload = node.castTag(.string_slice).?.data;
   1166 
   1167             const string = try renderNode(c, payload.string);
   1168             const l_bracket = try c.addToken(.l_bracket, "[");
   1169             const start = try c.addNode(.{
   1170                 .tag = .number_literal,
   1171                 .main_token = try c.addToken(.number_literal, "0"),
   1172                 .data = undefined,
   1173             });
   1174             _ = try c.addToken(.ellipsis2, "..");
   1175             const end = try c.addNode(.{
   1176                 .tag = .number_literal,
   1177                 .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}),
   1178                 .data = undefined,
   1179             });
   1180             _ = try c.addToken(.r_bracket, "]");
   1181 
   1182             return c.addNode(.{
   1183                 .tag = .slice,
   1184                 .main_token = l_bracket,
   1185                 .data = .{ .node_and_extra = .{
   1186                     string,
   1187                     try c.addExtra(std.zig.Ast.Node.Slice{
   1188                         .start = start,
   1189                         .end = end,
   1190                     }),
   1191                 } },
   1192             });
   1193         },
   1194         .fail_decl => {
   1195             const payload = node.castTag(.fail_decl).?.data;
   1196             // pub const name = @compileError(msg);
   1197             _ = try c.addToken(.keyword_pub, "pub");
   1198             const const_tok = try c.addToken(.keyword_const, "const");
   1199             _ = try c.addIdentifier(payload.actual);
   1200             _ = try c.addToken(.equal, "=");
   1201 
   1202             const compile_error_tok = try c.addToken(.builtin, "@compileError");
   1203             _ = try c.addToken(.l_paren, "(");
   1204             const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)});
   1205             const err_msg = try c.addNode(.{
   1206                 .tag = .string_literal,
   1207                 .main_token = err_msg_tok,
   1208                 .data = undefined,
   1209             });
   1210             _ = try c.addToken(.r_paren, ")");
   1211             const compile_error = try c.addNode(.{
   1212                 .tag = .builtin_call_two,
   1213                 .main_token = compile_error_tok,
   1214                 .data = .{ .opt_node_and_opt_node = .{ err_msg.toOptional(), .none } },
   1215             });
   1216             _ = try c.addToken(.semicolon, ";");
   1217 
   1218             return c.addNode(.{
   1219                 .tag = .simple_var_decl,
   1220                 .main_token = const_tok,
   1221                 .data = .{ .opt_node_and_opt_node = .{
   1222                     .none,
   1223                     compile_error.toOptional(),
   1224                 } },
   1225             });
   1226         },
   1227         .pub_var_simple, .var_simple => {
   1228             const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   1229             if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub");
   1230             const const_tok = try c.addToken(.keyword_const, "const");
   1231             _ = try c.addIdentifier(payload.name);
   1232             _ = try c.addToken(.equal, "=");
   1233 
   1234             const init = try renderNode(c, payload.init);
   1235             _ = try c.addToken(.semicolon, ";");
   1236 
   1237             return c.addNode(.{
   1238                 .tag = .simple_var_decl,
   1239                 .main_token = const_tok,
   1240                 .data = .{ .opt_node_and_opt_node = .{
   1241                     .none,
   1242                     init.toOptional(),
   1243                 } },
   1244             });
   1245         },
   1246         .static_local_var => {
   1247             const payload = node.castTag(.static_local_var).?.data;
   1248 
   1249             const const_tok = try c.addToken(.keyword_const, "const");
   1250             _ = try c.addIdentifier(payload.name);
   1251             _ = try c.addToken(.equal, "=");
   1252 
   1253             const kind_tok = try c.addToken(.keyword_struct, "struct");
   1254             _ = try c.addToken(.l_brace, "{");
   1255 
   1256             const container_def = try c.addNode(.{
   1257                 .tag = .container_decl_two_trailing,
   1258                 .main_token = kind_tok,
   1259                 .data = .{ .opt_node_and_opt_node = .{
   1260                     (try renderNode(c, payload.init)).toOptional(),
   1261                     .none,
   1262                 } },
   1263             });
   1264             _ = try c.addToken(.r_brace, "}");
   1265             _ = try c.addToken(.semicolon, ";");
   1266 
   1267             return c.addNode(.{
   1268                 .tag = .simple_var_decl,
   1269                 .main_token = const_tok,
   1270                 .data = .{ .opt_node_and_opt_node = .{
   1271                     .none,
   1272                     container_def.toOptional(),
   1273                 } },
   1274             });
   1275         },
   1276         .extern_local_var, .extern_local_fn => {
   1277             const payload = if (node.tag() == .extern_local_var)
   1278                 node.castTag(.extern_local_var).?.data
   1279             else
   1280                 node.castTag(.extern_local_fn).?.data;
   1281 
   1282             const const_tok = try c.addToken(.keyword_const, "const");
   1283             _ = try c.addIdentifier(payload.name);
   1284             _ = try c.addToken(.equal, "=");
   1285 
   1286             const kind_tok = try c.addToken(.keyword_struct, "struct");
   1287             _ = try c.addToken(.l_brace, "{");
   1288 
   1289             const container_def = try c.addNode(.{
   1290                 .tag = .container_decl_two_trailing,
   1291                 .main_token = kind_tok,
   1292                 .data = .{ .opt_node_and_opt_node = .{
   1293                     (try renderNode(c, payload.init)).toOptional(),
   1294                     .none,
   1295                 } },
   1296             });
   1297             _ = try c.addToken(.r_brace, "}");
   1298             _ = try c.addToken(.semicolon, ";");
   1299 
   1300             return c.addNode(.{
   1301                 .tag = .simple_var_decl,
   1302                 .main_token = const_tok,
   1303                 .data = .{ .opt_node_and_opt_node = .{
   1304                     .none,
   1305                     container_def.toOptional(),
   1306                 } },
   1307             });
   1308         },
   1309         .mut_str => {
   1310             const payload = node.castTag(.mut_str).?.data;
   1311 
   1312             const var_tok = try c.addToken(.keyword_var, "var");
   1313             _ = try c.addIdentifier(payload.name);
   1314             _ = try c.addToken(.equal, "=");
   1315 
   1316             const deref = try c.addNode(.{
   1317                 .tag = .deref,
   1318                 .data = .{ .node = try renderNodeGrouped(c, payload.init) },
   1319                 .main_token = try c.addToken(.period_asterisk, ".*"),
   1320             });
   1321             _ = try c.addToken(.semicolon, ";");
   1322 
   1323             return c.addNode(.{
   1324                 .tag = .simple_var_decl,
   1325                 .main_token = var_tok,
   1326                 .data = .{ .opt_node_and_opt_node = .{
   1327                     .none,
   1328                     deref.toOptional(),
   1329                 } },
   1330             });
   1331         },
   1332         .var_decl => return renderVar(c, node),
   1333         .arg_redecl, .alias => {
   1334             const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   1335             if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub");
   1336             const mut_tok = if (node.tag() == .alias)
   1337                 try c.addToken(.keyword_const, "const")
   1338             else
   1339                 try c.addToken(.keyword_var, "var");
   1340             _ = try c.addIdentifier(payload.actual);
   1341             _ = try c.addToken(.equal, "=");
   1342 
   1343             const init = try c.addNode(.{
   1344                 .tag = .identifier,
   1345                 .main_token = try c.addIdentifier(payload.mangled),
   1346                 .data = undefined,
   1347             });
   1348             _ = try c.addToken(.semicolon, ";");
   1349 
   1350             return c.addNode(.{
   1351                 .tag = .simple_var_decl,
   1352                 .main_token = mut_tok,
   1353                 .data = .{ .opt_node_and_opt_node = .{
   1354                     .none,
   1355                     init.toOptional(),
   1356                 } },
   1357             });
   1358         },
   1359         .int_cast => {
   1360             const payload = node.castTag(.int_cast).?.data;
   1361             return renderBuiltinCall(c, "@intCast", &.{payload});
   1362         },
   1363         .const_cast => {
   1364             const payload = node.castTag(.const_cast).?.data;
   1365             return renderBuiltinCall(c, "@constCast", &.{payload});
   1366         },
   1367         .volatile_cast => {
   1368             const payload = node.castTag(.volatile_cast).?.data;
   1369             return renderBuiltinCall(c, "@volatileCast", &.{payload});
   1370         },
   1371         .signed_remainder => {
   1372             const payload = node.castTag(.signed_remainder).?.data;
   1373             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "signedRemainder" });
   1374             return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
   1375         },
   1376         .div_trunc => {
   1377             const payload = node.castTag(.div_trunc).?.data;
   1378             return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs });
   1379         },
   1380         .int_from_bool => {
   1381             const payload = node.castTag(.int_from_bool).?.data;
   1382             return renderBuiltinCall(c, "@intFromBool", &.{payload});
   1383         },
   1384         .as => {
   1385             const payload = node.castTag(.as).?.data;
   1386             return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs });
   1387         },
   1388         .truncate => {
   1389             const payload = node.castTag(.truncate).?.data;
   1390             return renderBuiltinCall(c, "@truncate", &.{payload});
   1391         },
   1392         .bit_cast => {
   1393             const payload = node.castTag(.bit_cast).?.data;
   1394             return renderBuiltinCall(c, "@bitCast", &.{payload});
   1395         },
   1396         .float_cast => {
   1397             const payload = node.castTag(.float_cast).?.data;
   1398             return renderBuiltinCall(c, "@floatCast", &.{payload});
   1399         },
   1400         .int_from_float => {
   1401             const payload = node.castTag(.int_from_float).?.data;
   1402             return renderBuiltinCall(c, "@intFromFloat", &.{payload});
   1403         },
   1404         .float_from_int => {
   1405             const payload = node.castTag(.float_from_int).?.data;
   1406             return renderBuiltinCall(c, "@floatFromInt", &.{payload});
   1407         },
   1408         .ptr_from_int => {
   1409             const payload = node.castTag(.ptr_from_int).?.data;
   1410             return renderBuiltinCall(c, "@ptrFromInt", &.{payload});
   1411         },
   1412         .int_from_ptr => {
   1413             const payload = node.castTag(.int_from_ptr).?.data;
   1414             return renderBuiltinCall(c, "@intFromPtr", &.{payload});
   1415         },
   1416         .align_cast => {
   1417             const payload = node.castTag(.align_cast).?.data;
   1418             return renderBuiltinCall(c, "@alignCast", &.{payload});
   1419         },
   1420         .ptr_cast => {
   1421             const payload = node.castTag(.ptr_cast).?.data;
   1422             return renderBuiltinCall(c, "@ptrCast", &.{payload});
   1423         },
   1424         .div_exact => {
   1425             const payload = node.castTag(.div_exact).?.data;
   1426             return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs });
   1427         },
   1428         .offset_of => {
   1429             const payload = node.castTag(.offset_of).?.data;
   1430             return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs });
   1431         },
   1432         .sizeof => {
   1433             const payload = node.castTag(.sizeof).?.data;
   1434             return renderBuiltinCall(c, "@sizeOf", &.{payload});
   1435         },
   1436         .shuffle => {
   1437             const payload = node.castTag(.shuffle).?.data;
   1438             return renderBuiltinCall(c, "@shuffle", &.{
   1439                 payload.element_type,
   1440                 payload.a,
   1441                 payload.b,
   1442                 payload.mask_vector,
   1443             });
   1444         },
   1445         .builtin_extern => {
   1446             const payload = node.castTag(.builtin_extern).?.data;
   1447 
   1448             var info_inits: [1]Payload.ContainerInitDot.Initializer = .{
   1449                 .{ .name = "name", .value = payload.name },
   1450             };
   1451             var info_payload: Payload.ContainerInitDot = .{
   1452                 .base = .{ .tag = .container_init_dot },
   1453                 .data = &info_inits,
   1454             };
   1455 
   1456             return renderBuiltinCall(c, "@extern", &.{
   1457                 payload.type,
   1458                 .{ .ptr_otherwise = &info_payload.base },
   1459             });
   1460         },
   1461         .macro_arithmetic => {
   1462             const payload = node.castTag(.macro_arithmetic).?.data;
   1463             const op = @tagName(payload.op);
   1464             const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "MacroArithmetic", op });
   1465             return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
   1466         },
   1467         .alignof => {
   1468             const payload = node.castTag(.alignof).?.data;
   1469             return renderBuiltinCall(c, "@alignOf", &.{payload});
   1470         },
   1471         .typeof => {
   1472             const payload = node.castTag(.typeof).?.data;
   1473             return renderBuiltinCall(c, "@TypeOf", &.{payload});
   1474         },
   1475         .typeinfo => {
   1476             const payload = node.castTag(.typeinfo).?.data;
   1477             return renderBuiltinCall(c, "@typeInfo", &.{payload});
   1478         },
   1479         .negate => return renderPrefixOp(c, node, .negation, .minus, "-"),
   1480         .negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"),
   1481         .bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"),
   1482         .not => return renderPrefixOp(c, node, .bool_not, .bang, "!"),
   1483         .optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"),
   1484         .address_of => {
   1485             const payload = node.castTag(.address_of).?.data;
   1486 
   1487             const ampersand = try c.addToken(.ampersand, "&");
   1488             const base = if (payload.tag() == .fn_identifier)
   1489                 try c.addNode(.{
   1490                     .tag = .identifier,
   1491                     .main_token = try c.addIdentifier(payload.castTag(.fn_identifier).?.data),
   1492                     .data = undefined,
   1493                 })
   1494             else
   1495                 try renderNodeGrouped(c, payload);
   1496             return c.addNode(.{
   1497                 .tag = .address_of,
   1498                 .main_token = ampersand,
   1499                 .data = .{ .node = base },
   1500             });
   1501         },
   1502         .deref => {
   1503             const payload = node.castTag(.deref).?.data;
   1504             const operand = try renderNodeGrouped(c, payload);
   1505             const deref_tok = try c.addToken(.period_asterisk, ".*");
   1506             return c.addNode(.{
   1507                 .tag = .deref,
   1508                 .main_token = deref_tok,
   1509                 .data = .{ .node = operand },
   1510             });
   1511         },
   1512         .unwrap => {
   1513             const payload = node.castTag(.unwrap).?.data;
   1514             const operand = try renderNodeGrouped(c, payload);
   1515             const period = try c.addToken(.period, ".");
   1516             const question_mark = try c.addToken(.question_mark, "?");
   1517             return c.addNode(.{
   1518                 .tag = .unwrap_optional,
   1519                 .main_token = period,
   1520                 .data = .{ .node_and_token = .{
   1521                     operand,
   1522                     question_mark,
   1523                 } },
   1524             });
   1525         },
   1526         .c_pointer, .single_pointer => {
   1527             const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   1528 
   1529             const main_token = if (node.tag() == .single_pointer)
   1530                 try c.addToken(.asterisk, "*")
   1531             else blk: {
   1532                 const res = try c.addToken(.l_bracket, "[");
   1533                 _ = try c.addToken(.asterisk, "*");
   1534                 _ = try c.addIdentifier("c");
   1535                 _ = try c.addToken(.r_bracket, "]");
   1536                 break :blk res;
   1537             };
   1538             if (payload.is_const) _ = try c.addToken(.keyword_const, "const");
   1539             if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile");
   1540             const elem_type = try renderNodeGrouped(c, payload.elem_type);
   1541 
   1542             return c.addNode(.{
   1543                 .tag = .ptr_type_aligned,
   1544                 .main_token = main_token,
   1545                 .data = .{ .opt_node_and_node = .{
   1546                     .none,
   1547                     elem_type,
   1548                 } },
   1549             });
   1550         },
   1551         .add => return renderBinOpGrouped(c, node, .add, .plus, "+"),
   1552         .add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="),
   1553         .add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"),
   1554         .add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="),
   1555         .sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"),
   1556         .sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="),
   1557         .sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"),
   1558         .sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="),
   1559         .mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"),
   1560         .mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="),
   1561         .mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"),
   1562         .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="),
   1563         .div => return renderBinOpGrouped(c, node, .div, .slash, "/"),
   1564         .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="),
   1565         .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"),
   1566         .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="),
   1567         .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"),
   1568         .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="),
   1569         .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"),
   1570         .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="),
   1571         .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"),
   1572         .@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"),
   1573         .less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"),
   1574         .less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="),
   1575         .greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="),
   1576         .greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="),
   1577         .equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="),
   1578         .not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="),
   1579         .bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"),
   1580         .bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="),
   1581         .bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"),
   1582         .bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="),
   1583         .bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"),
   1584         .bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="),
   1585         .array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"),
   1586         .ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."),
   1587         .assign => return renderBinOp(c, node, .assign, .equal, "="),
   1588         .empty_block => {
   1589             const l_brace = try c.addToken(.l_brace, "{");
   1590             _ = try c.addToken(.r_brace, "}");
   1591             return c.addNode(.{
   1592                 .tag = .block_two,
   1593                 .main_token = l_brace,
   1594                 .data = .{ .opt_node_and_opt_node = .{
   1595                     .none,
   1596                     .none,
   1597                 } },
   1598             });
   1599         },
   1600         .block_single => {
   1601             const payload = node.castTag(.block_single).?.data;
   1602             const l_brace = try c.addToken(.l_brace, "{");
   1603 
   1604             const stmt = try renderNode(c, payload);
   1605             try addSemicolonIfNeeded(c, payload);
   1606 
   1607             _ = try c.addToken(.r_brace, "}");
   1608             return c.addNode(.{
   1609                 .tag = .block_two_semicolon,
   1610                 .main_token = l_brace,
   1611                 .data = .{ .opt_node_and_opt_node = .{
   1612                     stmt.toOptional(),
   1613                     .none,
   1614                 } },
   1615             });
   1616         },
   1617         .block => {
   1618             const payload = node.castTag(.block).?.data;
   1619             if (payload.label) |some| {
   1620                 _ = try c.addIdentifier(some);
   1621                 _ = try c.addToken(.colon, ":");
   1622             }
   1623             const l_brace = try c.addToken(.l_brace, "{");
   1624 
   1625             var stmts = std.array_list.Managed(NodeIndex).init(c.gpa);
   1626             defer stmts.deinit();
   1627             for (payload.stmts) |stmt| {
   1628                 const res = try renderNode(c, stmt);
   1629                 if (@intFromEnum(res) == 0) continue;
   1630                 try addSemicolonIfNeeded(c, stmt);
   1631                 try stmts.append(res);
   1632             }
   1633             const span = try c.listToSpan(stmts.items);
   1634             _ = try c.addToken(.r_brace, "}");
   1635 
   1636             const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon;
   1637             return c.addNode(.{
   1638                 .tag = if (semicolon) .block_semicolon else .block,
   1639                 .main_token = l_brace,
   1640                 .data = .{ .extra_range = span },
   1641             });
   1642         },
   1643         .func => return renderFunc(c, node),
   1644         .pub_inline_fn => return renderMacroFunc(c, node),
   1645         .discard => {
   1646             const payload = node.castTag(.discard).?.data;
   1647             if (payload.should_skip) return @enumFromInt(0);
   1648 
   1649             const lhs = try c.addNode(.{
   1650                 .tag = .identifier,
   1651                 .main_token = try c.addToken(.identifier, "_"),
   1652                 .data = undefined,
   1653             });
   1654             const main_token = try c.addToken(.equal, "=");
   1655             if (payload.value.tag() == .identifier) {
   1656                 // Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors.
   1657                 var addr_of_pl: Payload.UnOp = .{
   1658                     .base = .{ .tag = .address_of },
   1659                     .data = payload.value,
   1660                 };
   1661                 const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base };
   1662                 return c.addNode(.{
   1663                     .tag = .assign,
   1664                     .main_token = main_token,
   1665                     .data = .{ .node_and_node = .{
   1666                         lhs,
   1667                         try renderNode(c, addr_of),
   1668                     } },
   1669                 });
   1670             } else {
   1671                 return c.addNode(.{
   1672                     .tag = .assign,
   1673                     .main_token = main_token,
   1674                     .data = .{ .node_and_node = .{
   1675                         lhs,
   1676                         try renderNode(c, payload.value),
   1677                     } },
   1678                 });
   1679             }
   1680         },
   1681         .@"while" => {
   1682             const payload = node.castTag(.@"while").?.data;
   1683             const while_tok = try c.addToken(.keyword_while, "while");
   1684             _ = try c.addToken(.l_paren, "(");
   1685             const cond = try renderNode(c, payload.cond);
   1686             _ = try c.addToken(.r_paren, ")");
   1687 
   1688             const cont_expr = if (payload.cont_expr) |some| blk: {
   1689                 _ = try c.addToken(.colon, ":");
   1690                 _ = try c.addToken(.l_paren, "(");
   1691                 const res = try renderNode(c, some);
   1692                 _ = try c.addToken(.r_paren, ")");
   1693                 break :blk res;
   1694             } else null;
   1695             const body = try renderNode(c, payload.body);
   1696 
   1697             if (cont_expr == null) {
   1698                 return c.addNode(.{
   1699                     .tag = .while_simple,
   1700                     .main_token = while_tok,
   1701                     .data = .{ .node_and_node = .{
   1702                         cond,
   1703                         body,
   1704                     } },
   1705                 });
   1706             } else {
   1707                 return c.addNode(.{
   1708                     .tag = .while_cont,
   1709                     .main_token = while_tok,
   1710                     .data = .{ .node_and_extra = .{
   1711                         cond,
   1712                         try c.addExtra(std.zig.Ast.Node.WhileCont{
   1713                             .cont_expr = cont_expr.?,
   1714                             .then_expr = body,
   1715                         }),
   1716                     } },
   1717                 });
   1718             }
   1719         },
   1720         .while_true => {
   1721             const payload = node.castTag(.while_true).?.data;
   1722             const while_tok = try c.addToken(.keyword_while, "while");
   1723             _ = try c.addToken(.l_paren, "(");
   1724             const cond = try c.addNode(.{
   1725                 .tag = .identifier,
   1726                 .main_token = try c.addToken(.identifier, "true"),
   1727                 .data = undefined,
   1728             });
   1729             _ = try c.addToken(.r_paren, ")");
   1730             const body = try renderNode(c, payload);
   1731 
   1732             return c.addNode(.{
   1733                 .tag = .while_simple,
   1734                 .main_token = while_tok,
   1735                 .data = .{ .node_and_node = .{
   1736                     cond,
   1737                     body,
   1738                 } },
   1739             });
   1740         },
   1741         .@"if" => {
   1742             const payload = node.castTag(.@"if").?.data;
   1743             const if_tok = try c.addToken(.keyword_if, "if");
   1744             _ = try c.addToken(.l_paren, "(");
   1745             const cond = try renderNode(c, payload.cond);
   1746             _ = try c.addToken(.r_paren, ")");
   1747 
   1748             const then_expr = try renderNode(c, payload.then);
   1749             const else_node = payload.@"else" orelse return c.addNode(.{
   1750                 .tag = .if_simple,
   1751                 .main_token = if_tok,
   1752                 .data = .{ .node_and_node = .{
   1753                     cond,
   1754                     then_expr,
   1755                 } },
   1756             });
   1757             _ = try c.addToken(.keyword_else, "else");
   1758             const else_expr = try renderNode(c, else_node);
   1759 
   1760             return c.addNode(.{
   1761                 .tag = .@"if",
   1762                 .main_token = if_tok,
   1763                 .data = .{ .node_and_extra = .{
   1764                     cond,
   1765                     try c.addExtra(std.zig.Ast.Node.If{
   1766                         .then_expr = then_expr,
   1767                         .else_expr = else_expr,
   1768                     }),
   1769                 } },
   1770             });
   1771         },
   1772         .if_not_break => {
   1773             const payload = node.castTag(.if_not_break).?.data;
   1774             const if_tok = try c.addToken(.keyword_if, "if");
   1775             _ = try c.addToken(.l_paren, "(");
   1776             const cond = try c.addNode(.{
   1777                 .tag = .bool_not,
   1778                 .main_token = try c.addToken(.bang, "!"),
   1779                 .data = .{ .node = try renderNodeGrouped(c, payload) },
   1780             });
   1781             _ = try c.addToken(.r_paren, ")");
   1782             const then_expr = try c.addNode(.{
   1783                 .tag = .@"break",
   1784                 .main_token = try c.addToken(.keyword_break, "break"),
   1785                 .data = .{ .opt_token_and_opt_node = .{
   1786                     .none,
   1787                     .none,
   1788                 } },
   1789             });
   1790 
   1791             return c.addNode(.{
   1792                 .tag = .if_simple,
   1793                 .main_token = if_tok,
   1794                 .data = .{ .node_and_node = .{
   1795                     cond,
   1796                     then_expr,
   1797                 } },
   1798             });
   1799         },
   1800         .@"switch" => {
   1801             const payload = node.castTag(.@"switch").?.data;
   1802             const switch_tok = try c.addToken(.keyword_switch, "switch");
   1803             _ = try c.addToken(.l_paren, "(");
   1804             const cond = try renderNode(c, payload.cond);
   1805             _ = try c.addToken(.r_paren, ")");
   1806 
   1807             _ = try c.addToken(.l_brace, "{");
   1808             var cases = try c.gpa.alloc(NodeIndex, payload.cases.len);
   1809             defer c.gpa.free(cases);
   1810             for (payload.cases, 0..) |case, i| {
   1811                 cases[i] = try renderNode(c, case);
   1812                 _ = try c.addToken(.comma, ",");
   1813             }
   1814             const span = try c.listToSpan(cases);
   1815             _ = try c.addToken(.r_brace, "}");
   1816             return c.addNode(.{
   1817                 .tag = .switch_comma,
   1818                 .main_token = switch_tok,
   1819                 .data = .{ .node_and_extra = .{
   1820                     cond, try c.addExtra(NodeSubRange{
   1821                         .start = span.start,
   1822                         .end = span.end,
   1823                     }),
   1824                 } },
   1825             });
   1826         },
   1827         .switch_else => {
   1828             const payload = node.castTag(.switch_else).?.data;
   1829             _ = try c.addToken(.keyword_else, "else");
   1830             return c.addNode(.{
   1831                 .tag = .switch_case_one,
   1832                 .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
   1833                 .data = .{ .opt_node_and_node = .{
   1834                     .none,
   1835                     try renderNode(c, payload),
   1836                 } },
   1837             });
   1838         },
   1839         .switch_prong => {
   1840             const payload = node.castTag(.switch_prong).?.data;
   1841             var items = try c.gpa.alloc(NodeIndex, payload.cases.len);
   1842             defer c.gpa.free(items);
   1843             for (payload.cases, items, 0..) |case, *item, i| {
   1844                 if (i != 0) _ = try c.addToken(.comma, ",");
   1845                 item.* = try renderNode(c, case);
   1846             }
   1847             _ = try c.addToken(.r_brace, "}");
   1848             if (items.len < 2) {
   1849                 return c.addNode(.{
   1850                     .tag = .switch_case_one,
   1851                     .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
   1852                     .data = .{ .opt_node_and_node = .{
   1853                         if (items.len == 0) .none else items[0].toOptional(),
   1854                         try renderNode(c, payload.cond),
   1855                     } },
   1856                 });
   1857             } else {
   1858                 const span = try c.listToSpan(items);
   1859                 return c.addNode(.{
   1860                     .tag = .switch_case,
   1861                     .main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
   1862                     .data = .{ .extra_and_node = .{
   1863                         try c.addExtra(NodeSubRange{
   1864                             .start = span.start,
   1865                             .end = span.end,
   1866                         }),
   1867                         try renderNode(c, payload.cond),
   1868                     } },
   1869                 });
   1870             }
   1871         },
   1872         .opaque_literal => {
   1873             const opaque_tok = try c.addToken(.keyword_opaque, "opaque");
   1874             _ = try c.addToken(.l_brace, "{");
   1875             _ = try c.addToken(.r_brace, "}");
   1876 
   1877             return c.addNode(.{
   1878                 .tag = .container_decl_two,
   1879                 .main_token = opaque_tok,
   1880                 .data = .{ .opt_node_and_opt_node = .{
   1881                     .none,
   1882                     .none,
   1883                 } },
   1884             });
   1885         },
   1886         .array_access => {
   1887             const payload = node.castTag(.array_access).?.data;
   1888             const lhs = try renderNodeGrouped(c, payload.lhs);
   1889             const l_bracket = try c.addToken(.l_bracket, "[");
   1890             const index_expr = try renderNode(c, payload.rhs);
   1891             _ = try c.addToken(.r_bracket, "]");
   1892             return c.addNode(.{
   1893                 .tag = .array_access,
   1894                 .main_token = l_bracket,
   1895                 .data = .{ .node_and_node = .{
   1896                     lhs,
   1897                     index_expr,
   1898                 } },
   1899             });
   1900         },
   1901         .array_type => {
   1902             const payload = node.castTag(.array_type).?.data;
   1903             return renderArrayType(c, payload.len, payload.elem_type);
   1904         },
   1905         .null_sentinel_array_type => {
   1906             const payload = node.castTag(.null_sentinel_array_type).?.data;
   1907             return renderNullSentinelArrayType(c, payload.len, payload.elem_type);
   1908         },
   1909         .array_filler => {
   1910             const payload = node.castTag(.array_filler).?.data;
   1911 
   1912             const type_expr = try renderArrayType(c, 1, payload.type);
   1913             const l_brace = try c.addToken(.l_brace, "{");
   1914             const val = try renderNode(c, payload.filler);
   1915             _ = try c.addToken(.r_brace, "}");
   1916 
   1917             const init = try c.addNode(.{
   1918                 .tag = .array_init_one,
   1919                 .main_token = l_brace,
   1920                 .data = .{ .node_and_node = .{
   1921                     type_expr,
   1922                     val,
   1923                 } },
   1924             });
   1925             return c.addNode(.{
   1926                 .tag = .array_cat,
   1927                 .main_token = try c.addToken(.asterisk_asterisk, "**"),
   1928                 .data = .{ .node_and_node = .{
   1929                     init,
   1930                     try c.addNode(.{
   1931                         .tag = .number_literal,
   1932                         .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.count}),
   1933                         .data = undefined,
   1934                     }),
   1935                 } },
   1936             });
   1937         },
   1938         .empty_array => {
   1939             const payload = node.castTag(.empty_array).?.data;
   1940 
   1941             const type_expr = try renderArrayType(c, 0, payload);
   1942             return renderArrayInit(c, type_expr, &.{});
   1943         },
   1944         .array_init => {
   1945             const payload = node.castTag(.array_init).?.data;
   1946             const type_expr = try renderNode(c, payload.cond);
   1947             return renderArrayInit(c, type_expr, payload.cases);
   1948         },
   1949         .vector_zero_init => {
   1950             const payload = node.castTag(.vector_zero_init).?.data;
   1951             return renderBuiltinCall(c, "@splat", &.{payload});
   1952         },
   1953         .field_access => {
   1954             const payload = node.castTag(.field_access).?.data;
   1955             const lhs = try renderNodeGrouped(c, payload.lhs);
   1956             return renderFieldAccess(c, lhs, payload.field_name);
   1957         },
   1958         .@"struct", .@"union" => return renderRecord(c, node),
   1959         .enum_constant => {
   1960             const payload = node.castTag(.enum_constant).?.data;
   1961 
   1962             if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub");
   1963             const const_tok = try c.addToken(.keyword_const, "const");
   1964             _ = try c.addIdentifier(payload.name);
   1965 
   1966             const type_node = if (payload.type) |enum_const_type| blk: {
   1967                 _ = try c.addToken(.colon, ":");
   1968                 break :blk try renderNode(c, enum_const_type);
   1969             } else null;
   1970 
   1971             _ = try c.addToken(.equal, "=");
   1972 
   1973             const init_node = try renderNode(c, payload.value);
   1974             _ = try c.addToken(.semicolon, ";");
   1975 
   1976             return c.addNode(.{
   1977                 .tag = .simple_var_decl,
   1978                 .main_token = const_tok,
   1979                 .data = .{ .opt_node_and_opt_node = .{
   1980                     .fromOptional(type_node),
   1981                     init_node.toOptional(),
   1982                 } },
   1983             });
   1984         },
   1985         .tuple => {
   1986             const payload = node.castTag(.tuple).?.data;
   1987             _ = try c.addToken(.period, ".");
   1988             const l_brace = try c.addToken(.l_brace, "{");
   1989             var inits = try c.gpa.alloc(NodeIndex, payload.len);
   1990             defer c.gpa.free(inits);
   1991             for (payload, 0..) |init, i| {
   1992                 if (i != 0) _ = try c.addToken(.comma, ",");
   1993                 inits[i] = try renderNode(c, init);
   1994             }
   1995             _ = try c.addToken(.r_brace, "}");
   1996             if (payload.len < 3) {
   1997                 return c.addNode(.{
   1998                     .tag = .array_init_dot_two,
   1999                     .main_token = l_brace,
   2000                     .data = .{ .opt_node_and_opt_node = .{
   2001                         if (inits.len < 1) .none else inits[0].toOptional(),
   2002                         if (inits.len < 2) .none else inits[1].toOptional(),
   2003                     } },
   2004                 });
   2005             } else {
   2006                 const span = try c.listToSpan(inits);
   2007                 return c.addNode(.{
   2008                     .tag = .array_init_dot,
   2009                     .main_token = l_brace,
   2010                     .data = .{ .extra_range = span },
   2011                 });
   2012             }
   2013         },
   2014         .container_init_dot => {
   2015             const payload = node.castTag(.container_init_dot).?.data;
   2016             _ = try c.addToken(.period, ".");
   2017             const l_brace = try c.addToken(.l_brace, "{");
   2018             var inits = try c.gpa.alloc(NodeIndex, payload.len);
   2019             defer c.gpa.free(inits);
   2020             for (payload, 0..) |init, i| {
   2021                 _ = try c.addToken(.period, ".");
   2022                 _ = try c.addIdentifier(init.name);
   2023                 _ = try c.addToken(.equal, "=");
   2024                 inits[i] = try renderNode(c, init.value);
   2025                 _ = try c.addToken(.comma, ",");
   2026             }
   2027             _ = try c.addToken(.r_brace, "}");
   2028 
   2029             if (payload.len < 3) {
   2030                 return c.addNode(.{
   2031                     .tag = .struct_init_dot_two_comma,
   2032                     .main_token = l_brace,
   2033                     .data = .{ .opt_node_and_opt_node = .{
   2034                         if (inits.len < 1) .none else inits[0].toOptional(),
   2035                         if (inits.len < 2) .none else inits[1].toOptional(),
   2036                     } },
   2037                 });
   2038             } else {
   2039                 const span = try c.listToSpan(inits);
   2040                 return c.addNode(.{
   2041                     .tag = .struct_init_dot_comma,
   2042                     .main_token = l_brace,
   2043                     .data = .{ .extra_range = span },
   2044                 });
   2045             }
   2046         },
   2047         .container_init => {
   2048             const payload = node.castTag(.container_init).?.data;
   2049             const lhs = try renderNode(c, payload.lhs);
   2050 
   2051             const l_brace = try c.addToken(.l_brace, "{");
   2052             var inits = try c.gpa.alloc(NodeIndex, payload.inits.len);
   2053             defer c.gpa.free(inits);
   2054             for (payload.inits, 0..) |init, i| {
   2055                 _ = try c.addToken(.period, ".");
   2056                 _ = try c.addIdentifier(init.name);
   2057                 _ = try c.addToken(.equal, "=");
   2058                 inits[i] = try renderNode(c, init.value);
   2059                 _ = try c.addToken(.comma, ",");
   2060             }
   2061             _ = try c.addToken(.r_brace, "}");
   2062 
   2063             return switch (payload.inits.len) {
   2064                 0 => c.addNode(.{
   2065                     .tag = .struct_init_one,
   2066                     .main_token = l_brace,
   2067                     .data = .{ .node_and_opt_node = .{
   2068                         lhs,
   2069                         .none,
   2070                     } },
   2071                 }),
   2072                 1 => c.addNode(.{
   2073                     .tag = .struct_init_one_comma,
   2074                     .main_token = l_brace,
   2075                     .data = .{ .node_and_opt_node = .{
   2076                         lhs,
   2077                         inits[0].toOptional(),
   2078                     } },
   2079                 }),
   2080                 else => blk: {
   2081                     const span = try c.listToSpan(inits);
   2082                     break :blk c.addNode(.{
   2083                         .tag = .struct_init_comma,
   2084                         .main_token = l_brace,
   2085                         .data = .{ .node_and_extra = .{
   2086                             lhs, try c.addExtra(NodeSubRange{
   2087                                 .start = span.start,
   2088                                 .end = span.end,
   2089                             }),
   2090                         } },
   2091                     });
   2092                 },
   2093             };
   2094         },
   2095         .@"anytype" => unreachable, // Handled in renderParams
   2096     }
   2097 }
   2098 
   2099 fn renderRecord(c: *Context, node: Node) !NodeIndex {
   2100     const payload = @as(*Payload.Record, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   2101     if (payload.layout == .@"packed")
   2102         _ = try c.addToken(.keyword_packed, "packed")
   2103     else if (payload.layout == .@"extern")
   2104         _ = try c.addToken(.keyword_extern, "extern");
   2105     const kind_tok = if (node.tag() == .@"struct")
   2106         try c.addToken(.keyword_struct, "struct")
   2107     else
   2108         try c.addToken(.keyword_union, "union");
   2109 
   2110     _ = try c.addToken(.l_brace, "{");
   2111 
   2112     const num_vars = payload.variables.len;
   2113     const num_funcs = payload.functions.len;
   2114     const total_members = payload.fields.len + num_vars + num_funcs;
   2115     const members = try c.gpa.alloc(NodeIndex, total_members);
   2116     defer c.gpa.free(members);
   2117 
   2118     for (payload.fields, 0..) |field, i| {
   2119         const name_tok = try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true })});
   2120         _ = try c.addToken(.colon, ":");
   2121         const type_expr = try renderNode(c, field.type);
   2122 
   2123         const align_expr = if (field.alignment) |alignment| blk: {
   2124             _ = try c.addToken(.keyword_align, "align");
   2125             _ = try c.addToken(.l_paren, "(");
   2126             const align_expr = try c.addNode(.{
   2127                 .tag = .number_literal,
   2128                 .main_token = try c.addTokenFmt(.number_literal, "{d}", .{alignment}),
   2129                 .data = undefined,
   2130             });
   2131             _ = try c.addToken(.r_paren, ")");
   2132             break :blk align_expr;
   2133         } else null;
   2134 
   2135         const value_expr = if (field.default_value) |value| blk: {
   2136             _ = try c.addToken(.equal, "=");
   2137             break :blk try renderNode(c, value);
   2138         } else null;
   2139 
   2140         members[i] = try c.addNode(if (align_expr == null) .{
   2141             .tag = .container_field_init,
   2142             .main_token = name_tok,
   2143             .data = .{ .node_and_opt_node = .{
   2144                 type_expr,
   2145                 .fromOptional(value_expr),
   2146             } },
   2147         } else if (value_expr == null) .{
   2148             .tag = .container_field_align,
   2149             .main_token = name_tok,
   2150             .data = .{ .node_and_node = .{
   2151                 type_expr,
   2152                 align_expr.?,
   2153             } },
   2154         } else .{
   2155             .tag = .container_field,
   2156             .main_token = name_tok,
   2157             .data = .{ .node_and_extra = .{
   2158                 type_expr, try c.addExtra(std.zig.Ast.Node.ContainerField{
   2159                     .align_expr = align_expr.?,
   2160                     .value_expr = value_expr.?,
   2161                 }),
   2162             } },
   2163         });
   2164         _ = try c.addToken(.comma, ",");
   2165     }
   2166     for (payload.variables, 0..) |variable, i| {
   2167         members[payload.fields.len + i] = try renderNode(c, variable);
   2168     }
   2169     for (payload.functions, 0..) |function, i| {
   2170         members[payload.fields.len + num_vars + i] = try renderNode(c, function);
   2171     }
   2172     _ = try c.addToken(.r_brace, "}");
   2173 
   2174     if (total_members == 0) {
   2175         return c.addNode(.{
   2176             .tag = .container_decl_two,
   2177             .main_token = kind_tok,
   2178             .data = .{ .opt_node_and_opt_node = .{
   2179                 .none,
   2180                 .none,
   2181             } },
   2182         });
   2183     } else if (total_members <= 2) {
   2184         return c.addNode(.{
   2185             .tag = if (num_funcs == 0) .container_decl_two_trailing else .container_decl_two,
   2186             .main_token = kind_tok,
   2187             .data = .{ .opt_node_and_opt_node = .{
   2188                 if (members.len < 1) .none else members[0].toOptional(),
   2189                 if (members.len < 2) .none else members[1].toOptional(),
   2190             } },
   2191         });
   2192     } else {
   2193         const span = try c.listToSpan(members);
   2194         return c.addNode(.{
   2195             .tag = if (num_funcs == 0) .container_decl_trailing else .container_decl,
   2196             .main_token = kind_tok,
   2197             .data = .{ .extra_range = span },
   2198         });
   2199     }
   2200 }
   2201 
   2202 fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex {
   2203     return c.addNode(.{
   2204         .tag = .field_access,
   2205         .main_token = try c.addToken(.period, "."),
   2206         .data = .{ .node_and_token = .{
   2207             lhs,
   2208             try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true })}),
   2209         } },
   2210     });
   2211 }
   2212 
   2213 fn renderArrayInit(c: *Context, lhs: NodeIndex, inits: []const Node) !NodeIndex {
   2214     const l_brace = try c.addToken(.l_brace, "{");
   2215     var rendered = try c.gpa.alloc(NodeIndex, inits.len);
   2216     defer c.gpa.free(rendered);
   2217     for (inits, 0..) |init, i| {
   2218         rendered[i] = try renderNode(c, init);
   2219         _ = try c.addToken(.comma, ",");
   2220     }
   2221     _ = try c.addToken(.r_brace, "}");
   2222     switch (inits.len) {
   2223         0 => return c.addNode(.{
   2224             .tag = .struct_init_one,
   2225             .main_token = l_brace,
   2226             .data = .{ .node_and_opt_node = .{
   2227                 lhs,
   2228                 .none,
   2229             } },
   2230         }),
   2231         1 => return c.addNode(.{
   2232             .tag = .array_init_one_comma,
   2233             .main_token = l_brace,
   2234             .data = .{ .node_and_node = .{
   2235                 lhs,
   2236                 rendered[0],
   2237             } },
   2238         }),
   2239         else => {
   2240             const span = try c.listToSpan(rendered);
   2241             return c.addNode(.{
   2242                 .tag = .array_init_comma,
   2243                 .main_token = l_brace,
   2244                 .data = .{ .node_and_extra = .{
   2245                     lhs, try c.addExtra(NodeSubRange{
   2246                         .start = span.start,
   2247                         .end = span.end,
   2248                     }),
   2249                 } },
   2250             });
   2251         },
   2252     }
   2253 }
   2254 
   2255 fn renderArrayType(c: *Context, len: usize, elem_type: Node) !NodeIndex {
   2256     const l_bracket = try c.addToken(.l_bracket, "[");
   2257     const len_expr = try c.addNode(.{
   2258         .tag = .number_literal,
   2259         .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}),
   2260         .data = undefined,
   2261     });
   2262     _ = try c.addToken(.r_bracket, "]");
   2263     const elem_type_expr = try renderNode(c, elem_type);
   2264     return c.addNode(.{
   2265         .tag = .array_type,
   2266         .main_token = l_bracket,
   2267         .data = .{ .node_and_node = .{
   2268             len_expr,
   2269             elem_type_expr,
   2270         } },
   2271     });
   2272 }
   2273 
   2274 fn renderNullSentinelArrayType(c: *Context, len: usize, elem_type: Node) !NodeIndex {
   2275     const l_bracket = try c.addToken(.l_bracket, "[");
   2276     const len_expr = try c.addNode(.{
   2277         .tag = .number_literal,
   2278         .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}),
   2279         .data = undefined,
   2280     });
   2281     _ = try c.addToken(.colon, ":");
   2282 
   2283     const sentinel_expr = try c.addNode(.{
   2284         .tag = .number_literal,
   2285         .main_token = try c.addToken(.number_literal, "0"),
   2286         .data = undefined,
   2287     });
   2288 
   2289     _ = try c.addToken(.r_bracket, "]");
   2290     const elem_type_expr = try renderNode(c, elem_type);
   2291     return c.addNode(.{
   2292         .tag = .array_type_sentinel,
   2293         .main_token = l_bracket,
   2294         .data = .{ .node_and_extra = .{
   2295             len_expr,
   2296             try c.addExtra(std.zig.Ast.Node.ArrayTypeSentinel{
   2297                 .sentinel = sentinel_expr,
   2298                 .elem_type = elem_type_expr,
   2299             }),
   2300         } },
   2301     });
   2302 }
   2303 
   2304 fn addSemicolonIfNeeded(c: *Context, node: Node) !void {
   2305     switch (node.tag()) {
   2306         .warning => unreachable,
   2307         .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .static_local_var, .extern_local_var, .extern_local_fn, .mut_str => {},
   2308         .while_true => {
   2309             const payload = node.castTag(.while_true).?.data;
   2310             return addSemicolonIfNotBlock(c, payload);
   2311         },
   2312         .@"while" => {
   2313             const payload = node.castTag(.@"while").?.data;
   2314             return addSemicolonIfNotBlock(c, payload.body);
   2315         },
   2316         .@"if" => {
   2317             const payload = node.castTag(.@"if").?.data;
   2318             if (payload.@"else") |some|
   2319                 return addSemicolonIfNeeded(c, some);
   2320             return addSemicolonIfNotBlock(c, payload.then);
   2321         },
   2322         else => _ = try c.addToken(.semicolon, ";"),
   2323     }
   2324 }
   2325 
   2326 fn addSemicolonIfNotBlock(c: *Context, node: Node) !void {
   2327     switch (node.tag()) {
   2328         .block, .empty_block, .block_single => {},
   2329         else => _ = try c.addToken(.semicolon, ";"),
   2330     }
   2331 }
   2332 
   2333 fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
   2334     switch (node.tag()) {
   2335         .declaration => unreachable,
   2336         .null_literal,
   2337         .undefined_literal,
   2338         .true_literal,
   2339         .false_literal,
   2340         .return_void,
   2341         .zero_literal,
   2342         .one_literal,
   2343         .void_type,
   2344         .noreturn_type,
   2345         .@"anytype",
   2346         .div_trunc,
   2347         .signed_remainder,
   2348         .int_cast,
   2349         .const_cast,
   2350         .volatile_cast,
   2351         .as,
   2352         .truncate,
   2353         .bit_cast,
   2354         .float_cast,
   2355         .int_from_float,
   2356         .float_from_int,
   2357         .ptr_from_int,
   2358         .std_mem_zeroes,
   2359         .int_from_ptr,
   2360         .sizeof,
   2361         .alignof,
   2362         .typeof,
   2363         .typeinfo,
   2364         .vector,
   2365         .helpers_sizeof,
   2366         .helpers_cast,
   2367         .helpers_promoteIntLiteral,
   2368         .helpers_shuffle_vector_index,
   2369         .helpers_flexible_array_type,
   2370         .std_mem_zeroinit,
   2371         .integer_literal,
   2372         .float_literal,
   2373         .string_literal,
   2374         .string_slice,
   2375         .char_literal,
   2376         .enum_literal,
   2377         .identifier,
   2378         .fn_identifier,
   2379         .field_access,
   2380         .ptr_cast,
   2381         .type,
   2382         .array_access,
   2383         .align_cast,
   2384         .optional_type,
   2385         .c_pointer,
   2386         .single_pointer,
   2387         .unwrap,
   2388         .deref,
   2389         .not,
   2390         .negate,
   2391         .negate_wrap,
   2392         .bit_not,
   2393         .func,
   2394         .call,
   2395         .array_type,
   2396         .null_sentinel_array_type,
   2397         .int_from_bool,
   2398         .div_exact,
   2399         .offset_of,
   2400         .shuffle,
   2401         .builtin_extern,
   2402         .static_local_var,
   2403         .extern_local_var,
   2404         .extern_local_fn,
   2405         .mut_str,
   2406         .macro_arithmetic,
   2407         => {
   2408             // no grouping needed
   2409             return renderNode(c, node);
   2410         },
   2411 
   2412         .opaque_literal,
   2413         .empty_array,
   2414         .block_single,
   2415         .add,
   2416         .add_wrap,
   2417         .sub,
   2418         .sub_wrap,
   2419         .mul,
   2420         .mul_wrap,
   2421         .div,
   2422         .shl,
   2423         .shr,
   2424         .mod,
   2425         .@"and",
   2426         .@"or",
   2427         .less_than,
   2428         .less_than_equal,
   2429         .greater_than,
   2430         .greater_than_equal,
   2431         .equal,
   2432         .not_equal,
   2433         .bit_and,
   2434         .bit_or,
   2435         .bit_xor,
   2436         .empty_block,
   2437         .array_cat,
   2438         .array_filler,
   2439         .@"if",
   2440         .@"struct",
   2441         .@"union",
   2442         .array_init,
   2443         .vector_zero_init,
   2444         .tuple,
   2445         .container_init,
   2446         .container_init_dot,
   2447         .block,
   2448         .address_of,
   2449         => return c.addNode(.{
   2450             .tag = .grouped_expression,
   2451             .main_token = try c.addToken(.l_paren, "("),
   2452             .data = .{ .node_and_token = .{
   2453                 try renderNode(c, node),
   2454                 try c.addToken(.r_paren, ")"),
   2455             } },
   2456         }),
   2457         .ellipsis3,
   2458         .switch_prong,
   2459         .warning,
   2460         .var_decl,
   2461         .fail_decl,
   2462         .arg_redecl,
   2463         .alias,
   2464         .var_simple,
   2465         .pub_var_simple,
   2466         .enum_constant,
   2467         .@"while",
   2468         .@"switch",
   2469         .@"break",
   2470         .break_val,
   2471         .pub_inline_fn,
   2472         .discard,
   2473         .@"continue",
   2474         .@"return",
   2475         .@"comptime",
   2476         .@"defer",
   2477         .asm_simple,
   2478         .while_true,
   2479         .if_not_break,
   2480         .switch_else,
   2481         .add_assign,
   2482         .add_wrap_assign,
   2483         .sub_assign,
   2484         .sub_wrap_assign,
   2485         .mul_assign,
   2486         .mul_wrap_assign,
   2487         .div_assign,
   2488         .shl_assign,
   2489         .shr_assign,
   2490         .mod_assign,
   2491         .bit_and_assign,
   2492         .bit_or_assign,
   2493         .bit_xor_assign,
   2494         .assign,
   2495         .helpers_macro,
   2496         .import_c_builtin,
   2497         => {
   2498             // these should never appear in places where grouping might be needed.
   2499             unreachable;
   2500         },
   2501     }
   2502 }
   2503 
   2504 fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
   2505     const payload = @as(*Payload.UnOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   2506     return c.addNode(.{
   2507         .tag = tag,
   2508         .main_token = try c.addToken(tok_tag, bytes),
   2509         .data = .{ .node = try renderNodeGrouped(c, payload) },
   2510     });
   2511 }
   2512 
   2513 fn renderBinOpGrouped(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
   2514     const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   2515     const lhs = try renderNodeGrouped(c, payload.lhs);
   2516     return c.addNode(.{
   2517         .tag = tag,
   2518         .main_token = try c.addToken(tok_tag, bytes),
   2519         .data = .{ .node_and_node = .{
   2520             lhs,
   2521             try renderNodeGrouped(c, payload.rhs),
   2522         } },
   2523     });
   2524 }
   2525 
   2526 fn renderBinOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
   2527     const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
   2528     const lhs = try renderNode(c, payload.lhs);
   2529     return c.addNode(.{
   2530         .tag = tag,
   2531         .main_token = try c.addToken(tok_tag, bytes),
   2532         .data = .{ .node_and_node = .{
   2533             lhs,
   2534             try renderNode(c, payload.rhs),
   2535         } },
   2536     });
   2537 }
   2538 
   2539 fn renderStdImport(c: *Context, parts: []const []const u8) !NodeIndex {
   2540     const import_tok = try c.addToken(.builtin, "@import");
   2541     _ = try c.addToken(.l_paren, "(");
   2542     const std_tok = try c.addToken(.string_literal, "\"std\"");
   2543     const std_node = try c.addNode(.{
   2544         .tag = .string_literal,
   2545         .main_token = std_tok,
   2546         .data = undefined,
   2547     });
   2548     _ = try c.addToken(.r_paren, ")");
   2549 
   2550     const import_node = try c.addNode(.{
   2551         .tag = .builtin_call_two,
   2552         .main_token = import_tok,
   2553         .data = .{ .opt_node_and_opt_node = .{ std_node.toOptional(), .none } },
   2554     });
   2555 
   2556     var access_chain = import_node;
   2557     for (parts) |part| {
   2558         access_chain = try renderFieldAccess(c, access_chain, part);
   2559     }
   2560     return access_chain;
   2561 }
   2562 
   2563 fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex {
   2564     const lparen = try c.addToken(.l_paren, "(");
   2565     const res = switch (args.len) {
   2566         0 => try c.addNode(.{
   2567             .tag = .call_one,
   2568             .main_token = lparen,
   2569             .data = .{ .node_and_opt_node = .{ lhs, .none } },
   2570         }),
   2571         1 => blk: {
   2572             const arg = try renderNode(c, args[0]);
   2573             break :blk try c.addNode(.{
   2574                 .tag = .call_one,
   2575                 .main_token = lparen,
   2576                 .data = .{ .node_and_opt_node = .{ lhs, arg.toOptional() } },
   2577             });
   2578         },
   2579         else => blk: {
   2580             var rendered = try c.gpa.alloc(NodeIndex, args.len);
   2581             defer c.gpa.free(rendered);
   2582 
   2583             for (args, 0..) |arg, i| {
   2584                 if (i != 0) _ = try c.addToken(.comma, ",");
   2585                 rendered[i] = try renderNode(c, arg);
   2586             }
   2587             const span = try c.listToSpan(rendered);
   2588             break :blk try c.addNode(.{
   2589                 .tag = .call,
   2590                 .main_token = lparen,
   2591                 .data = .{ .node_and_extra = .{
   2592                     lhs,
   2593                     try c.addExtra(NodeSubRange{ .start = span.start, .end = span.end }),
   2594                 } },
   2595             });
   2596         },
   2597     };
   2598     _ = try c.addToken(.r_paren, ")");
   2599     return res;
   2600 }
   2601 
   2602 fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !NodeIndex {
   2603     const builtin_tok = try c.addToken(.builtin, builtin);
   2604     _ = try c.addToken(.l_paren, "(");
   2605     var arg_1: NodeIndex = undefined;
   2606     var arg_2: NodeIndex = undefined;
   2607     var arg_3: NodeIndex = undefined;
   2608     var arg_4: NodeIndex = undefined;
   2609     switch (args.len) {
   2610         0 => {},
   2611         1 => {
   2612             arg_1 = try renderNode(c, args[0]);
   2613         },
   2614         2 => {
   2615             arg_1 = try renderNode(c, args[0]);
   2616             _ = try c.addToken(.comma, ",");
   2617             arg_2 = try renderNode(c, args[1]);
   2618         },
   2619         4 => {
   2620             arg_1 = try renderNode(c, args[0]);
   2621             _ = try c.addToken(.comma, ",");
   2622             arg_2 = try renderNode(c, args[1]);
   2623             _ = try c.addToken(.comma, ",");
   2624             arg_3 = try renderNode(c, args[2]);
   2625             _ = try c.addToken(.comma, ",");
   2626             arg_4 = try renderNode(c, args[3]);
   2627         },
   2628         else => unreachable, // expand this function as needed.
   2629     }
   2630 
   2631     _ = try c.addToken(.r_paren, ")");
   2632     if (args.len <= 2) {
   2633         return c.addNode(.{
   2634             .tag = .builtin_call_two,
   2635             .main_token = builtin_tok,
   2636             .data = .{ .opt_node_and_opt_node = .{
   2637                 if (args.len < 1) .none else arg_1.toOptional(),
   2638                 if (args.len < 2) .none else arg_2.toOptional(),
   2639             } },
   2640         });
   2641     } else {
   2642         std.debug.assert(args.len == 4);
   2643 
   2644         const params = try c.listToSpan(&.{ arg_1, arg_2, arg_3, arg_4 });
   2645         return c.addNode(.{
   2646             .tag = .builtin_call,
   2647             .main_token = builtin_tok,
   2648             .data = .{ .extra_range = params },
   2649         });
   2650     }
   2651 }
   2652 
   2653 fn renderVar(c: *Context, node: Node) !NodeIndex {
   2654     const payload = node.castTag(.var_decl).?.data;
   2655     if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub");
   2656     if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern");
   2657     if (payload.is_export) _ = try c.addToken(.keyword_export, "export");
   2658     if (payload.is_threadlocal) _ = try c.addToken(.keyword_threadlocal, "threadlocal");
   2659     const mut_tok = if (payload.is_const)
   2660         try c.addToken(.keyword_const, "const")
   2661     else
   2662         try c.addToken(.keyword_var, "var");
   2663     _ = try c.addIdentifier(payload.name);
   2664     _ = try c.addToken(.colon, ":");
   2665     const type_node = try renderNode(c, payload.type);
   2666 
   2667     const align_node = if (payload.alignment) |some| blk: {
   2668         _ = try c.addToken(.keyword_align, "align");
   2669         _ = try c.addToken(.l_paren, "(");
   2670         const res = try c.addNode(.{
   2671             .tag = .number_literal,
   2672             .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}),
   2673             .data = undefined,
   2674         });
   2675         _ = try c.addToken(.r_paren, ")");
   2676         break :blk res;
   2677     } else null;
   2678 
   2679     const section_node = if (payload.linksection_string) |some| blk: {
   2680         _ = try c.addToken(.keyword_linksection, "linksection");
   2681         _ = try c.addToken(.l_paren, "(");
   2682         const res = try c.addNode(.{
   2683             .tag = .string_literal,
   2684             .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}),
   2685             .data = undefined,
   2686         });
   2687         _ = try c.addToken(.r_paren, ")");
   2688         break :blk res;
   2689     } else null;
   2690 
   2691     const init_node = if (payload.init) |some| blk: {
   2692         _ = try c.addToken(.equal, "=");
   2693         break :blk try renderNode(c, some);
   2694     } else null;
   2695     _ = try c.addToken(.semicolon, ";");
   2696 
   2697     if (section_node == null) {
   2698         if (align_node == null) {
   2699             return c.addNode(.{
   2700                 .tag = .simple_var_decl,
   2701                 .main_token = mut_tok,
   2702                 .data = .{ .opt_node_and_opt_node = .{
   2703                     type_node.toOptional(),
   2704                     .fromOptional(init_node),
   2705                 } },
   2706             });
   2707         } else {
   2708             return c.addNode(.{
   2709                 .tag = .local_var_decl,
   2710                 .main_token = mut_tok,
   2711                 .data = .{ .extra_and_opt_node = .{
   2712                     try c.addExtra(std.zig.Ast.Node.LocalVarDecl{
   2713                         .type_node = type_node,
   2714                         .align_node = align_node.?,
   2715                     }),
   2716                     .fromOptional(init_node),
   2717                 } },
   2718             });
   2719         }
   2720     } else {
   2721         return c.addNode(.{
   2722             .tag = .global_var_decl,
   2723             .main_token = mut_tok,
   2724             .data = .{ .extra_and_opt_node = .{
   2725                 try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{
   2726                     .type_node = type_node.toOptional(),
   2727                     .align_node = .fromOptional(align_node),
   2728                     .section_node = .fromOptional(section_node),
   2729                     .addrspace_node = .none,
   2730                 }),
   2731                 .fromOptional(init_node),
   2732             } },
   2733         });
   2734     }
   2735 }
   2736 
   2737 fn renderFunc(c: *Context, node: Node) !NodeIndex {
   2738     const payload = node.castTag(.func).?.data;
   2739     if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub");
   2740     if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern");
   2741     if (payload.is_export) _ = try c.addToken(.keyword_export, "export");
   2742     if (payload.is_inline) _ = try c.addToken(.keyword_inline, "inline");
   2743     const fn_token = try c.addToken(.keyword_fn, "fn");
   2744     if (payload.name) |some| _ = try c.addIdentifier(some);
   2745 
   2746     const params = try renderParams(c, payload.params, payload.is_var_args);
   2747     defer params.deinit();
   2748     var span: NodeSubRange = undefined;
   2749     if (params.items.len > 1) span = try c.listToSpan(params.items);
   2750 
   2751     const align_expr = if (payload.alignment) |some| blk: {
   2752         _ = try c.addToken(.keyword_align, "align");
   2753         _ = try c.addToken(.l_paren, "(");
   2754         const res = try c.addNode(.{
   2755             .tag = .number_literal,
   2756             .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}),
   2757             .data = undefined,
   2758         });
   2759         _ = try c.addToken(.r_paren, ")");
   2760         break :blk res;
   2761     } else null;
   2762 
   2763     const section_expr = if (payload.linksection_string) |some| blk: {
   2764         _ = try c.addToken(.keyword_linksection, "linksection");
   2765         _ = try c.addToken(.l_paren, "(");
   2766         const res = try c.addNode(.{
   2767             .tag = .string_literal,
   2768             .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}),
   2769             .data = undefined,
   2770         });
   2771         _ = try c.addToken(.r_paren, ")");
   2772         break :blk res;
   2773     } else null;
   2774 
   2775     const callconv_expr = if (payload.explicit_callconv) |some| blk: {
   2776         _ = try c.addToken(.keyword_callconv, "callconv");
   2777         _ = try c.addToken(.l_paren, "(");
   2778         const cc_node = switch (some) {
   2779             .c => cc_node: {
   2780                 _ = try c.addToken(.period, ".");
   2781                 break :cc_node try c.addNode(.{
   2782                     .tag = .enum_literal,
   2783                     .main_token = try c.addToken(.identifier, "c"),
   2784                     .data = undefined,
   2785                 });
   2786             },
   2787             .x86_64_sysv,
   2788             .x86_64_win,
   2789             .x86_stdcall,
   2790             .x86_fastcall,
   2791             .x86_thiscall,
   2792             .x86_vectorcall,
   2793             .aarch64_vfabi,
   2794             .arm_aapcs,
   2795             .arm_aapcs_vfp,
   2796             .m68k_rtd,
   2797             => cc_node: {
   2798                 // .{ .foo = .{} }
   2799                 _ = try c.addToken(.period, ".");
   2800                 const outer_lbrace = try c.addToken(.l_brace, "{");
   2801                 _ = try c.addToken(.period, ".");
   2802                 _ = try c.addToken(.identifier, @tagName(some));
   2803                 _ = try c.addToken(.equal, "=");
   2804                 _ = try c.addToken(.period, ".");
   2805                 const inner_lbrace = try c.addToken(.l_brace, "{");
   2806                 _ = try c.addToken(.r_brace, "}");
   2807                 _ = try c.addToken(.r_brace, "}");
   2808                 const inner_node = try c.addNode(.{
   2809                     .tag = .struct_init_dot_two,
   2810                     .main_token = inner_lbrace,
   2811                     .data = .{ .opt_node_and_opt_node = .{
   2812                         .none,
   2813                         .none,
   2814                     } },
   2815                 });
   2816                 break :cc_node try c.addNode(.{
   2817                     .tag = .struct_init_dot_two,
   2818                     .main_token = outer_lbrace,
   2819                     .data = .{ .opt_node_and_opt_node = .{
   2820                         inner_node.toOptional(),
   2821                         .none,
   2822                     } },
   2823                 });
   2824             },
   2825         };
   2826         _ = try c.addToken(.r_paren, ")");
   2827         break :blk cc_node;
   2828     } else null;
   2829 
   2830     const return_type_expr = try renderNode(c, payload.return_type);
   2831 
   2832     const fn_proto = try blk: {
   2833         if (align_expr == null and section_expr == null and callconv_expr == null) {
   2834             if (params.items.len < 2)
   2835                 break :blk c.addNode(.{
   2836                     .tag = .fn_proto_simple,
   2837                     .main_token = fn_token,
   2838                     .data = .{ .opt_node_and_opt_node = .{
   2839                         if (params.items.len == 0) .none else params.items[0].toOptional(),
   2840                         return_type_expr.toOptional(),
   2841                     } },
   2842                 })
   2843             else
   2844                 break :blk c.addNode(.{
   2845                     .tag = .fn_proto_multi,
   2846                     .main_token = fn_token,
   2847                     .data = .{ .extra_and_opt_node = .{
   2848                         try c.addExtra(NodeSubRange{
   2849                             .start = span.start,
   2850                             .end = span.end,
   2851                         }),
   2852                         return_type_expr.toOptional(),
   2853                     } },
   2854                 });
   2855         }
   2856         if (params.items.len < 2)
   2857             break :blk c.addNode(.{
   2858                 .tag = .fn_proto_one,
   2859                 .main_token = fn_token,
   2860                 .data = .{
   2861                     .extra_and_opt_node = .{
   2862                         try c.addExtra(std.zig.Ast.Node.FnProtoOne{
   2863                             .param = if (params.items.len == 0) .none else params.items[0].toOptional(),
   2864                             .align_expr = .fromOptional(align_expr),
   2865                             .addrspace_expr = .none, // TODO
   2866                             .section_expr = .fromOptional(section_expr),
   2867                             .callconv_expr = .fromOptional(callconv_expr),
   2868                         }),
   2869                         return_type_expr.toOptional(),
   2870                     },
   2871                 },
   2872             })
   2873         else
   2874             break :blk c.addNode(.{
   2875                 .tag = .fn_proto,
   2876                 .main_token = fn_token,
   2877                 .data = .{
   2878                     .extra_and_opt_node = .{
   2879                         try c.addExtra(std.zig.Ast.Node.FnProto{
   2880                             .params_start = span.start,
   2881                             .params_end = span.end,
   2882                             .align_expr = .fromOptional(align_expr),
   2883                             .addrspace_expr = .none, // TODO
   2884                             .section_expr = .fromOptional(section_expr),
   2885                             .callconv_expr = .fromOptional(callconv_expr),
   2886                         }),
   2887                         return_type_expr.toOptional(),
   2888                     },
   2889                 },
   2890             });
   2891     };
   2892 
   2893     const payload_body = payload.body orelse {
   2894         if (payload.is_extern) {
   2895             _ = try c.addToken(.semicolon, ";");
   2896         }
   2897         return fn_proto;
   2898     };
   2899     const body = try renderNode(c, payload_body);
   2900     return c.addNode(.{
   2901         .tag = .fn_decl,
   2902         .main_token = fn_token,
   2903         .data = .{ .node_and_node = .{
   2904             fn_proto,
   2905             body,
   2906         } },
   2907     });
   2908 }
   2909 
   2910 fn renderMacroFunc(c: *Context, node: Node) !NodeIndex {
   2911     const payload = node.castTag(.pub_inline_fn).?.data;
   2912     _ = try c.addToken(.keyword_pub, "pub");
   2913     _ = try c.addToken(.keyword_inline, "inline");
   2914     const fn_token = try c.addToken(.keyword_fn, "fn");
   2915     _ = try c.addIdentifier(payload.name);
   2916 
   2917     const params = try renderParams(c, payload.params, false);
   2918     defer params.deinit();
   2919 
   2920     const return_type_expr = try renderNodeGrouped(c, payload.return_type);
   2921 
   2922     const fn_proto = blk: {
   2923         if (params.items.len < 2) {
   2924             break :blk try c.addNode(.{
   2925                 .tag = .fn_proto_simple,
   2926                 .main_token = fn_token,
   2927                 .data = .{ .opt_node_and_opt_node = .{
   2928                     if (params.items.len == 0) .none else params.items[0].toOptional(),
   2929                     return_type_expr.toOptional(),
   2930                 } },
   2931             });
   2932         } else {
   2933             const span: NodeSubRange = try c.listToSpan(params.items);
   2934             break :blk try c.addNode(.{
   2935                 .tag = .fn_proto_multi,
   2936                 .main_token = fn_token,
   2937                 .data = .{ .extra_and_opt_node = .{
   2938                     try c.addExtra(std.zig.Ast.Node.SubRange{
   2939                         .start = span.start,
   2940                         .end = span.end,
   2941                     }),
   2942                     return_type_expr.toOptional(),
   2943                 } },
   2944             });
   2945         }
   2946     };
   2947     return c.addNode(.{
   2948         .tag = .fn_decl,
   2949         .main_token = fn_token,
   2950         .data = .{ .node_and_node = .{
   2951             fn_proto,
   2952             try renderNode(c, payload.body),
   2953         } },
   2954     });
   2955 }
   2956 
   2957 fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.array_list.Managed(NodeIndex) {
   2958     _ = try c.addToken(.l_paren, "(");
   2959     var rendered = try std.array_list.Managed(NodeIndex).initCapacity(c.gpa, params.len);
   2960     errdefer rendered.deinit();
   2961 
   2962     for (params, 0..) |param, i| {
   2963         if (i != 0) _ = try c.addToken(.comma, ",");
   2964         if (param.is_noalias) _ = try c.addToken(.keyword_noalias, "noalias");
   2965         if (param.name) |some| {
   2966             _ = try c.addIdentifier(some);
   2967             _ = try c.addToken(.colon, ":");
   2968         }
   2969         if (param.type.tag() == .@"anytype") {
   2970             _ = try c.addToken(.keyword_anytype, "anytype");
   2971             continue;
   2972         }
   2973         rendered.appendAssumeCapacity(try renderNode(c, param.type));
   2974     }
   2975     if (is_var_args) {
   2976         if (params.len != 0) _ = try c.addToken(.comma, ",");
   2977         _ = try c.addToken(.ellipsis3, "...");
   2978     }
   2979     _ = try c.addToken(.r_paren, ")");
   2980 
   2981     return rendered;
   2982 }