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