Commit 1a83b29bea

Andrew Kelley <andrew@ziglang.org>
2021-02-03 05:05:53
zig fmt: implement if, call, field access, assignment
1 parent 0c6b98b
Changed files (4)
lib/std/zig/ast.zig
@@ -16,11 +16,7 @@ pub const TokenList = std.MultiArrayList(struct {
     tag: Token.Tag,
     start: ByteOffset,
 });
-pub const NodeList = std.MultiArrayList(struct {
-    tag: Node.Tag,
-    main_token: TokenIndex,
-    data: Node.Data,
-});
+pub const NodeList = std.MultiArrayList(Node);
 
 pub const Tree = struct {
     /// Reference to externally-owned data.
@@ -76,6 +72,16 @@ pub const Tree = struct {
         return loc;
     }
 
+    pub fn extraData(tree: Tree, index: usize, comptime T: type) T {
+        const fields = std.meta.fields(T);
+        var result: T = undefined;
+        inline for (fields) |field, i| {
+            comptime assert(field.field_type == Node.Index);
+            @field(result, field.name) = tree.extra_data[index + i];
+        }
+        return result;
+    }
+
     pub fn renderError(tree: Tree, parse_error: Error, stream: anytype) !void {
         const tokens = tree.tokens.items(.tag);
         switch (parse_error) {
@@ -189,7 +195,8 @@ pub const Tree = struct {
         const tags = tree.nodes.items(.tag);
         const datas = tree.nodes.items(.data);
         const main_tokens = tree.nodes.items(.main_token);
-        switch (tags[node]) {
+        var n = node;
+        while (true) switch (tags[n]) {
             .Root => return 0,
 
             .UsingNamespace,
@@ -210,20 +217,25 @@ pub const Tree = struct {
             .StructInitDot,
             .Switch,
             .IfSimple,
-            .IfSimpleOptional,
             .If,
-            .IfOptional,
-            .IfError,
             .Suspend,
             .Resume,
             .Continue,
             .Break,
             .Return,
             .AnyFrameType,
-            .OneToken,
             .Identifier,
+            .AnyFrameLiteral,
+            .CharLiteral,
+            .IntegerLiteral,
+            .FloatLiteral,
+            .FalseLiteral,
+            .TrueLiteral,
+            .NullLiteral,
+            .UndefinedLiteral,
+            .UnreachableLiteral,
             .EnumLiteral,
-            .MultilineStringLiteral,
+            .StringLiteral,
             .GroupedExpression,
             .BuiltinCallTwo,
             .BuiltinCall,
@@ -234,7 +246,7 @@ pub const Tree = struct {
             .Block,
             .AsmSimple,
             .Asm,
-            => return main_tokens[node],
+            => return main_tokens[n],
 
             .Catch,
             .FieldAccess,
@@ -290,7 +302,7 @@ pub const Tree = struct {
             .SwitchCaseOne,
             .SwitchRange,
             .FnDecl,
-            => return tree.firstToken(datas[node].lhs),
+            => n = datas[n].lhs,
 
             .GlobalVarDecl,
             .LocalVarDecl,
@@ -305,12 +317,8 @@ pub const Tree = struct {
             .StructInit,
             .SwitchCaseMulti,
             .WhileSimple,
-            .WhileSimpleOptional,
             .WhileCont,
-            .WhileContOptional,
             .While,
-            .WhileOptional,
-            .WhileError,
             .ForSimple,
             .For,
             .FnProtoSimple,
@@ -329,19 +337,19 @@ pub const Tree = struct {
             .ErrorValue,
             .ErrorUnion,
             => @panic("TODO finish implementing firstToken"),
-        }
+        };
     }
 
     pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
         const tags = tree.nodes.items(.tag);
         const datas = tree.nodes.items(.data);
         const main_tokens = tree.nodes.items(.main_token);
-        switch (tags[node]) {
-            .Root,
+        var n = node;
+        var end_offset: TokenIndex = 0;
+        while (true) switch (tags[n]) {
+            .Root => return @intCast(TokenIndex, tree.tokens.len - 1),
+
             .UsingNamespace,
-            .TestDecl,
-            .ErrDefer,
-            .Defer,
             .BoolNot,
             .Negation,
             .BitNot,
@@ -350,39 +358,16 @@ pub const Tree = struct {
             .Try,
             .Await,
             .OptionalType,
-            .ArrayInitDotTwo,
-            .ArrayInitDot,
-            .StructInitDotTwo,
-            .StructInitDot,
-            .Switch,
-            .IfSimple,
-            .IfSimpleOptional,
-            .If,
-            .IfOptional,
-            .IfError,
             .Suspend,
             .Resume,
-            .Continue,
             .Break,
             .Return,
-            .AnyFrameType,
-            .OneToken,
-            .Identifier,
-            .EnumLiteral,
-            .MultilineStringLiteral,
-            .GroupedExpression,
-            .BuiltinCallTwo,
-            .BuiltinCall,
-            .ErrorSetDecl,
-            .AnyType,
-            .Comptime,
-            .Nosuspend,
-            .Block,
-            .AsmSimple,
-            .Asm,
+            => n = datas[n].lhs,
+
+            .TestDecl,
+            .ErrDefer,
+            .Defer,
             .Catch,
-            .FieldAccess,
-            .UnwrapOptional,
             .EqualEqual,
             .BangEqual,
             .LessThan,
@@ -422,6 +407,63 @@ pub const Tree = struct {
             .OrElse,
             .BoolAnd,
             .BoolOr,
+            .AnyFrameType,
+            .ErrorUnion,
+            .Comptime,
+            .Nosuspend,
+            .IfSimple,
+            .WhileSimple,
+            => n = datas[n].rhs,
+
+            .FieldAccess,
+            .UnwrapOptional,
+            .GroupedExpression,
+            .StringLiteral,
+            => return datas[n].rhs + end_offset,
+
+            .AnyType,
+            .AnyFrameLiteral,
+            .CharLiteral,
+            .IntegerLiteral,
+            .FloatLiteral,
+            .FalseLiteral,
+            .TrueLiteral,
+            .NullLiteral,
+            .UndefinedLiteral,
+            .UnreachableLiteral,
+            => return main_tokens[n] + end_offset,
+
+            .Call => {
+                end_offset += 1; // for the `)`
+                const params = tree.extraData(datas[n].rhs, Node.SubRange);
+                if (params.end - params.start == 0) {
+                    return main_tokens[n] + end_offset;
+                }
+                n = tree.extra_data[params.end - 1]; // last parameter
+            },
+            .CallOne => {
+                end_offset += 1; // for the `)`
+                if (datas[n].rhs == 0) {
+                    return main_tokens[n] + end_offset;
+                }
+                n = datas[n].rhs;
+            },
+
+            .ArrayInitDotTwo,
+            .ArrayInitDot,
+            .StructInitDotTwo,
+            .StructInitDot,
+            .Switch,
+            .If,
+            .Continue,
+            .Identifier,
+            .EnumLiteral,
+            .BuiltinCallTwo,
+            .BuiltinCall,
+            .ErrorSetDecl,
+            .Block,
+            .AsmSimple,
+            .Asm,
             .SliceOpen,
             .Slice,
             .Deref,
@@ -429,8 +471,6 @@ pub const Tree = struct {
             .ArrayInitOne,
             .ArrayInit,
             .StructInitOne,
-            .CallOne,
-            .Call,
             .SwitchCaseOne,
             .SwitchRange,
             .FnDecl,
@@ -446,13 +486,8 @@ pub const Tree = struct {
             .SliceType,
             .StructInit,
             .SwitchCaseMulti,
-            .WhileSimple,
-            .WhileSimpleOptional,
             .WhileCont,
-            .WhileContOptional,
             .While,
-            .WhileOptional,
-            .WhileError,
             .ForSimple,
             .For,
             .FnProtoSimple,
@@ -469,12 +504,178 @@ pub const Tree = struct {
             .AsmOutput,
             .AsmInput,
             .ErrorValue,
-            .ErrorUnion,
             => @panic("TODO finish implementing lastToken"),
+        };
+    }
+
+    pub fn tokensOnSameLine(tree: Tree, token1: TokenIndex, token2: TokenIndex) bool {
+        const token_starts = tree.tokens.items(.start);
+        const source = tree.source[token_starts[token1]..token_starts[token2]];
+        return mem.indexOfScalar(u8, source, '\n') == null;
+    }
+
+    pub fn globalVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+        assert(tree.nodes.items(.tag)[node] == .GlobalVarDecl);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.lhs, Node.GlobalVarDecl);
+        return tree.fullVarDecl(.{
+            .type_node = extra.type_node,
+            .align_node = extra.align_node,
+            .section_node = extra.section_node,
+            .init_node = data.rhs,
+            .mut_token = tree.nodes.items(.main_token)[node],
+        });
+    }
+
+    pub fn localVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+        assert(tree.nodes.items(.tag)[node] == .LocalVarDecl);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.lhs, Node.LocalVarDecl);
+        return tree.fullVarDecl(.{
+            .type_node = extra.type_node,
+            .align_node = extra.align_node,
+            .section_node = 0,
+            .init_node = data.rhs,
+            .mut_token = tree.nodes.items(.main_token)[node],
+        });
+    }
+
+    pub fn simpleVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+        assert(tree.nodes.items(.tag)[node] == .SimpleVarDecl);
+        const data = tree.nodes.items(.data)[node];
+        return tree.fullVarDecl(.{
+            .type_node = data.lhs,
+            .align_node = 0,
+            .section_node = 0,
+            .init_node = data.rhs,
+            .mut_token = tree.nodes.items(.main_token)[node],
+        });
+    }
+
+    pub fn alignedVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+        assert(tree.nodes.items(.tag)[node] == .AlignedVarDecl);
+        const data = tree.nodes.items(.data)[node];
+        return tree.fullVarDecl(.{
+            .type_node = 0,
+            .align_node = data.lhs,
+            .section_node = 0,
+            .init_node = data.rhs,
+            .mut_token = tree.nodes.items(.main_token)[node],
+        });
+    }
+
+    pub fn ifSimple(tree: Tree, node: Node.Index) Full.If {
+        assert(tree.nodes.items(.tag)[node] == .IfSimple);
+        const data = tree.nodes.items(.data)[node];
+        return tree.fullIf(.{
+            .cond_expr = data.lhs,
+            .then_expr = data.rhs,
+            .else_expr = 0,
+            .if_token = tree.nodes.items(.main_token)[node],
+        });
+    }
+
+    pub fn ifFull(tree: Tree, node: Node.Index) Full.If {
+        assert(tree.nodes.items(.tag)[node] == .If);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.rhs, Node.If);
+        return tree.fullIf(.{
+            .cond_expr = data.lhs,
+            .then_expr = extra.then_expr,
+            .else_expr = extra.else_expr,
+            .if_token = tree.nodes.items(.main_token)[node],
+        });
+    }
+
+    fn fullVarDecl(tree: Tree, info: Full.VarDecl.Ast) Full.VarDecl {
+        const token_tags = tree.tokens.items(.tag);
+        var result: Full.VarDecl = .{
+            .ast = info,
+            .visib_token = null,
+            .extern_export_token = null,
+            .lib_name = null,
+            .threadlocal_token = null,
+            .comptime_token = null,
+        };
+        var i = info.mut_token;
+        while (i > 0) {
+            i -= 1;
+            switch (token_tags[i]) {
+                .Keyword_extern, .Keyword_export => result.extern_export_token = i,
+                .Keyword_comptime => result.comptime_token = i,
+                .Keyword_pub => result.visib_token = i,
+                .Keyword_threadlocal => result.threadlocal_token = i,
+                .StringLiteral => result.lib_name = i,
+                else => break,
+            }
+        }
+        return result;
+    }
+
+    fn fullIf(tree: Tree, info: Full.If.Ast) Full.If {
+        const token_tags = tree.tokens.items(.tag);
+        var result: Full.If = .{
+            .ast = info,
+            .payload_token = null,
+            .error_token = null,
+            .else_token = undefined,
+        };
+        // if (cond_expr) |x|
+        //              ^ ^
+        const payload_pipe = tree.lastToken(info.cond_expr) + 2;
+        if (token_tags[payload_pipe] == .Pipe) {
+            result.payload_token = payload_pipe + 1;
+        }
+        if (info.else_expr != 0) {
+            // then_expr else |x|
+            //           ^    ^
+            result.else_token = tree.lastToken(info.then_expr) + 1;
+            if (token_tags[result.else_token + 1] == .Pipe) {
+                result.error_token = result.else_token + 2;
+            }
         }
+        return result;
     }
 };
 
+/// Fully assembled AST node information.
+pub const Full = struct {
+    pub const VarDecl = struct {
+        visib_token: ?TokenIndex,
+        extern_export_token: ?TokenIndex,
+        lib_name: ?TokenIndex,
+        threadlocal_token: ?TokenIndex,
+        comptime_token: ?TokenIndex,
+        ast: Ast,
+
+        pub const Ast = struct {
+            mut_token: TokenIndex,
+            type_node: Node.Index,
+            align_node: Node.Index,
+            section_node: Node.Index,
+            init_node: Node.Index,
+        };
+    };
+
+    pub const If = struct {
+        // Points to the first token after the `|`. Will either be an identifier or
+        // a `*` (with an identifier immediately after it).
+        payload_token: ?TokenIndex,
+        // Points to the identifier after the `|`.
+        error_token: ?TokenIndex,
+        // Populated only if else_expr != 0.
+        else_token: TokenIndex,
+        ast: Ast,
+
+        pub const Ast = struct {
+            if_token: TokenIndex,
+            cond_expr: Node.Index,
+            then_expr: Node.Index,
+            else_expr: Node.Index,
+        };
+    };
+};
+
 pub const Error = union(enum) {
     InvalidToken: InvalidToken,
     ExpectedContainerMembers: ExpectedContainerMembers,
@@ -706,7 +907,9 @@ pub const Error = union(enum) {
 };
 
 pub const Node = struct {
-    index: Index,
+    tag: Tag,
+    main_token: TokenIndex,
+    data: Data,
 
     pub const Index = u32;
 
@@ -718,22 +921,26 @@ pub const Node = struct {
     pub const Tag = enum {
         /// sub_list[lhs...rhs]
         Root,
-        /// lhs is the sub-expression. rhs is unused.
+        /// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`.
         UsingNamespace,
         /// lhs is test name token (must be string literal), if any.
         /// rhs is the body node.
         TestDecl,
         /// lhs is the index into extra_data.
         /// rhs is the initialization expression, if any.
+        /// main_token is `var` or `const`.
         GlobalVarDecl,
         /// `var a: x align(y) = rhs`
-        /// lhs is the index into local_var_decl_list.
+        /// lhs is the index into extra_data.
+        /// main_token is `var` or `const`.
         LocalVarDecl,
         /// `var a: lhs = rhs`. lhs and rhs may be unused.
         /// Can be local or global.
+        /// main_token is `var` or `const`.
         SimpleVarDecl,
         /// `var a align(lhs) = rhs`. lhs and rhs may be unused.
         /// Can be local or global.
+        /// main_token is `var` or `const`.
         AlignedVarDecl,
         /// lhs is the identifier token payload if any,
         /// rhs is the deferred expression.
@@ -901,33 +1108,26 @@ pub const Node = struct {
         /// `lhs...rhs`.
         SwitchRange,
         /// `while (lhs) rhs`.
-        WhileSimple,
         /// `while (lhs) |x| rhs`.
-        WhileSimpleOptional,
+        WhileSimple,
         /// `while (lhs) : (a) b`. `WhileCont[rhs]`.
-        WhileCont,
         /// `while (lhs) : (a) b`. `WhileCont[rhs]`.
-        WhileContOptional,
+        WhileCont,
         /// `while (lhs) : (a) b else c`. `While[rhs]`.
-        While,
         /// `while (lhs) |x| : (a) b else c`. `While[rhs]`.
-        WhileOptional,
         /// `while (lhs) |x| : (a) b else |y| c`. `While[rhs]`.
-        WhileError,
+        While,
         /// `for (lhs) rhs`.
         ForSimple,
         /// `for (lhs) a else b`. `if_list[rhs]`.
         For,
         /// `if (lhs) rhs`.
-        IfSimple,
         /// `if (lhs) |a| rhs`.
-        IfSimpleOptional,
+        IfSimple,
         /// `if (lhs) a else b`. `if_list[rhs]`.
-        If,
         /// `if (lhs) |x| a else b`. `if_list[rhs]`.
-        IfOptional,
         /// `if (lhs) |x| a else |y| b`. `if_list[rhs]`.
-        IfError,
+        If,
         /// `suspend lhs`. lhs can be omitted. rhs is unused.
         Suspend,
         /// `resume lhs`. rhs is unused.
@@ -955,10 +1155,24 @@ pub const Node = struct {
         FnDecl,
         /// `anyframe->rhs`. main_token is `anyframe`. `lhs` is arrow token index.
         AnyFrameType,
-        /// Could be integer literal, float literal, char literal, bool literal,
-        /// null literal, undefined literal, unreachable, depending on the token.
         /// Both lhs and rhs unused.
-        OneToken,
+        AnyFrameLiteral,
+        /// Both lhs and rhs unused.
+        CharLiteral,
+        /// Both lhs and rhs unused.
+        IntegerLiteral,
+        /// Both lhs and rhs unused.
+        FloatLiteral,
+        /// Both lhs and rhs unused.
+        FalseLiteral,
+        /// Both lhs and rhs unused.
+        TrueLiteral,
+        /// Both lhs and rhs unused.
+        NullLiteral,
+        /// Both lhs and rhs unused.
+        UndefinedLiteral,
+        /// Both lhs and rhs unused.
+        UnreachableLiteral,
         /// Both lhs and rhs unused.
         /// Most identifiers will not have explicit AST nodes, however for expressions
         /// which could be one of many different kinds of AST nodes, there will be an
@@ -966,8 +1180,11 @@ pub const Node = struct {
         Identifier,
         /// lhs is the dot token index, rhs unused, main_token is the identifier.
         EnumLiteral,
-        /// Both lhs and rhs unused.
-        MultilineStringLiteral,
+        /// main_token is the first token index (redundant with lhs)
+        /// lhs is the first token index; rhs is the last token index.
+        /// Could be a series of MultilineStringLiteralLine tokens, or a single
+        /// StringLiteral token.
+        StringLiteral,
         /// `(lhs)`. main_token is the `(`; rhs is the token index of the `)`.
         GroupedExpression,
         /// `@a(lhs, rhs)`. lhs and rhs may be omitted.
lib/std/zig/parse.zig
@@ -832,7 +832,7 @@ const Parser = struct {
             }
             if (p.eatToken(.Semicolon)) |_| {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .IfSimple else .IfSimpleOptional,
+                    .tag = .IfSimple,
                     .main_token = if_token,
                     .data = .{
                         .lhs = condition,
@@ -848,7 +848,7 @@ const Parser = struct {
                 return p.fail(.{ .ExpectedSemiOrElse = .{ .token = p.tok_i } });
             }
             return p.addNode(.{
-                .tag = if (then_payload == 0) .IfSimple else .IfSimpleOptional,
+                .tag = .IfSimple,
                 .main_token = if_token,
                 .data = .{
                     .lhs = condition,
@@ -858,14 +858,8 @@ const Parser = struct {
         };
         const else_payload = try p.parsePayload();
         const else_expr = try p.expectStatement();
-        const tag = if (else_payload != 0)
-            Node.Tag.IfError
-        else if (then_payload != 0)
-            Node.Tag.IfOptional
-        else
-            Node.Tag.If;
         return p.addNode(.{
-            .tag = tag,
+            .tag = .If,
             .main_token = if_token,
             .data = .{
                 .lhs = condition,
@@ -994,7 +988,7 @@ const Parser = struct {
             if (p.eatToken(.Semicolon)) |_| {
                 if (continue_expr == 0) {
                     return p.addNode(.{
-                        .tag = if (then_payload == 0) .WhileSimple else .WhileSimpleOptional,
+                        .tag = .WhileSimple,
                         .main_token = while_token,
                         .data = .{
                             .lhs = condition,
@@ -1003,7 +997,7 @@ const Parser = struct {
                     });
                 } else {
                     return p.addNode(.{
-                        .tag = if (then_payload == 0) .WhileCont else .WhileContOptional,
+                        .tag = .WhileCont,
                         .main_token = while_token,
                         .data = .{
                             .lhs = condition,
@@ -1024,7 +1018,7 @@ const Parser = struct {
             }
             if (continue_expr == 0) {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .WhileSimple else .WhileSimpleOptional,
+                    .tag = .WhileSimple,
                     .main_token = while_token,
                     .data = .{
                         .lhs = condition,
@@ -1033,7 +1027,7 @@ const Parser = struct {
                 });
             } else {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .WhileCont else .WhileContOptional,
+                    .tag = .WhileCont,
                     .main_token = while_token,
                     .data = .{
                         .lhs = condition,
@@ -1047,14 +1041,8 @@ const Parser = struct {
         };
         const else_payload = try p.parsePayload();
         const else_expr = try p.expectStatement();
-        const tag = if (else_payload != 0)
-            Node.Tag.WhileError
-        else if (then_payload != 0)
-            Node.Tag.WhileOptional
-        else
-            Node.Tag.While;
         return p.addNode(.{
-            .tag = tag,
+            .tag = .While,
             .main_token = while_token,
             .data = .{
                 .lhs = condition,
@@ -1948,7 +1936,7 @@ const Parser = struct {
         const else_token = p.eatToken(.Keyword_else) orelse {
             if (continue_expr == 0) {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .WhileSimple else .WhileSimpleOptional,
+                    .tag = .WhileSimple,
                     .main_token = while_token,
                     .data = .{
                         .lhs = condition,
@@ -1957,7 +1945,7 @@ const Parser = struct {
                 });
             } else {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .WhileCont else .WhileContOptional,
+                    .tag = .WhileCont,
                     .main_token = while_token,
                     .data = .{
                         .lhs = condition,
@@ -1971,14 +1959,8 @@ const Parser = struct {
         };
         const else_payload = try p.parsePayload();
         const else_expr = try p.expectExpr();
-        const tag = if (else_payload != 0)
-            Node.Tag.WhileError
-        else if (then_payload != 0)
-            Node.Tag.WhileOptional
-        else
-            Node.Tag.While;
         return p.addNode(.{
-            .tag = tag,
+            .tag = .While,
             .main_token = while_token,
             .data = .{
                 .lhs = condition,
@@ -2229,24 +2211,89 @@ const Parser = struct {
     /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
     fn parsePrimaryTypeExpr(p: *Parser) !Node.Index {
         switch (p.token_tags[p.tok_i]) {
-            .CharLiteral,
-            .IntegerLiteral,
-            .FloatLiteral,
-            .StringLiteral,
-            .Keyword_false,
-            .Keyword_true,
-            .Keyword_null,
-            .Keyword_undefined,
-            .Keyword_unreachable,
-            .Keyword_anyframe,
-            => return p.addNode(.{
-                .tag = .OneToken,
+            .CharLiteral => return p.addNode(.{
+                .tag = .CharLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .IntegerLiteral => return p.addNode(.{
+                .tag = .IntegerLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .FloatLiteral => return p.addNode(.{
+                .tag = .FloatLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .Keyword_false => return p.addNode(.{
+                .tag = .FalseLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .Keyword_true => return p.addNode(.{
+                .tag = .TrueLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .Keyword_null => return p.addNode(.{
+                .tag = .NullLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .Keyword_undefined => return p.addNode(.{
+                .tag = .UndefinedLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .Keyword_unreachable => return p.addNode(.{
+                .tag = .UnreachableLiteral,
+                .main_token = p.nextToken(),
+                .data = .{
+                    .lhs = undefined,
+                    .rhs = undefined,
+                },
+            }),
+            .Keyword_anyframe => return p.addNode(.{
+                .tag = .AnyFrameLiteral,
                 .main_token = p.nextToken(),
                 .data = .{
                     .lhs = undefined,
                     .rhs = undefined,
                 },
             }),
+            .StringLiteral => {
+                const main_token = p.nextToken();
+                return p.addNode(.{
+                    .tag = .StringLiteral,
+                    .main_token = main_token,
+                    .data = .{
+                        .lhs = main_token,
+                        .rhs = main_token,
+                    },
+                });
+            },
 
             .Builtin => return p.parseBuiltinCall(),
             .Keyword_fn => return p.parseFnProto(),
@@ -2280,11 +2327,11 @@ const Parser = struct {
                     p.tok_i += 1;
                 }
                 return p.addNode(.{
-                    .tag = .OneToken,
+                    .tag = .StringLiteral,
                     .main_token = first_line,
                     .data = .{
-                        .lhs = undefined,
-                        .rhs = undefined,
+                        .lhs = first_line,
+                        .rhs = p.tok_i - 1,
                     },
                 });
             },
@@ -2641,7 +2688,7 @@ const Parser = struct {
         const else_token = p.eatToken(.Keyword_else) orelse {
             if (continue_expr == 0) {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .WhileSimple else .WhileSimpleOptional,
+                    .tag = .WhileSimple,
                     .main_token = while_token,
                     .data = .{
                         .lhs = condition,
@@ -2650,7 +2697,7 @@ const Parser = struct {
                 });
             } else {
                 return p.addNode(.{
-                    .tag = if (then_payload == 0) .WhileCont else .WhileContOptional,
+                    .tag = .WhileCont,
                     .main_token = while_token,
                     .data = .{
                         .lhs = condition,
@@ -2664,14 +2711,8 @@ const Parser = struct {
         };
         const else_payload = try p.parsePayload();
         const else_expr = try p.expectTypeExpr();
-        const tag = if (else_payload != 0)
-            Node.Tag.WhileError
-        else if (then_payload != 0)
-            Node.Tag.WhileOptional
-        else
-            Node.Tag.While;
         return p.addNode(.{
-            .tag = tag,
+            .tag = .While,
             .main_token = while_token,
             .data = .{
                 .lhs = condition,
@@ -3450,7 +3491,7 @@ const Parser = struct {
             });
             // Pretend this was an identifier so we can continue parsing.
             return p.addNode(.{
-                .tag = .OneToken,
+                .tag = .Identifier,
                 .main_token = builtin_token,
                 .data = .{
                     .lhs = undefined,
@@ -3470,59 +3511,31 @@ const Parser = struct {
         });
     }
 
-    fn parseOneToken(p: *Parser, token_tag: Token.Tag) !Node.Index {
-        const token = p.eatToken(token_tag) orelse return null_node;
-        return p.addNode(.{
-            .tag = .OneToken,
-            .main_token = token,
-            .data = .{
-                .lhs = undefined,
-                .rhs = undefined,
-            },
-        });
-    }
-
-    fn expectOneToken(p: *Parser, token_tag: Token.Tag) !Node.Index {
-        const node = try p.expectOneTokenRecoverable(token_tag);
-        if (node == 0) return error.ParseError;
-        return node;
-    }
-
-    fn expectOneTokenRecoverable(p: *Parser, token_tag: Token.Tag) !Node.Index {
-        const node = p.parseOneToken(token_tag);
-        if (node == 0) {
-            try p.warn(.{
-                .ExpectedToken = .{
-                    .token = p.tok_i,
-                    .expected_id = token_tag,
-                },
-            });
-        }
-        return node;
-    }
-
     // string literal or multiline string literal
     fn parseStringLiteral(p: *Parser) !Node.Index {
         switch (p.token_tags[p.tok_i]) {
-            .StringLiteral => return p.addNode(.{
-                .tag = .OneToken,
-                .main_token = p.nextToken(),
-                .data = .{
-                    .lhs = undefined,
-                    .rhs = undefined,
-                },
-            }),
+            .StringLiteral => {
+                const main_token = p.nextToken();
+                return p.addNode(.{
+                    .tag = .StringLiteral,
+                    .main_token = main_token,
+                    .data = .{
+                        .lhs = main_token,
+                        .rhs = main_token,
+                    },
+                });
+            },
             .MultilineStringLiteralLine => {
                 const first_line = p.nextToken();
                 while (p.token_tags[p.tok_i] == .MultilineStringLiteralLine) {
                     p.tok_i += 1;
                 }
                 return p.addNode(.{
-                    .tag = .OneToken,
+                    .tag = .StringLiteral,
                     .main_token = first_line,
                     .data = .{
-                        .lhs = undefined,
-                        .rhs = undefined,
+                        .lhs = first_line,
+                        .rhs = p.tok_i - 1,
                     },
                 });
             },
@@ -3539,11 +3552,14 @@ const Parser = struct {
     }
 
     fn expectIntegerLiteral(p: *Parser) !Node.Index {
-        const node = p.parseOneToken(.IntegerLiteral);
-        if (node != 0) {
-            return p.fail(.{ .ExpectedIntegerLiteral = .{ .token = p.tok_i } });
-        }
-        return node;
+        return p.addNode(.{
+            .tag = .IntegerLiteral,
+            .main_token = try p.expectToken(.IntegerLiteral),
+            .data = .{
+                .lhs = undefined,
+                .rhs = undefined,
+            },
+        });
     }
 
     /// KEYWORD_if LPAREN Expr RPAREN PtrPayload? Body (KEYWORD_else Payload? Body)?
@@ -3558,7 +3574,7 @@ const Parser = struct {
         if (then_expr == 0) return p.fail(.{ .InvalidToken = .{ .token = p.tok_i } });
 
         const else_token = p.eatToken(.Keyword_else) orelse return p.addNode(.{
-            .tag = if (then_payload == 0) .IfSimple else .IfSimpleOptional,
+            .tag = .IfSimple,
             .main_token = if_token,
             .data = .{
                 .lhs = condition,
@@ -3569,14 +3585,8 @@ const Parser = struct {
         const else_expr = try bodyParseFn(p);
         if (else_expr == 0) return p.fail(.{ .InvalidToken = .{ .token = p.tok_i } });
 
-        const tag = if (else_payload != 0)
-            Node.Tag.IfError
-        else if (then_payload != 0)
-            Node.Tag.IfOptional
-        else
-            Node.Tag.If;
         return p.addNode(.{
-            .tag = tag,
+            .tag = .If,
             .main_token = if_token,
             .data = .{
                 .lhs = condition,
lib/std/zig/parser_test.zig
@@ -81,16 +81,16 @@ test "zig fmt: doc comments on test" {
     );
 }
 
-//test "zig fmt: if statment" {
-//    try testCanonical(
-//        \\test "" {
-//        \\    if (optional()) |some|
-//        \\        bar = some.foo();
-//        \\}
-//        \\
-//    );
-//}
-//
+test "zig fmt: if statment" {
+    try testCanonical(
+        \\test "" {
+        \\    if (optional()) |some|
+        \\        bar = some.foo();
+        \\}
+        \\
+    );
+}
+
 //test "zig fmt: top-level fields" {
 //    try testCanonical(
 //        \\a: did_you_know,
lib/std/zig/render.zig
@@ -69,15 +69,15 @@ fn renderRoot(ais: *Ais, tree: ast.Tree) Error!void {
     if (root_decls.len == 0) return;
 
     for (root_decls) |decl| {
-        try renderTopLevelDecl(ais, tree, decl);
+        return renderContainerDecl(ais, tree, decl, .Newline);
     }
 }
 
-fn renderExtraNewline(tree: ast.Tree, ais: *Ais, node: ast.Node.Index) Error!void {
-    return renderExtraNewlineToken(tree, ais, tree.firstToken(node));
+fn renderExtraNewline(ais: *Ais, tree: ast.Tree, node: ast.Node.Index) Error!void {
+    return renderExtraNewlineToken(ais, tree, tree.firstToken(node));
 }
 
-fn renderExtraNewlineToken(tree: ast.Tree, ais: *Ais, first_token: ast.TokenIndex) Error!void {
+fn renderExtraNewlineToken(ais: *Ais, tree: ast.Tree, first_token: ast.TokenIndex) Error!void {
     @panic("TODO implement renderExtraNewlineToken");
     //var prev_token = first_token;
     //if (prev_token == 0) return;
@@ -96,14 +96,11 @@ fn renderExtraNewlineToken(tree: ast.Tree, ais: *Ais, first_token: ast.TokenInde
     //}
 }
 
-fn renderTopLevelDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index) Error!void {
-    return renderContainerDecl(ais, tree, decl, .Newline);
-}
-
 fn renderContainerDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);
     const main_tokens = tree.nodes.items(.main_token);
     const datas = tree.nodes.items(.data);
+    try renderDocComments(ais, tree, tree.firstToken(decl));
     switch (tree.nodes.items(.tag)[decl]) {
         .UsingNamespace,
         .FnProtoSimple,
@@ -111,10 +108,6 @@ fn renderContainerDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: S
         .FnProtoOne,
         .FnProto,
         .FnDecl,
-        .GlobalVarDecl,
-        .LocalVarDecl,
-        .SimpleVarDecl,
-        .AlignedVarDecl,
         .ContainerFieldInit,
         .ContainerFieldAlign,
         .ContainerField,
@@ -145,16 +138,13 @@ fn renderContainerDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: S
         //        try renderToken(ais, tree, use_decl.semicolon_token, space); // ;
         //    },
 
-        //    .VarDecl => {
-        //        const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl);
-
-        //        try renderDocComments(ais, tree, var_decl, var_decl.getDocComments());
-        //        try renderVarDecl(allocator, ais, tree, var_decl);
-        //    },
+        .GlobalVarDecl => return renderVarDecl(ais, tree, tree.globalVarDecl(decl)),
+        .LocalVarDecl => return renderVarDecl(ais, tree, tree.localVarDecl(decl)),
+        .SimpleVarDecl => return renderVarDecl(ais, tree, tree.simpleVarDecl(decl)),
+        .AlignedVarDecl => return renderVarDecl(ais, tree, tree.alignedVarDecl(decl)),
 
         .TestDecl => {
             const test_token = main_tokens[decl];
-            try renderDocComments(ais, tree, test_token);
             try renderToken(ais, tree, test_token, .Space);
             if (token_tags[test_token + 1] == .StringLiteral) {
                 try renderToken(ais, tree, test_token + 1, .Space);
@@ -251,21 +241,20 @@ fn renderContainerDecl(ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: S
 fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);
     const main_tokens = tree.nodes.items(.main_token);
-    switch (tree.nodes.items(.tag)[node]) {
-        //.Identifier,
-        //.IntegerLiteral,
-        //.FloatLiteral,
-        //.StringLiteral,
-        //.CharLiteral,
-        //.BoolLiteral,
-        //.NullLiteral,
-        //.Unreachable,
-        //.ErrorType,
-        //.UndefinedLiteral,
-        //=> {
-        //    const casted_node = base.cast(ast.Node.OneToken).?;
-        //    return renderToken(ais, tree, casted_node.token, space);
-        //},
+    const node_tags = tree.nodes.items(.tag);
+    const datas = tree.nodes.items(.data);
+    switch (node_tags[node]) {
+        .Identifier,
+        .IntegerLiteral,
+        .FloatLiteral,
+        .StringLiteral,
+        .CharLiteral,
+        .TrueLiteral,
+        .FalseLiteral,
+        .NullLiteral,
+        .UnreachableLiteral,
+        .UndefinedLiteral,
+        => return renderToken(ais, tree, main_tokens[node], space),
 
         //.AnyType => {
         //    const any_type = base.castTag(.AnyType).?;
@@ -299,16 +288,37 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 
                 try renderToken(ais, tree, lbrace, .Newline);
 
-                for (statements) |statement, i| {
-                    try renderStatement(ais, tree, statement);
+                for (statements) |stmt, i| {
+                    switch (node_tags[stmt]) {
+                        .GlobalVarDecl => try renderVarDecl(ais, tree, tree.globalVarDecl(stmt)),
+                        .LocalVarDecl => try renderVarDecl(ais, tree, tree.localVarDecl(stmt)),
+                        .SimpleVarDecl => try renderVarDecl(ais, tree, tree.simpleVarDecl(stmt)),
+                        .AlignedVarDecl => try renderVarDecl(ais, tree, tree.alignedVarDecl(stmt)),
+                        else => {
+                            const semicolon = tree.lastToken(stmt) + 1;
+                            if (token_tags[semicolon] == .Semicolon) {
+                                try renderExpression(ais, tree, stmt, .None);
+                                try renderToken(ais, tree, semicolon, .Newline);
+                            } else {
+                                try renderExpression(ais, tree, stmt, .Newline);
+                            }
+                        },
+                    }
 
                     if (i + 1 < statements.len) {
-                        try renderExtraNewline(tree, ais, statements[i + 1]);
+                        try renderExtraNewline(ais, tree, statements[i + 1]);
                     }
                 }
                 ais.popIndent();
-                const rbrace = tree.lastToken(statements[statements.len - 1]) + 1;
-                return renderToken(ais, tree, rbrace, space);
+                // The rbrace could be +1 or +2 from the last token of the last
+                // statement in the block because lastToken() does not count semicolons.
+                const maybe_rbrace = tree.lastToken(statements[statements.len - 1]) + 1;
+                if (token_tags[maybe_rbrace] == .RBrace) {
+                    return renderToken(ais, tree, maybe_rbrace, space);
+                } else {
+                    assert(token_tags[maybe_rbrace + 1] == .RBrace);
+                    return renderToken(ais, tree, maybe_rbrace + 1, space);
+                }
             }
         },
 
@@ -322,8 +332,8 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    return renderExpression(ais, tree, defer_node.expr, space);
         //},
         .Comptime => {
-            const comptime_token = tree.nodes.items(.main_token)[node];
-            const block = tree.nodes.items(.data)[node].lhs;
+            const comptime_token = main_tokens[node];
+            const block = datas[node].lhs;
             try renderToken(ais, tree, comptime_token, .Space);
             return renderExpression(ais, tree, block, space);
         },
@@ -369,71 +379,78 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    ais.pushIndentOneShot();
         //    return renderExpression(ais, tree, infix_op_node.rhs, space);
         //},
+        .FieldAccess => {
+            const field_access = datas[node];
+            try renderExpression(ais, tree, field_access.lhs, .None);
+            try renderToken(ais, tree, main_tokens[node], .None);
+            return renderToken(ais, tree, field_access.rhs, .None);
+        },
 
-        //.Add,
-        //.AddWrap,
-        //.ArrayCat,
-        //.ArrayMult,
-        //.Assign,
-        //.AssignBitAnd,
-        //.AssignBitOr,
-        //.AssignBitShiftLeft,
-        //.AssignBitShiftRight,
-        //.AssignBitXor,
-        //.AssignDiv,
-        //.AssignSub,
-        //.AssignSubWrap,
-        //.AssignMod,
-        //.AssignAdd,
-        //.AssignAddWrap,
-        //.AssignMul,
-        //.AssignMulWrap,
-        //.BangEqual,
-        //.BitAnd,
-        //.BitOr,
-        //.BitShiftLeft,
-        //.BitShiftRight,
-        //.BitXor,
-        //.BoolAnd,
-        //.BoolOr,
-        //.Div,
-        //.EqualEqual,
-        //.ErrorUnion,
-        //.GreaterOrEqual,
-        //.GreaterThan,
-        //.LessOrEqual,
-        //.LessThan,
-        //.MergeErrorSets,
-        //.Mod,
-        //.Mul,
-        //.MulWrap,
-        //.Period,
-        //.Range,
-        //.Sub,
-        //.SubWrap,
-        //.OrElse,
-        //=> {
-        //    const infix_op_node = @fieldParentPtr(ast.Node.SimpleInfixOp, "base", base);
-
-        //    const op_space = switch (base.tag) {
-        //        .Period, .ErrorUnion, .Range => Space.None,
-        //        else => Space.Space,
-        //    };
-        //    try renderExpression(ais, tree, infix_op_node.lhs, op_space);
-
-        //    const after_op_space = blk: {
-        //        const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token));
-        //        break :blk if (loc.line == 0) op_space else Space.Newline;
-        //    };
+        .ErrorUnion,
+        .SwitchRange,
+        => {
+            const infix = datas[node];
+            try renderExpression(ais, tree, infix.lhs, .None);
+            try renderToken(ais, tree, main_tokens[node], .None);
+            return renderExpression(ais, tree, infix.rhs, space);
+        },
 
-        //    {
-        //        ais.pushIndent();
-        //        defer ais.popIndent();
-        //        try renderToken(ais, tree, infix_op_node.op_token, after_op_space);
-        //    }
-        //    ais.pushIndentOneShot();
-        //    return renderExpression(ais, tree, infix_op_node.rhs, space);
-        //},
+        .Add,
+        .AddWrap,
+        .ArrayCat,
+        .ArrayMult,
+        .Assign,
+        .AssignBitAnd,
+        .AssignBitOr,
+        .AssignBitShiftLeft,
+        .AssignBitShiftRight,
+        .AssignBitXor,
+        .AssignDiv,
+        .AssignSub,
+        .AssignSubWrap,
+        .AssignMod,
+        .AssignAdd,
+        .AssignAddWrap,
+        .AssignMul,
+        .AssignMulWrap,
+        .BangEqual,
+        .BitAnd,
+        .BitOr,
+        .BitShiftLeft,
+        .BitShiftRight,
+        .BitXor,
+        .BoolAnd,
+        .BoolOr,
+        .Div,
+        .EqualEqual,
+        .GreaterOrEqual,
+        .GreaterThan,
+        .LessOrEqual,
+        .LessThan,
+        .MergeErrorSets,
+        .Mod,
+        .Mul,
+        .MulWrap,
+        .Sub,
+        .SubWrap,
+        .OrElse,
+        => {
+            const infix = datas[node];
+            try renderExpression(ais, tree, infix.lhs, .Space);
+
+            const op_token = main_tokens[node];
+            const after_op_space: Space = if (tree.tokensOnSameLine(op_token, op_token + 1))
+                .Space
+            else
+                .Newline;
+            {
+                ais.pushIndent();
+                try renderToken(ais, tree, op_token, after_op_space);
+                ais.popIndent();
+            }
+            ais.pushIndentOneShot();
+            return renderExpression(ais, tree, infix.rhs, space);
+        },
 
         //.BitNot,
         //.BoolNot,
@@ -769,7 +786,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 
         //                        column_counter = 0;
         //                        try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                        try renderExtraNewline(tree, ais, next_expr);
+        //                        try renderExtraNewline(ais, tree, next_expr);
         //                    } else {
         //                        const maybe_comma = tree.nextToken(expr.*.lastToken());
         //                        if (tree.token_tags[maybe_comma] == .Comma) {
@@ -932,7 +949,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //                const comma = tree.nextToken(field_init.lastToken());
         //                try renderToken(ais, tree, comma, Space.Newline);
 
-        //                try renderExtraNewline(tree, ais, next_field_init);
+        //                try renderExtraNewline(ais, tree, next_field_init);
         //            } else {
         //                try renderExpression(ais, tree, field_init, Space.Comma);
         //            }
@@ -942,74 +959,63 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    return renderToken(ais, tree, rtoken, space);
         //},
 
-        //.Call => {
-        //    const call = @fieldParentPtr(ast.Node.Call, "base", base);
-        //    if (call.async_token) |async_token| {
-        //        try renderToken(ais, tree, async_token, Space.Space);
-        //    }
-
-        //    try renderExpression(ais, tree, call.lhs, Space.None);
-
-        //    const lparen = tree.nextToken(call.lhs.lastToken());
+        .Call => {
+            const call = datas[node];
+            const params_range = tree.extraData(call.rhs, ast.Node.SubRange);
+            const params = tree.extra_data[params_range.start..params_range.end];
+            const async_token = tree.firstToken(call.lhs) - 1;
+            if (token_tags[async_token] == .Keyword_async) {
+                try renderToken(ais, tree, async_token, .Space);
+            }
+            try renderExpression(ais, tree, call.lhs, .None);
 
-        //    if (call.params_len == 0) {
-        //        try renderToken(ais, tree, lparen, Space.None);
-        //        return renderToken(ais, tree, call.rtoken, space);
-        //    }
+            const lparen = main_tokens[node];
 
-        //    const src_has_trailing_comma = blk: {
-        //        const maybe_comma = tree.prevToken(call.rtoken);
-        //        break :blk tree.token_tags[maybe_comma] == .Comma;
-        //    };
+            if (params.len == 0) {
+                try renderToken(ais, tree, lparen, .None);
+                return renderToken(ais, tree, lparen + 1, space); // )
+            }
 
-        //    if (src_has_trailing_comma) {
-        //        {
-        //            ais.pushIndent();
-        //            defer ais.popIndent();
+            const last_param = params[params.len - 1];
+            const after_last_param_tok = tree.lastToken(last_param) + 1;
+            if (token_tags[after_last_param_tok] == .Comma) {
+                ais.pushIndent();
+                try renderToken(ais, tree, lparen, Space.Newline); // (
+                for (params) |param_node, i| {
+                    if (i + 1 < params.len) {
+                        try renderExpression(ais, tree, param_node, Space.None);
 
-        //            try renderToken(ais, tree, lparen, Space.Newline); // (
-        //            const params = call.params();
-        //            for (params) |param_node, i| {
-        //                if (i + 1 < params.len) {
-        //                    const next_node = params[i + 1];
-        //                    try renderExpression(ais, tree, param_node, Space.None);
+                        // Unindent the comma for multiline string literals
+                        const is_multiline_string = node_tags[param_node] == .StringLiteral and
+                            token_tags[main_tokens[param_node]] == .MultilineStringLiteralLine;
+                        if (is_multiline_string) ais.popIndent();
 
-        //                    // Unindent the comma for multiline string literals
-        //                    const maybe_multiline_string = param_node.firstToken();
-        //                    const is_multiline_string = tree.token_tags[maybe_multiline_string] == .MultilineStringLiteralLine;
-        //                    if (is_multiline_string) ais.popIndent();
-        //                    defer if (is_multiline_string) ais.pushIndent();
+                        const comma = tree.lastToken(param_node) + 1;
+                        try renderToken(ais, tree, comma, Space.Newline); // ,
 
-        //                    const comma = tree.nextToken(param_node.lastToken());
-        //                    try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                    try renderExtraNewline(tree, ais, next_node);
-        //                } else {
-        //                    try renderExpression(ais, tree, param_node, Space.Comma);
-        //                }
-        //            }
-        //        }
-        //        return renderToken(ais, tree, call.rtoken, space);
-        //    }
+                        if (is_multiline_string) ais.pushIndent();
 
-        //    try renderToken(ais, tree, lparen, Space.None); // (
+                        try renderExtraNewline(ais, tree, params[i + 1]);
+                    } else {
+                        try renderExpression(ais, tree, param_node, Space.Comma);
+                    }
+                }
+                ais.popIndent();
+                return renderToken(ais, tree, after_last_param_tok + 1, space); // )
+            }
 
-        //    const params = call.params();
-        //    for (params) |param_node, i| {
-        //        const maybe_comment = param_node.firstToken() - 1;
-        //        const maybe_multiline_string = param_node.firstToken();
-        //        if (tree.token_tags[maybe_multiline_string] == .MultilineStringLiteralLine or tree.token_tags[maybe_comment] == .LineComment) {
-        //            ais.pushIndentOneShot();
-        //        }
+            try renderToken(ais, tree, lparen, Space.None); // (
 
-        //        try renderExpression(ais, tree, param_node, Space.None);
+            for (params) |param_node, i| {
+                try renderExpression(ais, tree, param_node, Space.None);
 
-        //        if (i + 1 < params.len) {
-        //            const comma = tree.nextToken(param_node.lastToken());
-        //            try renderToken(ais, tree, comma, Space.Space);
-        //        }
-        //    }
-        //    return renderToken(ais, tree, call.rtoken, space); // )
-        //},
+                if (i + 1 < params.len) {
+                    const comma = tree.lastToken(param_node) + 1;
+                    try renderToken(ais, tree, comma, Space.Space);
+                }
+            }
+            return renderToken(ais, tree, after_last_param_tok, space); // )
+        },
 
         //.ArrayAccess => {
         //    const suffix_op = base.castTag(.ArrayAccess).?;
@@ -1118,14 +1124,6 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    }
         //},
 
-        //.Payload => {
-        //    const payload = @fieldParentPtr(ast.Node.Payload, "base", base);
-
-        //    try renderToken(ais, tree, payload.lpipe, Space.None);
-        //    try renderExpression(ais, tree, payload.error_symbol, Space.None);
-        //    return renderToken(ais, tree, payload.rpipe, space);
-        //},
-
         //.PointerPayload => {
         //    const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base);
 
@@ -1264,7 +1262,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //            try renderContainerDecl(allocator, ais, tree, decl, .Newline);
 
         //            if (i + 1 < fields_and_decls.len) {
-        //                try renderExtraNewline(tree, ais, fields_and_decls[i + 1]);
+        //                try renderExtraNewline(ais, tree, fields_and_decls[i + 1]);
         //            }
         //        }
         //    } else if (src_has_newline) {
@@ -1338,7 +1336,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //                    try renderExpression(ais, tree, node, Space.None);
         //                    try renderToken(ais, tree, tree.nextToken(node.lastToken()), Space.Newline); // ,
 
-        //                    try renderExtraNewline(tree, ais, decls[i + 1]);
+        //                    try renderExtraNewline(ais, tree, decls[i + 1]);
         //                } else {
         //                    try renderExpression(ais, tree, node, Space.Comma);
         //                }
@@ -1357,7 +1355,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //                const comma_token = tree.nextToken(node.lastToken());
         //                assert(tree.token_tags[comma_token] == .Comma);
         //                try renderToken(ais, tree, comma_token, Space.Space); // ,
-        //                try renderExtraNewline(tree, ais, decls[i + 1]);
+        //                try renderExtraNewline(ais, tree, decls[i + 1]);
         //            } else {
         //                try renderExpression(ais, tree, node, Space.Space);
         //            }
@@ -1624,7 +1622,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //            try renderExpression(ais, tree, node, Space.Comma);
 
         //            if (i + 1 < cases.len) {
-        //                try renderExtraNewline(tree, ais, cases[i + 1]);
+        //                try renderExtraNewline(ais, tree, cases[i + 1]);
         //            }
         //        }
         //    }
@@ -1650,7 +1648,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 
         //                const comma_token = tree.nextToken(node.lastToken());
         //                try renderToken(ais, tree, comma_token, Space.Space); // ,
-        //                try renderExtraNewline(tree, ais, items[i + 1]);
+        //                try renderExtraNewline(ais, tree, items[i + 1]);
         //            } else {
         //                try renderExpression(ais, tree, node, Space.Space);
         //            }
@@ -1663,7 +1661,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 
         //                const comma_token = tree.nextToken(node.lastToken());
         //                try renderToken(ais, tree, comma_token, Space.Newline); // ,
-        //                try renderExtraNewline(tree, ais, items[i + 1]);
+        //                try renderExtraNewline(ais, tree, items[i + 1]);
         //            } else {
         //                try renderExpression(ais, tree, node, Space.Comma);
         //            }
@@ -1682,28 +1680,6 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base);
         //    return renderToken(ais, tree, switch_else.token, space);
         //},
-        //.Else => {
-        //    const else_node = @fieldParentPtr(ast.Node.Else, "base", base);
-
-        //    const body_is_block = nodeIsBlock(else_node.body);
-        //    const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken());
-
-        //    const after_else_space = if (same_line or else_node.payload != null) Space.Space else Space.Newline;
-        //    try renderToken(ais, tree, else_node.else_token, after_else_space);
-
-        //    if (else_node.payload) |payload| {
-        //        const payload_space = if (same_line) Space.Space else Space.Newline;
-        //        try renderExpression(ais, tree, payload, payload_space);
-        //    }
-
-        //    if (same_line) {
-        //        return renderExpression(ais, tree, else_node.body, space);
-        //    } else {
-        //        ais.pushIndent();
-        //        defer ais.popIndent();
-        //        return renderExpression(ais, tree, else_node.body, space);
-        //    }
-        //},
 
         //.While => {
         //    const while_node = @fieldParentPtr(ast.Node.While, "base", base);
@@ -1824,111 +1800,8 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         //    }
         //},
 
-        //.If => {
-        //    const if_node = @fieldParentPtr(ast.Node.If, "base", base);
-
-        //    const lparen = tree.nextToken(if_node.if_token);
-        //    const rparen = tree.nextToken(if_node.condition.lastToken());
-
-        //    try renderToken(ais, tree, if_node.if_token, Space.Space); // if
-        //    try renderToken(ais, tree, lparen, Space.None); // (
-
-        //    try renderExpression(ais, tree, if_node.condition, Space.None); // condition
-
-        //    const body_is_if_block = if_node.body.tag == .If;
-        //    const body_is_block = nodeIsBlock(if_node.body);
-
-        //    if (body_is_if_block) {
-        //        try renderExtraNewline(tree, ais, if_node.body);
-        //    } else if (body_is_block) {
-        //        const after_rparen_space = if (if_node.payload == null) Space.BlockStart else Space.Space;
-        //        try renderToken(ais, tree, rparen, after_rparen_space); // )
-
-        //        if (if_node.payload) |payload| {
-        //            try renderExpression(ais, tree, payload, Space.BlockStart); // |x|
-        //        }
-
-        //        if (if_node.@"else") |@"else"| {
-        //            try renderExpression(ais, tree, if_node.body, Space.SpaceOrOutdent);
-        //            return renderExpression(ais, tree, &@"else".base, space);
-        //        } else {
-        //            return renderExpression(ais, tree, if_node.body, space);
-        //        }
-        //    }
-
-        //    const src_has_newline = !tree.tokensOnSameLine(rparen, if_node.body.lastToken());
-
-        //    if (src_has_newline) {
-        //        const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space;
-
-        //        {
-        //            ais.pushIndent();
-        //            defer ais.popIndent();
-        //            try renderToken(ais, tree, rparen, after_rparen_space); // )
-        //        }
-
-        //        if (if_node.payload) |payload| {
-        //            try renderExpression(ais, tree, payload, Space.Newline);
-        //        }
-
-        //        if (if_node.@"else") |@"else"| {
-        //            const else_is_block = nodeIsBlock(@"else".body);
-
-        //            {
-        //                ais.pushIndent();
-        //                defer ais.popIndent();
-        //                try renderExpression(ais, tree, if_node.body, Space.Newline);
-        //            }
-
-        //            if (else_is_block) {
-        //                try renderToken(ais, tree, @"else".else_token, Space.Space); // else
-
-        //                if (@"else".payload) |payload| {
-        //                    try renderExpression(ais, tree, payload, Space.Space);
-        //                }
-
-        //                return renderExpression(ais, tree, @"else".body, space);
-        //            } else {
-        //                const after_else_space = if (@"else".payload == null) Space.Newline else Space.Space;
-        //                try renderToken(ais, tree, @"else".else_token, after_else_space); // else
-
-        //                if (@"else".payload) |payload| {
-        //                    try renderExpression(ais, tree, payload, Space.Newline);
-        //                }
-
-        //                ais.pushIndent();
-        //                defer ais.popIndent();
-        //                return renderExpression(ais, tree, @"else".body, space);
-        //            }
-        //        } else {
-        //            ais.pushIndent();
-        //            defer ais.popIndent();
-        //            return renderExpression(ais, tree, if_node.body, space);
-        //        }
-        //    }
-
-        //    // Single line if statement
-
-        //    try renderToken(ais, tree, rparen, Space.Space); // )
-
-        //    if (if_node.payload) |payload| {
-        //        try renderExpression(ais, tree, payload, Space.Space);
-        //    }
-
-        //    if (if_node.@"else") |@"else"| {
-        //        try renderExpression(ais, tree, if_node.body, Space.Space);
-        //        try renderToken(ais, tree, @"else".else_token, Space.Space);
-
-        //        if (@"else".payload) |payload| {
-        //            try renderExpression(ais, tree, payload, Space.Space);
-        //        }
-
-        //        return renderExpression(ais, tree, @"else".body, space);
-        //    } else {
-        //        return renderExpression(ais, tree, if_node.body, space);
-        //    }
-        //},
-
+        .IfSimple => return renderIf(ais, tree, tree.ifSimple(node), space),
+        .If => return renderIf(ais, tree, tree.ifFull(node), space),
         //.Asm => {
         //    const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base);
 
@@ -1974,7 +1847,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 
         //                    const comma = tree.prevToken(next_asm_output.firstToken());
         //                    try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                    try renderExtraNewlineToken(tree, ais, next_asm_output.firstToken());
+        //                    try renderExtraNewlineToken(ais, tree, next_asm_output.firstToken());
         //                } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
         //                    try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
         //                    break :asmblk;
@@ -2004,7 +1877,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 
         //                    const comma = tree.prevToken(next_asm_input.firstToken());
         //                    try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                    try renderExtraNewlineToken(tree, ais, next_asm_input.firstToken());
+        //                    try renderExtraNewlineToken(ais, tree, next_asm_input.firstToken());
         //                } else if (asm_node.clobbers.len == 0) {
         //                    try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline);
         //                    break :asmblk;
@@ -2140,89 +2013,225 @@ fn renderAsmInput(
     return renderToken(ais, tree, asm_input.lastToken(), space); // )
 }
 
-fn renderVarDecl(
-    allocator: *mem.Allocator,
-    ais: *Ais,
-    tree: ast.Tree,
-    var_decl: ast.Node.Index.VarDecl,
-) Error!void {
-    if (var_decl.getVisibToken()) |visib_token| {
+fn renderVarDecl(ais: *Ais, tree: ast.Tree, var_decl: ast.Full.VarDecl) Error!void {
+    if (var_decl.visib_token) |visib_token| {
         try renderToken(ais, tree, visib_token, Space.Space); // pub
     }
 
-    if (var_decl.getExternExportToken()) |extern_export_token| {
+    if (var_decl.extern_export_token) |extern_export_token| {
         try renderToken(ais, tree, extern_export_token, Space.Space); // extern
 
-        if (var_decl.getLibName()) |lib_name| {
+        if (var_decl.lib_name) |lib_name| {
             try renderExpression(ais, tree, lib_name, Space.Space); // "lib"
         }
     }
 
-    if (var_decl.getComptimeToken()) |comptime_token| {
-        try renderToken(ais, tree, comptime_token, Space.Space); // comptime
+    if (var_decl.threadlocal_token) |thread_local_token| {
+        try renderToken(ais, tree, thread_local_token, Space.Space); // threadlocal
     }
 
-    if (var_decl.getThreadLocalToken()) |thread_local_token| {
-        try renderToken(ais, tree, thread_local_token, Space.Space); // threadlocal
+    if (var_decl.comptime_token) |comptime_token| {
+        try renderToken(ais, tree, comptime_token, Space.Space); // comptime
     }
-    try renderToken(ais, tree, var_decl.mut_token, Space.Space); // var
 
-    const name_space = if (var_decl.getTypeNode() == null and
-        (var_decl.getAlignNode() != null or
-        var_decl.getSectionNode() != null or
-        var_decl.getInitNode() != null))
+    try renderToken(ais, tree, var_decl.ast.mut_token, .Space); // var
+
+    const name_space = if (var_decl.ast.type_node == 0 and
+        (var_decl.ast.align_node != 0 or
+        var_decl.ast.section_node != 0 or
+        var_decl.ast.init_node != 0))
         Space.Space
     else
         Space.None;
-    try renderToken(ais, tree, var_decl.name_token, name_space);
-
-    if (var_decl.getTypeNode()) |type_node| {
-        try renderToken(ais, tree, tree.nextToken(var_decl.name_token), Space.Space);
-        const s = if (var_decl.getAlignNode() != null or
-            var_decl.getSectionNode() != null or
-            var_decl.getInitNode() != null) Space.Space else Space.None;
-        try renderExpression(ais, tree, type_node, s);
+    try renderToken(ais, tree, var_decl.ast.mut_token + 1, name_space); // name
+
+    if (var_decl.ast.type_node != 0) {
+        try renderToken(ais, tree, var_decl.ast.mut_token + 2, Space.Space); // :
+        if (var_decl.ast.align_node != 0 or var_decl.ast.section_node != 0 or
+            var_decl.ast.init_node != 0)
+        {
+            try renderExpression(ais, tree, var_decl.ast.type_node, .Space);
+        } else {
+            try renderExpression(ais, tree, var_decl.ast.type_node, .None);
+            const semicolon = tree.lastToken(var_decl.ast.type_node) + 1;
+            return renderToken(ais, tree, semicolon, Space.Newline); // ;
+        }
     }
 
-    if (var_decl.getAlignNode()) |align_node| {
-        const lparen = tree.prevToken(align_node.firstToken());
-        const align_kw = tree.prevToken(lparen);
-        const rparen = tree.nextToken(align_node.lastToken());
+    if (var_decl.ast.align_node != 0) {
+        const lparen = tree.firstToken(var_decl.ast.align_node) - 1;
+        const align_kw = lparen - 1;
+        const rparen = tree.lastToken(var_decl.ast.align_node) + 1;
         try renderToken(ais, tree, align_kw, Space.None); // align
         try renderToken(ais, tree, lparen, Space.None); // (
-        try renderExpression(ais, tree, align_node, Space.None);
-        const s = if (var_decl.getSectionNode() != null or var_decl.getInitNode() != null) Space.Space else Space.None;
-        try renderToken(ais, tree, rparen, s); // )
+        try renderExpression(ais, tree, var_decl.ast.align_node, Space.None);
+        if (var_decl.ast.section_node != 0 or var_decl.ast.init_node != 0) {
+            try renderToken(ais, tree, rparen, .Space); // )
+        } else {
+            try renderToken(ais, tree, rparen, .None); // )
+            return renderToken(ais, tree, rparen + 1, Space.Newline); // ;
+        }
     }
 
-    if (var_decl.getSectionNode()) |section_node| {
-        const lparen = tree.prevToken(section_node.firstToken());
-        const section_kw = tree.prevToken(lparen);
-        const rparen = tree.nextToken(section_node.lastToken());
+    if (var_decl.ast.section_node != 0) {
+        const lparen = tree.firstToken(var_decl.ast.section_node) - 1;
+        const section_kw = lparen - 1;
+        const rparen = tree.lastToken(var_decl.ast.section_node) + 1;
         try renderToken(ais, tree, section_kw, Space.None); // linksection
         try renderToken(ais, tree, lparen, Space.None); // (
-        try renderExpression(ais, tree, section_node, Space.None);
-        const s = if (var_decl.getInitNode() != null) Space.Space else Space.None;
-        try renderToken(ais, tree, rparen, s); // )
+        try renderExpression(ais, tree, var_decl.ast.section_node, Space.None);
+        if (var_decl.ast.init_node != 0) {
+            try renderToken(ais, tree, rparen, .Space); // )
+        } else {
+            try renderToken(ais, tree, rparen, .None); // )
+            return renderToken(ais, tree, rparen + 1, Space.Newline); // ;
+        }
     }
 
-    if (var_decl.getInitNode()) |init_node| {
-        const eq_token = var_decl.getEqToken().?;
-        const eq_space = blk: {
-            const loc = tree.tokenLocation(tree.token_locs[eq_token].end, tree.nextToken(eq_token));
-            break :blk if (loc.line == 0) Space.Space else Space.Newline;
-        };
+    assert(var_decl.ast.init_node != 0);
+    const eq_token = tree.firstToken(var_decl.ast.init_node) - 1;
+    const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .Space else .Newline;
+    {
+        ais.pushIndent();
+        try renderToken(ais, tree, eq_token, eq_space); // =
+        ais.popIndent();
+    }
+    ais.pushIndentOneShot();
+    try renderExpression(ais, tree, var_decl.ast.init_node, Space.None);
 
-        {
+    const semicolon = tree.lastToken(var_decl.ast.init_node) + 1;
+    return renderToken(ais, tree, semicolon, Space.Newline);
+}
+
+fn renderIf(ais: *Ais, tree: ast.Tree, if_node: ast.Full.If, space: Space) Error!void {
+    const node_tags = tree.nodes.items(.tag);
+    const token_tags = tree.tokens.items(.tag);
+
+    try renderToken(ais, tree, if_node.ast.if_token, .Space); // if
+
+    const lparen = if_node.ast.if_token + 1;
+
+    try renderToken(ais, tree, lparen, .None); // (
+    try renderExpression(ais, tree, if_node.ast.cond_expr, .None); // condition
+
+    switch (node_tags[if_node.ast.then_expr]) {
+        .If, .IfSimple => {
+            try renderExtraNewline(ais, tree, if_node.ast.then_expr);
+        },
+        .Block, .For, .ForSimple, .While, .WhileSimple, .Switch => {
+            if (if_node.payload_token) |payload_token| {
+                try renderToken(ais, tree, payload_token - 2, .Space); // )
+                try renderToken(ais, tree, payload_token - 1, .None); // |
+                if (token_tags[payload_token] == .Asterisk) {
+                    try renderToken(ais, tree, payload_token, .None); // *
+                    try renderToken(ais, tree, payload_token + 1, .None); // identifier
+                    try renderToken(ais, tree, payload_token + 2, .BlockStart); // |
+                } else {
+                    try renderToken(ais, tree, payload_token, .None); // identifier
+                    try renderToken(ais, tree, payload_token + 1, .BlockStart); // |
+                }
+            } else {
+                const rparen = tree.lastToken(if_node.ast.cond_expr) + 1;
+                try renderToken(ais, tree, rparen, .BlockStart); // )
+            }
+            if (if_node.ast.else_expr != 0) {
+                try renderExpression(ais, tree, if_node.ast.then_expr, Space.SpaceOrOutdent);
+                try renderToken(ais, tree, if_node.else_token, .Space); // else
+                if (if_node.error_token) |error_token| {
+                    try renderToken(ais, tree, error_token - 1, .None); // |
+                    try renderToken(ais, tree, error_token, .None); // identifier
+                    try renderToken(ais, tree, error_token + 1, .Space); // |
+                }
+                return renderExpression(ais, tree, if_node.ast.else_expr, space);
+            } else {
+                return renderExpression(ais, tree, if_node.ast.then_expr, space);
+            }
+        },
+        else => {},
+    }
+
+    const rparen = tree.lastToken(if_node.ast.cond_expr) + 1;
+    const last_then_token = tree.lastToken(if_node.ast.then_expr);
+    const src_has_newline = !tree.tokensOnSameLine(rparen, last_then_token);
+
+    if (src_has_newline) {
+        if (if_node.payload_token) |payload_token| {
+            try renderToken(ais, tree, payload_token - 2, .Space); // )
+            try renderToken(ais, tree, payload_token - 1, .None); // |
+            try renderToken(ais, tree, payload_token, .None); // identifier
+            try renderToken(ais, tree, payload_token + 1, .Newline); // |
+        } else {
             ais.pushIndent();
-            defer ais.popIndent();
-            try renderToken(ais, tree, eq_token, eq_space); // =
+            try renderToken(ais, tree, rparen, .Newline); // )
+            ais.popIndent();
+        }
+        if (if_node.ast.else_expr != 0) {
+            ais.pushIndent();
+            try renderExpression(ais, tree, if_node.ast.then_expr, Space.Newline);
+            ais.popIndent();
+            const else_is_block = nodeIsBlock(node_tags[if_node.ast.else_expr]);
+            if (else_is_block) {
+                try renderToken(ais, tree, if_node.else_token, .Space); // else
+                if (if_node.error_token) |error_token| {
+                    try renderToken(ais, tree, error_token - 1, .None); // |
+                    try renderToken(ais, tree, error_token, .None); // identifier
+                    try renderToken(ais, tree, error_token + 1, .Space); // |
+                }
+                return renderExpression(ais, tree, if_node.ast.else_expr, space);
+            } else {
+                if (if_node.error_token) |error_token| {
+                    try renderToken(ais, tree, if_node.else_token, .Space); // else
+                    try renderToken(ais, tree, error_token - 1, .None); // |
+                    try renderToken(ais, tree, error_token, .None); // identifier
+                    try renderToken(ais, tree, error_token + 1, .Space); // |
+                } else {
+                    try renderToken(ais, tree, if_node.else_token, .Newline); // else
+                }
+                ais.pushIndent();
+                try renderExpression(ais, tree, if_node.ast.else_expr, space);
+                ais.popIndent();
+                return;
+            }
+        } else {
+            ais.pushIndent();
+            try renderExpression(ais, tree, if_node.ast.then_expr, space);
+            ais.popIndent();
+            return;
         }
-        ais.pushIndentOneShot();
-        try renderExpression(ais, tree, init_node, Space.None);
     }
 
-    try renderToken(ais, tree, var_decl.semicolon_token, Space.Newline);
+    // Single line if statement.
+
+    if (if_node.payload_token) |payload_token| {
+        assert(payload_token - 2 == rparen);
+        try renderToken(ais, tree, payload_token - 2, .Space); // )
+        try renderToken(ais, tree, payload_token - 1, .None); // |
+        if (token_tags[payload_token] == .Asterisk) {
+            try renderToken(ais, tree, payload_token, .None); // *
+            try renderToken(ais, tree, payload_token + 1, .None); // identifier
+            try renderToken(ais, tree, payload_token + 2, .Space); // |
+        } else {
+            try renderToken(ais, tree, payload_token, .None); // identifier
+            try renderToken(ais, tree, payload_token + 1, .Space); // |
+        }
+    } else {
+        try renderToken(ais, tree, rparen, .Space); // )
+    }
+
+    if (if_node.ast.else_expr != 0) {
+        try renderExpression(ais, tree, if_node.ast.then_expr, .Space);
+        try renderToken(ais, tree, if_node.else_token, .Space); // else
+
+        if (if_node.error_token) |error_token| {
+            try renderToken(ais, tree, error_token - 1, .None); // |
+            try renderToken(ais, tree, error_token, .None); // identifier
+            try renderToken(ais, tree, error_token + 1, .Space); // |
+        }
+
+        return renderExpression(ais, tree, if_node.ast.else_expr, space);
+    } else {
+        return renderExpression(ais, tree, if_node.ast.then_expr, space);
+    }
 }
 
 fn renderParamDecl(
@@ -2249,27 +2258,6 @@ fn renderParamDecl(
     }
 }
 
-fn renderStatement(ais: *Ais, tree: ast.Tree, base: ast.Node.Index) Error!void {
-    @panic("TODO render statement");
-    //switch (base.tag) {
-    //    .VarDecl => {
-    //        const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base);
-    //        try renderVarDecl(allocator, ais, tree, var_decl);
-    //    },
-    //    else => {
-    //        if (base.requireSemiColon()) {
-    //            try renderExpression(ais, tree, base, Space.None);
-
-    //            const semicolon_index = tree.nextToken(base.lastToken());
-    //            assert(tree.token_tags[semicolon_index] == .Semicolon);
-    //            try renderToken(ais, tree, semicolon_index, Space.Newline);
-    //        } else {
-    //            try renderExpression(ais, tree, base, Space.Newline);
-    //        }
-    //    },
-    //}
-}
-
 const Space = enum {
     None,
     Newline,
@@ -2367,13 +2355,15 @@ fn renderDocComments(ais: *Ais, tree: ast.Tree, end_token: ast.TokenIndex) Error
     }
 }
 
-fn nodeIsBlock(base: *const ast.Node) bool {
-    return switch (base.tag) {
+fn nodeIsBlock(tag: ast.Node.Tag) bool {
+    return switch (tag) {
         .Block,
-        .LabeledBlock,
         .If,
+        .IfSimple,
         .For,
+        .ForSimple,
         .While,
+        .WhileSimple,
         .Switch,
         => true,
         else => false,