Commit 93384f7428

Andrew Kelley <andrew@ziglang.org>
2020-05-20 03:11:30
use singly linked lists for std.zig.parse
std.ast uses a singly linked list for lists of things. This is a breaking change to the self-hosted parser API. std.ast.Tree has been separated into a private "Parser" type which represents in-progress parsing, and std.ast.Tree which has only "output" data. This means cleaner, but breaking, API for parse results. Specifically, `tokens` and `errors` are no longer SegmentedList but a slice. The way to iterate over AST nodes has necessarily changed since lists of nodes are now singly linked lists rather than SegmentedList. From these changes, I observe the following on the self-hosted-parser benchmark from ziglang/gotta-go-fast: throughput: 45.6 MiB/s => 55.6 MiB/s maxrss: 359 KB => 342 KB This commit breaks the build; more updates are necessary to fix API usage of the self-hosted parser.
1 parent ed137d2
lib/std/zig/ast.zig
@@ -1,42 +1,39 @@
 const std = @import("../std.zig");
 const assert = std.debug.assert;
 const testing = std.testing;
-const SegmentedList = std.SegmentedList;
+const LinkedList = std.SinglyLinkedList;
 const mem = std.mem;
 const Token = std.zig.Token;
 
 pub const TokenIndex = usize;
 
 pub const Tree = struct {
+    /// Reference to externally-owned data.
     source: []const u8,
-    tokens: TokenList,
-
-    /// undefined on parse error (errors not empty)
+    tokens: []const Token,
+    errors: []const Error,
+    /// undefined on parse error (when errors field is not empty)
     root_node: *Node.Root,
-    arena_allocator: std.heap.ArenaAllocator,
-    errors: ErrorList,
+
+    arena: std.heap.ArenaAllocator.State,
+    gpa: *mem.Allocator,
 
     /// translate-c uses this to avoid having to emit correct newlines
     /// TODO get rid of this hack
     generated: bool = false,
 
-    pub const TokenList = SegmentedList(Token, 64);
-    pub const ErrorList = SegmentedList(Error, 0);
-
     pub fn deinit(self: *Tree) void {
-        // Here we copy the arena allocator into stack memory, because
-        // otherwise it would destroy itself while it was still working.
-        var arena_allocator = self.arena_allocator;
-        arena_allocator.deinit();
-        // self is destroyed
+        self.gpa.free(self.tokens);
+        self.gpa.free(self.errors);
+        self.arena.promote(self.gpa).deinit();
     }
 
-    pub fn renderError(self: *Tree, parse_error: *Error, stream: var) !void {
-        return parse_error.render(&self.tokens, stream);
+    pub fn renderError(self: *Tree, parse_error: *const Error, stream: var) !void {
+        return parse_error.render(self.tokens, stream);
     }
 
     pub fn tokenSlice(self: *Tree, token_index: TokenIndex) []const u8 {
-        return self.tokenSlicePtr(self.tokens.at(token_index));
+        return self.tokenSlicePtr(self.tokens[token_index]);
     }
 
     pub fn tokenSlicePtr(self: *Tree, token: *const Token) []const u8 {
@@ -44,8 +41,8 @@ pub const Tree = struct {
     }
 
     pub fn getNodeSource(self: *const Tree, node: *const Node) []const u8 {
-        const first_token = self.tokens.at(node.firstToken());
-        const last_token = self.tokens.at(node.lastToken());
+        const first_token = self.tokens[node.firstToken()];
+        const last_token = self.tokens[node.lastToken()];
         return self.source[first_token.start..last_token.end];
     }
 
@@ -57,7 +54,7 @@ pub const Tree = struct {
     };
 
     /// Return the Location of the token relative to the offset specified by `start_index`.
-    pub fn tokenLocationPtr(self: *Tree, start_index: usize, token: *const Token) Location {
+    pub fn tokenLocationPtr(self: *Tree, start_index: usize, token: Token) Location {
         var loc = Location{
             .line = 0,
             .column = 0,
@@ -85,11 +82,11 @@ pub const Tree = struct {
     }
 
     pub fn tokenLocation(self: *Tree, start_index: usize, token_index: TokenIndex) Location {
-        return self.tokenLocationPtr(start_index, self.tokens.at(token_index));
+        return self.tokenLocationPtr(start_index, self.tokens[token_index]);
     }
 
     pub fn tokensOnSameLine(self: *Tree, token1_index: TokenIndex, token2_index: TokenIndex) bool {
-        return self.tokensOnSameLinePtr(self.tokens.at(token1_index), self.tokens.at(token2_index));
+        return self.tokensOnSameLinePtr(self.tokens[token1_index], self.tokens[token2_index]);
     }
 
     pub fn tokensOnSameLinePtr(self: *Tree, token1: *const Token, token2: *const Token) bool {
@@ -103,7 +100,7 @@ pub const Tree = struct {
     /// Skips over comments
     pub fn prevToken(self: *Tree, token_index: TokenIndex) TokenIndex {
         var index = token_index - 1;
-        while (self.tokens.at(index).id == Token.Id.LineComment) {
+        while (self.tokens[index].id == Token.Id.LineComment) {
             index -= 1;
         }
         return index;
@@ -112,7 +109,7 @@ pub const Tree = struct {
     /// Skips over comments
     pub fn nextToken(self: *Tree, token_index: TokenIndex) TokenIndex {
         var index = token_index + 1;
-        while (self.tokens.at(index).id == Token.Id.LineComment) {
+        while (self.tokens[index].id == Token.Id.LineComment) {
             index += 1;
         }
         return index;
@@ -169,7 +166,7 @@ pub const Error = union(enum) {
     DeclBetweenFields: DeclBetweenFields,
     InvalidAnd: InvalidAnd,
 
-    pub fn render(self: *const Error, tokens: *Tree.TokenList, stream: var) !void {
+    pub fn render(self: *const Error, tokens: []const Token, stream: var) !void {
         switch (self.*) {
             .InvalidToken => |*x| return x.render(tokens, stream),
             .ExpectedContainerMembers => |*x| return x.render(tokens, stream),
@@ -324,7 +321,7 @@ pub const Error = union(enum) {
     pub const ExpectedCall = struct {
         node: *Node,
 
-        pub fn render(self: *const ExpectedCall, tokens: *Tree.TokenList, stream: var) !void {
+        pub fn render(self: *const ExpectedCall, tokens: []const Token, stream: var) !void {
             return stream.print("expected " ++ @tagName(@TagType(Node.SuffixOp.Op).Call) ++ ", found {}", .{
                 @tagName(self.node.id),
             });
@@ -334,7 +331,7 @@ pub const Error = union(enum) {
     pub const ExpectedCallOrFnProto = struct {
         node: *Node,
 
-        pub fn render(self: *const ExpectedCallOrFnProto, tokens: *Tree.TokenList, stream: var) !void {
+        pub fn render(self: *const ExpectedCallOrFnProto, tokens: []const Token, stream: var) !void {
             return stream.print("expected " ++ @tagName(@TagType(Node.SuffixOp.Op).Call) ++ " or " ++
                 @tagName(Node.Id.FnProto) ++ ", found {}", .{@tagName(self.node.id)});
         }
@@ -344,8 +341,8 @@ pub const Error = union(enum) {
         token: TokenIndex,
         expected_id: Token.Id,
 
-        pub fn render(self: *const ExpectedToken, tokens: *Tree.TokenList, stream: var) !void {
-            const found_token = tokens.at(self.token);
+        pub fn render(self: *const ExpectedToken, tokens: []const Token, stream: var) !void {
+            const found_token = tokens[self.token];
             switch (found_token.id) {
                 .Invalid => {
                     return stream.print("expected '{}', found invalid bytes", .{self.expected_id.symbol()});
@@ -362,8 +359,8 @@ pub const Error = union(enum) {
         token: TokenIndex,
         end_id: Token.Id,
 
-        pub fn render(self: *const ExpectedCommaOrEnd, tokens: *Tree.TokenList, stream: var) !void {
-            const actual_token = tokens.at(self.token);
+        pub fn render(self: *const ExpectedCommaOrEnd, tokens: []const Token, stream: var) !void {
+            const actual_token = tokens[self.token];
             return stream.print("expected ',' or '{}', found '{}'", .{
                 self.end_id.symbol(),
                 actual_token.id.symbol(),
@@ -377,8 +374,8 @@ pub const Error = union(enum) {
 
             token: TokenIndex,
 
-            pub fn render(self: *const ThisError, tokens: *Tree.TokenList, stream: var) !void {
-                const actual_token = tokens.at(self.token);
+            pub fn render(self: *const ThisError, tokens: []const Token, stream: var) !void {
+                const actual_token = tokens[self.token];
                 return stream.print(msg, .{actual_token.id.symbol()});
             }
         };
@@ -390,7 +387,7 @@ pub const Error = union(enum) {
 
             token: TokenIndex,
 
-            pub fn render(self: *const ThisError, tokens: *Tree.TokenList, stream: var) !void {
+            pub fn render(self: *const ThisError, tokens: []const Token, stream: var) !void {
                 return stream.writeAll(msg);
             }
         };
@@ -400,6 +397,23 @@ pub const Error = union(enum) {
 pub const Node = struct {
     id: Id,
 
+    /// All the child Node types use this same Iterator state for their iteration.
+    pub const Iterator = struct {
+        parent_node: *const Node,
+        node: ?*LinkedList(*Node).Node,
+        index: usize,
+
+        pub fn next(it: *Iterator) ?*Node {
+            inline for (@typeInfo(Id).Enum.fields) |f| {
+                if (it.parent_node.id == @field(Id, f.name)) {
+                    const T = @field(Node, f.name);
+                    return @fieldParentPtr(T, "base", it.parent_node).iterateNext(it);
+                }
+            }
+            unreachable;
+        }
+    };
+
     pub const Id = enum {
         // Top level
         Root,
@@ -473,11 +487,11 @@ pub const Node = struct {
         return null;
     }
 
-    pub fn iterate(base: *Node, index: usize) ?*Node {
+    pub fn iterate(base: *Node) Iterator {
         inline for (@typeInfo(Id).Enum.fields) |f| {
             if (base.id == @field(Id, f.name)) {
                 const T = @field(Node, f.name);
-                return @fieldParentPtr(T, "base", base).iterate(index);
+                return @fieldParentPtr(T, "base", base).iterate();
             }
         }
         unreachable;
@@ -607,21 +621,35 @@ pub const Node = struct {
         decls: DeclList,
         eof_token: TokenIndex,
 
-        pub const DeclList = SegmentedList(*Node, 4);
+        pub const DeclList = LinkedList(*Node);
 
-        pub fn iterate(self: *Root, index: usize) ?*Node {
-            if (index < self.decls.len) {
-                return self.decls.at(index).*;
-            }
-            return null;
+        pub fn iterate(self: *const Root) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.decls.first };
+        }
+
+        pub fn iterateNext(self: *const Root, it: *Node.Iterator) ?*Node {
+            const decl = it.node orelse return null;
+            it.node = decl.next;
+            return decl.data;
         }
 
         pub fn firstToken(self: *const Root) TokenIndex {
-            return if (self.decls.len == 0) self.eof_token else (self.decls.at(0).*).firstToken();
+            if (self.decls.first) |first| {
+                return first.data.firstToken();
+            } else {
+                return self.eof_token;
+            }
         }
 
         pub fn lastToken(self: *const Root) TokenIndex {
-            return if (self.decls.len == 0) self.eof_token else (self.decls.at(self.decls.len - 1).*).lastToken();
+            if (self.decls.first) |first| {
+                var node = first;
+                while (true) {
+                    node = node.next orelse return node.data.lastToken();
+                }
+            } else {
+                return self.eof_token;
+            }
         }
     };
 
@@ -642,8 +670,13 @@ pub const Node = struct {
         init_node: ?*Node,
         semicolon_token: TokenIndex,
 
-        pub fn iterate(self: *VarDecl, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const VarDecl) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const VarDecl, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.type_node) |type_node| {
                 if (i < 1) return type_node;
@@ -668,6 +701,7 @@ pub const Node = struct {
             return null;
         }
 
+
         pub fn firstToken(self: *const VarDecl) TokenIndex {
             if (self.visib_token) |visib_token| return visib_token;
             if (self.thread_local_token) |thread_local_token| return thread_local_token;
@@ -690,8 +724,13 @@ pub const Node = struct {
         expr: *Node,
         semicolon_token: TokenIndex,
 
-        pub fn iterate(self: *Use, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Use) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Use, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
@@ -715,15 +754,16 @@ pub const Node = struct {
         decls: DeclList,
         rbrace_token: TokenIndex,
 
-        pub const DeclList = SegmentedList(*Node, 2);
+        pub const DeclList = LinkedList(*Node);
 
-        pub fn iterate(self: *ErrorSetDecl, index: usize) ?*Node {
-            var i = index;
-
-            if (i < self.decls.len) return self.decls.at(i).*;
-            i -= self.decls.len;
+        pub fn iterate(self: *const ErrorSetDecl) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.decls.first };
+        }
 
-            return null;
+        pub fn iterateNext(self: *const ErrorSetDecl, it: *Node.Iterator) ?*Node {
+            const decl = it.node orelse return null;
+            it.node = decl.next;
+            return decl.data;
         }
 
         pub fn firstToken(self: *const ErrorSetDecl) TokenIndex {
@@ -752,8 +792,13 @@ pub const Node = struct {
             Type: *Node,
         };
 
-        pub fn iterate(self: *ContainerDecl, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const ContainerDecl) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.fields_and_decls.first };
+        }
+
+        pub fn iterateNext(self: *const ContainerDecl, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             switch (self.init_arg_expr) {
                 .Type => |t| {
@@ -763,8 +808,10 @@ pub const Node = struct {
                 .None, .Enum => {},
             }
 
-            if (i < self.fields_and_decls.len) return self.fields_and_decls.at(i).*;
-            i -= self.fields_and_decls.len;
+            if (it.node) |child| {
+                it.node = child.next;
+                return child.data;
+            }
 
             return null;
         }
@@ -790,8 +837,13 @@ pub const Node = struct {
         value_expr: ?*Node,
         align_expr: ?*Node,
 
-        pub fn iterate(self: *ContainerField, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const ContainerField) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const ContainerField, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.type_expr) |type_expr| {
                 if (i < 1) return type_expr;
@@ -837,8 +889,13 @@ pub const Node = struct {
         doc_comments: ?*DocComment,
         name_token: TokenIndex,
 
-        pub fn iterate(self: *ErrorTag, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const ErrorTag) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const ErrorTag, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.doc_comments) |comments| {
                 if (i < 1) return &comments.base;
@@ -861,7 +918,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .Identifier },
         token: TokenIndex,
 
-        pub fn iterate(self: *Identifier, index: usize) ?*Node {
+        pub fn iterate(self: *const Identifier) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Identifier, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -892,7 +953,7 @@ pub const Node = struct {
         is_extern_prototype: bool = false, // TODO: Remove once extern fn rewriting is
         is_async: bool = false, // TODO: remove once async fn rewriting is
 
-        pub const ParamList = SegmentedList(*Node, 2);
+        pub const ParamList = LinkedList(*Node);
 
         pub const ReturnType = union(enum) {
             Explicit: *Node,
@@ -900,16 +961,24 @@ pub const Node = struct {
             Invalid: TokenIndex,
         };
 
-        pub fn iterate(self: *FnProto, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const FnProto) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.params.first };
+        }
+
+        pub fn iterateNext(self: *const FnProto, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.lib_name) |lib_name| {
                 if (i < 1) return lib_name;
                 i -= 1;
             }
 
-            if (i < self.params.len) return self.params.at(self.params.len - i - 1).*;
-            i -= self.params.len;
+            if (it.node) |param| {
+                it.index -= 1;
+                it.node = param.next;
+                return param.data;
+            }
 
             if (self.align_expr) |align_expr| {
                 if (i < 1) return align_expr;
@@ -963,8 +1032,13 @@ pub const Node = struct {
             return_type: *Node,
         };
 
-        pub fn iterate(self: *AnyFrameType, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const AnyFrameType) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const AnyFrameType, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.result) |result| {
                 if (i < 1) return result.return_type;
@@ -998,8 +1072,13 @@ pub const Node = struct {
             type_expr: *Node,
         };
 
-        pub fn iterate(self: *ParamDecl, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const ParamDecl) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const ParamDecl, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) {
                 switch (self.param_type) {
@@ -1039,13 +1118,14 @@ pub const Node = struct {
 
         pub const StatementList = Root.DeclList;
 
-        pub fn iterate(self: *Block, index: usize) ?*Node {
-            var i = index;
-
-            if (i < self.statements.len) return self.statements.at(i).*;
-            i -= self.statements.len;
+        pub fn iterate(self: *const Block) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.statements.first };
+        }
 
-            return null;
+        pub fn iterateNext(self: *const Block, it: *Node.Iterator) ?*Node {
+            const child = it.node orelse return null;
+            it.node = child.next;
+            return child.data;
         }
 
         pub fn firstToken(self: *const Block) TokenIndex {
@@ -1067,8 +1147,13 @@ pub const Node = struct {
         payload: ?*Node,
         expr: *Node,
 
-        pub fn iterate(self: *Defer, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Defer) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Defer, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
@@ -1091,8 +1176,13 @@ pub const Node = struct {
         comptime_token: TokenIndex,
         expr: *Node,
 
-        pub fn iterate(self: *Comptime, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Comptime) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Comptime, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
@@ -1114,8 +1204,13 @@ pub const Node = struct {
         nosuspend_token: TokenIndex,
         expr: *Node,
 
-        pub fn iterate(self: *Nosuspend, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Nosuspend) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Nosuspend, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
@@ -1138,8 +1233,13 @@ pub const Node = struct {
         error_symbol: *Node,
         rpipe: TokenIndex,
 
-        pub fn iterate(self: *Payload, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Payload) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Payload, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.error_symbol;
             i -= 1;
@@ -1163,8 +1263,13 @@ pub const Node = struct {
         value_symbol: *Node,
         rpipe: TokenIndex,
 
-        pub fn iterate(self: *PointerPayload, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const PointerPayload) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const PointerPayload, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.value_symbol;
             i -= 1;
@@ -1189,8 +1294,13 @@ pub const Node = struct {
         index_symbol: ?*Node,
         rpipe: TokenIndex,
 
-        pub fn iterate(self: *PointerIndexPayload, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const PointerIndexPayload) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const PointerIndexPayload, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.value_symbol;
             i -= 1;
@@ -1218,8 +1328,13 @@ pub const Node = struct {
         payload: ?*Node,
         body: *Node,
 
-        pub fn iterate(self: *Else, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Else) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Else, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.payload) |payload| {
                 if (i < 1) return payload;
@@ -1250,16 +1365,24 @@ pub const Node = struct {
         cases: CaseList,
         rbrace: TokenIndex,
 
-        pub const CaseList = SegmentedList(*Node, 2);
+        pub const CaseList = LinkedList(*Node);
+
+        pub fn iterate(self: *const Switch) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.cases.first };
+        }
 
-        pub fn iterate(self: *Switch, index: usize) ?*Node {
-            var i = index;
+        pub fn iterateNext(self: *const Switch, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
 
-            if (i < self.cases.len) return self.cases.at(i).*;
-            i -= self.cases.len;
+            if (it.node) |child| {
+                it.index -= 1;
+                it.node = child.next;
+                return child.data;
+            }
 
             return null;
         }
@@ -1280,13 +1403,21 @@ pub const Node = struct {
         payload: ?*Node,
         expr: *Node,
 
-        pub const ItemList = SegmentedList(*Node, 1);
+        pub const ItemList = LinkedList(*Node);
+
+        pub fn iterate(self: *const SwitchCase) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.items.first };
+        }
 
-        pub fn iterate(self: *SwitchCase, index: usize) ?*Node {
-            var i = index;
+        pub fn iterateNext(self: *const SwitchCase, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
-            if (i < self.items.len) return self.items.at(i).*;
-            i -= self.items.len;
+            if (it.node) |child| {
+                it.index -= 1;
+                it.node = child.next;
+                return child.data;
+            }
 
             if (self.payload) |payload| {
                 if (i < 1) return payload;
@@ -1300,7 +1431,7 @@ pub const Node = struct {
         }
 
         pub fn firstToken(self: *const SwitchCase) TokenIndex {
-            return (self.items.at(0).*).firstToken();
+            return self.items.first.?.data.firstToken();
         }
 
         pub fn lastToken(self: *const SwitchCase) TokenIndex {
@@ -1312,7 +1443,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .SwitchElse },
         token: TokenIndex,
 
-        pub fn iterate(self: *SwitchElse, index: usize) ?*Node {
+        pub fn iterate(self: *const SwitchElse) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const SwitchElse, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -1336,8 +1471,13 @@ pub const Node = struct {
         body: *Node,
         @"else": ?*Else,
 
-        pub fn iterate(self: *While, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const While) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const While, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.condition;
             i -= 1;
@@ -1394,8 +1534,13 @@ pub const Node = struct {
         body: *Node,
         @"else": ?*Else,
 
-        pub fn iterate(self: *For, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const For) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const For, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.array_expr;
             i -= 1;
@@ -1443,8 +1588,13 @@ pub const Node = struct {
         body: *Node,
         @"else": ?*Else,
 
-        pub fn iterate(self: *If, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const If) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const If, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.condition;
             i -= 1;
@@ -1531,8 +1681,13 @@ pub const Node = struct {
             UnwrapOptional,
         };
 
-        pub fn iterate(self: *InfixOp, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const InfixOp) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const InfixOp, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.lhs;
             i -= 1;
@@ -1649,8 +1804,13 @@ pub const Node = struct {
             };
         };
 
-        pub fn iterate(self: *PrefixOp, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const PrefixOp) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const PrefixOp, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             switch (self.op) {
                 .PtrType, .SliceType => |addr_of_info| {
@@ -1707,8 +1867,13 @@ pub const Node = struct {
         name_token: TokenIndex,
         expr: *Node,
 
-        pub fn iterate(self: *FieldInitializer, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const FieldInitializer) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const FieldInitializer, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
@@ -1745,13 +1910,13 @@ pub const Node = struct {
             Deref,
             UnwrapOptional,
 
-            pub const InitList = SegmentedList(*Node, 2);
+            pub const InitList = LinkedList(*Node);
 
             pub const Call = struct {
                 params: ParamList,
                 async_token: ?TokenIndex,
 
-                pub const ParamList = SegmentedList(*Node, 2);
+                pub const ParamList = LinkedList(*Node);
             };
 
             pub const Slice = struct {
@@ -1761,8 +1926,20 @@ pub const Node = struct {
             };
         };
 
-        pub fn iterate(self: *SuffixOp, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const SuffixOp) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0,
+                .node = switch(self.op) {
+                    .Call => |call| call.params.first,
+                    .ArrayInitializer => |ai| ai.first,
+                    .StructInitializer => |si| si.first,
+                    else => null,
+                },
+            };
+        }
+
+        pub fn iterateNext(self: *const SuffixOp, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             switch (self.lhs) {
                 .node => |node| {
@@ -1773,9 +1950,12 @@ pub const Node = struct {
             }
 
             switch (self.op) {
-                .Call => |*call_info| {
-                    if (i < call_info.params.len) return call_info.params.at(i).*;
-                    i -= call_info.params.len;
+                .Call => |call_info| {
+                    if (it.node) |child| {
+                        it.index -= 1;
+                        it.node = child.next;
+                        return child.data;
+                    }
                 },
                 .ArrayAccess => |index_expr| {
                     if (i < 1) return index_expr;
@@ -1794,13 +1974,19 @@ pub const Node = struct {
                         i -= 1;
                     }
                 },
-                .ArrayInitializer => |*exprs| {
-                    if (i < exprs.len) return exprs.at(i).*;
-                    i -= exprs.len;
+                .ArrayInitializer => |exprs| {
+                    if (it.node) |child| {
+                        it.index -= 1;
+                        it.node = child.next;
+                        return child.data;
+                    }
                 },
-                .StructInitializer => |*fields| {
-                    if (i < fields.len) return fields.at(i).*;
-                    i -= fields.len;
+                .StructInitializer => |fields| {
+                    if (it.node) |child| {
+                        it.index -= 1;
+                        it.node = child.next;
+                        return child.data;
+                    }
                 },
                 .UnwrapOptional,
                 .Deref,
@@ -1832,8 +2018,13 @@ pub const Node = struct {
         expr: *Node,
         rparen: TokenIndex,
 
-        pub fn iterate(self: *GroupedExpression, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const GroupedExpression) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const GroupedExpression, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.expr;
             i -= 1;
@@ -1862,8 +2053,13 @@ pub const Node = struct {
             Return,
         };
 
-        pub fn iterate(self: *ControlFlowExpression, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const ControlFlowExpression) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const ControlFlowExpression, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             switch (self.kind) {
                 .Break, .Continue => |maybe_label| {
@@ -1910,8 +2106,13 @@ pub const Node = struct {
         suspend_token: TokenIndex,
         body: ?*Node,
 
-        pub fn iterate(self: *Suspend, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Suspend) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Suspend, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (self.body) |body| {
                 if (i < 1) return body;
@@ -1938,7 +2139,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .IntegerLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *IntegerLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const IntegerLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const IntegerLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -1956,7 +2161,11 @@ pub const Node = struct {
         dot: TokenIndex,
         name: TokenIndex,
 
-        pub fn iterate(self: *EnumLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const EnumLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const EnumLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -1973,7 +2182,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .FloatLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *FloatLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const FloatLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const FloatLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -1992,15 +2205,16 @@ pub const Node = struct {
         params: ParamList,
         rparen_token: TokenIndex,
 
-        pub const ParamList = SegmentedList(*Node, 2);
+        pub const ParamList = LinkedList(*Node);
 
-        pub fn iterate(self: *BuiltinCall, index: usize) ?*Node {
-            var i = index;
-
-            if (i < self.params.len) return self.params.at(i).*;
-            i -= self.params.len;
+        pub fn iterate(self: *const BuiltinCall) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = self.params.first };
+        }
 
-            return null;
+        pub fn iterateNext(self: *const BuiltinCall, it: *Node.Iterator) ?*Node {
+            const param = it.node orelse return null;
+            it.node = param.next;
+            return param.data;
         }
 
         pub fn firstToken(self: *const BuiltinCall) TokenIndex {
@@ -2016,7 +2230,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .StringLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *StringLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const StringLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const StringLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2033,18 +2251,25 @@ pub const Node = struct {
         base: Node = Node{ .id = .MultilineStringLiteral },
         lines: LineList,
 
-        pub const LineList = SegmentedList(TokenIndex, 4);
+        pub const LineList = LinkedList(TokenIndex);
+
+        pub fn iterate(self: *const MultilineStringLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
 
-        pub fn iterate(self: *MultilineStringLiteral, index: usize) ?*Node {
+        pub fn iterateNext(self: *const MultilineStringLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
         pub fn firstToken(self: *const MultilineStringLiteral) TokenIndex {
-            return self.lines.at(0).*;
+            return self.lines.first.?.data;
         }
 
         pub fn lastToken(self: *const MultilineStringLiteral) TokenIndex {
-            return self.lines.at(self.lines.len - 1).*;
+            var node = self.lines.first.?;
+            while (true) {
+                node = node.next orelse return node.data;
+            }
         }
     };
 
@@ -2052,7 +2277,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .CharLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *CharLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const CharLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const CharLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2069,7 +2298,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .BoolLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *BoolLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const BoolLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const BoolLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2086,7 +2319,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .NullLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *NullLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const NullLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const NullLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2103,7 +2340,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .UndefinedLiteral },
         token: TokenIndex,
 
-        pub fn iterate(self: *UndefinedLiteral, index: usize) ?*Node {
+        pub fn iterate(self: *const UndefinedLiteral) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const UndefinedLiteral, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2129,8 +2370,13 @@ pub const Node = struct {
             Return: *Node,
         };
 
-        pub fn iterate(self: *AsmOutput, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const AsmOutput) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const AsmOutput, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.symbolic_name;
             i -= 1;
@@ -2169,8 +2415,13 @@ pub const Node = struct {
         expr: *Node,
         rparen: TokenIndex,
 
-        pub fn iterate(self: *AsmInput, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const AsmInput) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const AsmInput, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.symbolic_name;
             i -= 1;
@@ -2203,18 +2454,31 @@ pub const Node = struct {
         clobbers: ClobberList,
         rparen: TokenIndex,
 
-        pub const OutputList = SegmentedList(*AsmOutput, 2);
-        pub const InputList = SegmentedList(*AsmInput, 2);
-        pub const ClobberList = SegmentedList(*Node, 2);
+        pub const OutputList = LinkedList(*AsmOutput);
+        pub const InputList = LinkedList(*AsmInput);
+        pub const ClobberList = LinkedList(*Node);
 
-        pub fn iterate(self: *Asm, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const Asm) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null};
+        }
 
-            if (i < self.outputs.len) return &self.outputs.at(i).*.base;
-            i -= self.outputs.len;
+        pub fn iterateNext(self: *const Asm, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
-            if (i < self.inputs.len) return &self.inputs.at(i).*.base;
-            i -= self.inputs.len;
+            var output: ?*LinkedList(*AsmOutput).Node = self.outputs.first;
+            while (output) |o| {
+                if (i < 1) return &o.data.base;
+                i -= 1;
+                output = o.next;
+            }
+
+            var input: ?*LinkedList(*AsmInput).Node = self.inputs.first;
+            while (input) |o| {
+                if (i < 1) return &o.data.base;
+                i -= 1;
+                input = o.next;
+            }
 
             return null;
         }
@@ -2232,7 +2496,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .Unreachable },
         token: TokenIndex,
 
-        pub fn iterate(self: *Unreachable, index: usize) ?*Node {
+        pub fn iterate(self: *const Unreachable) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const Unreachable, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2249,7 +2517,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .ErrorType },
         token: TokenIndex,
 
-        pub fn iterate(self: *ErrorType, index: usize) ?*Node {
+        pub fn iterate(self: *const ErrorType) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const ErrorType, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2266,7 +2538,11 @@ pub const Node = struct {
         base: Node = Node{ .id = .VarType },
         token: TokenIndex,
 
-        pub fn iterate(self: *VarType, index: usize) ?*Node {
+        pub fn iterate(self: *const VarType) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const VarType, it: *Node.Iterator) ?*Node {
             return null;
         }
 
@@ -2283,18 +2559,25 @@ pub const Node = struct {
         base: Node = Node{ .id = .DocComment },
         lines: LineList,
 
-        pub const LineList = SegmentedList(TokenIndex, 4);
+        pub const LineList = LinkedList(TokenIndex);
+
+        pub fn iterate(self: *const DocComment) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
 
-        pub fn iterate(self: *DocComment, index: usize) ?*Node {
+        pub fn iterateNext(self: *const DocComment, it: *Node.Iterator) ?*Node {
             return null;
         }
 
         pub fn firstToken(self: *const DocComment) TokenIndex {
-            return self.lines.at(0).*;
+            return self.lines.first.?.data;
         }
 
         pub fn lastToken(self: *const DocComment) TokenIndex {
-            return self.lines.at(self.lines.len - 1).*;
+            var node = self.lines.first.?;
+            while (true) {
+                node = node.next orelse return node.data;
+            }
         }
     };
 
@@ -2305,8 +2588,13 @@ pub const Node = struct {
         name: *Node,
         body_node: *Node,
 
-        pub fn iterate(self: *TestDecl, index: usize) ?*Node {
-            var i = index;
+        pub fn iterate(self: *const TestDecl) Node.Iterator {
+            return .{ .parent_node = &self.base, .index = 0, .node = null };
+        }
+
+        pub fn iterateNext(self: *const TestDecl, it: *Node.Iterator) ?*Node {
+            var i = it.index;
+            it.index += 1;
 
             if (i < 1) return self.body_node;
             i -= 1;
@@ -2331,5 +2619,6 @@ test "iterate" {
         .eof_token = 0,
     };
     var base = &root.base;
-    testing.expect(base.iterate(0) == null);
+    var it = base.iterate();
+    testing.expect(it.next() == null);
 }
lib/std/zig/parse.zig
@@ -7,3303 +7,3349 @@ const Tree = ast.Tree;
 const AstError = ast.Error;
 const TokenIndex = ast.TokenIndex;
 const Token = std.zig.Token;
-const TokenIterator = Tree.TokenList.Iterator;
 
 pub const Error = error{ParseError} || Allocator.Error;
 
 /// Result should be freed with tree.deinit() when there are
 /// no more references to any of the tokens or nodes.
-pub fn parse(allocator: *Allocator, source: []const u8) Allocator.Error!*Tree {
-    const tree = blk: {
-        // This block looks unnecessary, but is a "foot-shield" to prevent the SegmentedLists
-        // from being initialized with a pointer to this `arena`, which is created on
-        // the stack. Following code should instead refer to `&tree.arena_allocator`, a
-        // pointer to data which lives safely on the heap and will outlive `parse`. See:
-        // https://github.com/ziglang/zig/commit/cb4fb14b6e66bd213575f69eec9598be8394fae6
-        var arena = std.heap.ArenaAllocator.init(allocator);
-        errdefer arena.deinit();
-        const tree = try arena.allocator.create(ast.Tree);
-        tree.* = .{
-            .source = source,
-            .root_node = undefined,
-            .arena_allocator = arena,
-            .tokens = undefined,
-            .errors = undefined,
-        };
-        break :blk tree;
-    };
-    errdefer tree.deinit();
-    const arena = &tree.arena_allocator.allocator;
-
-    tree.tokens = ast.Tree.TokenList.init(arena);
-    tree.errors = ast.Tree.ErrorList.init(arena);
+pub fn parse(gpa: *Allocator, source: []const u8) Allocator.Error!*Tree {
+    // TODO optimization idea: ensureCapacity on the tokens list and
+    // then appendAssumeCapacity inside the loop.
+    var tokens = std.ArrayList(Token).init(gpa);
+    defer tokens.deinit();
 
     var tokenizer = std.zig.Tokenizer.init(source);
     while (true) {
-        const tree_token = try tree.tokens.addOne();
+        const tree_token = try tokens.addOne();
         tree_token.* = tokenizer.next();
         if (tree_token.id == .Eof) break;
     }
-    var it = tree.tokens.iterator(0);
 
-    while (it.peek().?.id == .LineComment) _ = it.next();
+    var parser: Parser = .{
+        .source = source,
+        .arena = std.heap.ArenaAllocator.init(gpa),
+        .gpa = gpa,
+        .tokens = tokens.items,
+        .errors = .{},
+        .tok_i = 0,
+    };
+    defer parser.errors.deinit(gpa);
+    errdefer parser.arena.deinit();
 
-    tree.root_node = try parseRoot(arena, &it, tree);
+    while (tokens.items[parser.tok_i].id == .LineComment) parser.tok_i += 1;
 
-    return tree;
-}
+    const root_node = try parser.parseRoot();
 
-/// Root <- skip ContainerMembers eof
-fn parseRoot(arena: *Allocator, it: *TokenIterator, tree: *Tree) Allocator.Error!*Node.Root {
-    const node = try arena.create(Node.Root);
-    node.* = .{
-        .decls = try parseContainerMembers(arena, it, tree, true),
-        // parseContainerMembers will try to skip as much
-        // invalid tokens as it can so this can only be the EOF
-        .eof_token = eatToken(it, .Eof).?,
+    const tree = try parser.arena.allocator.create(Tree);
+    tree.* = .{
+        .gpa = gpa,
+        .source = source,
+        .tokens = tokens.toOwnedSlice(),
+        .errors = parser.errors.toOwnedSlice(gpa),
+        .root_node = root_node,
+        .arena = parser.arena.state,
     };
-    return node;
+    return tree;
 }
 
-/// ContainerMembers
-///     <- TestDecl ContainerMembers
-///      / TopLevelComptime ContainerMembers
-///      / KEYWORD_pub? TopLevelDecl ContainerMembers
-///      / ContainerField COMMA ContainerMembers
-///      / ContainerField
-///      /
-fn parseContainerMembers(arena: *Allocator, it: *TokenIterator, tree: *Tree, top_level: bool) !Node.Root.DeclList {
-    var list = Node.Root.DeclList.init(arena);
-
-    var field_state: union(enum) {
-        /// no fields have been seen
-        none,
-        /// currently parsing fields
-        seen,
-        /// saw fields and then a declaration after them.
-        /// payload is first token of previous declaration.
-        end: TokenIndex,
-        /// ther was a declaration between fields, don't report more errors
-        err,
-    } = .none;
+/// Represents in-progress parsing, will be converted to an ast.Tree after completion.
+const Parser = struct {
+    arena: std.heap.ArenaAllocator,
+    gpa: *Allocator,
+    source: []const u8,
+    tokens: []const Token,
+    tok_i: TokenIndex,
+    errors: std.ArrayListUnmanaged(AstError),
 
-    while (true) {
-        if (try parseContainerDocComments(arena, it, tree)) |node| {
-            try list.push(node);
-            continue;
-        }
+    /// Root <- skip ContainerMembers eof
+    fn parseRoot(p: *Parser) Allocator.Error!*Node.Root {
+        const node = try p.arena.allocator.create(Node.Root);
+        node.* = .{
+            .decls = try parseContainerMembers(p, true),
+            // parseContainerMembers will try to skip as much
+            // invalid tokens as it can so this can only be the EOF
+            .eof_token = p.eatToken(.Eof).?,
+        };
+        return node;
+    }
 
-        const doc_comments = try parseDocComment(arena, it, tree);
+    fn llpush(
+        p: *Parser,
+        comptime T: type,
+        it: *?*std.SinglyLinkedList(T).Node,
+        data: T,
+    ) !*?*std.SinglyLinkedList(T).Node {
+        const llnode = try p.arena.allocator.create(std.SinglyLinkedList(T).Node);
+        llnode.* = .{ .data = data };
+        it.* = llnode;
+        return &llnode.next;
+    }
+
+    /// ContainerMembers
+    ///     <- TestDecl ContainerMembers
+    ///      / TopLevelComptime ContainerMembers
+    ///      / KEYWORD_pub? TopLevelDecl ContainerMembers
+    ///      / ContainerField COMMA ContainerMembers
+    ///      / ContainerField
+    ///      /
+    fn parseContainerMembers(p: *Parser, top_level: bool) !Node.Root.DeclList {
+        var list = Node.Root.DeclList{};
+        var list_it = &list.first;
+
+        var field_state: union(enum) {
+            /// no fields have been seen
+            none,
+            /// currently parsing fields
+            seen,
+            /// saw fields and then a declaration after them.
+            /// payload is first token of previous declaration.
+            end: TokenIndex,
+            /// ther was a declaration between fields, don't report more errors
+            err,
+        } = .none;
 
-        if (parseTestDecl(arena, it, tree) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.ParseError => {
-                findNextContainerMember(it);
+        while (true) {
+            if (try p.parseContainerDocComments()) |node| {
+                list_it = try p.llpush(*Node, list_it, node);
                 continue;
-            },
-        }) |node| {
-            if (field_state == .seen) {
-                field_state = .{ .end = node.firstToken() };
             }
-            node.cast(Node.TestDecl).?.doc_comments = doc_comments;
-            try list.push(node);
-            continue;
-        }
 
-        if (parseTopLevelComptime(arena, it, tree) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.ParseError => {
-                findNextContainerMember(it);
+            const doc_comments = try p.parseDocComment();
+
+            if (p.parseTestDecl() catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.ParseError => {
+                    p.findNextContainerMember();
+                    continue;
+                },
+            }) |node| {
+                if (field_state == .seen) {
+                    field_state = .{ .end = node.firstToken() };
+                }
+                node.cast(Node.TestDecl).?.doc_comments = doc_comments;
+                list_it = try p.llpush(*Node, list_it, node);
                 continue;
-            },
-        }) |node| {
-            if (field_state == .seen) {
-                field_state = .{ .end = node.firstToken() };
             }
-            node.cast(Node.Comptime).?.doc_comments = doc_comments;
-            try list.push(node);
-            continue;
-        }
-
-        const visib_token = eatToken(it, .Keyword_pub);
 
-        if (parseTopLevelDecl(arena, it, tree) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.ParseError => {
-                findNextContainerMember(it);
+            if (p.parseTopLevelComptime() catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.ParseError => {
+                    p.findNextContainerMember();
+                    continue;
+                },
+            }) |node| {
+                if (field_state == .seen) {
+                    field_state = .{ .end = node.firstToken() };
+                }
+                node.cast(Node.Comptime).?.doc_comments = doc_comments;
+                list_it = try p.llpush(*Node, list_it, node);
                 continue;
-            },
-        }) |node| {
-            if (field_state == .seen) {
-                field_state = .{ .end = visib_token orelse node.firstToken() };
             }
-            switch (node.id) {
-                .FnProto => {
-                    node.cast(Node.FnProto).?.doc_comments = doc_comments;
-                    node.cast(Node.FnProto).?.visib_token = visib_token;
-                },
-                .VarDecl => {
-                    node.cast(Node.VarDecl).?.doc_comments = doc_comments;
-                    node.cast(Node.VarDecl).?.visib_token = visib_token;
-                },
-                .Use => {
-                    node.cast(Node.Use).?.doc_comments = doc_comments;
-                    node.cast(Node.Use).?.visib_token = visib_token;
+
+            const visib_token = p.eatToken(.Keyword_pub);
+
+            if (p.parseTopLevelDecl() catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.ParseError => {
+                    p.findNextContainerMember();
+                    continue;
                 },
-                else => unreachable,
-            }
-            try list.push(node);
-            if (try parseAppendedDocComment(arena, it, tree, node.lastToken())) |appended_comment| {
+            }) |node| {
+                if (field_state == .seen) {
+                    field_state = .{ .end = visib_token orelse node.firstToken() };
+                }
                 switch (node.id) {
-                    .FnProto => {},
-                    .VarDecl => node.cast(Node.VarDecl).?.doc_comments = appended_comment,
-                    .Use => node.cast(Node.Use).?.doc_comments = appended_comment,
+                    .FnProto => {
+                        node.cast(Node.FnProto).?.doc_comments = doc_comments;
+                        node.cast(Node.FnProto).?.visib_token = visib_token;
+                    },
+                    .VarDecl => {
+                        node.cast(Node.VarDecl).?.doc_comments = doc_comments;
+                        node.cast(Node.VarDecl).?.visib_token = visib_token;
+                    },
+                    .Use => {
+                        node.cast(Node.Use).?.doc_comments = doc_comments;
+                        node.cast(Node.Use).?.visib_token = visib_token;
+                    },
                     else => unreachable,
                 }
+                list_it = try p.llpush(*Node, list_it, node);
+                if (try p.parseAppendedDocComment(node.lastToken())) |appended_comment| {
+                    switch (node.id) {
+                        .FnProto => {},
+                        .VarDecl => node.cast(Node.VarDecl).?.doc_comments = appended_comment,
+                        .Use => node.cast(Node.Use).?.doc_comments = appended_comment,
+                        else => unreachable,
+                    }
+                }
+                continue;
             }
-            continue;
-        }
-
-        if (visib_token != null) {
-            try tree.errors.push(.{
-                .ExpectedPubItem = .{ .token = it.index },
-            });
-            // ignore this pub
-            continue;
-        }
 
-        if (parseContainerField(arena, it, tree) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.ParseError => {
-                // attempt to recover
-                findNextContainerMember(it);
+            if (visib_token != null) {
+                try p.errors.append(p.gpa, .{
+                    .ExpectedPubItem = .{ .token = p.tok_i },
+                });
+                // ignore this pub
                 continue;
-            },
-        }) |node| {
-            switch (field_state) {
-                .none => field_state = .seen,
-                .err, .seen => {},
-                .end => |tok| {
-                    try tree.errors.push(.{
-                        .DeclBetweenFields = .{ .token = tok },
-                    });
-                    // continue parsing, error will be reported later
-                    field_state = .err;
-                },
             }
 
-            const field = node.cast(Node.ContainerField).?;
-            field.doc_comments = doc_comments;
-            try list.push(node);
-            const comma = eatToken(it, .Comma) orelse {
-                // try to continue parsing
-                const index = it.index;
-                findNextContainerMember(it);
-                const next = it.peek().?.id;
-                switch (next) {
-                    .Eof => break,
-                    else => {
-                        if (next == .RBrace) {
-                            if (!top_level) break;
-                            _ = nextToken(it);
-                        }
-
-                        // add error and continue
-                        try tree.errors.push(.{
-                            .ExpectedToken = .{ .token = index, .expected_id = .Comma },
+            if (p.parseContainerField() catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.ParseError => {
+                    // attempt to recover
+                    p.findNextContainerMember();
+                    continue;
+                },
+            }) |node| {
+                switch (field_state) {
+                    .none => field_state = .seen,
+                    .err, .seen => {},
+                    .end => |tok| {
+                        try p.errors.append(p.gpa, .{
+                            .DeclBetweenFields = .{ .token = tok },
                         });
-                        continue;
+                        // continue parsing, error will be reported later
+                        field_state = .err;
                     },
                 }
-            };
-            if (try parseAppendedDocComment(arena, it, tree, comma)) |appended_comment|
-                field.doc_comments = appended_comment;
-            continue;
-        }
 
-        // Dangling doc comment
-        if (doc_comments != null) {
-            try tree.errors.push(.{
-                .UnattachedDocComment = .{ .token = doc_comments.?.firstToken() },
-            });
-        }
+                const field = node.cast(Node.ContainerField).?;
+                field.doc_comments = doc_comments;
+                list_it = try p.llpush(*Node, list_it, node);
+                const comma = p.eatToken(.Comma) orelse {
+                    // try to continue parsing
+                    const index = p.tok_i;
+                    p.findNextContainerMember();
+                    const next = p.tokens[p.tok_i].id;
+                    switch (next) {
+                        .Eof => break,
+                        else => {
+                            if (next == .RBrace) {
+                                if (!top_level) break;
+                                _ = p.nextToken();
+                            }
+
+                            // add error and continue
+                            try p.errors.append(p.gpa, .{
+                                .ExpectedToken = .{ .token = index, .expected_id = .Comma },
+                            });
+                            continue;
+                        },
+                    }
+                };
+                if (try p.parseAppendedDocComment(comma)) |appended_comment|
+                    field.doc_comments = appended_comment;
+                continue;
+            }
 
-        const next = it.peek().?.id;
-        switch (next) {
-            .Eof => break,
-            .Keyword_comptime => {
-                _ = nextToken(it);
-                try tree.errors.push(.{
-                    .ExpectedBlockOrField = .{ .token = it.index },
+            // Dangling doc comment
+            if (doc_comments != null) {
+                try p.errors.append(p.gpa, .{
+                    .UnattachedDocComment = .{ .token = doc_comments.?.firstToken() },
                 });
-            },
-            else => {
-                const index = it.index;
-                if (next == .RBrace) {
-                    if (!top_level) break;
-                    _ = nextToken(it);
-                }
+            }
 
-                // this was likely not supposed to end yet,
-                // try to find the next declaration
-                findNextContainerMember(it);
-                try tree.errors.push(.{
-                    .ExpectedContainerMembers = .{ .token = index },
-                });
-            },
+            const next = p.tokens[p.tok_i].id;
+            switch (next) {
+                .Eof => break,
+                .Keyword_comptime => {
+                    _ = p.nextToken();
+                    try p.errors.append(p.gpa, .{
+                        .ExpectedBlockOrField = .{ .token = p.tok_i },
+                    });
+                },
+                else => {
+                    const index = p.tok_i;
+                    if (next == .RBrace) {
+                        if (!top_level) break;
+                        _ = p.nextToken();
+                    }
+
+                    // this was likely not supposed to end yet,
+                    // try to find the next declaration
+                    p.findNextContainerMember();
+                    try p.errors.append(p.gpa, .{
+                        .ExpectedContainerMembers = .{ .token = index },
+                    });
+                },
+            }
         }
-    }
 
-    return list;
-}
+        return list;
+    }
 
-/// Attempts to find next container member by searching for certain tokens
-fn findNextContainerMember(it: *TokenIterator) void {
-    var level: u32 = 0;
-    while (true) {
-        const tok = nextToken(it);
-        switch (tok.ptr.id) {
-            // any of these can start a new top level declaration
-            .Keyword_test,
-            .Keyword_comptime,
-            .Keyword_pub,
-            .Keyword_export,
-            .Keyword_extern,
-            .Keyword_inline,
-            .Keyword_noinline,
-            .Keyword_usingnamespace,
-            .Keyword_threadlocal,
-            .Keyword_const,
-            .Keyword_var,
-            .Keyword_fn,
-            .Identifier,
-            => {
-                if (level == 0) {
-                    putBackToken(it, tok.index);
-                    return;
-                }
-            },
-            .Comma, .Semicolon => {
-                // this decl was likely meant to end here
-                if (level == 0) {
-                    return;
-                }
-            },
-            .LParen, .LBracket, .LBrace => level += 1,
-            .RParen, .RBracket => {
-                if (level != 0) level -= 1;
-            },
-            .RBrace => {
-                if (level == 0) {
-                    // end of container, exit
-                    putBackToken(it, tok.index);
+    /// Attempts to find next container member by searching for certain tokens
+    fn findNextContainerMember(p: *Parser) void {
+        var level: u32 = 0;
+        while (true) {
+            const tok = p.nextToken();
+            switch (tok.ptr.id) {
+                // any of these can start a new top level declaration
+                .Keyword_test,
+                .Keyword_comptime,
+                .Keyword_pub,
+                .Keyword_export,
+                .Keyword_extern,
+                .Keyword_inline,
+                .Keyword_noinline,
+                .Keyword_usingnamespace,
+                .Keyword_threadlocal,
+                .Keyword_const,
+                .Keyword_var,
+                .Keyword_fn,
+                .Identifier,
+                => {
+                    if (level == 0) {
+                        p.putBackToken(tok.index);
+                        return;
+                    }
+                },
+                .Comma, .Semicolon => {
+                    // this decl was likely meant to end here
+                    if (level == 0) {
+                        return;
+                    }
+                },
+                .LParen, .LBracket, .LBrace => level += 1,
+                .RParen, .RBracket => {
+                    if (level != 0) level -= 1;
+                },
+                .RBrace => {
+                    if (level == 0) {
+                        // end of container, exit
+                        p.putBackToken(tok.index);
+                        return;
+                    }
+                    level -= 1;
+                },
+                .Eof => {
+                    p.putBackToken(tok.index);
                     return;
-                }
-                level -= 1;
-            },
-            .Eof => {
-                putBackToken(it, tok.index);
-                return;
-            },
-            else => {},
+                },
+                else => {},
+            }
         }
     }
-}
 
-/// Attempts to find the next statement by searching for a semicolon
-fn findNextStmt(it: *TokenIterator) void {
-    var level: u32 = 0;
-    while (true) {
-        const tok = nextToken(it);
-        switch (tok.ptr.id) {
-            .LBrace => level += 1,
-            .RBrace => {
-                if (level == 0) {
-                    putBackToken(it, tok.index);
-                    return;
-                }
-                level -= 1;
-            },
-            .Semicolon => {
-                if (level == 0) {
+    /// Attempts to find the next statement by searching for a semicolon
+    fn findNextStmt(p: *Parser) void {
+        var level: u32 = 0;
+        while (true) {
+            const tok = p.nextToken();
+            switch (tok.ptr.id) {
+                .LBrace => level += 1,
+                .RBrace => {
+                    if (level == 0) {
+                        p.putBackToken(tok.index);
+                        return;
+                    }
+                    level -= 1;
+                },
+                .Semicolon => {
+                    if (level == 0) {
+                        return;
+                    }
+                },
+                .Eof => {
+                    p.putBackToken(tok.index);
                     return;
-                }
-            },
-            .Eof => {
-                putBackToken(it, tok.index);
-                return;
-            },
-            else => {},
+                },
+                else => {},
+            }
         }
     }
-}
-
-/// Eat a multiline container doc comment
-fn parseContainerDocComments(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    var lines = Node.DocComment.LineList.init(arena);
-    while (eatToken(it, .ContainerDocComment)) |line| {
-        try lines.push(line);
-    }
-
-    if (lines.len == 0) return null;
-
-    const node = try arena.create(Node.DocComment);
-    node.* = .{
-        .lines = lines,
-    };
-    return &node.base;
-}
-
-/// TestDecl <- KEYWORD_test STRINGLITERALSINGLE Block
-fn parseTestDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const test_token = eatToken(it, .Keyword_test) orelse return null;
-    const name_node = try expectNode(arena, it, tree, parseStringLiteralSingle, .{
-        .ExpectedStringLiteral = .{ .token = it.index },
-    });
-    const block_node = try expectNode(arena, it, tree, parseBlock, .{
-        .ExpectedLBrace = .{ .token = it.index },
-    });
-
-    const test_node = try arena.create(Node.TestDecl);
-    test_node.* = .{
-        .doc_comments = null,
-        .test_token = test_token,
-        .name = name_node,
-        .body_node = block_node,
-    };
-    return &test_node.base;
-}
-
-/// TopLevelComptime <- KEYWORD_comptime BlockExpr
-fn parseTopLevelComptime(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const tok = eatToken(it, .Keyword_comptime) orelse return null;
-    const lbrace = eatToken(it, .LBrace) orelse {
-        putBackToken(it, tok);
-        return null;
-    };
-    putBackToken(it, lbrace);
-    const block_node = try expectNode(arena, it, tree, parseBlockExpr, .{
-        .ExpectedLabelOrLBrace = .{ .token = it.index },
-    });
-
-    const comptime_node = try arena.create(Node.Comptime);
-    comptime_node.* = .{
-        .doc_comments = null,
-        .comptime_token = tok,
-        .expr = block_node,
-    };
-    return &comptime_node.base;
-}
 
-/// TopLevelDecl
-///     <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block)
-///      / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
-///      / KEYWORD_usingnamespace Expr SEMICOLON
-fn parseTopLevelDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    var lib_name: ?*Node = null;
-    const extern_export_inline_token = blk: {
-        if (eatToken(it, .Keyword_export)) |token| break :blk token;
-        if (eatToken(it, .Keyword_extern)) |token| {
-            lib_name = try parseStringLiteralSingle(arena, it, tree);
-            break :blk token;
-        }
-        if (eatToken(it, .Keyword_inline)) |token| break :blk token;
-        if (eatToken(it, .Keyword_noinline)) |token| break :blk token;
-        break :blk null;
-    };
-
-    if (try parseFnProto(arena, it, tree)) |node| {
-        const fn_node = node.cast(Node.FnProto).?;
-        fn_node.*.extern_export_inline_token = extern_export_inline_token;
-        fn_node.*.lib_name = lib_name;
-        if (eatToken(it, .Semicolon)) |_| return node;
-
-        if (try expectNodeRecoverable(arena, it, tree, parseBlock, .{
-            // since parseBlock only return error.ParseError on
-            // a missing '}' we can assume this function was
-            // supposed to end here.
-            .ExpectedSemiOrLBrace = .{ .token = it.index },
-        })) |body_node| {
-            fn_node.body_node = body_node;
-        }
-        return node;
-    }
+    /// Eat a multiline container doc comment
+    fn parseContainerDocComments(p: *Parser) !?*Node {
+        var lines = Node.DocComment.LineList{};
+        var lines_it: *?*Node.DocComment.LineList.Node = &lines.first;
 
-    if (extern_export_inline_token) |token| {
-        if (tree.tokens.at(token).id == .Keyword_inline or
-            tree.tokens.at(token).id == .Keyword_noinline)
-        {
-            try tree.errors.push(.{
-                .ExpectedFn = .{ .token = it.index },
-            });
-            return error.ParseError;
+        while (p.eatToken(.ContainerDocComment)) |line| {
+            lines_it = try p.llpush(TokenIndex, lines_it, line);
         }
-    }
 
-    const thread_local_token = eatToken(it, .Keyword_threadlocal);
+        if (lines.first == null) return null;
 
-    if (try parseVarDecl(arena, it, tree)) |node| {
-        var var_decl = node.cast(Node.VarDecl).?;
-        var_decl.*.thread_local_token = thread_local_token;
-        var_decl.*.comptime_token = null;
-        var_decl.*.extern_export_token = extern_export_inline_token;
-        var_decl.*.lib_name = lib_name;
-        return node;
+        const node = try p.arena.allocator.create(Node.DocComment);
+        node.* = .{
+            .lines = lines,
+        };
+        return &node.base;
     }
 
-    if (thread_local_token != null) {
-        try tree.errors.push(.{
-            .ExpectedVarDecl = .{ .token = it.index },
+    /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE Block
+    fn parseTestDecl(p: *Parser) !?*Node {
+        const test_token = p.eatToken(.Keyword_test) orelse return null;
+        const name_node = try p.expectNode(parseStringLiteralSingle, .{
+            .ExpectedStringLiteral = .{ .token = p.tok_i },
         });
-        // ignore this and try again;
-        return error.ParseError;
-    }
-
-    if (extern_export_inline_token) |token| {
-        try tree.errors.push(.{
-            .ExpectedVarDeclOrFn = .{ .token = it.index },
+        const block_node = try p.expectNode(parseBlock, .{
+            .ExpectedLBrace = .{ .token = p.tok_i },
         });
-        // ignore this and try again;
-        return error.ParseError;
+
+        const test_node = try p.arena.allocator.create(Node.TestDecl);
+        test_node.* = .{
+            .doc_comments = null,
+            .test_token = test_token,
+            .name = name_node,
+            .body_node = block_node,
+        };
+        return &test_node.base;
     }
 
-    return try parseUse(arena, it, tree);
-}
+    /// TopLevelComptime <- KEYWORD_comptime BlockExpr
+    fn parseTopLevelComptime(p: *Parser) !?*Node {
+        const tok = p.eatToken(.Keyword_comptime) orelse return null;
+        const lbrace = p.eatToken(.LBrace) orelse {
+            p.putBackToken(tok);
+            return null;
+        };
+        p.putBackToken(lbrace);
+        const block_node = try p.expectNode(parseBlockExpr, .{
+            .ExpectedLabelOrLBrace = .{ .token = p.tok_i },
+        });
 
-/// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr)
-fn parseFnProto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    // TODO: Remove once extern/async fn rewriting is
-    var is_async = false;
-    var is_extern = false;
-    const cc_token: ?usize = blk: {
-        if (eatToken(it, .Keyword_extern)) |token| {
-            is_extern = true;
-            break :blk token;
-        }
-        if (eatToken(it, .Keyword_async)) |token| {
-            is_async = true;
-            break :blk token;
-        }
-        break :blk null;
-    };
-    const fn_token = eatToken(it, .Keyword_fn) orelse {
-        if (cc_token) |token|
-            putBackToken(it, token);
-        return null;
-    };
-    const name_token = eatToken(it, .Identifier);
-    const lparen = try expectToken(it, tree, .LParen);
-    const params = try parseParamDeclList(arena, it, tree);
-    const rparen = try expectToken(it, tree, .RParen);
-    const align_expr = try parseByteAlign(arena, it, tree);
-    const section_expr = try parseLinkSection(arena, it, tree);
-    const callconv_expr = try parseCallconv(arena, it, tree);
-    const exclamation_token = eatToken(it, .Bang);
-
-    const return_type_expr = (try parseVarType(arena, it, tree)) orelse
-        try expectNodeRecoverable(arena, it, tree, parseTypeExpr, .{
-        // most likely the user forgot to specify the return type.
-        // Mark return type as invalid and try to continue.
-        .ExpectedReturnType = .{ .token = it.index },
-    });
-
-    // TODO https://github.com/ziglang/zig/issues/3750
-    const R = Node.FnProto.ReturnType;
-    const return_type = if (return_type_expr == null)
-        R{ .Invalid = rparen }
-    else if (exclamation_token != null)
-        R{ .InferErrorSet = return_type_expr.? }
-    else
-        R{ .Explicit = return_type_expr.? };
-
-    const var_args_token = if (params.len > 0) blk: {
-        const param_type = params.at(params.len - 1).*.cast(Node.ParamDecl).?.param_type;
-        break :blk if (param_type == .var_args) param_type.var_args else null;
-    } else
-        null;
-
-    const fn_proto_node = try arena.create(Node.FnProto);
-    fn_proto_node.* = .{
-        .doc_comments = null,
-        .visib_token = null,
-        .fn_token = fn_token,
-        .name_token = name_token,
-        .params = params,
-        .return_type = return_type,
-        .var_args_token = var_args_token,
-        .extern_export_inline_token = null,
-        .body_node = null,
-        .lib_name = null,
-        .align_expr = align_expr,
-        .section_expr = section_expr,
-        .callconv_expr = callconv_expr,
-        .is_extern_prototype = is_extern,
-        .is_async = is_async,
-    };
+        const comptime_node = try p.arena.allocator.create(Node.Comptime);
+        comptime_node.* = .{
+            .doc_comments = null,
+            .comptime_token = tok,
+            .expr = block_node,
+        };
+        return &comptime_node.base;
+    }
+
+    /// TopLevelDecl
+    ///     <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block)
+    ///      / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
+    ///      / KEYWORD_usingnamespace Expr SEMICOLON
+    fn parseTopLevelDecl(p: *Parser) !?*Node {
+        var lib_name: ?*Node = null;
+        const extern_export_inline_token = blk: {
+            if (p.eatToken(.Keyword_export)) |token| break :blk token;
+            if (p.eatToken(.Keyword_extern)) |token| {
+                lib_name = try p.parseStringLiteralSingle();
+                break :blk token;
+            }
+            if (p.eatToken(.Keyword_inline)) |token| break :blk token;
+            if (p.eatToken(.Keyword_noinline)) |token| break :blk token;
+            break :blk null;
+        };
 
-    return &fn_proto_node.base;
-}
+        if (try p.parseFnProto()) |node| {
+            const fn_node = node.cast(Node.FnProto).?;
+            fn_node.*.extern_export_inline_token = extern_export_inline_token;
+            fn_node.*.lib_name = lib_name;
+            if (p.eatToken(.Semicolon)) |_| return node;
+
+            if (try p.expectNodeRecoverable(parseBlock, .{
+                // since parseBlock only return error.ParseError on
+                // a missing '}' we can assume this function was
+                // supposed to end here.
+                .ExpectedSemiOrLBrace = .{ .token = p.tok_i },
+            })) |body_node| {
+                fn_node.body_node = body_node;
+            }
+            return node;
+        }
 
-/// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON
-fn parseVarDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const mut_token = eatToken(it, .Keyword_const) orelse
-        eatToken(it, .Keyword_var) orelse
-        return null;
+        if (extern_export_inline_token) |token| {
+            if (p.tokens[token].id == .Keyword_inline or
+                p.tokens[token].id == .Keyword_noinline)
+            {
+                try p.errors.append(p.gpa, .{
+                    .ExpectedFn = .{ .token = p.tok_i },
+                });
+                return error.ParseError;
+            }
+        }
 
-    const name_token = try expectToken(it, tree, .Identifier);
-    const type_node = if (eatToken(it, .Colon) != null)
-        try expectNode(arena, it, tree, parseTypeExpr, .{
-            .ExpectedTypeExpr = .{ .token = it.index },
-        })
-    else
-        null;
-    const align_node = try parseByteAlign(arena, it, tree);
-    const section_node = try parseLinkSection(arena, it, tree);
-    const eq_token = eatToken(it, .Equal);
-    const init_node = if (eq_token != null) blk: {
-        break :blk try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        });
-    } else null;
-    const semicolon_token = try expectToken(it, tree, .Semicolon);
-
-    const node = try arena.create(Node.VarDecl);
-    node.* = .{
-        .doc_comments = null,
-        .visib_token = null,
-        .thread_local_token = null,
-        .name_token = name_token,
-        .eq_token = eq_token,
-        .mut_token = mut_token,
-        .comptime_token = null,
-        .extern_export_token = null,
-        .lib_name = null,
-        .type_node = type_node,
-        .align_node = align_node,
-        .section_node = section_node,
-        .init_node = init_node,
-        .semicolon_token = semicolon_token,
-    };
-    return &node.base;
-}
+        const thread_local_token = p.eatToken(.Keyword_threadlocal);
 
-/// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
-fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const comptime_token = eatToken(it, .Keyword_comptime);
-    const name_token = eatToken(it, .Identifier) orelse {
-        if (comptime_token) |t| putBackToken(it, t);
-        return null;
-    };
+        if (try p.parseVarDecl()) |node| {
+            var var_decl = node.cast(Node.VarDecl).?;
+            var_decl.*.thread_local_token = thread_local_token;
+            var_decl.*.comptime_token = null;
+            var_decl.*.extern_export_token = extern_export_inline_token;
+            var_decl.*.lib_name = lib_name;
+            return node;
+        }
 
-    var align_expr: ?*Node = null;
-    var type_expr: ?*Node = null;
-    if (eatToken(it, .Colon)) |_| {
-        if (eatToken(it, .Keyword_var)) |var_tok| {
-            const node = try arena.create(ast.Node.VarType);
-            node.* = .{ .token = var_tok };
-            type_expr = &node.base;
-        } else {
-            type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
-                .ExpectedTypeExpr = .{ .token = it.index },
+        if (thread_local_token != null) {
+            try p.errors.append(p.gpa, .{
+                .ExpectedVarDecl = .{ .token = p.tok_i },
             });
-            align_expr = try parseByteAlign(arena, it, tree);
+            // ignore this and try again;
+            return error.ParseError;
         }
-    }
-
-    const value_expr = if (eatToken(it, .Equal)) |_|
-        try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        })
-    else
-        null;
 
-    const node = try arena.create(Node.ContainerField);
-    node.* = .{
-        .doc_comments = null,
-        .comptime_token = comptime_token,
-        .name_token = name_token,
-        .type_expr = type_expr,
-        .value_expr = value_expr,
-        .align_expr = align_expr,
-    };
-    return &node.base;
-}
+        if (extern_export_inline_token) |token| {
+            try p.errors.append(p.gpa, .{
+                .ExpectedVarDeclOrFn = .{ .token = p.tok_i },
+            });
+            // ignore this and try again;
+            return error.ParseError;
+        }
 
-/// Statement
-///     <- KEYWORD_comptime? VarDecl
-///      / KEYWORD_comptime BlockExprStatement
-///      / KEYWORD_nosuspend BlockExprStatement
-///      / KEYWORD_suspend (SEMICOLON / BlockExprStatement)
-///      / KEYWORD_defer BlockExprStatement
-///      / KEYWORD_errdefer Payload? BlockExprStatement
-///      / IfStatement
-///      / LabeledStatement
-///      / SwitchExpr
-///      / AssignExpr SEMICOLON
-fn parseStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
-    const comptime_token = eatToken(it, .Keyword_comptime);
-
-    const var_decl_node = try parseVarDecl(arena, it, tree);
-    if (var_decl_node) |node| {
-        const var_decl = node.cast(Node.VarDecl).?;
-        var_decl.comptime_token = comptime_token;
-        return node;
+        return p.parseUse();
     }
 
-    if (comptime_token) |token| {
-        const block_expr = try expectNode(arena, it, tree, parseBlockExprStatement, .{
-            .ExpectedBlockOrAssignment = .{ .token = it.index },
+    /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr)
+    fn parseFnProto(p: *Parser) !?*Node {
+        // TODO: Remove once extern/async fn rewriting is
+        var is_async = false;
+        var is_extern = false;
+        const cc_token: ?TokenIndex = blk: {
+            if (p.eatToken(.Keyword_extern)) |token| {
+                is_extern = true;
+                break :blk token;
+            }
+            if (p.eatToken(.Keyword_async)) |token| {
+                is_async = true;
+                break :blk token;
+            }
+            break :blk null;
+        };
+        const fn_token = p.eatToken(.Keyword_fn) orelse {
+            if (cc_token) |token|
+                p.putBackToken(token);
+            return null;
+        };
+        var var_args_token: ?TokenIndex = null;
+        const name_token = p.eatToken(.Identifier);
+        const lparen = try p.expectToken(.LParen);
+        const params = try p.parseParamDeclList(&var_args_token);
+        const rparen = try p.expectToken(.RParen);
+        const align_expr = try p.parseByteAlign();
+        const section_expr = try p.parseLinkSection();
+        const callconv_expr = try p.parseCallconv();
+        const exclamation_token = p.eatToken(.Bang);
+
+        const return_type_expr = (try p.parseVarType()) orelse
+            try p.expectNodeRecoverable(parseTypeExpr, .{
+            // most likely the user forgot to specify the return type.
+            // Mark return type as invalid and try to continue.
+            .ExpectedReturnType = .{ .token = p.tok_i },
         });
 
-        const node = try arena.create(Node.Comptime);
-        node.* = .{
+        // TODO https://github.com/ziglang/zig/issues/3750
+        const R = Node.FnProto.ReturnType;
+        const return_type = if (return_type_expr == null)
+            R{ .Invalid = rparen }
+        else if (exclamation_token != null)
+            R{ .InferErrorSet = return_type_expr.? }
+        else
+            R{ .Explicit = return_type_expr.? };
+
+        const fn_proto_node = try p.arena.allocator.create(Node.FnProto);
+        fn_proto_node.* = .{
             .doc_comments = null,
-            .comptime_token = token,
-            .expr = block_expr,
+            .visib_token = null,
+            .fn_token = fn_token,
+            .name_token = name_token,
+            .params = params,
+            .return_type = return_type,
+            .var_args_token = var_args_token,
+            .extern_export_inline_token = null,
+            .body_node = null,
+            .lib_name = null,
+            .align_expr = align_expr,
+            .section_expr = section_expr,
+            .callconv_expr = callconv_expr,
+            .is_extern_prototype = is_extern,
+            .is_async = is_async,
         };
-        return &node.base;
-    }
-
-    if (eatToken(it, .Keyword_nosuspend)) |nosuspend_token| {
-        const block_expr = try expectNode(arena, it, tree, parseBlockExprStatement, .{
-            .ExpectedBlockOrAssignment = .{ .token = it.index },
-        });
 
-        const node = try arena.create(Node.Nosuspend);
-        node.* = .{
-            .nosuspend_token = nosuspend_token,
-            .expr = block_expr,
-        };
-        return &node.base;
+        return &fn_proto_node.base;
     }
 
-    if (eatToken(it, .Keyword_suspend)) |suspend_token| {
-        const semicolon = eatToken(it, .Semicolon);
+    /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON
+    fn parseVarDecl(p: *Parser) !?*Node {
+        const mut_token = p.eatToken(.Keyword_const) orelse
+            p.eatToken(.Keyword_var) orelse
+            return null;
 
-        const body_node = if (semicolon == null) blk: {
-            break :blk try expectNode(arena, it, tree, parseBlockExprStatement, .{
-                .ExpectedBlockOrExpression = .{ .token = it.index },
+        const name_token = try p.expectToken(.Identifier);
+        const type_node = if (p.eatToken(.Colon) != null)
+            try p.expectNode(parseTypeExpr, .{
+                .ExpectedTypeExpr = .{ .token = p.tok_i },
+            })
+        else
+            null;
+        const align_node = try p.parseByteAlign();
+        const section_node = try p.parseLinkSection();
+        const eq_token = p.eatToken(.Equal);
+        const init_node = if (eq_token != null) blk: {
+            break :blk try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
             });
         } else null;
+        const semicolon_token = try p.expectToken(.Semicolon);
 
-        const node = try arena.create(Node.Suspend);
+        const node = try p.arena.allocator.create(Node.VarDecl);
         node.* = .{
-            .suspend_token = suspend_token,
-            .body = body_node,
+            .doc_comments = null,
+            .visib_token = null,
+            .thread_local_token = null,
+            .name_token = name_token,
+            .eq_token = eq_token,
+            .mut_token = mut_token,
+            .comptime_token = null,
+            .extern_export_token = null,
+            .lib_name = null,
+            .type_node = type_node,
+            .align_node = align_node,
+            .section_node = section_node,
+            .init_node = init_node,
+            .semicolon_token = semicolon_token,
         };
         return &node.base;
     }
 
-    const defer_token = eatToken(it, .Keyword_defer) orelse eatToken(it, .Keyword_errdefer);
-    if (defer_token) |token| {
-        const payload = if (tree.tokens.at(token).id == .Keyword_errdefer)
-            try parsePayload(arena, it, tree)
+    /// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
+    fn parseContainerField(p: *Parser) !?*Node {
+        const comptime_token = p.eatToken(.Keyword_comptime);
+        const name_token = p.eatToken(.Identifier) orelse {
+            if (comptime_token) |t| p.putBackToken(t);
+            return null;
+        };
+
+        var align_expr: ?*Node = null;
+        var type_expr: ?*Node = null;
+        if (p.eatToken(.Colon)) |_| {
+            if (p.eatToken(.Keyword_var)) |var_tok| {
+                const node = try p.arena.allocator.create(ast.Node.VarType);
+                node.* = .{ .token = var_tok };
+                type_expr = &node.base;
+            } else {
+                type_expr = try p.expectNode(parseTypeExpr, .{
+                    .ExpectedTypeExpr = .{ .token = p.tok_i },
+                });
+                align_expr = try p.parseByteAlign();
+            }
+        }
+
+        const value_expr = if (p.eatToken(.Equal)) |_|
+            try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            })
         else
             null;
-        const expr_node = try expectNode(arena, it, tree, parseBlockExprStatement, .{
-            .ExpectedBlockOrExpression = .{ .token = it.index },
-        });
-        const node = try arena.create(Node.Defer);
+
+        const node = try p.arena.allocator.create(Node.ContainerField);
         node.* = .{
-            .defer_token = token,
-            .expr = expr_node,
-            .payload = payload,
+            .doc_comments = null,
+            .comptime_token = comptime_token,
+            .name_token = name_token,
+            .type_expr = type_expr,
+            .value_expr = value_expr,
+            .align_expr = align_expr,
         };
         return &node.base;
     }
 
-    if (try parseIfStatement(arena, it, tree)) |node| return node;
-    if (try parseLabeledStatement(arena, it, tree)) |node| return node;
-    if (try parseSwitchExpr(arena, it, tree)) |node| return node;
-    if (try parseAssignExpr(arena, it, tree)) |node| {
-        _ = try expectTokenRecoverable(it, tree, .Semicolon);
-        return node;
-    }
-
-    return null;
-}
-
-/// IfStatement
-///     <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
-///      / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
-fn parseIfStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const if_node = (try parseIfPrefix(arena, it, tree)) orelse return null;
-    const if_prefix = if_node.cast(Node.If).?;
-
-    const block_expr = (try parseBlockExpr(arena, it, tree));
-    const assign_expr = if (block_expr == null)
-        try expectNode(arena, it, tree, parseAssignExpr, .{
-            .ExpectedBlockOrAssignment = .{ .token = it.index },
-        })
-    else
-        null;
-
-    const semicolon = if (assign_expr != null) eatToken(it, .Semicolon) else null;
-
-    const else_node = if (semicolon == null) blk: {
-        const else_token = eatToken(it, .Keyword_else) orelse break :blk null;
-        const payload = try parsePayload(arena, it, tree);
-        const else_body = try expectNode(arena, it, tree, parseStatement, .{
-            .InvalidToken = .{ .token = it.index },
-        });
+    /// Statement
+    ///     <- KEYWORD_comptime? VarDecl
+    ///      / KEYWORD_comptime BlockExprStatement
+    ///      / KEYWORD_nosuspend BlockExprStatement
+    ///      / KEYWORD_suspend (SEMICOLON / BlockExprStatement)
+    ///      / KEYWORD_defer BlockExprStatement
+    ///      / KEYWORD_errdefer Payload? BlockExprStatement
+    ///      / IfStatement
+    ///      / LabeledStatement
+    ///      / SwitchExpr
+    ///      / AssignExpr SEMICOLON
+    fn parseStatement(p: *Parser) Error!?*Node {
+        const comptime_token = p.eatToken(.Keyword_comptime);
+
+        const var_decl_node = try p.parseVarDecl();
+        if (var_decl_node) |node| {
+            const var_decl = node.cast(Node.VarDecl).?;
+            var_decl.comptime_token = comptime_token;
+            return node;
+        }
 
-        const node = try arena.create(Node.Else);
-        node.* = .{
-            .else_token = else_token,
-            .payload = payload,
-            .body = else_body,
-        };
+        if (comptime_token) |token| {
+            const block_expr = try p.expectNode(parseBlockExprStatement, .{
+                .ExpectedBlockOrAssignment = .{ .token = p.tok_i },
+            });
 
-        break :blk node;
-    } else null;
+            const node = try p.arena.allocator.create(Node.Comptime);
+            node.* = .{
+                .doc_comments = null,
+                .comptime_token = token,
+                .expr = block_expr,
+            };
+            return &node.base;
+        }
 
-    if (block_expr) |body| {
-        if_prefix.body = body;
-        if_prefix.@"else" = else_node;
-        return if_node;
-    }
+        if (p.eatToken(.Keyword_nosuspend)) |nosuspend_token| {
+            const block_expr = try p.expectNode(parseBlockExprStatement, .{
+                .ExpectedBlockOrAssignment = .{ .token = p.tok_i },
+            });
 
-    if (assign_expr) |body| {
-        if_prefix.body = body;
-        if (semicolon != null) return if_node;
-        if (else_node != null) {
-            if_prefix.@"else" = else_node;
-            return if_node;
+            const node = try p.arena.allocator.create(Node.Nosuspend);
+            node.* = .{
+                .nosuspend_token = nosuspend_token,
+                .expr = block_expr,
+            };
+            return &node.base;
         }
-        try tree.errors.push(.{
-            .ExpectedSemiOrElse = .{ .token = it.index },
-        });
-    }
-
-    return if_node;
-}
 
-/// LabeledStatement <- BlockLabel? (Block / LoopStatement)
-fn parseLabeledStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    var colon: TokenIndex = undefined;
-    const label_token = parseBlockLabel(arena, it, tree, &colon);
+        if (p.eatToken(.Keyword_suspend)) |suspend_token| {
+            const semicolon = p.eatToken(.Semicolon);
 
-    if (try parseBlock(arena, it, tree)) |node| {
-        node.cast(Node.Block).?.label = label_token;
-        return node;
-    }
+            const body_node = if (semicolon == null) blk: {
+                break :blk try p.expectNode(parseBlockExprStatement, .{
+                    .ExpectedBlockOrExpression = .{ .token = p.tok_i },
+                });
+            } else null;
 
-    if (try parseLoopStatement(arena, it, tree)) |node| {
-        if (node.cast(Node.For)) |for_node| {
-            for_node.label = label_token;
-        } else if (node.cast(Node.While)) |while_node| {
-            while_node.label = label_token;
-        } else unreachable;
-        return node;
-    }
-
-    if (label_token != null) {
-        try tree.errors.push(.{
-            .ExpectedLabelable = .{ .token = it.index },
-        });
-        return error.ParseError;
-    }
-
-    return null;
-}
+            const node = try p.arena.allocator.create(Node.Suspend);
+            node.* = .{
+                .suspend_token = suspend_token,
+                .body = body_node,
+            };
+            return &node.base;
+        }
 
-/// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
-fn parseLoopStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const inline_token = eatToken(it, .Keyword_inline);
+        const defer_token = p.eatToken(.Keyword_defer) orelse p.eatToken(.Keyword_errdefer);
+        if (defer_token) |token| {
+            const payload = if (p.tokens[token].id == .Keyword_errdefer)
+                try p.parsePayload()
+            else
+                null;
+            const expr_node = try p.expectNode(parseBlockExprStatement, .{
+                .ExpectedBlockOrExpression = .{ .token = p.tok_i },
+            });
+            const node = try p.arena.allocator.create(Node.Defer);
+            node.* = .{
+                .defer_token = token,
+                .expr = expr_node,
+                .payload = payload,
+            };
+            return &node.base;
+        }
 
-    if (try parseForStatement(arena, it, tree)) |node| {
-        node.cast(Node.For).?.inline_token = inline_token;
-        return node;
-    }
+        if (try p.parseIfStatement()) |node| return node;
+        if (try p.parseLabeledStatement()) |node| return node;
+        if (try p.parseSwitchExpr()) |node| return node;
+        if (try p.parseAssignExpr()) |node| {
+            _ = try p.expectTokenRecoverable(.Semicolon);
+            return node;
+        }
 
-    if (try parseWhileStatement(arena, it, tree)) |node| {
-        node.cast(Node.While).?.inline_token = inline_token;
-        return node;
+        return null;
     }
-    if (inline_token == null) return null;
 
-    // If we've seen "inline", there should have been a "for" or "while"
-    try tree.errors.push(.{
-        .ExpectedInlinable = .{ .token = it.index },
-    });
-    return error.ParseError;
-}
+    /// IfStatement
+    ///     <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
+    ///      / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
+    fn parseIfStatement(p: *Parser) !?*Node {
+        const if_node = (try p.parseIfPrefix()) orelse return null;
+        const if_prefix = if_node.cast(Node.If).?;
 
-/// ForStatement
-///     <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
-///      / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
-fn parseForStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseForPrefix(arena, it, tree)) orelse return null;
-    const for_prefix = node.cast(Node.For).?;
+        const block_expr = (try p.parseBlockExpr());
+        const assign_expr = if (block_expr == null)
+            try p.expectNode(parseAssignExpr, .{
+                .ExpectedBlockOrAssignment = .{ .token = p.tok_i },
+            })
+        else
+            null;
 
-    if (try parseBlockExpr(arena, it, tree)) |block_expr_node| {
-        for_prefix.body = block_expr_node;
+        const semicolon = if (assign_expr != null) p.eatToken(.Semicolon) else null;
 
-        if (eatToken(it, .Keyword_else)) |else_token| {
-            const statement_node = try expectNode(arena, it, tree, parseStatement, .{
-                .InvalidToken = .{ .token = it.index },
+        const else_node = if (semicolon == null) blk: {
+            const else_token = p.eatToken(.Keyword_else) orelse break :blk null;
+            const payload = try p.parsePayload();
+            const else_body = try p.expectNode(parseStatement, .{
+                .InvalidToken = .{ .token = p.tok_i },
             });
 
-            const else_node = try arena.create(Node.Else);
-            else_node.* = .{
+            const node = try p.arena.allocator.create(Node.Else);
+            node.* = .{
                 .else_token = else_token,
-                .payload = null,
-                .body = statement_node,
+                .payload = payload,
+                .body = else_body,
             };
-            for_prefix.@"else" = else_node;
 
-            return node;
+            break :blk node;
+        } else null;
+
+        if (block_expr) |body| {
+            if_prefix.body = body;
+            if_prefix.@"else" = else_node;
+            return if_node;
         }
 
-        return node;
+        if (assign_expr) |body| {
+            if_prefix.body = body;
+            if (semicolon != null) return if_node;
+            if (else_node != null) {
+                if_prefix.@"else" = else_node;
+                return if_node;
+            }
+            try p.errors.append(p.gpa, .{
+                .ExpectedSemiOrElse = .{ .token = p.tok_i },
+            });
+        }
+
+        return if_node;
     }
 
-    if (try parseAssignExpr(arena, it, tree)) |assign_expr| {
-        for_prefix.body = assign_expr;
+    /// LabeledStatement <- BlockLabel? (Block / LoopStatement)
+    fn parseLabeledStatement(p: *Parser) !?*Node {
+        var colon: TokenIndex = undefined;
+        const label_token = p.parseBlockLabel(&colon);
+
+        if (try p.parseBlock()) |node| {
+            node.cast(Node.Block).?.label = label_token;
+            return node;
+        }
 
-        if (eatToken(it, .Semicolon) != null) return node;
+        if (try p.parseLoopStatement()) |node| {
+            if (node.cast(Node.For)) |for_node| {
+                for_node.label = label_token;
+            } else if (node.cast(Node.While)) |while_node| {
+                while_node.label = label_token;
+            } else unreachable;
+            return node;
+        }
 
-        if (eatToken(it, .Keyword_else)) |else_token| {
-            const statement_node = try expectNode(arena, it, tree, parseStatement, .{
-                .ExpectedStatement = .{ .token = it.index },
+        if (label_token != null) {
+            try p.errors.append(p.gpa, .{
+                .ExpectedLabelable = .{ .token = p.tok_i },
             });
+            return error.ParseError;
+        }
 
-            const else_node = try arena.create(Node.Else);
-            else_node.* = .{
-                .else_token = else_token,
-                .payload = null,
-                .body = statement_node,
-            };
-            for_prefix.@"else" = else_node;
+        return null;
+    }
+
+    /// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
+    fn parseLoopStatement(p: *Parser) !?*Node {
+        const inline_token = p.eatToken(.Keyword_inline);
+
+        if (try p.parseForStatement()) |node| {
+            node.cast(Node.For).?.inline_token = inline_token;
             return node;
         }
 
-        try tree.errors.push(.{
-            .ExpectedSemiOrElse = .{ .token = it.index },
-        });
+        if (try p.parseWhileStatement()) |node| {
+            node.cast(Node.While).?.inline_token = inline_token;
+            return node;
+        }
+        if (inline_token == null) return null;
 
-        return node;
+        // If we've seen "inline", there should have been a "for" or "while"
+        try p.errors.append(p.gpa, .{
+            .ExpectedInlinable = .{ .token = p.tok_i },
+        });
+        return error.ParseError;
     }
 
-    return null;
-}
-
-/// WhileStatement
-///     <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
-///      / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
-fn parseWhileStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseWhilePrefix(arena, it, tree)) orelse return null;
-    const while_prefix = node.cast(Node.While).?;
+    /// ForStatement
+    ///     <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
+    ///      / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
+    fn parseForStatement(p: *Parser) !?*Node {
+        const node = (try p.parseForPrefix()) orelse return null;
+        const for_prefix = node.cast(Node.For).?;
 
-    if (try parseBlockExpr(arena, it, tree)) |block_expr_node| {
-        while_prefix.body = block_expr_node;
+        if (try p.parseBlockExpr()) |block_expr_node| {
+            for_prefix.body = block_expr_node;
 
-        if (eatToken(it, .Keyword_else)) |else_token| {
-            const payload = try parsePayload(arena, it, tree);
+            if (p.eatToken(.Keyword_else)) |else_token| {
+                const statement_node = try p.expectNode(parseStatement, .{
+                    .InvalidToken = .{ .token = p.tok_i },
+                });
 
-            const statement_node = try expectNode(arena, it, tree, parseStatement, .{
-                .InvalidToken = .{ .token = it.index },
-            });
+                const else_node = try p.arena.allocator.create(Node.Else);
+                else_node.* = .{
+                    .else_token = else_token,
+                    .payload = null,
+                    .body = statement_node,
+                };
+                for_prefix.@"else" = else_node;
 
-            const else_node = try arena.create(Node.Else);
-            else_node.* = .{
-                .else_token = else_token,
-                .payload = payload,
-                .body = statement_node,
-            };
-            while_prefix.@"else" = else_node;
+                return node;
+            }
 
             return node;
         }
 
-        return node;
-    }
+        if (try p.parseAssignExpr()) |assign_expr| {
+            for_prefix.body = assign_expr;
 
-    if (try parseAssignExpr(arena, it, tree)) |assign_expr_node| {
-        while_prefix.body = assign_expr_node;
+            if (p.eatToken(.Semicolon) != null) return node;
 
-        if (eatToken(it, .Semicolon) != null) return node;
+            if (p.eatToken(.Keyword_else)) |else_token| {
+                const statement_node = try p.expectNode(parseStatement, .{
+                    .ExpectedStatement = .{ .token = p.tok_i },
+                });
 
-        if (eatToken(it, .Keyword_else)) |else_token| {
-            const payload = try parsePayload(arena, it, tree);
+                const else_node = try p.arena.allocator.create(Node.Else);
+                else_node.* = .{
+                    .else_token = else_token,
+                    .payload = null,
+                    .body = statement_node,
+                };
+                for_prefix.@"else" = else_node;
+                return node;
+            }
 
-            const statement_node = try expectNode(arena, it, tree, parseStatement, .{
-                .ExpectedStatement = .{ .token = it.index },
+            try p.errors.append(p.gpa, .{
+                .ExpectedSemiOrElse = .{ .token = p.tok_i },
             });
 
-            const else_node = try arena.create(Node.Else);
-            else_node.* = .{
-                .else_token = else_token,
-                .payload = payload,
-                .body = statement_node,
-            };
-            while_prefix.@"else" = else_node;
             return node;
         }
 
-        try tree.errors.push(.{
-            .ExpectedSemiOrElse = .{ .token = it.index },
-        });
-
-        return node;
+        return null;
     }
 
-    return null;
-}
+    /// WhileStatement
+    ///     <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
+    ///      / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
+    fn parseWhileStatement(p: *Parser) !?*Node {
+        const node = (try p.parseWhilePrefix()) orelse return null;
+        const while_prefix = node.cast(Node.While).?;
 
-/// BlockExprStatement
-///     <- BlockExpr
-///      / AssignExpr SEMICOLON
-fn parseBlockExprStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (try parseBlockExpr(arena, it, tree)) |node| return node;
-    if (try parseAssignExpr(arena, it, tree)) |node| {
-        _ = try expectTokenRecoverable(it, tree, .Semicolon);
-        return node;
-    }
-    return null;
-}
+        if (try p.parseBlockExpr()) |block_expr_node| {
+            while_prefix.body = block_expr_node;
 
-/// BlockExpr <- BlockLabel? Block
-fn parseBlockExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
-    var colon: TokenIndex = undefined;
-    const label_token = parseBlockLabel(arena, it, tree, &colon);
-    const block_node = (try parseBlock(arena, it, tree)) orelse {
-        if (label_token) |label| {
-            putBackToken(it, label + 1); // ":"
-            putBackToken(it, label); // IDENTIFIER
-        }
-        return null;
-    };
-    block_node.cast(Node.Block).?.label = label_token;
-    return block_node;
-}
+            if (p.eatToken(.Keyword_else)) |else_token| {
+                const payload = try p.parsePayload();
 
-/// AssignExpr <- Expr (AssignOp Expr)?
-fn parseAssignExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(arena, it, tree, parseAssignOp, parseExpr, .Once);
-}
+                const statement_node = try p.expectNode(parseStatement, .{
+                    .InvalidToken = .{ .token = p.tok_i },
+                });
 
-/// Expr <- KEYWORD_try* BoolOrExpr
-fn parseExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
-    return parsePrefixOpExpr(arena, it, tree, parseTry, parseBoolOrExpr);
-}
+                const else_node = try p.arena.allocator.create(Node.Else);
+                else_node.* = .{
+                    .else_token = else_token,
+                    .payload = payload,
+                    .body = statement_node,
+                };
+                while_prefix.@"else" = else_node;
 
-/// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
-fn parseBoolOrExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(
-        arena,
-        it,
-        tree,
-        SimpleBinOpParseFn(.Keyword_or, Node.InfixOp.Op.BoolOr),
-        parseBoolAndExpr,
-        .Infinitely,
-    );
-}
+                return node;
+            }
 
-/// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
-fn parseBoolAndExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(
-        arena,
-        it,
-        tree,
-        SimpleBinOpParseFn(.Keyword_and, .BoolAnd),
-        parseCompareExpr,
-        .Infinitely,
-    );
-}
+            return node;
+        }
 
-/// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
-fn parseCompareExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(arena, it, tree, parseCompareOp, parseBitwiseExpr, .Once);
-}
+        if (try p.parseAssignExpr()) |assign_expr_node| {
+            while_prefix.body = assign_expr_node;
 
-/// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*
-fn parseBitwiseExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(arena, it, tree, parseBitwiseOp, parseBitShiftExpr, .Infinitely);
-}
+            if (p.eatToken(.Semicolon) != null) return node;
 
-/// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*
-fn parseBitShiftExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(arena, it, tree, parseBitShiftOp, parseAdditionExpr, .Infinitely);
-}
+            if (p.eatToken(.Keyword_else)) |else_token| {
+                const payload = try p.parsePayload();
 
-/// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*
-fn parseAdditionExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(arena, it, tree, parseAdditionOp, parseMultiplyExpr, .Infinitely);
-}
+                const statement_node = try p.expectNode(parseStatement, .{
+                    .ExpectedStatement = .{ .token = p.tok_i },
+                });
 
-/// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*
-fn parseMultiplyExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseBinOpExpr(arena, it, tree, parseMultiplyOp, parsePrefixExpr, .Infinitely);
-}
+                const else_node = try p.arena.allocator.create(Node.Else);
+                else_node.* = .{
+                    .else_token = else_token,
+                    .payload = payload,
+                    .body = statement_node,
+                };
+                while_prefix.@"else" = else_node;
+                return node;
+            }
 
-/// PrefixExpr <- PrefixOp* PrimaryExpr
-fn parsePrefixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parsePrefixOpExpr(arena, it, tree, parsePrefixOp, parsePrimaryExpr);
-}
+            try p.errors.append(p.gpa, .{
+                .ExpectedSemiOrElse = .{ .token = p.tok_i },
+            });
 
-/// PrimaryExpr
-///     <- AsmExpr
-///      / IfExpr
-///      / KEYWORD_break BreakLabel? Expr?
-///      / KEYWORD_comptime Expr
-///      / KEYWORD_nosuspend Expr
-///      / KEYWORD_continue BreakLabel?
-///      / KEYWORD_resume Expr
-///      / KEYWORD_return Expr?
-///      / BlockLabel? LoopExpr
-///      / Block
-///      / CurlySuffixExpr
-fn parsePrimaryExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (try parseAsmExpr(arena, it, tree)) |node| return node;
-    if (try parseIfExpr(arena, it, tree)) |node| return node;
-
-    if (eatToken(it, .Keyword_break)) |token| {
-        const label = try parseBreakLabel(arena, it, tree);
-        const expr_node = try parseExpr(arena, it, tree);
-        const node = try arena.create(Node.ControlFlowExpression);
-        node.* = .{
-            .ltoken = token,
-            .kind = .{ .Break = label },
-            .rhs = expr_node,
-        };
-        return &node.base;
-    }
+            return node;
+        }
 
-    if (eatToken(it, .Keyword_comptime)) |token| {
-        const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        });
-        const node = try arena.create(Node.Comptime);
-        node.* = .{
-            .doc_comments = null,
-            .comptime_token = token,
-            .expr = expr_node,
-        };
-        return &node.base;
+        return null;
     }
 
-    if (eatToken(it, .Keyword_nosuspend)) |token| {
-        const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        });
-        const node = try arena.create(Node.Nosuspend);
-        node.* = .{
-            .nosuspend_token = token,
-            .expr = expr_node,
-        };
-        return &node.base;
+    /// BlockExprStatement
+    ///     <- BlockExpr
+    ///      / AssignExpr SEMICOLON
+    fn parseBlockExprStatement(p: *Parser) !?*Node {
+        if (try p.parseBlockExpr()) |node| return node;
+        if (try p.parseAssignExpr()) |node| {
+            _ = try p.expectTokenRecoverable(.Semicolon);
+            return node;
+        }
+        return null;
     }
 
-    if (eatToken(it, .Keyword_continue)) |token| {
-        const label = try parseBreakLabel(arena, it, tree);
-        const node = try arena.create(Node.ControlFlowExpression);
-        node.* = .{
-            .ltoken = token,
-            .kind = .{ .Continue = label },
-            .rhs = null,
+    /// BlockExpr <- BlockLabel? Block
+    fn parseBlockExpr(p: *Parser) Error!?*Node {
+        var colon: TokenIndex = undefined;
+        const label_token = p.parseBlockLabel(&colon);
+        const block_node = (try p.parseBlock()) orelse {
+            if (label_token) |label| {
+                p.putBackToken(label + 1); // ":"
+                p.putBackToken(label); // IDENTIFIER
+            }
+            return null;
         };
-        return &node.base;
+        block_node.cast(Node.Block).?.label = label_token;
+        return block_node;
     }
 
-    if (eatToken(it, .Keyword_resume)) |token| {
-        const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        });
-        const node = try arena.create(Node.PrefixOp);
-        node.* = .{
-            .op_token = token,
-            .op = .Resume,
-            .rhs = expr_node,
-        };
-        return &node.base;
+    /// AssignExpr <- Expr (AssignOp Expr)?
+    fn parseAssignExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(parseAssignOp, parseExpr, .Once);
     }
 
-    if (eatToken(it, .Keyword_return)) |token| {
-        const expr_node = try parseExpr(arena, it, tree);
-        const node = try arena.create(Node.ControlFlowExpression);
-        node.* = .{
-            .ltoken = token,
-            .kind = .Return,
-            .rhs = expr_node,
-        };
-        return &node.base;
+    /// Expr <- KEYWORD_try* BoolOrExpr
+    fn parseExpr(p: *Parser) Error!?*Node {
+        return p.parsePrefixOpExpr(parseTry, parseBoolOrExpr);
     }
 
-    var colon: TokenIndex = undefined;
-    const label = parseBlockLabel(arena, it, tree, &colon);
-    if (try parseLoopExpr(arena, it, tree)) |node| {
-        if (node.cast(Node.For)) |for_node| {
-            for_node.label = label;
-        } else if (node.cast(Node.While)) |while_node| {
-            while_node.label = label;
-        } else unreachable;
-        return node;
-    }
-    if (label) |token| {
-        putBackToken(it, token + 1); // ":"
-        putBackToken(it, token); // IDENTIFIER
+    /// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
+    fn parseBoolOrExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(
+            SimpleBinOpParseFn(.Keyword_or, Node.InfixOp.Op.BoolOr),
+            parseBoolAndExpr,
+            .Infinitely,
+        );
     }
 
-    if (try parseBlock(arena, it, tree)) |node| return node;
-    if (try parseCurlySuffixExpr(arena, it, tree)) |node| return node;
-
-    return null;
-}
+    /// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
+    fn parseBoolAndExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(
+            SimpleBinOpParseFn(.Keyword_and, .BoolAnd),
+            parseCompareExpr,
+            .Infinitely,
+        );
+    }
 
-/// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
-fn parseIfExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseIf(arena, it, tree, parseExpr);
-}
+    /// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
+    fn parseCompareExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(parseCompareOp, parseBitwiseExpr, .Once);
+    }
 
-/// Block <- LBRACE Statement* RBRACE
-fn parseBlock(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const lbrace = eatToken(it, .LBrace) orelse return null;
+    /// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*
+    fn parseBitwiseExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(parseBitwiseOp, parseBitShiftExpr, .Infinitely);
+    }
 
-    var statements = Node.Block.StatementList.init(arena);
-    while (true) {
-        const statement = (parseStatement(arena, it, tree) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.ParseError => {
-                // try to skip to the next statement
-                findNextStmt(it);
-                continue;
-            },
-        }) orelse break;
-        try statements.push(statement);
+    /// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*
+    fn parseBitShiftExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(parseBitShiftOp, parseAdditionExpr, .Infinitely);
     }
 
-    const rbrace = try expectToken(it, tree, .RBrace);
+    /// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*
+    fn parseAdditionExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(parseAdditionOp, parseMultiplyExpr, .Infinitely);
+    }
 
-    const block_node = try arena.create(Node.Block);
-    block_node.* = .{
-        .label = null,
-        .lbrace = lbrace,
-        .statements = statements,
-        .rbrace = rbrace,
-    };
+    /// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*
+    fn parseMultiplyExpr(p: *Parser) !?*Node {
+        return p.parseBinOpExpr(parseMultiplyOp, parsePrefixExpr, .Infinitely);
+    }
 
-    return &block_node.base;
-}
+    /// PrefixExpr <- PrefixOp* PrimaryExpr
+    fn parsePrefixExpr(p: *Parser) !?*Node {
+        return p.parsePrefixOpExpr(parsePrefixOp, parsePrimaryExpr);
+    }
 
-/// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
-fn parseLoopExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const inline_token = eatToken(it, .Keyword_inline);
+    /// PrimaryExpr
+    ///     <- AsmExpr
+    ///      / IfExpr
+    ///      / KEYWORD_break BreakLabel? Expr?
+    ///      / KEYWORD_comptime Expr
+    ///      / KEYWORD_nosuspend Expr
+    ///      / KEYWORD_continue BreakLabel?
+    ///      / KEYWORD_resume Expr
+    ///      / KEYWORD_return Expr?
+    ///      / BlockLabel? LoopExpr
+    ///      / Block
+    ///      / CurlySuffixExpr
+    fn parsePrimaryExpr(p: *Parser) !?*Node {
+        if (try p.parseAsmExpr()) |node| return node;
+        if (try p.parseIfExpr()) |node| return node;
 
-    if (try parseForExpr(arena, it, tree)) |node| {
-        node.cast(Node.For).?.inline_token = inline_token;
-        return node;
-    }
+        if (p.eatToken(.Keyword_break)) |token| {
+            const label = try p.parseBreakLabel();
+            const expr_node = try p.parseExpr();
+            const node = try p.arena.allocator.create(Node.ControlFlowExpression);
+            node.* = .{
+                .ltoken = token,
+                .kind = .{ .Break = label },
+                .rhs = expr_node,
+            };
+            return &node.base;
+        }
 
-    if (try parseWhileExpr(arena, it, tree)) |node| {
-        node.cast(Node.While).?.inline_token = inline_token;
-        return node;
-    }
+        if (p.eatToken(.Keyword_comptime)) |token| {
+            const expr_node = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
+            const node = try p.arena.allocator.create(Node.Comptime);
+            node.* = .{
+                .doc_comments = null,
+                .comptime_token = token,
+                .expr = expr_node,
+            };
+            return &node.base;
+        }
 
-    if (inline_token == null) return null;
+        if (p.eatToken(.Keyword_nosuspend)) |token| {
+            const expr_node = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
+            const node = try p.arena.allocator.create(Node.Nosuspend);
+            node.* = .{
+                .nosuspend_token = token,
+                .expr = expr_node,
+            };
+            return &node.base;
+        }
 
-    // If we've seen "inline", there should have been a "for" or "while"
-    try tree.errors.push(.{
-        .ExpectedInlinable = .{ .token = it.index },
-    });
-    return error.ParseError;
-}
+        if (p.eatToken(.Keyword_continue)) |token| {
+            const label = try p.parseBreakLabel();
+            const node = try p.arena.allocator.create(Node.ControlFlowExpression);
+            node.* = .{
+                .ltoken = token,
+                .kind = .{ .Continue = label },
+                .rhs = null,
+            };
+            return &node.base;
+        }
 
-/// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
-fn parseForExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseForPrefix(arena, it, tree)) orelse return null;
-    const for_prefix = node.cast(Node.For).?;
+        if (p.eatToken(.Keyword_resume)) |token| {
+            const expr_node = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
+            const node = try p.arena.allocator.create(Node.PrefixOp);
+            node.* = .{
+                .op_token = token,
+                .op = .Resume,
+                .rhs = expr_node,
+            };
+            return &node.base;
+        }
 
-    const body_node = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    for_prefix.body = body_node;
+        if (p.eatToken(.Keyword_return)) |token| {
+            const expr_node = try p.parseExpr();
+            const node = try p.arena.allocator.create(Node.ControlFlowExpression);
+            node.* = .{
+                .ltoken = token,
+                .kind = .Return,
+                .rhs = expr_node,
+            };
+            return &node.base;
+        }
 
-    if (eatToken(it, .Keyword_else)) |else_token| {
-        const body = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        });
+        var colon: TokenIndex = undefined;
+        const label = p.parseBlockLabel(&colon);
+        if (try p.parseLoopExpr()) |node| {
+            if (node.cast(Node.For)) |for_node| {
+                for_node.label = label;
+            } else if (node.cast(Node.While)) |while_node| {
+                while_node.label = label;
+            } else unreachable;
+            return node;
+        }
+        if (label) |token| {
+            p.putBackToken(token + 1); // ":"
+            p.putBackToken(token); // IDENTIFIER
+        }
 
-        const else_node = try arena.create(Node.Else);
-        else_node.* = .{
-            .else_token = else_token,
-            .payload = null,
-            .body = body,
-        };
+        if (try p.parseBlock()) |node| return node;
+        if (try p.parseCurlySuffixExpr()) |node| return node;
 
-        for_prefix.@"else" = else_node;
+        return null;
     }
 
-    return node;
-}
+    /// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
+    fn parseIfExpr(p: *Parser) !?*Node {
+        return p.parseIf(parseExpr);
+    }
 
-/// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
-fn parseWhileExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseWhilePrefix(arena, it, tree)) orelse return null;
-    const while_prefix = node.cast(Node.While).?;
+    /// Block <- LBRACE Statement* RBRACE
+    fn parseBlock(p: *Parser) !?*Node {
+        const lbrace = p.eatToken(.LBrace) orelse return null;
 
-    const body_node = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    while_prefix.body = body_node;
+        var statements = Node.Block.StatementList{};
+        var statements_it = &statements.first;
+        while (true) {
+            const statement = (p.parseStatement() catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.ParseError => {
+                    // try to skip to the next statement
+                    p.findNextStmt();
+                    continue;
+                },
+            }) orelse break;
+            statements_it = try p.llpush(*Node, statements_it, statement);
+        }
 
-    if (eatToken(it, .Keyword_else)) |else_token| {
-        const payload = try parsePayload(arena, it, tree);
-        const body = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        });
+        const rbrace = try p.expectToken(.RBrace);
 
-        const else_node = try arena.create(Node.Else);
-        else_node.* = .{
-            .else_token = else_token,
-            .payload = payload,
-            .body = body,
+        const block_node = try p.arena.allocator.create(Node.Block);
+        block_node.* = .{
+            .label = null,
+            .lbrace = lbrace,
+            .statements = statements,
+            .rbrace = rbrace,
         };
 
-        while_prefix.@"else" = else_node;
+        return &block_node.base;
     }
 
-    return node;
-}
-
-/// CurlySuffixExpr <- TypeExpr InitList?
-fn parseCurlySuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const type_expr = (try parseTypeExpr(arena, it, tree)) orelse return null;
-    const suffix_op = (try parseInitList(arena, it, tree)) orelse return type_expr;
-    suffix_op.lhs.node = type_expr;
-    return &suffix_op.base;
-}
+    /// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
+    fn parseLoopExpr(p: *Parser) !?*Node {
+        const inline_token = p.eatToken(.Keyword_inline);
 
-/// InitList
-///     <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
-///      / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
-///      / LBRACE RBRACE
-fn parseInitList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.SuffixOp {
-    const lbrace = eatToken(it, .LBrace) orelse return null;
-    var init_list = Node.SuffixOp.Op.InitList.init(arena);
-
-    const op: Node.SuffixOp.Op = blk: {
-        if (try parseFieldInit(arena, it, tree)) |field_init| {
-            try init_list.push(field_init);
-            while (eatToken(it, .Comma)) |_| {
-                const next = (try parseFieldInit(arena, it, tree)) orelse break;
-                try init_list.push(next);
-            }
-            break :blk .{ .StructInitializer = init_list };
+        if (try p.parseForExpr()) |node| {
+            node.cast(Node.For).?.inline_token = inline_token;
+            return node;
         }
 
-        if (try parseExpr(arena, it, tree)) |expr| {
-            try init_list.push(expr);
-            while (eatToken(it, .Comma)) |_| {
-                const next = (try parseExpr(arena, it, tree)) orelse break;
-                try init_list.push(next);
-            }
-            break :blk .{ .ArrayInitializer = init_list };
+        if (try p.parseWhileExpr()) |node| {
+            node.cast(Node.While).?.inline_token = inline_token;
+            return node;
         }
 
-        break :blk .{ .StructInitializer = init_list };
-    };
-
-    const node = try arena.create(Node.SuffixOp);
-    node.* = .{
-        .lhs = .{ .node = undefined }, // set by caller
-        .op = op,
-        .rtoken = try expectToken(it, tree, .RBrace),
-    };
-    return node;
-}
-
-/// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
-fn parseTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
-    return parsePrefixOpExpr(arena, it, tree, parsePrefixTypeOp, parseErrorUnionExpr);
-}
-
-/// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
-fn parseErrorUnionExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const suffix_expr = (try parseSuffixExpr(arena, it, tree)) orelse return null;
+        if (inline_token == null) return null;
 
-    if (try SimpleBinOpParseFn(.Bang, Node.InfixOp.Op.ErrorUnion)(arena, it, tree)) |node| {
-        const error_union = node.cast(Node.InfixOp).?;
-        const type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
-            .ExpectedTypeExpr = .{ .token = it.index },
+        // If we've seen "inline", there should have been a "for" or "while"
+        try p.errors.append(p.gpa, .{
+            .ExpectedInlinable = .{ .token = p.tok_i },
         });
-        error_union.lhs = suffix_expr;
-        error_union.rhs = type_expr;
-        return node;
+        return error.ParseError;
     }
 
-    return suffix_expr;
-}
+    /// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
+    fn parseForExpr(p: *Parser) !?*Node {
+        const node = (try p.parseForPrefix()) orelse return null;
+        const for_prefix = node.cast(Node.For).?;
 
-/// SuffixExpr
-///     <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
-///      / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
-fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const maybe_async = eatToken(it, .Keyword_async);
-    if (maybe_async) |async_token| {
-        const token_fn = eatToken(it, .Keyword_fn);
-        if (token_fn != null) {
-            // TODO: remove this hack when async fn rewriting is
-            // HACK: If we see the keyword `fn`, then we assume that
-            //       we are parsing an async fn proto, and not a call.
-            //       We therefore put back all tokens consumed by the async
-            //       prefix...
-            putBackToken(it, token_fn.?);
-            putBackToken(it, async_token);
-            return parsePrimaryTypeExpr(arena, it, tree);
-        }
-        var res = try expectNode(arena, it, tree, parsePrimaryTypeExpr, .{
-            .ExpectedPrimaryTypeExpr = .{ .token = it.index },
+        const body_node = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
         });
+        for_prefix.body = body_node;
 
-        while (try parseSuffixOp(arena, it, tree)) |node| {
-            switch (node.id) {
-                .SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{ .node = res },
-                .InfixOp => node.cast(Node.InfixOp).?.lhs = res,
-                else => unreachable,
-            }
-            res = node;
+        if (p.eatToken(.Keyword_else)) |else_token| {
+            const body = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
+
+            const else_node = try p.arena.allocator.create(Node.Else);
+            else_node.* = .{
+                .else_token = else_token,
+                .payload = null,
+                .body = body,
+            };
+
+            for_prefix.@"else" = else_node;
         }
 
-        const params = (try parseFnCallArguments(arena, it, tree)) orelse {
-            try tree.errors.push(.{
-                .ExpectedParamList = .{ .token = it.index },
-            });
-            // ignore this, continue parsing
-            return res;
-        };
-        const node = try arena.create(Node.SuffixOp);
-        node.* = .{
-            .lhs = .{ .node = res },
-            .op = .{
-                .Call = .{
-                    .params = params.list,
-                    .async_token = async_token,
-                },
-            },
-            .rtoken = params.rparen,
-        };
-        return &node.base;
+        return node;
     }
-    if (try parsePrimaryTypeExpr(arena, it, tree)) |expr| {
-        var res = expr;
 
-        while (true) {
-            if (try parseSuffixOp(arena, it, tree)) |node| {
+    /// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
+    fn parseWhileExpr(p: *Parser) !?*Node {
+        const node = (try p.parseWhilePrefix()) orelse return null;
+        const while_prefix = node.cast(Node.While).?;
+
+        const body_node = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        while_prefix.body = body_node;
+
+        if (p.eatToken(.Keyword_else)) |else_token| {
+            const payload = try p.parsePayload();
+            const body = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
+
+            const else_node = try p.arena.allocator.create(Node.Else);
+            else_node.* = .{
+                .else_token = else_token,
+                .payload = payload,
+                .body = body,
+            };
+
+            while_prefix.@"else" = else_node;
+        }
+
+        return node;
+    }
+
+    /// CurlySuffixExpr <- TypeExpr InitList?
+    fn parseCurlySuffixExpr(p: *Parser) !?*Node {
+        const type_expr = (try p.parseTypeExpr()) orelse return null;
+        const suffix_op = (try p.parseInitList()) orelse return type_expr;
+        suffix_op.lhs.node = type_expr;
+        return &suffix_op.base;
+    }
+
+    /// InitList
+    ///     <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
+    ///      / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
+    ///      / LBRACE RBRACE
+    fn parseInitList(p: *Parser) !?*Node.SuffixOp {
+        const lbrace = p.eatToken(.LBrace) orelse return null;
+        var init_list = Node.SuffixOp.Op.InitList{};
+        var init_list_it = &init_list.first;
+
+        const op: Node.SuffixOp.Op = blk: {
+            if (try p.parseFieldInit()) |field_init| {
+                init_list_it = try p.llpush(*Node, init_list_it, field_init);
+                while (p.eatToken(.Comma)) |_| {
+                    const next = (try p.parseFieldInit()) orelse break;
+                    init_list_it = try p.llpush(*Node, init_list_it, next);
+                }
+                break :blk .{ .StructInitializer = init_list };
+            }
+
+            if (try p.parseExpr()) |expr| {
+                init_list_it = try p.llpush(*Node, init_list_it, expr);
+                while (p.eatToken(.Comma)) |_| {
+                    const next = (try p.parseExpr()) orelse break;
+                    init_list_it = try p.llpush(*Node, init_list_it, next);
+                }
+                break :blk .{ .ArrayInitializer = init_list };
+            }
+
+            break :blk .{ .StructInitializer = init_list };
+        };
+
+        const node = try p.arena.allocator.create(Node.SuffixOp);
+        node.* = .{
+            .lhs = .{ .node = undefined }, // set by caller
+            .op = op,
+            .rtoken = try p.expectToken(.RBrace),
+        };
+        return node;
+    }
+
+    /// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
+    fn parseTypeExpr(p: *Parser) Error!?*Node {
+        return p.parsePrefixOpExpr(parsePrefixTypeOp, parseErrorUnionExpr);
+    }
+
+    /// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
+    fn parseErrorUnionExpr(p: *Parser) !?*Node {
+        const suffix_expr = (try p.parseSuffixExpr()) orelse return null;
+
+        if (try SimpleBinOpParseFn(.Bang, Node.InfixOp.Op.ErrorUnion)(p)) |node| {
+            const error_union = node.cast(Node.InfixOp).?;
+            const type_expr = try p.expectNode(parseTypeExpr, .{
+                .ExpectedTypeExpr = .{ .token = p.tok_i },
+            });
+            error_union.lhs = suffix_expr;
+            error_union.rhs = type_expr;
+            return node;
+        }
+
+        return suffix_expr;
+    }
+
+    /// SuffixExpr
+    ///     <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
+    ///      / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
+    fn parseSuffixExpr(p: *Parser) !?*Node {
+        const maybe_async = p.eatToken(.Keyword_async);
+        if (maybe_async) |async_token| {
+            const token_fn = p.eatToken(.Keyword_fn);
+            if (token_fn != null) {
+                // TODO: remove this hack when async fn rewriting is
+                // HACK: If we see the keyword `fn`, then we assume that
+                //       we are parsing an async fn proto, and not a call.
+                //       We therefore put back all tokens consumed by the async
+                //       prefix...
+                p.putBackToken(token_fn.?);
+                p.putBackToken(async_token);
+                return p.parsePrimaryTypeExpr();
+            }
+            var res = try p.expectNode(parsePrimaryTypeExpr, .{
+                .ExpectedPrimaryTypeExpr = .{ .token = p.tok_i },
+            });
+
+            while (try p.parseSuffixOp()) |node| {
                 switch (node.id) {
                     .SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{ .node = res },
                     .InfixOp => node.cast(Node.InfixOp).?.lhs = res,
                     else => unreachable,
                 }
                 res = node;
-                continue;
             }
-            if (try parseFnCallArguments(arena, it, tree)) |params| {
-                const call = try arena.create(Node.SuffixOp);
-                call.* = .{
-                    .lhs = .{ .node = res },
-                    .op = .{
-                        .Call = .{
-                            .params = params.list,
-                            .async_token = null,
-                        },
+
+            const params = (try p.parseFnCallArguments()) orelse {
+                try p.errors.append(p.gpa, .{
+                    .ExpectedParamList = .{ .token = p.tok_i },
+                });
+                // ignore this, continue parsing
+                return res;
+            };
+            const node = try p.arena.allocator.create(Node.SuffixOp);
+            node.* = .{
+                .lhs = .{ .node = res },
+                .op = .{
+                    .Call = .{
+                        .params = params.list,
+                        .async_token = async_token,
                     },
-                    .rtoken = params.rparen,
-                };
-                res = &call.base;
-                continue;
+                },
+                .rtoken = params.rparen,
+            };
+            return &node.base;
+        }
+        if (try p.parsePrimaryTypeExpr()) |expr| {
+            var res = expr;
+
+            while (true) {
+                if (try p.parseSuffixOp()) |node| {
+                    switch (node.id) {
+                        .SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{ .node = res },
+                        .InfixOp => node.cast(Node.InfixOp).?.lhs = res,
+                        else => unreachable,
+                    }
+                    res = node;
+                    continue;
+                }
+                if (try p.parseFnCallArguments()) |params| {
+                    const call = try p.arena.allocator.create(Node.SuffixOp);
+                    call.* = .{
+                        .lhs = .{ .node = res },
+                        .op = .{
+                            .Call = .{
+                                .params = params.list,
+                                .async_token = null,
+                            },
+                        },
+                        .rtoken = params.rparen,
+                    };
+                    res = &call.base;
+                    continue;
+                }
+                break;
             }
-            break;
+            return res;
         }
-        return res;
+
+        return null;
     }
 
-    return null;
-}
+    /// PrimaryTypeExpr
+    ///     <- BUILTINIDENTIFIER FnCallArguments
+    ///      / CHAR_LITERAL
+    ///      / ContainerDecl
+    ///      / DOT IDENTIFIER
+    ///      / ErrorSetDecl
+    ///      / FLOAT
+    ///      / FnProto
+    ///      / GroupedExpr
+    ///      / LabeledTypeExpr
+    ///      / IDENTIFIER
+    ///      / IfTypeExpr
+    ///      / INTEGER
+    ///      / KEYWORD_comptime TypeExpr
+    ///      / KEYWORD_error DOT IDENTIFIER
+    ///      / KEYWORD_false
+    ///      / KEYWORD_null
+    ///      / KEYWORD_anyframe
+    ///      / KEYWORD_true
+    ///      / KEYWORD_undefined
+    ///      / KEYWORD_unreachable
+    ///      / STRINGLITERAL
+    ///      / SwitchExpr
+    fn parsePrimaryTypeExpr(p: *Parser) !?*Node {
+        if (try p.parseBuiltinCall()) |node| return node;
+        if (p.eatToken(.CharLiteral)) |token| {
+            const node = try p.arena.allocator.create(Node.CharLiteral);
+            node.* = .{
+                .token = token,
+            };
+            return &node.base;
+        }
+        if (try p.parseContainerDecl()) |node| return node;
+        if (try p.parseAnonLiteral()) |node| return node;
+        if (try p.parseErrorSetDecl()) |node| return node;
+        if (try p.parseFloatLiteral()) |node| return node;
+        if (try p.parseFnProto()) |node| return node;
+        if (try p.parseGroupedExpr()) |node| return node;
+        if (try p.parseLabeledTypeExpr()) |node| return node;
+        if (try p.parseIdentifier()) |node| return node;
+        if (try p.parseIfTypeExpr()) |node| return node;
+        if (try p.parseIntegerLiteral()) |node| return node;
+        if (p.eatToken(.Keyword_comptime)) |token| {
+            const expr = (try p.parseTypeExpr()) orelse return null;
+            const node = try p.arena.allocator.create(Node.Comptime);
+            node.* = .{
+                .doc_comments = null,
+                .comptime_token = token,
+                .expr = expr,
+            };
+            return &node.base;
+        }
+        if (p.eatToken(.Keyword_error)) |token| {
+            const period = try p.expectTokenRecoverable(.Period);
+            const identifier = try p.expectNodeRecoverable(parseIdentifier, .{
+                .ExpectedIdentifier = .{ .token = p.tok_i },
+            });
+            const global_error_set = try p.createLiteral(Node.ErrorType, token);
+            if (period == null or identifier == null) return global_error_set;
 
-/// PrimaryTypeExpr
-///     <- BUILTINIDENTIFIER FnCallArguments
-///      / CHAR_LITERAL
-///      / ContainerDecl
-///      / DOT IDENTIFIER
-///      / ErrorSetDecl
-///      / FLOAT
-///      / FnProto
-///      / GroupedExpr
-///      / LabeledTypeExpr
-///      / IDENTIFIER
-///      / IfTypeExpr
-///      / INTEGER
-///      / KEYWORD_comptime TypeExpr
-///      / KEYWORD_error DOT IDENTIFIER
-///      / KEYWORD_false
-///      / KEYWORD_null
-///      / KEYWORD_anyframe
-///      / KEYWORD_true
-///      / KEYWORD_undefined
-///      / KEYWORD_unreachable
-///      / STRINGLITERAL
-///      / SwitchExpr
-fn parsePrimaryTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (try parseBuiltinCall(arena, it, tree)) |node| return node;
-    if (eatToken(it, .CharLiteral)) |token| {
-        const node = try arena.create(Node.CharLiteral);
-        node.* = .{
-            .token = token,
+            const node = try p.arena.allocator.create(Node.InfixOp);
+            node.* = .{
+                .op_token = period.?,
+                .lhs = global_error_set,
+                .op = .Period,
+                .rhs = identifier.?,
+            };
+            return &node.base;
+        }
+        if (p.eatToken(.Keyword_false)) |token| return p.createLiteral(Node.BoolLiteral, token);
+        if (p.eatToken(.Keyword_null)) |token| return p.createLiteral(Node.NullLiteral, token);
+        if (p.eatToken(.Keyword_anyframe)) |token| {
+            const node = try p.arena.allocator.create(Node.AnyFrameType);
+            node.* = .{
+                .anyframe_token = token,
+                .result = null,
+            };
+            return &node.base;
+        }
+        if (p.eatToken(.Keyword_true)) |token| return p.createLiteral(Node.BoolLiteral, token);
+        if (p.eatToken(.Keyword_undefined)) |token| return p.createLiteral(Node.UndefinedLiteral, token);
+        if (p.eatToken(.Keyword_unreachable)) |token| return p.createLiteral(Node.Unreachable, token);
+        if (try p.parseStringLiteral()) |node| return node;
+        if (try p.parseSwitchExpr()) |node| return node;
+
+        return null;
+    }
+
+    /// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
+    fn parseContainerDecl(p: *Parser) !?*Node {
+        const layout_token = p.eatToken(.Keyword_extern) orelse
+            p.eatToken(.Keyword_packed);
+
+        const node = (try p.parseContainerDeclAuto()) orelse {
+            if (layout_token) |token|
+                p.putBackToken(token);
+            return null;
         };
-        return &node.base;
+        node.cast(Node.ContainerDecl).?.*.layout_token = layout_token;
+        return node;
     }
-    if (try parseContainerDecl(arena, it, tree)) |node| return node;
-    if (try parseAnonLiteral(arena, it, tree)) |node| return node;
-    if (try parseErrorSetDecl(arena, it, tree)) |node| return node;
-    if (try parseFloatLiteral(arena, it, tree)) |node| return node;
-    if (try parseFnProto(arena, it, tree)) |node| return node;
-    if (try parseGroupedExpr(arena, it, tree)) |node| return node;
-    if (try parseLabeledTypeExpr(arena, it, tree)) |node| return node;
-    if (try parseIdentifier(arena, it, tree)) |node| return node;
-    if (try parseIfTypeExpr(arena, it, tree)) |node| return node;
-    if (try parseIntegerLiteral(arena, it, tree)) |node| return node;
-    if (eatToken(it, .Keyword_comptime)) |token| {
-        const expr = (try parseTypeExpr(arena, it, tree)) orelse return null;
-        const node = try arena.create(Node.Comptime);
+
+    /// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
+    fn parseErrorSetDecl(p: *Parser) !?*Node {
+        const error_token = p.eatToken(.Keyword_error) orelse return null;
+        if (p.eatToken(.LBrace) == null) {
+            // Might parse as `KEYWORD_error DOT IDENTIFIER` later in PrimaryTypeExpr, so don't error
+            p.putBackToken(error_token);
+            return null;
+        }
+        const decls = try p.parseErrorTagList();
+        const rbrace = try p.expectToken(.RBrace);
+
+        const node = try p.arena.allocator.create(Node.ErrorSetDecl);
         node.* = .{
-            .doc_comments = null,
-            .comptime_token = token,
-            .expr = expr,
+            .error_token = error_token,
+            .decls = decls,
+            .rbrace_token = rbrace,
         };
         return &node.base;
     }
-    if (eatToken(it, .Keyword_error)) |token| {
-        const period = try expectTokenRecoverable(it, tree, .Period);
-        const identifier = try expectNodeRecoverable(arena, it, tree, parseIdentifier, .{
-            .ExpectedIdentifier = .{ .token = it.index },
+
+    /// GroupedExpr <- LPAREN Expr RPAREN
+    fn parseGroupedExpr(p: *Parser) !?*Node {
+        const lparen = p.eatToken(.LParen) orelse return null;
+        const expr = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
         });
-        const global_error_set = try createLiteral(arena, Node.ErrorType, token);
-        if (period == null or identifier == null) return global_error_set;
+        const rparen = try p.expectToken(.RParen);
 
-        const node = try arena.create(Node.InfixOp);
+        const node = try p.arena.allocator.create(Node.GroupedExpression);
         node.* = .{
-            .op_token = period.?,
-            .lhs = global_error_set,
-            .op = .Period,
-            .rhs = identifier.?,
+            .lparen = lparen,
+            .expr = expr,
+            .rparen = rparen,
         };
         return &node.base;
     }
-    if (eatToken(it, .Keyword_false)) |token| return createLiteral(arena, Node.BoolLiteral, token);
-    if (eatToken(it, .Keyword_null)) |token| return createLiteral(arena, Node.NullLiteral, token);
-    if (eatToken(it, .Keyword_anyframe)) |token| {
-        const node = try arena.create(Node.AnyFrameType);
-        node.* = .{
-            .anyframe_token = token,
-            .result = null,
-        };
-        return &node.base;
+
+    /// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
+    fn parseIfTypeExpr(p: *Parser) !?*Node {
+        return p.parseIf(parseTypeExpr);
     }
-    if (eatToken(it, .Keyword_true)) |token| return createLiteral(arena, Node.BoolLiteral, token);
-    if (eatToken(it, .Keyword_undefined)) |token| return createLiteral(arena, Node.UndefinedLiteral, token);
-    if (eatToken(it, .Keyword_unreachable)) |token| return createLiteral(arena, Node.Unreachable, token);
-    if (try parseStringLiteral(arena, it, tree)) |node| return node;
-    if (try parseSwitchExpr(arena, it, tree)) |node| return node;
 
-    return null;
-}
+    /// LabeledTypeExpr
+    ///     <- BlockLabel Block
+    ///      / BlockLabel? LoopTypeExpr
+    fn parseLabeledTypeExpr(p: *Parser) !?*Node {
+        var colon: TokenIndex = undefined;
+        const label = p.parseBlockLabel(&colon);
 
-/// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
-fn parseContainerDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const layout_token = eatToken(it, .Keyword_extern) orelse
-        eatToken(it, .Keyword_packed);
+        if (label) |token| {
+            if (try p.parseBlock()) |node| {
+                node.cast(Node.Block).?.label = token;
+                return node;
+            }
+        }
 
-    const node = (try parseContainerDeclAuto(arena, it, tree)) orelse {
-        if (layout_token) |token|
-            putBackToken(it, token);
-        return null;
-    };
-    node.cast(Node.ContainerDecl).?.*.layout_token = layout_token;
-    return node;
-}
+        if (try p.parseLoopTypeExpr()) |node| {
+            switch (node.id) {
+                .For => node.cast(Node.For).?.label = label,
+                .While => node.cast(Node.While).?.label = label,
+                else => unreachable,
+            }
+            return node;
+        }
 
-/// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
-fn parseErrorSetDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const error_token = eatToken(it, .Keyword_error) orelse return null;
-    if (eatToken(it, .LBrace) == null) {
-        // Might parse as `KEYWORD_error DOT IDENTIFIER` later in PrimaryTypeExpr, so don't error
-        putBackToken(it, error_token);
+        if (label) |token| {
+            p.putBackToken(colon);
+            p.putBackToken(token);
+        }
         return null;
     }
-    const decls = try parseErrorTagList(arena, it, tree);
-    const rbrace = try expectToken(it, tree, .RBrace);
-
-    const node = try arena.create(Node.ErrorSetDecl);
-    node.* = .{
-        .error_token = error_token,
-        .decls = decls,
-        .rbrace_token = rbrace,
-    };
-    return &node.base;
-}
-
-/// GroupedExpr <- LPAREN Expr RPAREN
-fn parseGroupedExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const lparen = eatToken(it, .LParen) orelse return null;
-    const expr = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    const rparen = try expectToken(it, tree, .RParen);
-
-    const node = try arena.create(Node.GroupedExpression);
-    node.* = .{
-        .lparen = lparen,
-        .expr = expr,
-        .rparen = rparen,
-    };
-    return &node.base;
-}
-
-/// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
-fn parseIfTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    return parseIf(arena, it, tree, parseTypeExpr);
-}
 
-/// LabeledTypeExpr
-///     <- BlockLabel Block
-///      / BlockLabel? LoopTypeExpr
-fn parseLabeledTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    var colon: TokenIndex = undefined;
-    const label = parseBlockLabel(arena, it, tree, &colon);
+    /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
+    fn parseLoopTypeExpr(p: *Parser) !?*Node {
+        const inline_token = p.eatToken(.Keyword_inline);
 
-    if (label) |token| {
-        if (try parseBlock(arena, it, tree)) |node| {
-            node.cast(Node.Block).?.label = token;
+        if (try p.parseForTypeExpr()) |node| {
+            node.cast(Node.For).?.inline_token = inline_token;
             return node;
         }
-    }
 
-    if (try parseLoopTypeExpr(arena, it, tree)) |node| {
-        switch (node.id) {
-            .For => node.cast(Node.For).?.label = label,
-            .While => node.cast(Node.While).?.label = label,
-            else => unreachable,
+        if (try p.parseWhileTypeExpr()) |node| {
+            node.cast(Node.While).?.inline_token = inline_token;
+            return node;
         }
-        return node;
-    }
 
-    if (label) |token| {
-        putBackToken(it, colon);
-        putBackToken(it, token);
+        if (inline_token == null) return null;
+
+        // If we've seen "inline", there should have been a "for" or "while"
+        try p.errors.append(p.gpa, .{
+            .ExpectedInlinable = .{ .token = p.tok_i },
+        });
+        return error.ParseError;
     }
-    return null;
-}
 
-/// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
-fn parseLoopTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const inline_token = eatToken(it, .Keyword_inline);
+    /// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
+    fn parseForTypeExpr(p: *Parser) !?*Node {
+        const node = (try p.parseForPrefix()) orelse return null;
+        const for_prefix = node.cast(Node.For).?;
 
-    if (try parseForTypeExpr(arena, it, tree)) |node| {
-        node.cast(Node.For).?.inline_token = inline_token;
-        return node;
-    }
+        const type_expr = try p.expectNode(parseTypeExpr, .{
+            .ExpectedTypeExpr = .{ .token = p.tok_i },
+        });
+        for_prefix.body = type_expr;
 
-    if (try parseWhileTypeExpr(arena, it, tree)) |node| {
-        node.cast(Node.While).?.inline_token = inline_token;
-        return node;
-    }
+        if (p.eatToken(.Keyword_else)) |else_token| {
+            const else_expr = try p.expectNode(parseTypeExpr, .{
+                .ExpectedTypeExpr = .{ .token = p.tok_i },
+            });
 
-    if (inline_token == null) return null;
+            const else_node = try p.arena.allocator.create(Node.Else);
+            else_node.* = .{
+                .else_token = else_token,
+                .payload = null,
+                .body = else_expr,
+            };
 
-    // If we've seen "inline", there should have been a "for" or "while"
-    try tree.errors.push(.{
-        .ExpectedInlinable = .{ .token = it.index },
-    });
-    return error.ParseError;
-}
+            for_prefix.@"else" = else_node;
+        }
 
-/// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
-fn parseForTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseForPrefix(arena, it, tree)) orelse return null;
-    const for_prefix = node.cast(Node.For).?;
+        return node;
+    }
 
-    const type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
-        .ExpectedTypeExpr = .{ .token = it.index },
-    });
-    for_prefix.body = type_expr;
+    /// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
+    fn parseWhileTypeExpr(p: *Parser) !?*Node {
+        const node = (try p.parseWhilePrefix()) orelse return null;
+        const while_prefix = node.cast(Node.While).?;
 
-    if (eatToken(it, .Keyword_else)) |else_token| {
-        const else_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
-            .ExpectedTypeExpr = .{ .token = it.index },
+        const type_expr = try p.expectNode(parseTypeExpr, .{
+            .ExpectedTypeExpr = .{ .token = p.tok_i },
         });
+        while_prefix.body = type_expr;
 
-        const else_node = try arena.create(Node.Else);
-        else_node.* = .{
-            .else_token = else_token,
-            .payload = null,
-            .body = else_expr,
-        };
+        if (p.eatToken(.Keyword_else)) |else_token| {
+            const payload = try p.parsePayload();
 
-        for_prefix.@"else" = else_node;
-    }
+            const else_expr = try p.expectNode(parseTypeExpr, .{
+                .ExpectedTypeExpr = .{ .token = p.tok_i },
+            });
 
-    return node;
-}
+            const else_node = try p.arena.allocator.create(Node.Else);
+            else_node.* = .{
+                .else_token = else_token,
+                .payload = null,
+                .body = else_expr,
+            };
+
+            while_prefix.@"else" = else_node;
+        }
 
-/// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
-fn parseWhileTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseWhilePrefix(arena, it, tree)) orelse return null;
-    const while_prefix = node.cast(Node.While).?;
+        return node;
+    }
 
-    const type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
-        .ExpectedTypeExpr = .{ .token = it.index },
-    });
-    while_prefix.body = type_expr;
+    /// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
+    fn parseSwitchExpr(p: *Parser) !?*Node {
+        const switch_token = p.eatToken(.Keyword_switch) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const expr_node = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RParen);
+        _ = try p.expectToken(.LBrace);
+        const cases = try p.parseSwitchProngList();
+        const rbrace = try p.expectToken(.RBrace);
 
-    if (eatToken(it, .Keyword_else)) |else_token| {
-        const payload = try parsePayload(arena, it, tree);
+        const node = try p.arena.allocator.create(Node.Switch);
+        node.* = .{
+            .switch_token = switch_token,
+            .expr = expr_node,
+            .cases = cases,
+            .rbrace = rbrace,
+        };
+        return &node.base;
+    }
 
-        const else_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
-            .ExpectedTypeExpr = .{ .token = it.index },
+    /// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
+    fn parseAsmExpr(p: *Parser) !?*Node {
+        const asm_token = p.eatToken(.Keyword_asm) orelse return null;
+        const volatile_token = p.eatToken(.Keyword_volatile);
+        _ = try p.expectToken(.LParen);
+        const template = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
         });
 
-        const else_node = try arena.create(Node.Else);
-        else_node.* = .{
-            .else_token = else_token,
-            .payload = null,
-            .body = else_expr,
+        const node = try p.arena.allocator.create(Node.Asm);
+        node.* = .{
+            .asm_token = asm_token,
+            .volatile_token = volatile_token,
+            .template = template,
+            .outputs = Node.Asm.OutputList{},
+            .inputs = Node.Asm.InputList{},
+            .clobbers = Node.Asm.ClobberList{},
+            .rparen = undefined,
         };
 
-        while_prefix.@"else" = else_node;
+        try p.parseAsmOutput(node);
+        node.rparen = try p.expectToken(.RParen);
+        return &node.base;
     }
 
-    return node;
-}
+    /// DOT IDENTIFIER
+    fn parseAnonLiteral(p: *Parser) !?*Node {
+        const dot = p.eatToken(.Period) orelse return null;
 
-/// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
-fn parseSwitchExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const switch_token = eatToken(it, .Keyword_switch) orelse return null;
-    _ = try expectToken(it, tree, .LParen);
-    const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-    _ = try expectToken(it, tree, .LBrace);
-    const cases = try parseSwitchProngList(arena, it, tree);
-    const rbrace = try expectToken(it, tree, .RBrace);
-
-    const node = try arena.create(Node.Switch);
-    node.* = .{
-        .switch_token = switch_token,
-        .expr = expr_node,
-        .cases = cases,
-        .rbrace = rbrace,
-    };
-    return &node.base;
-}
+        // anon enum literal
+        if (p.eatToken(.Identifier)) |name| {
+            const node = try p.arena.allocator.create(Node.EnumLiteral);
+            node.* = .{
+                .dot = dot,
+                .name = name,
+            };
+            return &node.base;
+        }
 
-/// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
-fn parseAsmExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const asm_token = eatToken(it, .Keyword_asm) orelse return null;
-    const volatile_token = eatToken(it, .Keyword_volatile);
-    _ = try expectToken(it, tree, .LParen);
-    const template = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-
-    const node = try arena.create(Node.Asm);
-    node.* = .{
-        .asm_token = asm_token,
-        .volatile_token = volatile_token,
-        .template = template,
-        .outputs = Node.Asm.OutputList.init(arena),
-        .inputs = Node.Asm.InputList.init(arena),
-        .clobbers = Node.Asm.ClobberList.init(arena),
-        .rparen = undefined,
-    };
+        // anon container literal
+        if (try p.parseInitList()) |node| {
+            node.lhs = .{ .dot = dot };
+            return &node.base;
+        }
 
-    try parseAsmOutput(arena, it, tree, node);
-    node.rparen = try expectToken(it, tree, .RParen);
-    return &node.base;
-}
+        p.putBackToken(dot);
+        return null;
+    }
+
+    /// AsmOutput <- COLON AsmOutputList AsmInput?
+    fn parseAsmOutput(p: *Parser, asm_node: *Node.Asm) !void {
+        if (p.eatToken(.Colon) == null) return;
+        asm_node.outputs = try p.parseAsmOutputList();
+        try p.parseAsmInput(asm_node);
+    }
+
+    /// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
+    fn parseAsmOutputItem(p: *Parser) !?*Node.AsmOutput {
+        const lbracket = p.eatToken(.LBracket) orelse return null;
+        const name = try p.expectNode(parseIdentifier, .{
+            .ExpectedIdentifier = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RBracket);
+
+        const constraint = try p.expectNode(parseStringLiteral, .{
+            .ExpectedStringLiteral = .{ .token = p.tok_i },
+        });
 
-/// DOT IDENTIFIER
-fn parseAnonLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const dot = eatToken(it, .Period) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const kind: Node.AsmOutput.Kind = blk: {
+            if (p.eatToken(.Arrow) != null) {
+                const return_ident = try p.expectNode(parseTypeExpr, .{
+                    .ExpectedTypeExpr = .{ .token = p.tok_i },
+                });
+                break :blk .{ .Return = return_ident };
+            }
+            const variable = try p.expectNode(parseIdentifier, .{
+                .ExpectedIdentifier = .{ .token = p.tok_i },
+            });
+            break :blk .{ .Variable = variable.cast(Node.Identifier).? };
+        };
+        const rparen = try p.expectToken(.RParen);
 
-    // anon enum literal
-    if (eatToken(it, .Identifier)) |name| {
-        const node = try arena.create(Node.EnumLiteral);
+        const node = try p.arena.allocator.create(Node.AsmOutput);
         node.* = .{
-            .dot = dot,
-            .name = name,
+            .lbracket = lbracket,
+            .symbolic_name = name,
+            .constraint = constraint,
+            .kind = kind,
+            .rparen = rparen,
         };
-        return &node.base;
+        return node;
     }
 
-    // anon container literal
-    if (try parseInitList(arena, it, tree)) |node| {
-        node.lhs = .{ .dot = dot };
-        return &node.base;
+    /// AsmInput <- COLON AsmInputList AsmClobbers?
+    fn parseAsmInput(p: *Parser, asm_node: *Node.Asm) !void {
+        if (p.eatToken(.Colon) == null) return;
+        asm_node.inputs = try p.parseAsmInputList();
+        try p.parseAsmClobbers(asm_node);
     }
 
-    putBackToken(it, dot);
-    return null;
-}
-
-/// AsmOutput <- COLON AsmOutputList AsmInput?
-fn parseAsmOutput(arena: *Allocator, it: *TokenIterator, tree: *Tree, asm_node: *Node.Asm) !void {
-    if (eatToken(it, .Colon) == null) return;
-    asm_node.outputs = try parseAsmOutputList(arena, it, tree);
-    try parseAsmInput(arena, it, tree, asm_node);
-}
-
-/// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
-fn parseAsmOutputItem(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.AsmOutput {
-    const lbracket = eatToken(it, .LBracket) orelse return null;
-    const name = try expectNode(arena, it, tree, parseIdentifier, .{
-        .ExpectedIdentifier = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RBracket);
-
-    const constraint = try expectNode(arena, it, tree, parseStringLiteral, .{
-        .ExpectedStringLiteral = .{ .token = it.index },
-    });
-
-    _ = try expectToken(it, tree, .LParen);
-    const kind: Node.AsmOutput.Kind = blk: {
-        if (eatToken(it, .Arrow) != null) {
-            const return_ident = try expectNode(arena, it, tree, parseTypeExpr, .{
-                .ExpectedTypeExpr = .{ .token = it.index },
-            });
-            break :blk .{ .Return = return_ident };
-        }
-        const variable = try expectNode(arena, it, tree, parseIdentifier, .{
-            .ExpectedIdentifier = .{ .token = it.index },
+    /// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
+    fn parseAsmInputItem(p: *Parser) !?*Node.AsmInput {
+        const lbracket = p.eatToken(.LBracket) orelse return null;
+        const name = try p.expectNode(parseIdentifier, .{
+            .ExpectedIdentifier = .{ .token = p.tok_i },
         });
-        break :blk .{ .Variable = variable.cast(Node.Identifier).? };
-    };
-    const rparen = try expectToken(it, tree, .RParen);
-
-    const node = try arena.create(Node.AsmOutput);
-    node.* = .{
-        .lbracket = lbracket,
-        .symbolic_name = name,
-        .constraint = constraint,
-        .kind = kind,
-        .rparen = rparen,
-    };
-    return node;
-}
+        _ = try p.expectToken(.RBracket);
 
-/// AsmInput <- COLON AsmInputList AsmClobbers?
-fn parseAsmInput(arena: *Allocator, it: *TokenIterator, tree: *Tree, asm_node: *Node.Asm) !void {
-    if (eatToken(it, .Colon) == null) return;
-    asm_node.inputs = try parseAsmInputList(arena, it, tree);
-    try parseAsmClobbers(arena, it, tree, asm_node);
-}
+        const constraint = try p.expectNode(parseStringLiteral, .{
+            .ExpectedStringLiteral = .{ .token = p.tok_i },
+        });
 
-/// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
-fn parseAsmInputItem(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.AsmInput {
-    const lbracket = eatToken(it, .LBracket) orelse return null;
-    const name = try expectNode(arena, it, tree, parseIdentifier, .{
-        .ExpectedIdentifier = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RBracket);
-
-    const constraint = try expectNode(arena, it, tree, parseStringLiteral, .{
-        .ExpectedStringLiteral = .{ .token = it.index },
-    });
-
-    _ = try expectToken(it, tree, .LParen);
-    const expr = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    const rparen = try expectToken(it, tree, .RParen);
-
-    const node = try arena.create(Node.AsmInput);
-    node.* = .{
-        .lbracket = lbracket,
-        .symbolic_name = name,
-        .constraint = constraint,
-        .expr = expr,
-        .rparen = rparen,
-    };
-    return node;
-}
+        _ = try p.expectToken(.LParen);
+        const expr = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        const rparen = try p.expectToken(.RParen);
 
-/// AsmClobbers <- COLON StringList
-/// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
-fn parseAsmClobbers(arena: *Allocator, it: *TokenIterator, tree: *Tree, asm_node: *Node.Asm) !void {
-    if (eatToken(it, .Colon) == null) return;
-    asm_node.clobbers = try ListParseFn(
-        Node.Asm.ClobberList,
-        parseStringLiteral,
-    )(arena, it, tree);
-}
+        const node = try p.arena.allocator.create(Node.AsmInput);
+        node.* = .{
+            .lbracket = lbracket,
+            .symbolic_name = name,
+            .constraint = constraint,
+            .expr = expr,
+            .rparen = rparen,
+        };
+        return node;
+    }
 
-/// BreakLabel <- COLON IDENTIFIER
-fn parseBreakLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    _ = eatToken(it, .Colon) orelse return null;
-    return try expectNode(arena, it, tree, parseIdentifier, .{
-        .ExpectedIdentifier = .{ .token = it.index },
-    });
-}
+    /// AsmClobbers <- COLON StringList
+    /// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
+    fn parseAsmClobbers(p: *Parser, asm_node: *Node.Asm) !void {
+        if (p.eatToken(.Colon) == null) return;
+        asm_node.clobbers = try ListParseFn(
+            Node.Asm.ClobberList,
+            parseStringLiteral,
+        )(p);
+    }
 
-/// BlockLabel <- IDENTIFIER COLON
-fn parseBlockLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree, colon_token: *TokenIndex) ?TokenIndex {
-    const identifier = eatToken(it, .Identifier) orelse return null;
-    if (eatToken(it, .Colon)) |colon| {
-        colon_token.* = colon;
-        return identifier;
+    /// BreakLabel <- COLON IDENTIFIER
+    fn parseBreakLabel(p: *Parser) !?*Node {
+        _ = p.eatToken(.Colon) orelse return null;
+        return p.expectNode(parseIdentifier, .{
+            .ExpectedIdentifier = .{ .token = p.tok_i },
+        });
     }
-    putBackToken(it, identifier);
-    return null;
-}
 
-/// FieldInit <- DOT IDENTIFIER EQUAL Expr
-fn parseFieldInit(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const period_token = eatToken(it, .Period) orelse return null;
-    const name_token = eatToken(it, .Identifier) orelse {
-        // Because of anon literals `.{` is also valid.
-        putBackToken(it, period_token);
-        return null;
-    };
-    const eq_token = eatToken(it, .Equal) orelse {
-        // `.Name` may also be an enum literal, which is a later rule.
-        putBackToken(it, name_token);
-        putBackToken(it, period_token);
+    /// BlockLabel <- IDENTIFIER COLON
+    fn parseBlockLabel(p: *Parser, colon_token: *TokenIndex) ?TokenIndex {
+        const identifier = p.eatToken(.Identifier) orelse return null;
+        if (p.eatToken(.Colon)) |colon| {
+            colon_token.* = colon;
+            return identifier;
+        }
+        p.putBackToken(identifier);
         return null;
-    };
-    const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-
-    const node = try arena.create(Node.FieldInitializer);
-    node.* = .{
-        .period_token = period_token,
-        .name_token = name_token,
-        .expr = expr_node,
-    };
-    return &node.base;
-}
-
-/// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
-fn parseWhileContinueExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    _ = eatToken(it, .Colon) orelse return null;
-    _ = try expectToken(it, tree, .LParen);
-    const node = try expectNode(arena, it, tree, parseAssignExpr, .{
-        .ExpectedExprOrAssignment = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-    return node;
-}
+    }
 
-/// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
-fn parseLinkSection(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    _ = eatToken(it, .Keyword_linksection) orelse return null;
-    _ = try expectToken(it, tree, .LParen);
-    const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-    return expr_node;
-}
+    /// FieldInit <- DOT IDENTIFIER EQUAL Expr
+    fn parseFieldInit(p: *Parser) !?*Node {
+        const period_token = p.eatToken(.Period) orelse return null;
+        const name_token = p.eatToken(.Identifier) orelse {
+            // Because of anon literals `.{` is also valid.
+            p.putBackToken(period_token);
+            return null;
+        };
+        const eq_token = p.eatToken(.Equal) orelse {
+            // `.Name` may also be an enum literal, which is a later rule.
+            p.putBackToken(name_token);
+            p.putBackToken(period_token);
+            return null;
+        };
+        const expr_node = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
 
-/// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
-fn parseCallconv(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    _ = eatToken(it, .Keyword_callconv) orelse return null;
-    _ = try expectToken(it, tree, .LParen);
-    const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-    return expr_node;
-}
+        const node = try p.arena.allocator.create(Node.FieldInitializer);
+        node.* = .{
+            .period_token = period_token,
+            .name_token = name_token,
+            .expr = expr_node,
+        };
+        return &node.base;
+    }
 
-/// ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
-fn parseParamDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const doc_comments = try parseDocComment(arena, it, tree);
-    const noalias_token = eatToken(it, .Keyword_noalias);
-    const comptime_token = if (noalias_token == null) eatToken(it, .Keyword_comptime) else null;
-    const name_token = blk: {
-        const identifier = eatToken(it, .Identifier) orelse break :blk null;
-        if (eatToken(it, .Colon) != null) break :blk identifier;
-        putBackToken(it, identifier); // ParamType may also be an identifier
-        break :blk null;
-    };
-    const param_type = (try parseParamType(arena, it, tree)) orelse {
-        // Only return cleanly if no keyword, identifier, or doc comment was found
-        if (noalias_token == null and
-            comptime_token == null and
-            name_token == null and
-            doc_comments == null) return null;
-        try tree.errors.push(.{
-            .ExpectedParamType = .{ .token = it.index },
+    /// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
+    fn parseWhileContinueExpr(p: *Parser) !?*Node {
+        _ = p.eatToken(.Colon) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const node = try p.expectNode(parseAssignExpr, .{
+            .ExpectedExprOrAssignment = .{ .token = p.tok_i },
         });
-        return error.ParseError;
-    };
+        _ = try p.expectToken(.RParen);
+        return node;
+    }
 
-    const param_decl = try arena.create(Node.ParamDecl);
-    param_decl.* = .{
-        .doc_comments = doc_comments,
-        .comptime_token = comptime_token,
-        .noalias_token = noalias_token,
-        .name_token = name_token,
-        .param_type = param_type,
-    };
-    return &param_decl.base;
-}
+    /// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
+    fn parseLinkSection(p: *Parser) !?*Node {
+        _ = p.eatToken(.Keyword_linksection) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const expr_node = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RParen);
+        return expr_node;
+    }
 
-/// ParamType
-///     <- KEYWORD_var
-///      / DOT3
-///      / TypeExpr
-fn parseParamType(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?Node.ParamDecl.ParamType {
-    // TODO cast from tuple to error union is broken
-    const P = Node.ParamDecl.ParamType;
-    if (try parseVarType(arena, it, tree)) |node| return P{ .var_type = node };
-    if (eatToken(it, .Ellipsis3)) |token| return P{ .var_args = token };
-    if (try parseTypeExpr(arena, it, tree)) |node| return P{ .type_expr = node };
-    return null;
-}
-
-/// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
-fn parseIfPrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const if_token = eatToken(it, .Keyword_if) orelse return null;
-    _ = try expectToken(it, tree, .LParen);
-    const condition = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-    const payload = try parsePtrPayload(arena, it, tree);
-
-    const node = try arena.create(Node.If);
-    node.* = .{
-        .if_token = if_token,
-        .condition = condition,
-        .payload = payload,
-        .body = undefined, // set by caller
-        .@"else" = null,
-    };
-    return &node.base;
-}
+    /// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
+    fn parseCallconv(p: *Parser) !?*Node {
+        _ = p.eatToken(.Keyword_callconv) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const expr_node = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RParen);
+        return expr_node;
+    }
+
+    /// ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
+    fn parseParamDecl(p: *Parser) !?*Node {
+        const doc_comments = try p.parseDocComment();
+        const noalias_token = p.eatToken(.Keyword_noalias);
+        const comptime_token = if (noalias_token == null) p.eatToken(.Keyword_comptime) else null;
+        const name_token = blk: {
+            const identifier = p.eatToken(.Identifier) orelse break :blk null;
+            if (p.eatToken(.Colon) != null) break :blk identifier;
+            p.putBackToken(identifier); // ParamType may also be an identifier
+            break :blk null;
+        };
+        const param_type = (try p.parseParamType()) orelse {
+            // Only return cleanly if no keyword, identifier, or doc comment was found
+            if (noalias_token == null and
+                comptime_token == null and
+                name_token == null and
+                doc_comments == null) return null;
+            try p.errors.append(p.gpa, .{
+                .ExpectedParamType = .{ .token = p.tok_i },
+            });
+            return error.ParseError;
+        };
 
-/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
-fn parseWhilePrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const while_token = eatToken(it, .Keyword_while) orelse return null;
-
-    _ = try expectToken(it, tree, .LParen);
-    const condition = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-
-    const payload = try parsePtrPayload(arena, it, tree);
-    const continue_expr = try parseWhileContinueExpr(arena, it, tree);
-
-    const node = try arena.create(Node.While);
-    node.* = .{
-        .label = null,
-        .inline_token = null,
-        .while_token = while_token,
-        .condition = condition,
-        .payload = payload,
-        .continue_expr = continue_expr,
-        .body = undefined, // set by caller
-        .@"else" = null,
-    };
-    return &node.base;
-}
+        const param_decl = try p.arena.allocator.create(Node.ParamDecl);
+        param_decl.* = .{
+            .doc_comments = doc_comments,
+            .comptime_token = comptime_token,
+            .noalias_token = noalias_token,
+            .name_token = name_token,
+            .param_type = param_type,
+        };
+        return &param_decl.base;
+    }
+
+    /// ParamType
+    ///     <- KEYWORD_var
+    ///      / DOT3
+    ///      / TypeExpr
+    fn parseParamType(p: *Parser) !?Node.ParamDecl.ParamType {
+        // TODO cast from tuple to error union is broken
+        const P = Node.ParamDecl.ParamType;
+        if (try p.parseVarType()) |node| return P{ .var_type = node };
+        if (p.eatToken(.Ellipsis3)) |token| return P{ .var_args = token };
+        if (try p.parseTypeExpr()) |node| return P{ .type_expr = node };
+        return null;
+    }
 
-/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
-fn parseForPrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const for_token = eatToken(it, .Keyword_for) orelse return null;
-
-    _ = try expectToken(it, tree, .LParen);
-    const array_expr = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-
-    const payload = try expectNode(arena, it, tree, parsePtrIndexPayload, .{
-        .ExpectedPayload = .{ .token = it.index },
-    });
-
-    const node = try arena.create(Node.For);
-    node.* = .{
-        .label = null,
-        .inline_token = null,
-        .for_token = for_token,
-        .array_expr = array_expr,
-        .payload = payload,
-        .body = undefined, // set by caller
-        .@"else" = null,
-    };
-    return &node.base;
-}
+    /// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
+    fn parseIfPrefix(p: *Parser) !?*Node {
+        const if_token = p.eatToken(.Keyword_if) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const condition = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RParen);
+        const payload = try p.parsePtrPayload();
 
-/// Payload <- PIPE IDENTIFIER PIPE
-fn parsePayload(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const lpipe = eatToken(it, .Pipe) orelse return null;
-    const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
-        .ExpectedIdentifier = .{ .token = it.index },
-    });
-    const rpipe = try expectToken(it, tree, .Pipe);
-
-    const node = try arena.create(Node.Payload);
-    node.* = .{
-        .lpipe = lpipe,
-        .error_symbol = identifier,
-        .rpipe = rpipe,
-    };
-    return &node.base;
-}
+        const node = try p.arena.allocator.create(Node.If);
+        node.* = .{
+            .if_token = if_token,
+            .condition = condition,
+            .payload = payload,
+            .body = undefined, // set by caller
+            .@"else" = null,
+        };
+        return &node.base;
+    }
 
-/// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
-fn parsePtrPayload(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const lpipe = eatToken(it, .Pipe) orelse return null;
-    const asterisk = eatToken(it, .Asterisk);
-    const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
-        .ExpectedIdentifier = .{ .token = it.index },
-    });
-    const rpipe = try expectToken(it, tree, .Pipe);
-
-    const node = try arena.create(Node.PointerPayload);
-    node.* = .{
-        .lpipe = lpipe,
-        .ptr_token = asterisk,
-        .value_symbol = identifier,
-        .rpipe = rpipe,
-    };
-    return &node.base;
-}
+    /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
+    fn parseWhilePrefix(p: *Parser) !?*Node {
+        const while_token = p.eatToken(.Keyword_while) orelse return null;
 
-/// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
-fn parsePtrIndexPayload(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const lpipe = eatToken(it, .Pipe) orelse return null;
-    const asterisk = eatToken(it, .Asterisk);
-    const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
-        .ExpectedIdentifier = .{ .token = it.index },
-    });
-
-    const index = if (eatToken(it, .Comma) == null)
-        null
-    else
-        try expectNode(arena, it, tree, parseIdentifier, .{
-            .ExpectedIdentifier = .{ .token = it.index },
+        _ = try p.expectToken(.LParen);
+        const condition = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
         });
+        _ = try p.expectToken(.RParen);
 
-    const rpipe = try expectToken(it, tree, .Pipe);
+        const payload = try p.parsePtrPayload();
+        const continue_expr = try p.parseWhileContinueExpr();
 
-    const node = try arena.create(Node.PointerIndexPayload);
-    node.* = .{
-        .lpipe = lpipe,
-        .ptr_token = asterisk,
-        .value_symbol = identifier,
-        .index_symbol = index,
-        .rpipe = rpipe,
-    };
-    return &node.base;
-}
+        const node = try p.arena.allocator.create(Node.While);
+        node.* = .{
+            .label = null,
+            .inline_token = null,
+            .while_token = while_token,
+            .condition = condition,
+            .payload = payload,
+            .continue_expr = continue_expr,
+            .body = undefined, // set by caller
+            .@"else" = null,
+        };
+        return &node.base;
+    }
 
-/// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr
-fn parseSwitchProng(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseSwitchCase(arena, it, tree)) orelse return null;
-    const arrow = try expectToken(it, tree, .EqualAngleBracketRight);
-    const payload = try parsePtrPayload(arena, it, tree);
-    const expr = try expectNode(arena, it, tree, parseAssignExpr, .{
-        .ExpectedExprOrAssignment = .{ .token = it.index },
-    });
-
-    const switch_case = node.cast(Node.SwitchCase).?;
-    switch_case.arrow_token = arrow;
-    switch_case.payload = payload;
-    switch_case.expr = expr;
-
-    return node;
-}
+    /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
+    fn parseForPrefix(p: *Parser) !?*Node {
+        const for_token = p.eatToken(.Keyword_for) orelse return null;
 
-/// SwitchCase
-///     <- SwitchItem (COMMA SwitchItem)* COMMA?
-///      / KEYWORD_else
-fn parseSwitchCase(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    var list = Node.SwitchCase.ItemList.init(arena);
+        _ = try p.expectToken(.LParen);
+        const array_expr = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RParen);
 
-    if (try parseSwitchItem(arena, it, tree)) |first_item| {
-        try list.push(first_item);
-        while (eatToken(it, .Comma) != null) {
-            const next_item = (try parseSwitchItem(arena, it, tree)) orelse break;
-            try list.push(next_item);
-        }
-    } else if (eatToken(it, .Keyword_else)) |else_token| {
-        const else_node = try arena.create(Node.SwitchElse);
-        else_node.* = .{
-            .token = else_token,
-        };
-        try list.push(&else_node.base);
-    } else return null;
+        const payload = try p.expectNode(parsePtrIndexPayload, .{
+            .ExpectedPayload = .{ .token = p.tok_i },
+        });
 
-    const node = try arena.create(Node.SwitchCase);
-    node.* = .{
-        .items = list,
-        .arrow_token = undefined, // set by caller
-        .payload = null,
-        .expr = undefined, // set by caller
-    };
-    return &node.base;
-}
+        const node = try p.arena.allocator.create(Node.For);
+        node.* = .{
+            .label = null,
+            .inline_token = null,
+            .for_token = for_token,
+            .array_expr = array_expr,
+            .payload = payload,
+            .body = undefined, // set by caller
+            .@"else" = null,
+        };
+        return &node.base;
+    }
 
-/// SwitchItem <- Expr (DOT3 Expr)?
-fn parseSwitchItem(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const expr = (try parseExpr(arena, it, tree)) orelse return null;
-    if (eatToken(it, .Ellipsis3)) |token| {
-        const range_end = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
+    /// Payload <- PIPE IDENTIFIER PIPE
+    fn parsePayload(p: *Parser) !?*Node {
+        const lpipe = p.eatToken(.Pipe) orelse return null;
+        const identifier = try p.expectNode(parseIdentifier, .{
+            .ExpectedIdentifier = .{ .token = p.tok_i },
         });
+        const rpipe = try p.expectToken(.Pipe);
 
-        const node = try arena.create(Node.InfixOp);
+        const node = try p.arena.allocator.create(Node.Payload);
         node.* = .{
-            .op_token = token,
-            .lhs = expr,
-            .op = .Range,
-            .rhs = range_end,
+            .lpipe = lpipe,
+            .error_symbol = identifier,
+            .rpipe = rpipe,
         };
         return &node.base;
     }
-    return expr;
-}
 
-/// AssignOp
-///     <- ASTERISKEQUAL
-///      / SLASHEQUAL
-///      / PERCENTEQUAL
-///      / PLUSEQUAL
-///      / MINUSEQUAL
-///      / LARROW2EQUAL
-///      / RARROW2EQUAL
-///      / AMPERSANDEQUAL
-///      / CARETEQUAL
-///      / PIPEEQUAL
-///      / ASTERISKPERCENTEQUAL
-///      / PLUSPERCENTEQUAL
-///      / MINUSPERCENTEQUAL
-///      / EQUAL
-fn parseAssignOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.InfixOp.Op = switch (token.ptr.id) {
-        .AsteriskEqual => .AssignMul,
-        .SlashEqual => .AssignDiv,
-        .PercentEqual => .AssignMod,
-        .PlusEqual => .AssignAdd,
-        .MinusEqual => .AssignSub,
-        .AngleBracketAngleBracketLeftEqual => .AssignBitShiftLeft,
-        .AngleBracketAngleBracketRightEqual => .AssignBitShiftRight,
-        .AmpersandEqual => .AssignBitAnd,
-        .CaretEqual => .AssignBitXor,
-        .PipeEqual => .AssignBitOr,
-        .AsteriskPercentEqual => .AssignMulWrap,
-        .PlusPercentEqual => .AssignAddWrap,
-        .MinusPercentEqual => .AssignSubWrap,
-        .Equal => .Assign,
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+    /// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
+    fn parsePtrPayload(p: *Parser) !?*Node {
+        const lpipe = p.eatToken(.Pipe) orelse return null;
+        const asterisk = p.eatToken(.Asterisk);
+        const identifier = try p.expectNode(parseIdentifier, .{
+            .ExpectedIdentifier = .{ .token = p.tok_i },
+        });
+        const rpipe = try p.expectToken(.Pipe);
 
-    const node = try arena.create(Node.InfixOp);
-    node.* = .{
-        .op_token = token.index,
-        .lhs = undefined, // set by caller
-        .op = op,
-        .rhs = undefined, // set by caller
-    };
-    return &node.base;
-}
+        const node = try p.arena.allocator.create(Node.PointerPayload);
+        node.* = .{
+            .lpipe = lpipe,
+            .ptr_token = asterisk,
+            .value_symbol = identifier,
+            .rpipe = rpipe,
+        };
+        return &node.base;
+    }
 
-/// CompareOp
-///     <- EQUALEQUAL
-///      / EXCLAMATIONMARKEQUAL
-///      / LARROW
-///      / RARROW
-///      / LARROWEQUAL
-///      / RARROWEQUAL
-fn parseCompareOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.InfixOp.Op = switch (token.ptr.id) {
-        .EqualEqual => .EqualEqual,
-        .BangEqual => .BangEqual,
-        .AngleBracketLeft => .LessThan,
-        .AngleBracketRight => .GreaterThan,
-        .AngleBracketLeftEqual => .LessOrEqual,
-        .AngleBracketRightEqual => .GreaterOrEqual,
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+    /// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
+    fn parsePtrIndexPayload(p: *Parser) !?*Node {
+        const lpipe = p.eatToken(.Pipe) orelse return null;
+        const asterisk = p.eatToken(.Asterisk);
+        const identifier = try p.expectNode(parseIdentifier, .{
+            .ExpectedIdentifier = .{ .token = p.tok_i },
+        });
 
-    return try createInfixOp(arena, token.index, op);
-}
+        const index = if (p.eatToken(.Comma) == null)
+            null
+        else
+            try p.expectNode(parseIdentifier, .{
+                .ExpectedIdentifier = .{ .token = p.tok_i },
+            });
 
-/// BitwiseOp
-///     <- AMPERSAND
-///      / CARET
-///      / PIPE
-///      / KEYWORD_orelse
-///      / KEYWORD_catch Payload?
-fn parseBitwiseOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.InfixOp.Op = switch (token.ptr.id) {
-        .Ampersand => .BitAnd,
-        .Caret => .BitXor,
-        .Pipe => .BitOr,
-        .Keyword_orelse => .UnwrapOptional,
-        .Keyword_catch => .{ .Catch = try parsePayload(arena, it, tree) },
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+        const rpipe = try p.expectToken(.Pipe);
 
-    return try createInfixOp(arena, token.index, op);
-}
+        const node = try p.arena.allocator.create(Node.PointerIndexPayload);
+        node.* = .{
+            .lpipe = lpipe,
+            .ptr_token = asterisk,
+            .value_symbol = identifier,
+            .index_symbol = index,
+            .rpipe = rpipe,
+        };
+        return &node.base;
+    }
 
-/// BitShiftOp
-///     <- LARROW2
-///      / RARROW2
-fn parseBitShiftOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.InfixOp.Op = switch (token.ptr.id) {
-        .AngleBracketAngleBracketLeft => .BitShiftLeft,
-        .AngleBracketAngleBracketRight => .BitShiftRight,
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+    /// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr
+    fn parseSwitchProng(p: *Parser) !?*Node {
+        const node = (try p.parseSwitchCase()) orelse return null;
+        const arrow = try p.expectToken(.EqualAngleBracketRight);
+        const payload = try p.parsePtrPayload();
+        const expr = try p.expectNode(parseAssignExpr, .{
+            .ExpectedExprOrAssignment = .{ .token = p.tok_i },
+        });
 
-    return try createInfixOp(arena, token.index, op);
-}
+        const switch_case = node.cast(Node.SwitchCase).?;
+        switch_case.arrow_token = arrow;
+        switch_case.payload = payload;
+        switch_case.expr = expr;
 
-/// AdditionOp
-///     <- PLUS
-///      / MINUS
-///      / PLUS2
-///      / PLUSPERCENT
-///      / MINUSPERCENT
-fn parseAdditionOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.InfixOp.Op = switch (token.ptr.id) {
-        .Plus => .Add,
-        .Minus => .Sub,
-        .PlusPlus => .ArrayCat,
-        .PlusPercent => .AddWrap,
-        .MinusPercent => .SubWrap,
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+        return node;
+    }
 
-    return try createInfixOp(arena, token.index, op);
-}
+    /// SwitchCase
+    ///     <- SwitchItem (COMMA SwitchItem)* COMMA?
+    ///      / KEYWORD_else
+    fn parseSwitchCase(p: *Parser) !?*Node {
+        var list = Node.SwitchCase.ItemList{};
+        var list_it = &list.first;
 
-/// MultiplyOp
-///     <- PIPE2
-///      / ASTERISK
-///      / SLASH
-///      / PERCENT
-///      / ASTERISK2
-///      / ASTERISKPERCENT
-fn parseMultiplyOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.InfixOp.Op = switch (token.ptr.id) {
-        .PipePipe => .MergeErrorSets,
-        .Asterisk => .Mul,
-        .Slash => .Div,
-        .Percent => .Mod,
-        .AsteriskAsterisk => .ArrayMult,
-        .AsteriskPercent => .MulWrap,
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+        if (try p.parseSwitchItem()) |first_item| {
+            list_it = try p.llpush(*Node, list_it, first_item);
+            while (p.eatToken(.Comma) != null) {
+                const next_item = (try p.parseSwitchItem()) orelse break;
+                list_it = try p.llpush(*Node, list_it, next_item);
+            }
+        } else if (p.eatToken(.Keyword_else)) |else_token| {
+            const else_node = try p.arena.allocator.create(Node.SwitchElse);
+            else_node.* = .{
+                .token = else_token,
+            };
+            list_it = try p.llpush(*Node, list_it, &else_node.base);
+        } else return null;
 
-    return try createInfixOp(arena, token.index, op);
-}
+        const node = try p.arena.allocator.create(Node.SwitchCase);
+        node.* = .{
+            .items = list,
+            .arrow_token = undefined, // set by caller
+            .payload = null,
+            .expr = undefined, // set by caller
+        };
+        return &node.base;
+    }
 
-/// PrefixOp
-///     <- EXCLAMATIONMARK
-///      / MINUS
-///      / TILDE
-///      / MINUSPERCENT
-///      / AMPERSAND
-///      / KEYWORD_try
-///      / KEYWORD_await
-fn parsePrefixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = nextToken(it);
-    const op: Node.PrefixOp.Op = switch (token.ptr.id) {
-        .Bang => .BoolNot,
-        .Minus => .Negation,
-        .Tilde => .BitNot,
-        .MinusPercent => .NegationWrap,
-        .Ampersand => .AddressOf,
-        .Keyword_try => .Try,
-        .Keyword_await => .Await,
-        else => {
-            putBackToken(it, token.index);
-            return null;
-        },
-    };
+    /// SwitchItem <- Expr (DOT3 Expr)?
+    fn parseSwitchItem(p: *Parser) !?*Node {
+        const expr = (try p.parseExpr()) orelse return null;
+        if (p.eatToken(.Ellipsis3)) |token| {
+            const range_end = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
 
-    const node = try arena.create(Node.PrefixOp);
-    node.* = .{
-        .op_token = token.index,
-        .op = op,
-        .rhs = undefined, // set by caller
-    };
-    return &node.base;
-}
+            const node = try p.arena.allocator.create(Node.InfixOp);
+            node.* = .{
+                .op_token = token,
+                .lhs = expr,
+                .op = .Range,
+                .rhs = range_end,
+            };
+            return &node.base;
+        }
+        return expr;
+    }
+
+    /// AssignOp
+    ///     <- ASTERISKEQUAL
+    ///      / SLASHEQUAL
+    ///      / PERCENTEQUAL
+    ///      / PLUSEQUAL
+    ///      / MINUSEQUAL
+    ///      / LARROW2EQUAL
+    ///      / RARROW2EQUAL
+    ///      / AMPERSANDEQUAL
+    ///      / CARETEQUAL
+    ///      / PIPEEQUAL
+    ///      / ASTERISKPERCENTEQUAL
+    ///      / PLUSPERCENTEQUAL
+    ///      / MINUSPERCENTEQUAL
+    ///      / EQUAL
+    fn parseAssignOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.InfixOp.Op = switch (token.ptr.id) {
+            .AsteriskEqual => .AssignMul,
+            .SlashEqual => .AssignDiv,
+            .PercentEqual => .AssignMod,
+            .PlusEqual => .AssignAdd,
+            .MinusEqual => .AssignSub,
+            .AngleBracketAngleBracketLeftEqual => .AssignBitShiftLeft,
+            .AngleBracketAngleBracketRightEqual => .AssignBitShiftRight,
+            .AmpersandEqual => .AssignBitAnd,
+            .CaretEqual => .AssignBitXor,
+            .PipeEqual => .AssignBitOr,
+            .AsteriskPercentEqual => .AssignMulWrap,
+            .PlusPercentEqual => .AssignAddWrap,
+            .MinusPercentEqual => .AssignSubWrap,
+            .Equal => .Assign,
+            else => {
+                p.putBackToken(token.index);
+                return null;
+            },
+        };
 
-// TODO: ArrayTypeStart is either an array or a slice, but const/allowzero only work on
-//       pointers. Consider updating this rule:
-//       ...
-//       / ArrayTypeStart
-//       / SliceTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
-//       / PtrTypeStart ...
-
-/// PrefixTypeOp
-///     <- QUESTIONMARK
-///      / KEYWORD_anyframe MINUSRARROW
-///      / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
-///      / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
-fn parsePrefixTypeOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (eatToken(it, .QuestionMark)) |token| {
-        const node = try arena.create(Node.PrefixOp);
+        const node = try p.arena.allocator.create(Node.InfixOp);
         node.* = .{
-            .op_token = token,
-            .op = .OptionalType,
+            .op_token = token.index,
+            .lhs = undefined, // set by caller
+            .op = op,
             .rhs = undefined, // set by caller
         };
         return &node.base;
     }
 
-    // TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or
-    //       .return_type more difficult for the caller (see parsePrefixOpExpr helper).
-    //       Consider making the AnyFrameType a member of PrefixOp and add a
-    //       PrefixOp.AnyFrameType variant?
-    if (eatToken(it, .Keyword_anyframe)) |token| {
-        const arrow = eatToken(it, .Arrow) orelse {
-            putBackToken(it, token);
-            return null;
+    /// CompareOp
+    ///     <- EQUALEQUAL
+    ///      / EXCLAMATIONMARKEQUAL
+    ///      / LARROW
+    ///      / RARROW
+    ///      / LARROWEQUAL
+    ///      / RARROWEQUAL
+    fn parseCompareOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.InfixOp.Op = switch (token.ptr.id) {
+            .EqualEqual => .EqualEqual,
+            .BangEqual => .BangEqual,
+            .AngleBracketLeft => .LessThan,
+            .AngleBracketRight => .GreaterThan,
+            .AngleBracketLeftEqual => .LessOrEqual,
+            .AngleBracketRightEqual => .GreaterOrEqual,
+            else => {
+                p.putBackToken(token.index);
+                return null;
+            },
         };
-        const node = try arena.create(Node.AnyFrameType);
-        node.* = .{
-            .anyframe_token = token,
-            .result = .{
-                .arrow_token = arrow,
-                .return_type = undefined, // set by caller
+
+        return p.createInfixOp(token.index, op);
+    }
+
+    /// BitwiseOp
+    ///     <- AMPERSAND
+    ///      / CARET
+    ///      / PIPE
+    ///      / KEYWORD_orelse
+    ///      / KEYWORD_catch Payload?
+    fn parseBitwiseOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.InfixOp.Op = switch (token.ptr.id) {
+            .Ampersand => .BitAnd,
+            .Caret => .BitXor,
+            .Pipe => .BitOr,
+            .Keyword_orelse => .UnwrapOptional,
+            .Keyword_catch => .{ .Catch = try p.parsePayload() },
+            else => {
+                p.putBackToken(token.index);
+                return null;
+            },
+        };
+
+        return p.createInfixOp(token.index, op);
+    }
+
+    /// BitShiftOp
+    ///     <- LARROW2
+    ///      / RARROW2
+    fn parseBitShiftOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.InfixOp.Op = switch (token.ptr.id) {
+            .AngleBracketAngleBracketLeft => .BitShiftLeft,
+            .AngleBracketAngleBracketRight => .BitShiftRight,
+            else => {
+                p.putBackToken(token.index);
+                return null;
+            },
+        };
+
+        return p.createInfixOp(token.index, op);
+    }
+
+    /// AdditionOp
+    ///     <- PLUS
+    ///      / MINUS
+    ///      / PLUS2
+    ///      / PLUSPERCENT
+    ///      / MINUSPERCENT
+    fn parseAdditionOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.InfixOp.Op = switch (token.ptr.id) {
+            .Plus => .Add,
+            .Minus => .Sub,
+            .PlusPlus => .ArrayCat,
+            .PlusPercent => .AddWrap,
+            .MinusPercent => .SubWrap,
+            else => {
+                p.putBackToken(token.index);
+                return null;
+            },
+        };
+
+        return p.createInfixOp(token.index, op);
+    }
+
+    /// MultiplyOp
+    ///     <- PIPE2
+    ///      / ASTERISK
+    ///      / SLASH
+    ///      / PERCENT
+    ///      / ASTERISK2
+    ///      / ASTERISKPERCENT
+    fn parseMultiplyOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.InfixOp.Op = switch (token.ptr.id) {
+            .PipePipe => .MergeErrorSets,
+            .Asterisk => .Mul,
+            .Slash => .Div,
+            .Percent => .Mod,
+            .AsteriskAsterisk => .ArrayMult,
+            .AsteriskPercent => .MulWrap,
+            else => {
+                p.putBackToken(token.index);
+                return null;
+            },
+        };
+
+        return p.createInfixOp(token.index, op);
+    }
+
+    /// PrefixOp
+    ///     <- EXCLAMATIONMARK
+    ///      / MINUS
+    ///      / TILDE
+    ///      / MINUSPERCENT
+    ///      / AMPERSAND
+    ///      / KEYWORD_try
+    ///      / KEYWORD_await
+    fn parsePrefixOp(p: *Parser) !?*Node {
+        const token = p.nextToken();
+        const op: Node.PrefixOp.Op = switch (token.ptr.id) {
+            .Bang => .BoolNot,
+            .Minus => .Negation,
+            .Tilde => .BitNot,
+            .MinusPercent => .NegationWrap,
+            .Ampersand => .AddressOf,
+            .Keyword_try => .Try,
+            .Keyword_await => .Await,
+            else => {
+                p.putBackToken(token.index);
+                return null;
             },
         };
+
+        const node = try p.arena.allocator.create(Node.PrefixOp);
+        node.* = .{
+            .op_token = token.index,
+            .op = op,
+            .rhs = undefined, // set by caller
+        };
         return &node.base;
     }
 
-    if (try parsePtrTypeStart(arena, it, tree)) |node| {
-        // If the token encountered was **, there will be two nodes instead of one.
-        // The attributes should be applied to the rightmost operator.
-        const prefix_op = node.cast(Node.PrefixOp).?;
-        var ptr_info = if (tree.tokens.at(prefix_op.op_token).id == .AsteriskAsterisk)
-            &prefix_op.rhs.cast(Node.PrefixOp).?.op.PtrType
-        else
-            &prefix_op.op.PtrType;
+    // TODO: ArrayTypeStart is either an array or a slice, but const/allowzero only work on
+    //       pointers. Consider updating this rule:
+    //       ...
+    //       / ArrayTypeStart
+    //       / SliceTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
+    //       / PtrTypeStart ...
+
+    /// PrefixTypeOp
+    ///     <- QUESTIONMARK
+    ///      / KEYWORD_anyframe MINUSRARROW
+    ///      / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
+    ///      / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
+    fn parsePrefixTypeOp(p: *Parser) !?*Node {
+        if (p.eatToken(.QuestionMark)) |token| {
+            const node = try p.arena.allocator.create(Node.PrefixOp);
+            node.* = .{
+                .op_token = token,
+                .op = .OptionalType,
+                .rhs = undefined, // set by caller
+            };
+            return &node.base;
+        }
 
-        while (true) {
-            if (eatToken(it, .Keyword_align)) |align_token| {
-                const lparen = try expectToken(it, tree, .LParen);
-                const expr_node = try expectNode(arena, it, tree, parseExpr, .{
-                    .ExpectedExpr = .{ .token = it.index },
-                });
+        // TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or
+        //       .return_type more difficult for the caller (see parsePrefixOpExpr helper).
+        //       Consider making the AnyFrameType a member of PrefixOp and add a
+        //       PrefixOp.AnyFrameType variant?
+        if (p.eatToken(.Keyword_anyframe)) |token| {
+            const arrow = p.eatToken(.Arrow) orelse {
+                p.putBackToken(token);
+                return null;
+            };
+            const node = try p.arena.allocator.create(Node.AnyFrameType);
+            node.* = .{
+                .anyframe_token = token,
+                .result = .{
+                    .arrow_token = arrow,
+                    .return_type = undefined, // set by caller
+                },
+            };
+            return &node.base;
+        }
 
-                // Optional bit range
-                const bit_range = if (eatToken(it, .Colon)) |_| bit_range_value: {
-                    const range_start = try expectNode(arena, it, tree, parseIntegerLiteral, .{
-                        .ExpectedIntegerLiteral = .{ .token = it.index },
-                    });
-                    _ = try expectToken(it, tree, .Colon);
-                    const range_end = try expectNode(arena, it, tree, parseIntegerLiteral, .{
-                        .ExpectedIntegerLiteral = .{ .token = it.index },
+        if (try p.parsePtrTypeStart()) |node| {
+            // If the token encountered was **, there will be two nodes instead of one.
+            // The attributes should be applied to the rightmost operator.
+            const prefix_op = node.cast(Node.PrefixOp).?;
+            var ptr_info = if (p.tokens[prefix_op.op_token].id == .AsteriskAsterisk)
+                &prefix_op.rhs.cast(Node.PrefixOp).?.op.PtrType
+            else
+                &prefix_op.op.PtrType;
+
+            while (true) {
+                if (p.eatToken(.Keyword_align)) |align_token| {
+                    const lparen = try p.expectToken(.LParen);
+                    const expr_node = try p.expectNode(parseExpr, .{
+                        .ExpectedExpr = .{ .token = p.tok_i },
                     });
 
-                    break :bit_range_value Node.PrefixOp.PtrInfo.Align.BitRange{
-                        .start = range_start,
-                        .end = range_end,
+                    // Optional bit range
+                    const bit_range = if (p.eatToken(.Colon)) |_| bit_range_value: {
+                        const range_start = try p.expectNode(parseIntegerLiteral, .{
+                            .ExpectedIntegerLiteral = .{ .token = p.tok_i },
+                        });
+                        _ = try p.expectToken(.Colon);
+                        const range_end = try p.expectNode(parseIntegerLiteral, .{
+                            .ExpectedIntegerLiteral = .{ .token = p.tok_i },
+                        });
+
+                        break :bit_range_value Node.PrefixOp.PtrInfo.Align.BitRange{
+                            .start = range_start,
+                            .end = range_end,
+                        };
+                    } else null;
+                    _ = try p.expectToken(.RParen);
+
+                    if (ptr_info.align_info != null) {
+                        try p.errors.append(p.gpa, .{
+                            .ExtraAlignQualifier = .{ .token = p.tok_i - 1 },
+                        });
+                        continue;
+                    }
+
+                    ptr_info.align_info = Node.PrefixOp.PtrInfo.Align{
+                        .node = expr_node,
+                        .bit_range = bit_range,
                     };
-                } else null;
-                _ = try expectToken(it, tree, .RParen);
 
-                if (ptr_info.align_info != null) {
-                    try tree.errors.push(.{
-                        .ExtraAlignQualifier = .{ .token = it.index - 1 },
-                    });
                     continue;
                 }
-
-                ptr_info.align_info = Node.PrefixOp.PtrInfo.Align{
-                    .node = expr_node,
-                    .bit_range = bit_range,
-                };
-
-                continue;
-            }
-            if (eatToken(it, .Keyword_const)) |const_token| {
-                if (ptr_info.const_token != null) {
-                    try tree.errors.push(.{
-                        .ExtraConstQualifier = .{ .token = it.index - 1 },
-                    });
+                if (p.eatToken(.Keyword_const)) |const_token| {
+                    if (ptr_info.const_token != null) {
+                        try p.errors.append(p.gpa, .{
+                            .ExtraConstQualifier = .{ .token = p.tok_i - 1 },
+                        });
+                        continue;
+                    }
+                    ptr_info.const_token = const_token;
                     continue;
                 }
-                ptr_info.const_token = const_token;
-                continue;
-            }
-            if (eatToken(it, .Keyword_volatile)) |volatile_token| {
-                if (ptr_info.volatile_token != null) {
-                    try tree.errors.push(.{
-                        .ExtraVolatileQualifier = .{ .token = it.index - 1 },
-                    });
+                if (p.eatToken(.Keyword_volatile)) |volatile_token| {
+                    if (ptr_info.volatile_token != null) {
+                        try p.errors.append(p.gpa, .{
+                            .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 },
+                        });
+                        continue;
+                    }
+                    ptr_info.volatile_token = volatile_token;
                     continue;
                 }
-                ptr_info.volatile_token = volatile_token;
-                continue;
-            }
-            if (eatToken(it, .Keyword_allowzero)) |allowzero_token| {
-                if (ptr_info.allowzero_token != null) {
-                    try tree.errors.push(.{
-                        .ExtraAllowZeroQualifier = .{ .token = it.index - 1 },
-                    });
+                if (p.eatToken(.Keyword_allowzero)) |allowzero_token| {
+                    if (ptr_info.allowzero_token != null) {
+                        try p.errors.append(p.gpa, .{
+                            .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 },
+                        });
+                        continue;
+                    }
+                    ptr_info.allowzero_token = allowzero_token;
                     continue;
                 }
-                ptr_info.allowzero_token = allowzero_token;
-                continue;
+                break;
             }
-            break;
-        }
 
-        return node;
-    }
+            return node;
+        }
 
-    if (try parseArrayTypeStart(arena, it, tree)) |node| {
-        switch (node.cast(Node.PrefixOp).?.op) {
-            .ArrayType => {},
-            .SliceType => |*slice_type| {
-                // Collect pointer qualifiers in any order, but disallow duplicates
-                while (true) {
-                    if (try parseByteAlign(arena, it, tree)) |align_expr| {
-                        if (slice_type.align_info != null) {
-                            try tree.errors.push(.{
-                                .ExtraAlignQualifier = .{ .token = it.index - 1 },
-                            });
+        if (try p.parseArrayTypeStart()) |node| {
+            switch (node.cast(Node.PrefixOp).?.op) {
+                .ArrayType => {},
+                .SliceType => |*slice_type| {
+                    // Collect pointer qualifiers in any order, but disallow duplicates
+                    while (true) {
+                        if (try p.parseByteAlign()) |align_expr| {
+                            if (slice_type.align_info != null) {
+                                try p.errors.append(p.gpa, .{
+                                    .ExtraAlignQualifier = .{ .token = p.tok_i - 1 },
+                                });
+                                continue;
+                            }
+                            slice_type.align_info = Node.PrefixOp.PtrInfo.Align{
+                                .node = align_expr,
+                                .bit_range = null,
+                            };
                             continue;
                         }
-                        slice_type.align_info = Node.PrefixOp.PtrInfo.Align{
-                            .node = align_expr,
-                            .bit_range = null,
-                        };
-                        continue;
-                    }
-                    if (eatToken(it, .Keyword_const)) |const_token| {
-                        if (slice_type.const_token != null) {
-                            try tree.errors.push(.{
-                                .ExtraConstQualifier = .{ .token = it.index - 1 },
-                            });
+                        if (p.eatToken(.Keyword_const)) |const_token| {
+                            if (slice_type.const_token != null) {
+                                try p.errors.append(p.gpa, .{
+                                    .ExtraConstQualifier = .{ .token = p.tok_i - 1 },
+                                });
+                                continue;
+                            }
+                            slice_type.const_token = const_token;
                             continue;
                         }
-                        slice_type.const_token = const_token;
-                        continue;
-                    }
-                    if (eatToken(it, .Keyword_volatile)) |volatile_token| {
-                        if (slice_type.volatile_token != null) {
-                            try tree.errors.push(.{
-                                .ExtraVolatileQualifier = .{ .token = it.index - 1 },
-                            });
+                        if (p.eatToken(.Keyword_volatile)) |volatile_token| {
+                            if (slice_type.volatile_token != null) {
+                                try p.errors.append(p.gpa, .{
+                                    .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 },
+                                });
+                                continue;
+                            }
+                            slice_type.volatile_token = volatile_token;
                             continue;
                         }
-                        slice_type.volatile_token = volatile_token;
-                        continue;
+                        if (p.eatToken(.Keyword_allowzero)) |allowzero_token| {
+                            if (slice_type.allowzero_token != null) {
+                                try p.errors.append(p.gpa, .{
+                                    .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 },
+                                });
+                                continue;
+                            }
+                            slice_type.allowzero_token = allowzero_token;
+                            continue;
+                        }
+                        break;
                     }
-                    if (eatToken(it, .Keyword_allowzero)) |allowzero_token| {
-                        if (slice_type.allowzero_token != null) {
-                            try tree.errors.push(.{
-                                .ExtraAllowZeroQualifier = .{ .token = it.index - 1 },
+                },
+                else => unreachable,
+            }
+            return node;
+        }
+
+        return null;
+    }
+
+    /// SuffixOp
+    ///     <- LBRACKET Expr (DOT2 (Expr (COLON Expr)?)?)? RBRACKET
+    ///      / DOT IDENTIFIER
+    ///      / DOTASTERISK
+    ///      / DOTQUESTIONMARK
+    fn parseSuffixOp(p: *Parser) !?*Node {
+        const OpAndToken = struct {
+            op: Node.SuffixOp.Op,
+            token: TokenIndex,
+        };
+        const op_and_token: OpAndToken = blk: {
+            if (p.eatToken(.LBracket)) |_| {
+                const index_expr = try p.expectNode(parseExpr, .{
+                    .ExpectedExpr = .{ .token = p.tok_i },
+                });
+
+                if (p.eatToken(.Ellipsis2) != null) {
+                    const end_expr = try p.parseExpr();
+                    const sentinel: ?*ast.Node = if (p.eatToken(.Colon) != null)
+                        try p.parseExpr()
+                    else
+                        null;
+                    break :blk .{
+                        .op = .{
+                            .Slice = .{
+                                .start = index_expr,
+                                .end = end_expr,
+                                .sentinel = sentinel,
+                            },
+                        },
+                        .token = try p.expectToken(.RBracket),
+                    };
+                }
+
+                break :blk .{
+                    .op = .{ .ArrayAccess = index_expr },
+                    .token = try p.expectToken(.RBracket),
+                };
+            }
+
+            if (p.eatToken(.PeriodAsterisk)) |period_asterisk| {
+                break :blk .{ .op = .Deref, .token = period_asterisk };
+            }
+
+            if (p.eatToken(.Period)) |period| {
+                if (try p.parseIdentifier()) |identifier| {
+                    // TODO: It's a bit weird to return an InfixOp from the SuffixOp parser.
+                    // Should there be an ast.Node.SuffixOp.FieldAccess variant? Or should
+                    // this grammar rule be altered?
+                    const node = try p.arena.allocator.create(Node.InfixOp);
+                    node.* = .{
+                        .op_token = period,
+                        .lhs = undefined, // set by caller
+                        .op = .Period,
+                        .rhs = identifier,
+                    };
+                    return &node.base;
+                }
+                if (p.eatToken(.QuestionMark)) |question_mark| {
+                    break :blk .{ .op = .UnwrapOptional, .token = question_mark };
+                }
+                try p.errors.append(p.gpa, .{
+                    .ExpectedSuffixOp = .{ .token = p.tok_i },
+                });
+                return null;
+            }
+
+            return null;
+        };
+
+        const node = try p.arena.allocator.create(Node.SuffixOp);
+        node.* = .{
+            .lhs = undefined, // set by caller
+            .op = op_and_token.op,
+            .rtoken = op_and_token.token,
+        };
+        return &node.base;
+    }
+
+    /// FnCallArguments <- LPAREN ExprList RPAREN
+    /// ExprList <- (Expr COMMA)* Expr?
+    fn parseFnCallArguments(p: *Parser) !?AnnotatedParamList {
+        if (p.eatToken(.LParen) == null) return null;
+        const list = try ListParseFn(Node.FnProto.ParamList, parseExpr)(p);
+        const rparen = try p.expectToken(.RParen);
+        return AnnotatedParamList{ .list = list, .rparen = rparen };
+    }
+
+    const AnnotatedParamList = struct {
+        list: Node.FnProto.ParamList, // NOTE: may also be any other type SegmentedList(*Node, 2)
+        rparen: TokenIndex,
+    };
+
+    /// ArrayTypeStart <- LBRACKET Expr? RBRACKET
+    fn parseArrayTypeStart(p: *Parser) !?*Node {
+        const lbracket = p.eatToken(.LBracket) orelse return null;
+        const expr = try p.parseExpr();
+        const sentinel = if (p.eatToken(.Colon)) |_|
+            try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            })
+        else
+            null;
+        const rbracket = try p.expectToken(.RBracket);
+
+        const op: Node.PrefixOp.Op = if (expr) |len_expr|
+            .{
+                .ArrayType = .{
+                    .len_expr = len_expr,
+                    .sentinel = sentinel,
+                },
+            }
+        else
+            .{
+                .SliceType = Node.PrefixOp.PtrInfo{
+                    .allowzero_token = null,
+                    .align_info = null,
+                    .const_token = null,
+                    .volatile_token = null,
+                    .sentinel = sentinel,
+                },
+            };
+
+        const node = try p.arena.allocator.create(Node.PrefixOp);
+        node.* = .{
+            .op_token = lbracket,
+            .op = op,
+            .rhs = undefined, // set by caller
+        };
+        return &node.base;
+    }
+
+    /// PtrTypeStart
+    ///     <- ASTERISK
+    ///      / ASTERISK2
+    ///      / PTRUNKNOWN
+    ///      / PTRC
+    fn parsePtrTypeStart(p: *Parser) !?*Node {
+        if (p.eatToken(.Asterisk)) |asterisk| {
+            const sentinel = if (p.eatToken(.Colon)) |_|
+                try p.expectNode(parseExpr, .{
+                    .ExpectedExpr = .{ .token = p.tok_i },
+                })
+            else
+                null;
+            const node = try p.arena.allocator.create(Node.PrefixOp);
+            node.* = .{
+                .op_token = asterisk,
+                .op = .{ .PtrType = .{ .sentinel = sentinel } },
+                .rhs = undefined, // set by caller
+            };
+            return &node.base;
+        }
+
+        if (p.eatToken(.AsteriskAsterisk)) |double_asterisk| {
+            const node = try p.arena.allocator.create(Node.PrefixOp);
+            node.* = .{
+                .op_token = double_asterisk,
+                .op = .{ .PtrType = .{} },
+                .rhs = undefined, // set by caller
+            };
+
+            // Special case for **, which is its own token
+            const child = try p.arena.allocator.create(Node.PrefixOp);
+            child.* = .{
+                .op_token = double_asterisk,
+                .op = .{ .PtrType = .{} },
+                .rhs = undefined, // set by caller
+            };
+            node.rhs = &child.base;
+
+            return &node.base;
+        }
+        if (p.eatToken(.LBracket)) |lbracket| {
+            const asterisk = p.eatToken(.Asterisk) orelse {
+                p.putBackToken(lbracket);
+                return null;
+            };
+            if (p.eatToken(.Identifier)) |ident| {
+                const token_slice = p.source[p.tokens[ident].start..p.tokens[ident].end];
+                if (!std.mem.eql(u8, token_slice, "c")) {
+                    p.putBackToken(ident);
+                } else {
+                    _ = try p.expectToken(.RBracket);
+                    const node = try p.arena.allocator.create(Node.PrefixOp);
+                    node.* = .{
+                        .op_token = lbracket,
+                        .op = .{ .PtrType = .{} },
+                        .rhs = undefined, // set by caller
+                    };
+                    return &node.base;
+                }
+            }
+            const sentinel = if (p.eatToken(.Colon)) |_|
+                try p.expectNode(parseExpr, .{
+                    .ExpectedExpr = .{ .token = p.tok_i },
+                })
+            else
+                null;
+            _ = try p.expectToken(.RBracket);
+            const node = try p.arena.allocator.create(Node.PrefixOp);
+            node.* = .{
+                .op_token = lbracket,
+                .op = .{ .PtrType = .{ .sentinel = sentinel } },
+                .rhs = undefined, // set by caller
+            };
+            return &node.base;
+        }
+        return null;
+    }
+
+    /// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
+    fn parseContainerDeclAuto(p: *Parser) !?*Node {
+        const node = (try p.parseContainerDeclType()) orelse return null;
+        const lbrace = try p.expectToken(.LBrace);
+        const members = try p.parseContainerMembers(false);
+        const rbrace = try p.expectToken(.RBrace);
+
+        const decl_type = node.cast(Node.ContainerDecl).?;
+        decl_type.fields_and_decls = members;
+        decl_type.lbrace_token = lbrace;
+        decl_type.rbrace_token = rbrace;
+
+        return node;
+    }
+
+    /// ContainerDeclType
+    ///     <- KEYWORD_struct
+    ///      / KEYWORD_enum (LPAREN Expr RPAREN)?
+    ///      / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
+    fn parseContainerDeclType(p: *Parser) !?*Node {
+        const kind_token = p.nextToken();
+
+        const init_arg_expr = switch (kind_token.ptr.id) {
+            .Keyword_struct => Node.ContainerDecl.InitArg{ .None = {} },
+            .Keyword_enum => blk: {
+                if (p.eatToken(.LParen) != null) {
+                    const expr = try p.expectNode(parseExpr, .{
+                        .ExpectedExpr = .{ .token = p.tok_i },
+                    });
+                    _ = try p.expectToken(.RParen);
+                    break :blk Node.ContainerDecl.InitArg{ .Type = expr };
+                }
+                break :blk Node.ContainerDecl.InitArg{ .None = {} };
+            },
+            .Keyword_union => blk: {
+                if (p.eatToken(.LParen) != null) {
+                    if (p.eatToken(.Keyword_enum) != null) {
+                        if (p.eatToken(.LParen) != null) {
+                            const expr = try p.expectNode(parseExpr, .{
+                                .ExpectedExpr = .{ .token = p.tok_i },
                             });
-                            continue;
+                            _ = try p.expectToken(.RParen);
+                            _ = try p.expectToken(.RParen);
+                            break :blk Node.ContainerDecl.InitArg{ .Enum = expr };
                         }
-                        slice_type.allowzero_token = allowzero_token;
-                        continue;
+                        _ = try p.expectToken(.RParen);
+                        break :blk Node.ContainerDecl.InitArg{ .Enum = null };
+                    }
+                    const expr = try p.expectNode(parseExpr, .{
+                        .ExpectedExpr = .{ .token = p.tok_i },
+                    });
+                    _ = try p.expectToken(.RParen);
+                    break :blk Node.ContainerDecl.InitArg{ .Type = expr };
+                }
+                break :blk Node.ContainerDecl.InitArg{ .None = {} };
+            },
+            else => {
+                p.putBackToken(kind_token.index);
+                return null;
+            },
+        };
+
+        const node = try p.arena.allocator.create(Node.ContainerDecl);
+        node.* = .{
+            .layout_token = null,
+            .kind_token = kind_token.index,
+            .init_arg_expr = init_arg_expr,
+            .fields_and_decls = undefined, // set by caller
+            .lbrace_token = undefined, // set by caller
+            .rbrace_token = undefined, // set by caller
+        };
+        return &node.base;
+    }
+
+    /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
+    fn parseByteAlign(p: *Parser) !?*Node {
+        _ = p.eatToken(.Keyword_align) orelse return null;
+        _ = try p.expectToken(.LParen);
+        const expr = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        _ = try p.expectToken(.RParen);
+        return expr;
+    }
+
+    /// IdentifierList <- (IDENTIFIER COMMA)* IDENTIFIER?
+    /// Only ErrorSetDecl parses an IdentifierList
+    fn parseErrorTagList(p: *Parser) !Node.ErrorSetDecl.DeclList {
+        return ListParseFn(Node.ErrorSetDecl.DeclList, parseErrorTag)(p);
+    }
+
+    /// SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
+    fn parseSwitchProngList(p: *Parser) !Node.Switch.CaseList {
+        return ListParseFn(Node.Switch.CaseList, parseSwitchProng)(p);
+    }
+
+    /// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
+    fn parseAsmOutputList(p: *Parser) Error!Node.Asm.OutputList {
+        return ListParseFn(Node.Asm.OutputList, parseAsmOutputItem)(p);
+    }
+
+    /// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
+    fn parseAsmInputList(p: *Parser) Error!Node.Asm.InputList {
+        return ListParseFn(Node.Asm.InputList, parseAsmInputItem)(p);
+    }
+
+    /// ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
+    fn parseParamDeclList(p: *Parser, var_args_token: *?TokenIndex) !Node.FnProto.ParamList {
+        var list = Node.FnProto.ParamList{};
+        var list_it = &list.first;
+        var last: ?*Node = null;
+        while (try p.parseParamDecl()) |node| {
+            last = node;
+            list_it = try p.llpush(*Node, list_it, node);
+
+            switch (p.tokens[p.tok_i].id) {
+                .Comma => _ = p.nextToken(),
+                // all possible delimiters
+                .Colon, .RParen, .RBrace, .RBracket => break,
+                else => {
+                    // this is likely just a missing comma,
+                    // continue parsing this list and give an error
+                    try p.errors.append(p.gpa, .{
+                        .ExpectedToken = .{ .token = p.tok_i, .expected_id = .Comma },
+                    });
+                },
+            }
+        }
+        if (last) |node| {
+            const param_type = node.cast(Node.ParamDecl).?.param_type;
+            if (param_type == .var_args) {
+                var_args_token.* = param_type.var_args;
+            }
+        }
+        return list;
+    }
+
+    const NodeParseFn = fn (p: *Parser) Error!?*Node;
+
+    fn ListParseFn(comptime L: type, comptime nodeParseFn: var) ParseFn(L) {
+        return struct {
+            pub fn parse(p: *Parser) !L {
+                var list = L{};
+                var list_it = &list.first;
+                while (try nodeParseFn(p)) |node| {
+                    list_it = try p.llpush(L.Node.Data, list_it, node);
+
+                    switch (p.tokens[p.tok_i].id) {
+                        .Comma => _ = p.nextToken(),
+                        // all possible delimiters
+                        .Colon, .RParen, .RBrace, .RBracket => break,
+                        else => {
+                            // this is likely just a missing comma,
+                            // continue parsing this list and give an error
+                            try p.errors.append(p.gpa, .{
+                                .ExpectedToken = .{ .token = p.tok_i, .expected_id = .Comma },
+                            });
+                        },
                     }
-                    break;
                 }
-            },
-            else => unreachable,
-        }
-        return node;
+                return list;
+            }
+        }.parse;
     }
 
-    return null;
-}
-
-/// SuffixOp
-///     <- LBRACKET Expr (DOT2 (Expr (COLON Expr)?)?)? RBRACKET
-///      / DOT IDENTIFIER
-///      / DOTASTERISK
-///      / DOTQUESTIONMARK
-fn parseSuffixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const OpAndToken = struct {
-        op: Node.SuffixOp.Op,
-        token: TokenIndex,
-    };
-    const op_and_token: OpAndToken = blk: {
-        if (eatToken(it, .LBracket)) |_| {
-            const index_expr = try expectNode(arena, it, tree, parseExpr, .{
-                .ExpectedExpr = .{ .token = it.index },
-            });
-
-            if (eatToken(it, .Ellipsis2) != null) {
-                const end_expr = try parseExpr(arena, it, tree);
-                const sentinel: ?*ast.Node = if (eatToken(it, .Colon) != null)
-                    try parseExpr(arena, it, tree)
-                else
-                    null;
-                break :blk .{
-                    .op = .{
-                        .Slice = .{
-                            .start = index_expr,
-                            .end = end_expr,
-                            .sentinel = sentinel,
-                        },
+    fn SimpleBinOpParseFn(comptime token: Token.Id, comptime op: Node.InfixOp.Op) NodeParseFn {
+        return struct {
+            pub fn parse(p: *Parser) Error!?*Node {
+                const op_token = if (token == .Keyword_and) switch (p.tokens[p.tok_i].id) {
+                    .Keyword_and => p.nextToken().index,
+                    .Invalid_ampersands => blk: {
+                        try p.errors.append(p.gpa, .{
+                            .InvalidAnd = .{ .token = p.tok_i },
+                        });
+                        break :blk p.nextToken().index;
                     },
-                    .token = try expectToken(it, tree, .RBracket),
-                };
-            }
-
-            break :blk .{
-                .op = .{ .ArrayAccess = index_expr },
-                .token = try expectToken(it, tree, .RBracket),
-            };
-        }
-
-        if (eatToken(it, .PeriodAsterisk)) |period_asterisk| {
-            break :blk .{ .op = .Deref, .token = period_asterisk };
-        }
+                    else => return null,
+                } else p.eatToken(token) orelse return null;
 
-        if (eatToken(it, .Period)) |period| {
-            if (try parseIdentifier(arena, it, tree)) |identifier| {
-                // TODO: It's a bit weird to return an InfixOp from the SuffixOp parser.
-                // Should there be an ast.Node.SuffixOp.FieldAccess variant? Or should
-                // this grammar rule be altered?
-                const node = try arena.create(Node.InfixOp);
+                const node = try p.arena.allocator.create(Node.InfixOp);
                 node.* = .{
-                    .op_token = period,
+                    .op_token = op_token,
                     .lhs = undefined, // set by caller
-                    .op = .Period,
-                    .rhs = identifier,
+                    .op = op,
+                    .rhs = undefined, // set by caller
                 };
                 return &node.base;
             }
-            if (eatToken(it, .QuestionMark)) |question_mark| {
-                break :blk .{ .op = .UnwrapOptional, .token = question_mark };
-            }
-            try tree.errors.push(.{
-                .ExpectedSuffixOp = .{ .token = it.index },
-            });
-            return null;
-        }
-
-        return null;
-    };
-
-    const node = try arena.create(Node.SuffixOp);
-    node.* = .{
-        .lhs = undefined, // set by caller
-        .op = op_and_token.op,
-        .rtoken = op_and_token.token,
-    };
-    return &node.base;
-}
+        }.parse;
+    }
 
-/// FnCallArguments <- LPAREN ExprList RPAREN
-/// ExprList <- (Expr COMMA)* Expr?
-fn parseFnCallArguments(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?AnnotatedParamList {
-    if (eatToken(it, .LParen) == null) return null;
-    const list = try ListParseFn(Node.FnProto.ParamList, parseExpr)(arena, it, tree);
-    const rparen = try expectToken(it, tree, .RParen);
-    return AnnotatedParamList{ .list = list, .rparen = rparen };
-}
+    // Helper parsers not included in the grammar
 
-const AnnotatedParamList = struct {
-    list: Node.FnProto.ParamList, // NOTE: may also be any other type SegmentedList(*Node, 2)
-    rparen: TokenIndex,
-};
+    fn parseBuiltinCall(p: *Parser) !?*Node {
+        const token = p.eatToken(.Builtin) orelse return null;
+        const params = (try p.parseFnCallArguments()) orelse {
+            try p.errors.append(p.gpa, .{
+                .ExpectedParamList = .{ .token = p.tok_i },
+            });
 
-/// ArrayTypeStart <- LBRACKET Expr? RBRACKET
-fn parseArrayTypeStart(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const lbracket = eatToken(it, .LBracket) orelse return null;
-    const expr = try parseExpr(arena, it, tree);
-    const sentinel = if (eatToken(it, .Colon)) |_|
-        try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        })
-    else
-        null;
-    const rbracket = try expectToken(it, tree, .RBracket);
-
-    const op: Node.PrefixOp.Op = if (expr) |len_expr|
-        .{
-            .ArrayType = .{
-                .len_expr = len_expr,
-                .sentinel = sentinel,
-            },
-        }
-    else
-        .{
-            .SliceType = Node.PrefixOp.PtrInfo{
-                .allowzero_token = null,
-                .align_info = null,
-                .const_token = null,
-                .volatile_token = null,
-                .sentinel = sentinel,
-            },
+            // lets pretend this was an identifier so we can continue parsing
+            const node = try p.arena.allocator.create(Node.Identifier);
+            node.* = .{
+                .token = token,
+            };
+            return &node.base;
         };
-
-    const node = try arena.create(Node.PrefixOp);
-    node.* = .{
-        .op_token = lbracket,
-        .op = op,
-        .rhs = undefined, // set by caller
-    };
-    return &node.base;
-}
-
-/// PtrTypeStart
-///     <- ASTERISK
-///      / ASTERISK2
-///      / PTRUNKNOWN
-///      / PTRC
-fn parsePtrTypeStart(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (eatToken(it, .Asterisk)) |asterisk| {
-        const sentinel = if (eatToken(it, .Colon)) |_|
-            try expectNode(arena, it, tree, parseExpr, .{
-                .ExpectedExpr = .{ .token = it.index },
-            })
-        else
-            null;
-        const node = try arena.create(Node.PrefixOp);
+        const node = try p.arena.allocator.create(Node.BuiltinCall);
         node.* = .{
-            .op_token = asterisk,
-            .op = .{ .PtrType = .{ .sentinel = sentinel } },
-            .rhs = undefined, // set by caller
+            .builtin_token = token,
+            .params = params.list,
+            .rparen_token = params.rparen,
         };
         return &node.base;
     }
 
-    if (eatToken(it, .AsteriskAsterisk)) |double_asterisk| {
-        const node = try arena.create(Node.PrefixOp);
+    fn parseErrorTag(p: *Parser) !?*Node {
+        const doc_comments = try p.parseDocComment(); // no need to rewind on failure
+        const token = p.eatToken(.Identifier) orelse return null;
+
+        const node = try p.arena.allocator.create(Node.ErrorTag);
         node.* = .{
-            .op_token = double_asterisk,
-            .op = .{ .PtrType = .{} },
-            .rhs = undefined, // set by caller
+            .doc_comments = doc_comments,
+            .name_token = token,
         };
+        return &node.base;
+    }
 
-        // Special case for **, which is its own token
-        const child = try arena.create(Node.PrefixOp);
-        child.* = .{
-            .op_token = double_asterisk,
-            .op = .{ .PtrType = .{} },
-            .rhs = undefined, // set by caller
+    fn parseIdentifier(p: *Parser) !?*Node {
+        const token = p.eatToken(.Identifier) orelse return null;
+        const node = try p.arena.allocator.create(Node.Identifier);
+        node.* = .{
+            .token = token,
         };
-        node.rhs = &child.base;
-
         return &node.base;
     }
-    if (eatToken(it, .LBracket)) |lbracket| {
-        const asterisk = eatToken(it, .Asterisk) orelse {
-            putBackToken(it, lbracket);
-            return null;
-        };
-        if (eatToken(it, .Identifier)) |ident| {
-            if (!std.mem.eql(u8, tree.tokenSlice(ident), "c")) {
-                putBackToken(it, ident);
-            } else {
-                _ = try expectToken(it, tree, .RBracket);
-                const node = try arena.create(Node.PrefixOp);
-                node.* = .{
-                    .op_token = lbracket,
-                    .op = .{ .PtrType = .{} },
-                    .rhs = undefined, // set by caller
-                };
-                return &node.base;
-            }
-        }
-        const sentinel = if (eatToken(it, .Colon)) |_|
-            try expectNode(arena, it, tree, parseExpr, .{
-                .ExpectedExpr = .{ .token = it.index },
-            })
-        else
-            null;
-        _ = try expectToken(it, tree, .RBracket);
-        const node = try arena.create(Node.PrefixOp);
+
+    fn parseVarType(p: *Parser) !?*Node {
+        const token = p.eatToken(.Keyword_var) orelse return null;
+        const node = try p.arena.allocator.create(Node.VarType);
         node.* = .{
-            .op_token = lbracket,
-            .op = .{ .PtrType = .{ .sentinel = sentinel } },
-            .rhs = undefined, // set by caller
+            .token = token,
         };
         return &node.base;
     }
-    return null;
-}
-
-/// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
-fn parseContainerDeclAuto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const node = (try parseContainerDeclType(arena, it, tree)) orelse return null;
-    const lbrace = try expectToken(it, tree, .LBrace);
-    const members = try parseContainerMembers(arena, it, tree, false);
-    const rbrace = try expectToken(it, tree, .RBrace);
-
-    const decl_type = node.cast(Node.ContainerDecl).?;
-    decl_type.fields_and_decls = members;
-    decl_type.lbrace_token = lbrace;
-    decl_type.rbrace_token = rbrace;
-
-    return node;
-}
-
-/// ContainerDeclType
-///     <- KEYWORD_struct
-///      / KEYWORD_enum (LPAREN Expr RPAREN)?
-///      / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
-fn parseContainerDeclType(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const kind_token = nextToken(it);
-
-    const init_arg_expr = switch (kind_token.ptr.id) {
-        .Keyword_struct => Node.ContainerDecl.InitArg{ .None = {} },
-        .Keyword_enum => blk: {
-            if (eatToken(it, .LParen) != null) {
-                const expr = try expectNode(arena, it, tree, parseExpr, .{
-                    .ExpectedExpr = .{ .token = it.index },
-                });
-                _ = try expectToken(it, tree, .RParen);
-                break :blk Node.ContainerDecl.InitArg{ .Type = expr };
-            }
-            break :blk Node.ContainerDecl.InitArg{ .None = {} };
-        },
-        .Keyword_union => blk: {
-            if (eatToken(it, .LParen) != null) {
-                if (eatToken(it, .Keyword_enum) != null) {
-                    if (eatToken(it, .LParen) != null) {
-                        const expr = try expectNode(arena, it, tree, parseExpr, .{
-                            .ExpectedExpr = .{ .token = it.index },
-                        });
-                        _ = try expectToken(it, tree, .RParen);
-                        _ = try expectToken(it, tree, .RParen);
-                        break :blk Node.ContainerDecl.InitArg{ .Enum = expr };
-                    }
-                    _ = try expectToken(it, tree, .RParen);
-                    break :blk Node.ContainerDecl.InitArg{ .Enum = null };
-                }
-                const expr = try expectNode(arena, it, tree, parseExpr, .{
-                    .ExpectedExpr = .{ .token = it.index },
-                });
-                _ = try expectToken(it, tree, .RParen);
-                break :blk Node.ContainerDecl.InitArg{ .Type = expr };
-            }
-            break :blk Node.ContainerDecl.InitArg{ .None = {} };
-        },
-        else => {
-            putBackToken(it, kind_token.index);
-            return null;
-        },
-    };
-
-    const node = try arena.create(Node.ContainerDecl);
-    node.* = .{
-        .layout_token = null,
-        .kind_token = kind_token.index,
-        .init_arg_expr = init_arg_expr,
-        .fields_and_decls = undefined, // set by caller
-        .lbrace_token = undefined, // set by caller
-        .rbrace_token = undefined, // set by caller
-    };
-    return &node.base;
-}
-
-/// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
-fn parseByteAlign(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    _ = eatToken(it, .Keyword_align) orelse return null;
-    _ = try expectToken(it, tree, .LParen);
-    const expr = try expectNode(arena, it, tree, parseExpr, .{
-        .ExpectedExpr = .{ .token = it.index },
-    });
-    _ = try expectToken(it, tree, .RParen);
-    return expr;
-}
 
-/// IdentifierList <- (IDENTIFIER COMMA)* IDENTIFIER?
-/// Only ErrorSetDecl parses an IdentifierList
-fn parseErrorTagList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.ErrorSetDecl.DeclList {
-    return try ListParseFn(Node.ErrorSetDecl.DeclList, parseErrorTag)(arena, it, tree);
-}
-
-/// SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
-fn parseSwitchProngList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.Switch.CaseList {
-    return try ListParseFn(Node.Switch.CaseList, parseSwitchProng)(arena, it, tree);
-}
-
-/// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
-fn parseAsmOutputList(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!Node.Asm.OutputList {
-    return try ListParseFn(Node.Asm.OutputList, parseAsmOutputItem)(arena, it, tree);
-}
-
-/// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
-fn parseAsmInputList(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!Node.Asm.InputList {
-    return try ListParseFn(Node.Asm.InputList, parseAsmInputItem)(arena, it, tree);
-}
-
-/// ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
-fn parseParamDeclList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.FnProto.ParamList {
-    return try ListParseFn(Node.FnProto.ParamList, parseParamDecl)(arena, it, tree);
-}
-
-fn ParseFn(comptime T: type) type {
-    return fn (*Allocator, *TokenIterator, *Tree) Error!T;
-}
+    fn createLiteral(p: *Parser, comptime T: type, token: TokenIndex) !*Node {
+        const result = try p.arena.allocator.create(T);
+        result.* = T{
+            .base = Node{ .id = Node.typeToId(T) },
+            .token = token,
+        };
+        return &result.base;
+    }
 
-const NodeParseFn = fn (*Allocator, *TokenIterator, *Tree) Error!?*Node;
-
-fn ListParseFn(comptime L: type, comptime nodeParseFn: var) ParseFn(L) {
-    return struct {
-        pub fn parse(arena: *Allocator, it: *TokenIterator, tree: *Tree) !L {
-            var list = L.init(arena);
-            while (try nodeParseFn(arena, it, tree)) |node| {
-                try list.push(node);
-
-                switch (it.peek().?.id) {
-                    .Comma => _ = nextToken(it),
-                    // all possible delimiters
-                    .Colon, .RParen, .RBrace, .RBracket => break,
-                    else => {
-                        // this is likely just a missing comma,
-                        // continue parsing this list and give an error
-                        try tree.errors.push(.{
-                            .ExpectedToken = .{ .token = it.index, .expected_id = .Comma },
-                        });
-                    },
-                }
-            }
-            return list;
+    fn parseStringLiteralSingle(p: *Parser) !?*Node {
+        if (p.eatToken(.StringLiteral)) |token| {
+            const node = try p.arena.allocator.create(Node.StringLiteral);
+            node.* = .{
+                .token = token,
+            };
+            return &node.base;
         }
-    }.parse;
-}
+        return null;
+    }
 
-fn SimpleBinOpParseFn(comptime token: Token.Id, comptime op: Node.InfixOp.Op) NodeParseFn {
-    return struct {
-        pub fn parse(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
-            const op_token = if (token == .Keyword_and) switch (it.peek().?.id) {
-                .Keyword_and => nextToken(it).index,
-                .Invalid_ampersands => blk: {
-                    try tree.errors.push(.{
-                        .InvalidAnd = .{ .token = it.index },
-                    });
-                    break :blk nextToken(it).index;
-                },
-                else => return null,
-            } else eatToken(it, token) orelse return null;
+    // string literal or multiline string literal
+    fn parseStringLiteral(p: *Parser) !?*Node {
+        if (try p.parseStringLiteralSingle()) |node| return node;
 
-            const node = try arena.create(Node.InfixOp);
+        if (p.eatToken(.MultilineStringLiteralLine)) |first_line| {
+            const node = try p.arena.allocator.create(Node.MultilineStringLiteral);
             node.* = .{
-                .op_token = op_token,
-                .lhs = undefined, // set by caller
-                .op = op,
-                .rhs = undefined, // set by caller
+                .lines = Node.MultilineStringLiteral.LineList{},
             };
+            var lines_it = &node.lines.first;
+            lines_it = try p.llpush(TokenIndex, lines_it, first_line);
+            while (p.eatToken(.MultilineStringLiteralLine)) |line|
+                lines_it = try p.llpush(TokenIndex, lines_it, line);
+
             return &node.base;
         }
-    }.parse;
-}
 
-// Helper parsers not included in the grammar
-
-fn parseBuiltinCall(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .Builtin) orelse return null;
-    const params = (try parseFnCallArguments(arena, it, tree)) orelse {
-        try tree.errors.push(.{
-            .ExpectedParamList = .{ .token = it.index },
-        });
+        return null;
+    }
 
-        // lets pretend this was an identifier so we can continue parsing
-        const node = try arena.create(Node.Identifier);
+    fn parseIntegerLiteral(p: *Parser) !?*Node {
+        const token = p.eatToken(.IntegerLiteral) orelse return null;
+        const node = try p.arena.allocator.create(Node.IntegerLiteral);
         node.* = .{
             .token = token,
         };
         return &node.base;
-    };
-    const node = try arena.create(Node.BuiltinCall);
-    node.* = .{
-        .builtin_token = token,
-        .params = params.list,
-        .rparen_token = params.rparen,
-    };
-    return &node.base;
-}
-
-fn parseErrorTag(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const doc_comments = try parseDocComment(arena, it, tree); // no need to rewind on failure
-    const token = eatToken(it, .Identifier) orelse return null;
-
-    const node = try arena.create(Node.ErrorTag);
-    node.* = .{
-        .doc_comments = doc_comments,
-        .name_token = token,
-    };
-    return &node.base;
-}
-
-fn parseIdentifier(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .Identifier) orelse return null;
-    const node = try arena.create(Node.Identifier);
-    node.* = .{
-        .token = token,
-    };
-    return &node.base;
-}
-
-fn parseVarType(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .Keyword_var) orelse return null;
-    const node = try arena.create(Node.VarType);
-    node.* = .{
-        .token = token,
-    };
-    return &node.base;
-}
-
-fn createLiteral(arena: *Allocator, comptime T: type, token: TokenIndex) !*Node {
-    const result = try arena.create(T);
-    result.* = T{
-        .base = Node{ .id = Node.typeToId(T) },
-        .token = token,
-    };
-    return &result.base;
-}
+    }
 
-fn parseStringLiteralSingle(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (eatToken(it, .StringLiteral)) |token| {
-        const node = try arena.create(Node.StringLiteral);
+    fn parseFloatLiteral(p: *Parser) !?*Node {
+        const token = p.eatToken(.FloatLiteral) orelse return null;
+        const node = try p.arena.allocator.create(Node.FloatLiteral);
         node.* = .{
             .token = token,
         };
         return &node.base;
     }
-    return null;
-}
 
-// string literal or multiline string literal
-fn parseStringLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    if (try parseStringLiteralSingle(arena, it, tree)) |node| return node;
-
-    if (eatToken(it, .MultilineStringLiteralLine)) |first_line| {
-        const node = try arena.create(Node.MultilineStringLiteral);
+    fn parseTry(p: *Parser) !?*Node {
+        const token = p.eatToken(.Keyword_try) orelse return null;
+        const node = try p.arena.allocator.create(Node.PrefixOp);
         node.* = .{
-            .lines = Node.MultilineStringLiteral.LineList.init(arena),
+            .op_token = token,
+            .op = .Try,
+            .rhs = undefined, // set by caller
         };
-        try node.lines.push(first_line);
-        while (eatToken(it, .MultilineStringLiteralLine)) |line|
-            try node.lines.push(line);
+        return &node.base;
+    }
 
+    fn parseUse(p: *Parser) !?*Node {
+        const token = p.eatToken(.Keyword_usingnamespace) orelse return null;
+        const node = try p.arena.allocator.create(Node.Use);
+        node.* = .{
+            .doc_comments = null,
+            .visib_token = null,
+            .use_token = token,
+            .expr = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            }),
+            .semicolon_token = try p.expectToken(.Semicolon),
+        };
         return &node.base;
     }
 
-    return null;
-}
+    /// IfPrefix Body (KEYWORD_else Payload? Body)?
+    fn parseIf(p: *Parser, bodyParseFn: NodeParseFn) !?*Node {
+        const node = (try p.parseIfPrefix()) orelse return null;
+        const if_prefix = node.cast(Node.If).?;
 
-fn parseIntegerLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .IntegerLiteral) orelse return null;
-    const node = try arena.create(Node.IntegerLiteral);
-    node.* = .{
-        .token = token,
-    };
-    return &node.base;
-}
+        if_prefix.body = try p.expectNode(bodyParseFn, .{
+            .InvalidToken = .{ .token = p.tok_i },
+        });
 
-fn parseFloatLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .FloatLiteral) orelse return null;
-    const node = try arena.create(Node.FloatLiteral);
-    node.* = .{
-        .token = token,
-    };
-    return &node.base;
-}
+        const else_token = p.eatToken(.Keyword_else) orelse return node;
+        const payload = try p.parsePayload();
+        const else_expr = try p.expectNode(bodyParseFn, .{
+            .InvalidToken = .{ .token = p.tok_i },
+        });
+        const else_node = try p.arena.allocator.create(Node.Else);
+        else_node.* = .{
+            .else_token = else_token,
+            .payload = payload,
+            .body = else_expr,
+        };
+        if_prefix.@"else" = else_node;
 
-fn parseTry(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .Keyword_try) orelse return null;
-    const node = try arena.create(Node.PrefixOp);
-    node.* = .{
-        .op_token = token,
-        .op = .Try,
-        .rhs = undefined, // set by caller
-    };
-    return &node.base;
-}
+        return node;
+    }
 
-fn parseUse(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
-    const token = eatToken(it, .Keyword_usingnamespace) orelse return null;
-    const node = try arena.create(Node.Use);
-    node.* = .{
-        .doc_comments = null,
-        .visib_token = null,
-        .use_token = token,
-        .expr = try expectNode(arena, it, tree, parseExpr, .{
-            .ExpectedExpr = .{ .token = it.index },
-        }),
-        .semicolon_token = try expectToken(it, tree, .Semicolon),
-    };
-    return &node.base;
-}
+    /// Eat a multiline doc comment
+    fn parseDocComment(p: *Parser) !?*Node.DocComment {
+        var lines = Node.DocComment.LineList{};
+        var lines_it = &lines.first;
 
-/// IfPrefix Body (KEYWORD_else Payload? Body)?
-fn parseIf(arena: *Allocator, it: *TokenIterator, tree: *Tree, bodyParseFn: NodeParseFn) !?*Node {
-    const node = (try parseIfPrefix(arena, it, tree)) orelse return null;
-    const if_prefix = node.cast(Node.If).?;
-
-    if_prefix.body = try expectNode(arena, it, tree, bodyParseFn, .{
-        .InvalidToken = .{ .token = it.index },
-    });
-
-    const else_token = eatToken(it, .Keyword_else) orelse return node;
-    const payload = try parsePayload(arena, it, tree);
-    const else_expr = try expectNode(arena, it, tree, bodyParseFn, .{
-        .InvalidToken = .{ .token = it.index },
-    });
-    const else_node = try arena.create(Node.Else);
-    else_node.* = .{
-        .else_token = else_token,
-        .payload = payload,
-        .body = else_expr,
-    };
-    if_prefix.@"else" = else_node;
+        while (p.eatToken(.DocComment)) |line| {
+            lines_it = try p.llpush(TokenIndex, lines_it, line);
+        }
 
-    return node;
-}
+        if (lines.first == null) return null;
 
-/// Eat a multiline doc comment
-fn parseDocComment(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.DocComment {
-    var lines = Node.DocComment.LineList.init(arena);
-    while (eatToken(it, .DocComment)) |line| {
-        try lines.push(line);
+        const node = try p.arena.allocator.create(Node.DocComment);
+        node.* = .{
+            .lines = lines,
+        };
+        return node;
     }
 
-    if (lines.len == 0) return null;
+    fn tokensOnSameLine(p: *Parser, token1: TokenIndex, token2: TokenIndex) bool {
+        return std.mem.indexOfScalar(u8, p.source[p.tokens[token1].end..p.tokens[token2].start], '\n') == null;
+    }
 
-    const node = try arena.create(Node.DocComment);
-    node.* = .{
-        .lines = lines,
-    };
-    return node;
-}
+    /// Eat a single-line doc comment on the same line as another node
+    fn parseAppendedDocComment(p: *Parser, after_token: TokenIndex) !?*Node.DocComment {
+        const comment_token = p.eatToken(.DocComment) orelse return null;
+        if (p.tokensOnSameLine(after_token, comment_token)) {
+            var lines = Node.DocComment.LineList{};
+            _ = try p.llpush(TokenIndex, &lines.first, comment_token);
 
-/// Eat a single-line doc comment on the same line as another node
-fn parseAppendedDocComment(arena: *Allocator, it: *TokenIterator, tree: *Tree, after_token: TokenIndex) !?*Node.DocComment {
-    const comment_token = eatToken(it, .DocComment) orelse return null;
-    if (tree.tokensOnSameLine(after_token, comment_token)) {
-        const node = try arena.create(Node.DocComment);
-        node.* = .{
-            .lines = Node.DocComment.LineList.init(arena),
-        };
-        try node.lines.push(comment_token);
-        return node;
+            const node = try p.arena.allocator.create(Node.DocComment);
+            node.* = .{ .lines = lines };
+            return node;
+        }
+        p.putBackToken(comment_token);
+        return null;
     }
-    putBackToken(it, comment_token);
-    return null;
-}
 
-/// Op* Child
-fn parsePrefixOpExpr(
-    arena: *Allocator,
-    it: *TokenIterator,
-    tree: *Tree,
-    opParseFn: NodeParseFn,
-    childParseFn: NodeParseFn,
-) Error!?*Node {
-    if (try opParseFn(arena, it, tree)) |first_op| {
-        var rightmost_op = first_op;
-        while (true) {
+    /// Op* Child
+    fn parsePrefixOpExpr(p: *Parser, opParseFn: NodeParseFn, childParseFn: NodeParseFn) Error!?*Node {
+        if (try opParseFn(p)) |first_op| {
+            var rightmost_op = first_op;
+            while (true) {
+                switch (rightmost_op.id) {
+                    .PrefixOp => {
+                        var prefix_op = rightmost_op.cast(Node.PrefixOp).?;
+                        // If the token encountered was **, there will be two nodes
+                        if (p.tokens[prefix_op.op_token].id == .AsteriskAsterisk) {
+                            rightmost_op = prefix_op.rhs;
+                            prefix_op = rightmost_op.cast(Node.PrefixOp).?;
+                        }
+                        if (try opParseFn(p)) |rhs| {
+                            prefix_op.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .AnyFrameType => {
+                        const prom = rightmost_op.cast(Node.AnyFrameType).?;
+                        if (try opParseFn(p)) |rhs| {
+                            prom.result.?.return_type = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    else => unreachable,
+                }
+            }
+
+            // If any prefix op existed, a child node on the RHS is required
             switch (rightmost_op.id) {
                 .PrefixOp => {
-                    var prefix_op = rightmost_op.cast(Node.PrefixOp).?;
-                    // If the token encountered was **, there will be two nodes
-                    if (tree.tokens.at(prefix_op.op_token).id == .AsteriskAsterisk) {
-                        rightmost_op = prefix_op.rhs;
-                        prefix_op = rightmost_op.cast(Node.PrefixOp).?;
-                    }
-                    if (try opParseFn(arena, it, tree)) |rhs| {
-                        prefix_op.rhs = rhs;
-                        rightmost_op = rhs;
-                    } else break;
+                    const prefix_op = rightmost_op.cast(Node.PrefixOp).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
                 },
                 .AnyFrameType => {
                     const prom = rightmost_op.cast(Node.AnyFrameType).?;
-                    if (try opParseFn(arena, it, tree)) |rhs| {
-                        prom.result.?.return_type = rhs;
-                        rightmost_op = rhs;
-                    } else break;
+                    prom.result.?.return_type = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
                 },
                 else => unreachable,
             }
-        }
 
-        // If any prefix op existed, a child node on the RHS is required
-        switch (rightmost_op.id) {
-            .PrefixOp => {
-                const prefix_op = rightmost_op.cast(Node.PrefixOp).?;
-                prefix_op.rhs = try expectNode(arena, it, tree, childParseFn, .{
-                    .InvalidToken = .{ .token = it.index },
-                });
-            },
-            .AnyFrameType => {
-                const prom = rightmost_op.cast(Node.AnyFrameType).?;
-                prom.result.?.return_type = try expectNode(arena, it, tree, childParseFn, .{
-                    .InvalidToken = .{ .token = it.index },
-                });
-            },
-            else => unreachable,
+            return first_op;
         }
 
-        return first_op;
+        // Otherwise, the child node is optional
+        return childParseFn(p);
     }
 
-    // Otherwise, the child node is optional
-    return try childParseFn(arena, it, tree);
-}
+    /// Child (Op Child)*
+    /// Child (Op Child)?
+    fn parseBinOpExpr(
+        p: *Parser,
+        opParseFn: NodeParseFn,
+        childParseFn: NodeParseFn,
+        chain: enum {
+            Once,
+            Infinitely,
+        },
+    ) Error!?*Node {
+        var res = (try childParseFn(p)) orelse return null;
 
-/// Child (Op Child)*
-/// Child (Op Child)?
-fn parseBinOpExpr(
-    arena: *Allocator,
-    it: *TokenIterator,
-    tree: *Tree,
-    opParseFn: NodeParseFn,
-    childParseFn: NodeParseFn,
-    chain: enum {
-        Once,
-        Infinitely,
-    },
-) Error!?*Node {
-    var res = (try childParseFn(arena, it, tree)) orelse return null;
-
-    while (try opParseFn(arena, it, tree)) |node| {
-        const right = try expectNode(arena, it, tree, childParseFn, .{
-            .InvalidToken = .{ .token = it.index },
-        });
-        const left = res;
-        res = node;
+        while (try opParseFn(p)) |node| {
+            const right = try p.expectNode(childParseFn, .{
+                .InvalidToken = .{ .token = p.tok_i },
+            });
+            const left = res;
+            res = node;
 
-        const op = node.cast(Node.InfixOp).?;
-        op.*.lhs = left;
-        op.*.rhs = right;
+            const op = node.cast(Node.InfixOp).?;
+            op.*.lhs = left;
+            op.*.rhs = right;
 
-        switch (chain) {
-            .Once => break,
-            .Infinitely => continue,
+            switch (chain) {
+                .Once => break,
+                .Infinitely => continue,
+            }
         }
+
+        return res;
     }
 
-    return res;
-}
+    fn createInfixOp(p: *Parser, index: TokenIndex, op: Node.InfixOp.Op) !*Node {
+        const node = try p.arena.allocator.create(Node.InfixOp);
+        node.* = .{
+            .op_token = index,
+            .lhs = undefined, // set by caller
+            .op = op,
+            .rhs = undefined, // set by caller
+        };
+        return &node.base;
+    }
 
-fn createInfixOp(arena: *Allocator, index: TokenIndex, op: Node.InfixOp.Op) !*Node {
-    const node = try arena.create(Node.InfixOp);
-    node.* = .{
-        .op_token = index,
-        .lhs = undefined, // set by caller
-        .op = op,
-        .rhs = undefined, // set by caller
-    };
-    return &node.base;
-}
+    fn eatToken(p: *Parser, id: Token.Id) ?TokenIndex {
+        return if (p.eatAnnotatedToken(id)) |token| token.index else null;
+    }
 
-fn eatToken(it: *TokenIterator, id: Token.Id) ?TokenIndex {
-    return if (eatAnnotatedToken(it, id)) |token| token.index else null;
-}
+    fn eatAnnotatedToken(p: *Parser, id: Token.Id) ?AnnotatedToken {
+        return if (p.tokens[p.tok_i].id == id) p.nextToken() else null;
+    }
 
-fn eatAnnotatedToken(it: *TokenIterator, id: Token.Id) ?AnnotatedToken {
-    return if (it.peek().?.id == id) nextToken(it) else null;
-}
+    fn expectToken(p: *Parser, id: Token.Id) Error!TokenIndex {
+        return (try p.expectTokenRecoverable(id)) orelse
+            error.ParseError;
+    }
 
-fn expectToken(it: *TokenIterator, tree: *Tree, id: Token.Id) Error!TokenIndex {
-    return (try expectTokenRecoverable(it, tree, id)) orelse
-        error.ParseError;
-}
+    fn expectTokenRecoverable(p: *Parser, id: Token.Id) !?TokenIndex {
+        const token = p.nextToken();
+        if (token.ptr.id != id) {
+            try p.errors.append(p.gpa, .{
+                .ExpectedToken = .{ .token = token.index, .expected_id = id },
+            });
+            // go back so that we can recover properly
+            p.putBackToken(token.index);
+            return null;
+        }
+        return token.index;
+    }
 
-fn expectTokenRecoverable(it: *TokenIterator, tree: *Tree, id: Token.Id) !?TokenIndex {
-    const token = nextToken(it);
-    if (token.ptr.id != id) {
-        try tree.errors.push(.{
-            .ExpectedToken = .{ .token = token.index, .expected_id = id },
-        });
-        // go back so that we can recover properly
-        putBackToken(it, token.index);
-        return null;
+    fn nextToken(p: *Parser) AnnotatedToken {
+        const result = AnnotatedToken{
+            .index = p.tok_i,
+            .ptr = &p.tokens[p.tok_i],
+        };
+        if (p.tokens[p.tok_i].id == .Eof) {
+            return result;
+        }
+        p.tok_i += 1;
+        assert(result.ptr.id != .LineComment);
+
+        while (true) {
+            const next_tok = p.tokens[p.tok_i];
+            if (next_tok.id != .LineComment) return result;
+            p.tok_i += 1;
+        }
+    }
+
+    fn putBackToken(p: *Parser, putting_back: TokenIndex) void {
+        while (p.tok_i > 0) {
+            p.tok_i -= 1;
+            const prev_tok = p.tokens[p.tok_i];
+            if (prev_tok.id == .LineComment) continue;
+            assert(putting_back == p.tok_i);
+            return;
+        }
     }
-    return token.index;
-}
 
-fn nextToken(it: *TokenIterator) AnnotatedToken {
-    const result = AnnotatedToken{
-        .index = it.index,
-        .ptr = it.next().?,
+    const AnnotatedToken = struct {
+        index: TokenIndex,
+        ptr: *const Token,
     };
-    assert(result.ptr.id != .LineComment);
 
-    while (true) {
-        const next_tok = it.peek() orelse return result;
-        if (next_tok.id != .LineComment) return result;
-        _ = it.next();
+    fn expectNode(
+        p: *Parser,
+        parseFn: NodeParseFn,
+        /// if parsing fails
+        err: AstError,
+    ) Error!*Node {
+        return (try p.expectNodeRecoverable(parseFn, err)) orelse return error.ParseError;
     }
-}
 
-fn putBackToken(it: *TokenIterator, putting_back: TokenIndex) void {
-    while (true) {
-        const prev_tok = it.prev() orelse return;
-        if (prev_tok.id == .LineComment) continue;
-        assert(it.list.at(putting_back) == prev_tok);
-        return;
+    fn expectNodeRecoverable(
+        p: *Parser,
+        parseFn: NodeParseFn,
+        /// if parsing fails
+        err: AstError,
+    ) !?*Node {
+        return (try parseFn(p)) orelse {
+            try p.errors.append(p.gpa, err);
+            return null;
+        };
     }
-}
-
-const AnnotatedToken = struct {
-    index: TokenIndex,
-    ptr: *Token,
 };
 
-fn expectNode(
-    arena: *Allocator,
-    it: *TokenIterator,
-    tree: *Tree,
-    parseFn: NodeParseFn,
-    err: AstError, // if parsing fails
-) Error!*Node {
-    return (try expectNodeRecoverable(arena, it, tree, parseFn, err)) orelse
-        return error.ParseError;
+fn ParseFn(comptime T: type) type {
+    return fn (p: *Parser) Error!T;
 }
 
-fn expectNodeRecoverable(
-    arena: *Allocator,
-    it: *TokenIterator,
-    tree: *Tree,
-    parseFn: NodeParseFn,
-    err: AstError, // if parsing fails
-) !?*Node {
-    return (try parseFn(arena, it, tree)) orelse {
-        try tree.errors.push(err);
-        return null;
-    };
-}
 
 test "std.zig.parser" {
     _ = @import("parser_test.zig");
lib/std/zig/parser_test.zig
@@ -3180,9 +3180,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b
     const tree = try std.zig.parse(allocator, source);
     defer tree.deinit();
 
-    var error_it = tree.errors.iterator(0);
-    while (error_it.next()) |parse_error| {
-        const token = tree.tokens.at(parse_error.loc());
+    for (tree.errors) |*parse_error| {
+        const token = tree.tokens[parse_error.loc()];
         const loc = tree.tokenLocation(0, parse_error.loc());
         try stderr.print("(memory buffer):{}:{}: error: ", .{ loc.line + 1, loc.column + 1 });
         try tree.renderError(parse_error, stderr);
@@ -3271,8 +3270,6 @@ fn testError(source: []const u8, expected_errors: []const Error) !void {
 
     std.testing.expect(tree.errors.len == expected_errors.len);
     for (expected_errors) |expected, i| {
-        const err = tree.errors.at(i);
-
-        std.testing.expect(expected == err.*);
+        std.testing.expect(expected == tree.errors[i]);
     }
 }
lib/std/zig/render.zig
@@ -67,24 +67,21 @@ fn renderRoot(
     stream: var,
     tree: *ast.Tree,
 ) (@TypeOf(stream).Error || Error)!void {
-    var tok_it = tree.tokens.iterator(0);
-
     // render all the line comments at the beginning of the file
-    while (tok_it.next()) |token| {
+    for (tree.tokens) |*token, i| {
         if (token.id != .LineComment) break;
         try stream.print("{}\n", .{mem.trimRight(u8, tree.tokenSlicePtr(token), " ")});
-        if (tok_it.peek()) |next_token| {
-            const loc = tree.tokenLocationPtr(token.end, next_token);
-            if (loc.line >= 2) {
-                try stream.writeByte('\n');
-            }
+        const next_token = &tree.tokens[i + 1];
+        const loc = tree.tokenLocationPtr(token.end, next_token.*);
+        if (loc.line >= 2) {
+            try stream.writeByte('\n');
         }
     }
 
     var start_col: usize = 0;
-    var it = tree.root_node.decls.iterator(0);
+    var it = tree.root_node.decls.first orelse return;
     while (true) {
-        var decl = (it.next() orelse return).*;
+        var decl = it.data;
 
         // This loop does the following:
         //
@@ -103,7 +100,7 @@ fn renderRoot(
 
         while (token_index != 0) {
             token_index -= 1;
-            const token = tree.tokens.at(token_index);
+            const token = tree.tokens[token_index];
             switch (token.id) {
                 .LineComment => {},
                 .DocComment => {
@@ -133,17 +130,18 @@ fn renderRoot(
             token_index = decl.firstToken();
 
             while (!fmt_active) {
-                decl = (it.next() orelse {
+                it = it.next orelse {
                     // If there's no next reformatted `decl`, just copy the
                     // remaining input tokens and bail out.
-                    const start = tree.tokens.at(copy_start_token_index).start;
+                    const start = tree.tokens[copy_start_token_index].start;
                     try copyFixingWhitespace(stream, tree.source[start..]);
                     return;
-                }).*;
+                };
+                decl = it.data;
                 var decl_first_token_index = decl.firstToken();
 
                 while (token_index < decl_first_token_index) : (token_index += 1) {
-                    const token = tree.tokens.at(token_index);
+                    const token = tree.tokens[token_index];
                     switch (token.id) {
                         .LineComment => {},
                         .Eof => unreachable,
@@ -163,7 +161,7 @@ fn renderRoot(
             token_index = copy_end_token_index;
             while (token_index != 0) {
                 token_index -= 1;
-                const token = tree.tokens.at(token_index);
+                const token = tree.tokens[token_index];
                 switch (token.id) {
                     .LineComment => {},
                     .DocComment => {
@@ -174,15 +172,14 @@ fn renderRoot(
                 }
             }
 
-            const start = tree.tokens.at(copy_start_token_index).start;
-            const end = tree.tokens.at(copy_end_token_index).start;
+            const start = tree.tokens[copy_start_token_index].start;
+            const end = tree.tokens[copy_end_token_index].start;
             try copyFixingWhitespace(stream, tree.source[start..end]);
         }
 
         try renderTopLevelDecl(allocator, stream, tree, 0, &start_col, decl);
-        if (it.peek()) |next_decl| {
-            try renderExtraNewline(tree, stream, &start_col, next_decl.*);
-        }
+        it = it.next orelse return;
+        try renderExtraNewline(tree, stream, &start_col, it.data);
     }
 }
 
@@ -191,13 +188,13 @@ fn renderExtraNewline(tree: *ast.Tree, stream: var, start_col: *usize, node: *as
     var prev_token = first_token;
     if (prev_token == 0) return;
     var newline_threshold: usize = 2;
-    while (tree.tokens.at(prev_token - 1).id == .DocComment) {
-        if (tree.tokenLocation(tree.tokens.at(prev_token - 1).end, prev_token).line == 1) {
+    while (tree.tokens[prev_token - 1].id == .DocComment) {
+        if (tree.tokenLocation(tree.tokens[prev_token - 1].end, prev_token).line == 1) {
             newline_threshold += 1;
         }
         prev_token -= 1;
     }
-    const prev_token_end = tree.tokens.at(prev_token - 1).end;
+    const prev_token_end = tree.tokens[prev_token - 1].end;
     const loc = tree.tokenLocation(prev_token_end, first_token);
     if (loc.line >= newline_threshold) {
         try stream.writeByte('\n');
@@ -262,7 +259,7 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree,
 
             const src_has_trailing_comma = blk: {
                 const maybe_comma = tree.nextToken(field.lastToken());
-                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                break :blk tree.tokens[maybe_comma].id == .Comma;
             };
 
             // The trailing comma is emitted at the end, but if it's not present
@@ -426,13 +423,13 @@ fn renderExpression(
             try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space);
 
             const after_op_space = blk: {
-                const loc = tree.tokenLocation(tree.tokens.at(infix_op_node.op_token).end, tree.nextToken(infix_op_node.op_token));
+                const loc = tree.tokenLocation(tree.tokens[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token));
                 break :blk if (loc.line == 0) op_space else Space.Newline;
             };
 
             try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space);
             if (after_op_space == Space.Newline and
-                tree.tokens.at(tree.nextToken(infix_op_node.op_token)).id != .MultilineStringLiteralLine)
+                tree.tokens[tree.nextToken(infix_op_node.op_token)].id != .MultilineStringLiteralLine)
             {
                 try stream.writeByteNTimes(' ', indent + indent_delta);
                 start_col.* = indent + indent_delta;
@@ -453,10 +450,10 @@ fn renderExpression(
 
             switch (prefix_op_node.op) {
                 .PtrType => |ptr_info| {
-                    const op_tok_id = tree.tokens.at(prefix_op_node.op_token).id;
+                    const op_tok_id = tree.tokens[prefix_op_node.op_token].id;
                     switch (op_tok_id) {
                         .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),
-                        .LBracket => if (tree.tokens.at(prefix_op_node.op_token + 2).id == .Identifier)
+                        .LBracket => if (tree.tokens[prefix_op_node.op_token + 2].id == .Identifier)
                             try stream.writeAll("[*c")
                         else
                             try stream.writeAll("[*"),
@@ -568,8 +565,8 @@ fn renderExpression(
 
                     try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
 
-                    const starts_with_comment = tree.tokens.at(lbracket + 1).id == .LineComment;
-                    const ends_with_comment = tree.tokens.at(rbracket - 1).id == .LineComment;
+                    const starts_with_comment = tree.tokens[lbracket + 1].id == .LineComment;
+                    const ends_with_comment = tree.tokens[rbracket - 1].id == .LineComment;
                     const new_indent = if (ends_with_comment) indent + indent_delta else indent;
                     const new_space = if (ends_with_comment) Space.Newline else Space.None;
                     try renderExpression(allocator, stream, tree, new_indent, start_col, array_info.len_expr, new_space);
@@ -630,7 +627,7 @@ fn renderExpression(
 
                     const src_has_trailing_comma = blk: {
                         const maybe_comma = tree.prevToken(suffix_op.rtoken);
-                        break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                        break :blk tree.tokens[maybe_comma].id == .Comma;
                     };
 
                     if (src_has_trailing_comma) {
@@ -682,8 +679,8 @@ fn renderExpression(
                     try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs.node, Space.None);
                     try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
 
-                    const starts_with_comment = tree.tokens.at(lbracket + 1).id == .LineComment;
-                    const ends_with_comment = tree.tokens.at(rbracket - 1).id == .LineComment;
+                    const starts_with_comment = tree.tokens[lbracket + 1].id == .LineComment;
+                    const ends_with_comment = tree.tokens[rbracket - 1].id == .LineComment;
                     const new_indent = if (ends_with_comment) indent + indent_delta else indent;
                     const new_space = if (ends_with_comment) Space.Newline else Space.None;
                     try renderExpression(allocator, stream, tree, new_indent, start_col, index_expr, new_space);
@@ -750,11 +747,11 @@ fn renderExpression(
 
                     const src_has_trailing_comma = blk: {
                         const maybe_comma = tree.prevToken(suffix_op.rtoken);
-                        break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                        break :blk tree.tokens[maybe_comma].id == .Comma;
                     };
 
                     const src_same_line = blk: {
-                        const loc = tree.tokenLocation(tree.tokens.at(lbrace).end, suffix_op.rtoken);
+                        const loc = tree.tokenLocation(tree.tokens[lbrace].end, suffix_op.rtoken);
                         break :blk loc.line == 0;
                     };
 
@@ -858,7 +855,7 @@ fn renderExpression(
                         try renderToken(tree, stream, lbrace, indent, start_col, Space.None);
                         return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space);
                     }
-                    if (exprs.len == 1 and tree.tokens.at(exprs.at(0).*.lastToken() + 1).id == .RBrace) {
+                    if (exprs.len == 1 and tree.tokens[exprs.at(0).*.lastToken() + 1].id == .RBrace) {
                         const expr = exprs.at(0).*;
 
                         switch (suffix_op.lhs) {
@@ -883,17 +880,17 @@ fn renderExpression(
                             const expr = it.next().?.*;
                             if (it.peek()) |next_expr| {
                                 const expr_last_token = expr.*.lastToken() + 1;
-                                const loc = tree.tokenLocation(tree.tokens.at(expr_last_token).end, next_expr.*.firstToken());
+                                const loc = tree.tokenLocation(tree.tokens[expr_last_token].end, next_expr.*.firstToken());
                                 if (loc.line != 0) break :blk count;
                                 count += 1;
                             } else {
                                 const expr_last_token = expr.*.lastToken();
-                                const loc = tree.tokenLocation(tree.tokens.at(expr_last_token).end, suffix_op.rtoken);
+                                const loc = tree.tokenLocation(tree.tokens[expr_last_token].end, suffix_op.rtoken);
                                 if (loc.line == 0) {
                                     // all on one line
                                     const src_has_trailing_comma = trailblk: {
                                         const maybe_comma = tree.prevToken(suffix_op.rtoken);
-                                        break :trailblk tree.tokens.at(maybe_comma).id == .Comma;
+                                        break :trailblk tree.tokens[maybe_comma].id == .Comma;
                                     };
                                     if (src_has_trailing_comma) {
                                         break :blk 1; // force row size 1
@@ -933,7 +930,7 @@ fn renderExpression(
 
                         var new_indent = indent + indent_delta;
 
-                        if (tree.tokens.at(tree.nextToken(lbrace)).id != .MultilineStringLiteralLine) {
+                        if (tree.tokens[tree.nextToken(lbrace)].id != .MultilineStringLiteralLine) {
                             try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline);
                             try stream.writeByteNTimes(' ', new_indent);
                         } else {
@@ -961,7 +958,7 @@ fn renderExpression(
                                 }
                                 col = 1;
 
-                                if (tree.tokens.at(tree.nextToken(comma)).id != .MultilineStringLiteralLine) {
+                                if (tree.tokens[tree.nextToken(comma)].id != .MultilineStringLiteralLine) {
                                     try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // ,
                                 } else {
                                     try renderToken(tree, stream, comma, new_indent, start_col, Space.None); // ,
@@ -1188,9 +1185,9 @@ fn renderExpression(
                 var maybe_comma = tree.prevToken(container_decl.lastToken());
                 // Doc comments for a field may also appear after the comma, eg.
                 // field_name: T, // comment attached to field_name
-                if (tree.tokens.at(maybe_comma).id == .DocComment)
+                if (tree.tokens[maybe_comma].id == .DocComment)
                     maybe_comma = tree.prevToken(maybe_comma);
-                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                break :blk tree.tokens[maybe_comma].id == .Comma;
             };
 
             // Check if the first declaration and the { are on the same line
@@ -1285,7 +1282,7 @@ fn renderExpression(
 
             const src_has_trailing_comma = blk: {
                 const maybe_comma = tree.prevToken(err_set_decl.rbrace_token);
-                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                break :blk tree.tokens[maybe_comma].id == .Comma;
             };
 
             if (src_has_trailing_comma) {
@@ -1317,7 +1314,7 @@ fn renderExpression(
                         try renderExpression(allocator, stream, tree, indent, start_col, node.*, Space.None);
 
                         const comma_token = tree.nextToken(node.*.lastToken());
-                        assert(tree.tokens.at(comma_token).id == .Comma);
+                        assert(tree.tokens[comma_token].id == .Comma);
                         try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // ,
                         try renderExtraNewline(tree, stream, start_col, next_node.*);
                     } else {
@@ -1342,7 +1339,7 @@ fn renderExpression(
             const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base);
 
             var skip_first_indent = true;
-            if (tree.tokens.at(multiline_str_literal.firstToken() - 1).id != .LineComment) {
+            if (tree.tokens[multiline_str_literal.firstToken() - 1].id != .LineComment) {
                 try stream.print("\n", .{});
                 skip_first_indent = false;
             }
@@ -1372,7 +1369,7 @@ fn renderExpression(
                 if (builtin_call.params.len < 2) break :blk false;
                 const last_node = builtin_call.params.at(builtin_call.params.len - 1).*;
                 const maybe_comma = tree.nextToken(last_node.lastToken());
-                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                break :blk tree.tokens[maybe_comma].id == .Comma;
             };
 
             const lparen = tree.nextToken(builtin_call.builtin_token);
@@ -1410,7 +1407,7 @@ fn renderExpression(
             const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", base);
 
             if (fn_proto.visib_token) |visib_token_index| {
-                const visib_token = tree.tokens.at(visib_token_index);
+                const visib_token = tree.tokens[visib_token_index];
                 assert(visib_token.id == .Keyword_pub or visib_token.id == .Keyword_export);
 
                 try renderToken(tree, stream, visib_token_index, indent, start_col, Space.Space); // pub
@@ -1433,7 +1430,7 @@ fn renderExpression(
                 try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn
                 break :blk tree.nextToken(fn_proto.fn_token);
             };
-            assert(tree.tokens.at(lparen).id == .LParen);
+            assert(tree.tokens[lparen].id == .LParen);
 
             const rparen = tree.prevToken(
             // the first token for the annotation expressions is the left
@@ -1449,10 +1446,10 @@ fn renderExpression(
                 .InferErrorSet => |node| tree.prevToken(node.firstToken()),
                 .Invalid => unreachable,
             });
-            assert(tree.tokens.at(rparen).id == .RParen);
+            assert(tree.tokens[rparen].id == .RParen);
 
             const src_params_trailing_comma = blk: {
-                const maybe_comma = tree.tokens.at(rparen - 1).id;
+                const maybe_comma = tree.tokens[rparen - 1].id;
                 break :blk maybe_comma == .Comma or maybe_comma == .LineComment;
             };
 
@@ -1591,7 +1588,7 @@ fn renderExpression(
             const src_has_trailing_comma = blk: {
                 const last_node = switch_case.items.at(switch_case.items.len - 1).*;
                 const maybe_comma = tree.nextToken(last_node.lastToken());
-                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+                break :blk tree.tokens[maybe_comma].id == .Comma;
             };
 
             if (switch_case.items.len == 1 or !src_has_trailing_comma) {
@@ -1940,7 +1937,7 @@ fn renderExpression(
                         try renderExpression(allocator, stream, tree, indent_extra, start_col, node, Space.Newline);
                         try stream.writeByteNTimes(' ', indent_once);
                         const comma_or_colon = tree.nextToken(node.lastToken());
-                        break :blk switch (tree.tokens.at(comma_or_colon).id) {
+                        break :blk switch (tree.tokens[comma_or_colon].id) {
                             .Comma => tree.nextToken(comma_or_colon),
                             else => comma_or_colon,
                         };
@@ -1978,7 +1975,7 @@ fn renderExpression(
                         try renderExpression(allocator, stream, tree, indent_extra, start_col, node, Space.Newline);
                         try stream.writeByteNTimes(' ', indent_once);
                         const comma_or_colon = tree.nextToken(node.lastToken());
-                        break :blk switch (tree.tokens.at(comma_or_colon).id) {
+                        break :blk switch (tree.tokens[comma_or_colon].id) {
                             .Comma => tree.nextToken(comma_or_colon),
                             else => comma_or_colon,
                         };
@@ -2174,7 +2171,7 @@ fn renderStatement(
                 try renderExpression(allocator, stream, tree, indent, start_col, base, Space.None);
 
                 const semicolon_index = tree.nextToken(base.lastToken());
-                assert(tree.tokens.at(semicolon_index).id == .Semicolon);
+                assert(tree.tokens[semicolon_index].id == .Semicolon);
                 try renderToken(tree, stream, semicolon_index, indent, start_col, Space.Newline);
             } else {
                 try renderExpression(allocator, stream, tree, indent, start_col, base, Space.Newline);
@@ -2212,13 +2209,13 @@ fn renderTokenOffset(
         return;
     }
 
-    var token = tree.tokens.at(token_index);
+    var token = tree.tokens[token_index];
     try stream.writeAll(mem.trimRight(u8, tree.tokenSlicePtr(token)[token_skip_bytes..], " "));
 
     if (space == Space.NoComment)
         return;
 
-    var next_token = tree.tokens.at(token_index + 1);
+    var next_token = tree.tokens[token_index + 1];
 
     if (space == Space.Comma) switch (next_token.id) {
         .Comma => return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline),
@@ -2227,7 +2224,7 @@ fn renderTokenOffset(
             return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline);
         },
         else => {
-            if (token_index + 2 < tree.tokens.len and tree.tokens.at(token_index + 2).id == .MultilineStringLiteralLine) {
+            if (token_index + 2 < tree.tokens.len and tree.tokens[token_index + 2].id == .MultilineStringLiteralLine) {
                 try stream.writeAll(",");
                 return;
             } else {
@@ -2244,7 +2241,7 @@ fn renderTokenOffset(
         const loc = tree.tokenLocationPtr(token.end, next_token);
         if (loc.line == 0) {
             offset += 1;
-            next_token = tree.tokens.at(token_index + offset);
+            next_token = tree.tokens[token_index + offset];
         }
     }
 
@@ -2277,7 +2274,7 @@ fn renderTokenOffset(
                 Space.Newline => {
                     offset += 1;
                     token = next_token;
-                    next_token = tree.tokens.at(token_index + offset);
+                    next_token = tree.tokens[token_index + offset];
                     if (next_token.id != .LineComment) {
                         try stream.writeByte('\n');
                         start_col.* = 0;
@@ -2296,12 +2293,12 @@ fn renderTokenOffset(
         try stream.print(" {}", .{mem.trimRight(u8, tree.tokenSlicePtr(next_token), " ")});
         offset = 2;
         token = next_token;
-        next_token = tree.tokens.at(token_index + offset);
+        next_token = tree.tokens[token_index + offset];
         if (next_token.id != .LineComment) {
             switch (space) {
                 Space.None, Space.Space => {
                     try stream.writeByte('\n');
-                    const after_comment_token = tree.tokens.at(token_index + offset);
+                    const after_comment_token = tree.tokens[token_index + offset];
                     const next_line_indent = switch (after_comment_token.id) {
                         .RParen, .RBrace, .RBracket => indent,
                         else => indent + indent_delta,
@@ -2342,7 +2339,7 @@ fn renderTokenOffset(
 
         offset += 1;
         token = next_token;
-        next_token = tree.tokens.at(token_index + offset);
+        next_token = tree.tokens[token_index + offset];
         if (next_token.id != .LineComment) {
             switch (space) {
                 Space.Newline => {
@@ -2357,7 +2354,7 @@ fn renderTokenOffset(
                 Space.None, Space.Space => {
                     try stream.writeByte('\n');
 
-                    const after_comment_token = tree.tokens.at(token_index + offset);
+                    const after_comment_token = tree.tokens[token_index + offset];
                     const next_line_indent = switch (after_comment_token.id) {
                         .RParen, .RBrace, .RBracket => blk: {
                             if (indent > indent_delta) {
lib/std/linked_list.zig
@@ -21,6 +21,8 @@ pub fn SinglyLinkedList(comptime T: type) type {
             next: ?*Node = null,
             data: T,
 
+            pub const Data = T;
+
             pub fn init(data: T) Node {
                 return Node{
                     .data = data,
@@ -51,25 +53,6 @@ pub fn SinglyLinkedList(comptime T: type) type {
 
         first: ?*Node = null,
 
-        /// Initialize a linked list.
-        ///
-        /// Returns:
-        ///     An empty linked list.
-        pub fn init() Self {
-            return Self{
-                .first = null,
-            };
-        }
-
-        /// Insert a new node after an existing one.
-        ///
-        /// Arguments:
-        ///     node: Pointer to a node in the list.
-        ///     new_node: Pointer to the new node to insert.
-        pub fn insertAfter(list: *Self, node: *Node, new_node: *Node) void {
-            node.insertAfter(new_node);
-        }
-
         /// Insert a new node at the head.
         ///
         /// Arguments:
@@ -104,40 +87,6 @@ pub fn SinglyLinkedList(comptime T: type) type {
             list.first = first.next;
             return first;
         }
-
-        /// Allocate a new node.
-        ///
-        /// Arguments:
-        ///     allocator: Dynamic memory allocator.
-        ///
-        /// Returns:
-        ///     A pointer to the new node.
-        pub fn allocateNode(list: *Self, allocator: *Allocator) !*Node {
-            return allocator.create(Node);
-        }
-
-        /// Deallocate a node.
-        ///
-        /// Arguments:
-        ///     node: Pointer to the node to deallocate.
-        ///     allocator: Dynamic memory allocator.
-        pub fn destroyNode(list: *Self, node: *Node, allocator: *Allocator) void {
-            allocator.destroy(node);
-        }
-
-        /// Allocate and initialize a node and its data.
-        ///
-        /// Arguments:
-        ///     data: The data to put inside the node.
-        ///     allocator: Dynamic memory allocator.
-        ///
-        /// Returns:
-        ///     A pointer to the new node.
-        pub fn createNode(list: *Self, data: T, allocator: *Allocator) !*Node {
-            var node = try list.allocateNode(allocator);
-            node.* = Node.init(data);
-            return node;
-        }
     };
 }