Commit 44c53c9979

Jimmi Holst Christensen <jhc@liab.dk>
2018-04-13 10:15:12
std.zig.parser: Refactor round 2 * More work on ensuring that each state only eat one token * VarDecl parsing now constructs its node * Handling all fn parsing in the same case * Using eatToken instead of getNextToken where possible * All tokenIdTo* now takes @TagType(Token.Id) * Added a createToCtxLiteral function
1 parent fad54e6
Changed files (1)
std
std/zig/parser.zig
@@ -59,12 +59,26 @@ pub const Parser = struct {
         lib_name: ?&ast.Node,
     };
 
+    const VarDeclCtx = struct {
+        mut_token: Token,
+        visib_token: ?Token,
+        comptime_token: ?Token,
+        extern_export_token: ?Token,
+        lib_name: ?&ast.Node,
+        list: &ArrayList(&ast.Node),
+    };
+
     const TopLevelExternOrFieldCtx = struct {
         visib_token: Token,
         container_decl: &ast.NodeContainerDecl,
     };
 
-    const ContainerExternCtx = struct {
+    const ExternTypeCtx = struct {
+        opt_ctx: OptionalCtx,
+        extern_token: Token,
+    };
+
+    const ContainerKindCtx = struct {
         opt_ctx: OptionalCtx,
         ltoken: Token,
         layout: ast.NodeContainerDecl.Layout,
@@ -80,15 +94,6 @@ pub const Parser = struct {
         ptr: &?Token,
     };
 
-    const RevertState = struct {
-        parser: Parser,
-        tokenizer: Tokenizer,
-
-        // We expect, that if something is optional, then there is a field,
-        // that needs to be set to null, when we revert.
-        ptr: &?&ast.Node,
-    };
-
     const ExprListCtx = struct {
         list: &ArrayList(&ast.Node),
         end: Token.Id,
@@ -102,6 +107,11 @@ pub const Parser = struct {
         };
     }
 
+    const MaybeLabeledExpressionCtx = struct {
+        label: Token,
+        opt_ctx: OptionalCtx,
+    };
+
     const LabelCtx = struct {
         label: ?Token,
         opt_ctx: OptionalCtx,
@@ -179,12 +189,12 @@ pub const Parser = struct {
         TopLevelDecl: TopLevelDeclCtx,
         TopLevelExternOrField: TopLevelExternOrFieldCtx,
 
-        ContainerExtern: ContainerExternCtx,
+        ContainerKind: ContainerKindCtx,
         ContainerInitArgStart: &ast.NodeContainerDecl,
         ContainerInitArg: &ast.NodeContainerDecl,
         ContainerDecl: &ast.NodeContainerDecl,
 
-        VarDecl: &ast.NodeVarDecl,
+        VarDecl: VarDeclCtx,
         VarDeclAlign: &ast.NodeVarDecl,
         VarDeclEq: &ast.NodeVarDecl,
 
@@ -199,6 +209,7 @@ pub const Parser = struct {
         ParamDeclEnd: ParamDeclEndCtx,
         ParamDeclComma: &ast.NodeFnProto,
 
+        MaybeLabeledExpression: MaybeLabeledExpressionCtx,
         LabeledExpression: LabelCtx,
         Inline: InlineCtx,
         While: LoopCtx,
@@ -209,7 +220,7 @@ pub const Parser = struct {
         Block: &ast.NodeBlock,
         Statement: &ast.NodeBlock,
         ComptimeStatement: ComptimeStatementCtx,
-        Semicolon: &const &const ast.Node,
+        Semicolon: &&ast.Node,
 
         AsmOutputItems: &ArrayList(&ast.NodeAsmOutput),
         AsmOutputReturnOrType: &ast.NodeAsmOutput,
@@ -233,6 +244,7 @@ pub const Parser = struct {
         AsyncAllocator: &ast.NodeAsyncAttribute,
         AsyncEnd: AsyncEndCtx,
 
+        ExternType: ExternTypeCtx,
         SliceOrArrayAccess: &ast.NodeSuffixOp,
         SliceOrArrayType: &ast.NodePrefixOp,
         AddrOfModifiers: &ast.NodePrefixOp.AddrOfInfo,
@@ -491,6 +503,7 @@ pub const Parser = struct {
                             .lib_name = lib_name,
                         },
                     }) catch unreachable;
+                    continue;
                 },
                 State.TopLevelDecl => |ctx| {
                     const token = self.getNextToken();
@@ -524,49 +537,20 @@ pub const Parser = struct {
                                 }
                             }
 
-                            const var_decl_node = try self.createAttachNode(arena, ctx.decls, ast.NodeVarDecl,
-                                ast.NodeVarDecl {
-                                    .base = undefined,
+                            stack.append(State {
+                                .VarDecl = VarDeclCtx {
                                     .visib_token = ctx.visib_token,
-                                    .mut_token = token,
+                                    .lib_name = ctx.lib_name,
                                     .comptime_token = null,
                                     .extern_export_token = ctx.extern_export_inline_token,
-                                    .type_node = null,
-                                    .align_node = null,
-                                    .init_node = null,
-                                    .lib_name = ctx.lib_name,
-                                    // initialized later
-                                    .name_token = undefined,
-                                    .eq_token = undefined,
-                                    .semicolon_token = undefined,
-                                }
-                            );
-                            stack.append(State { .VarDecl = var_decl_node }) catch unreachable;
-                            continue;
-                        },
-                        Token.Id.Keyword_fn => {
-                            const fn_proto = try self.createAttachNode(arena, ctx.decls, ast.NodeFnProto,
-                                ast.NodeFnProto {
-                                    .base = undefined,
-                                    .visib_token = ctx.visib_token,
-                                    .name_token = null,
-                                    .fn_token = token,
-                                    .params = ArrayList(&ast.Node).init(arena),
-                                    .return_type = undefined,
-                                    .var_args_token = null,
-                                    .extern_export_inline_token = ctx.extern_export_inline_token,
-                                    .cc_token = null,
-                                    .async_attr = null,
-                                    .body_node = null,
-                                    .lib_name = ctx.lib_name,
-                                    .align_expr = null,
+                                    .mut_token = token,
+                                    .list = ctx.decls
                                 }
-                            );
-                            stack.append(State { .FnDef = fn_proto }) catch unreachable;
-                            try stack.append(State { .FnProto = fn_proto });
+                            }) catch unreachable;
                             continue;
                         },
-                        Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => {
+                        Token.Id.Keyword_fn, Token.Id.Keyword_nakedcc,
+                        Token.Id.Keyword_stdcallcc, Token.Id.Keyword_async => {
                             const fn_proto = try self.createAttachNode(arena, ctx.decls, ast.NodeFnProto,
                                 ast.NodeFnProto {
                                     .base = undefined,
@@ -577,7 +561,7 @@ pub const Parser = struct {
                                     .return_type = undefined,
                                     .var_args_token = null,
                                     .extern_export_inline_token = ctx.extern_export_inline_token,
-                                    .cc_token = token,
+                                    .cc_token = null,
                                     .async_attr = null,
                                     .body_node = null,
                                     .lib_name = ctx.lib_name,
@@ -586,52 +570,44 @@ pub const Parser = struct {
                             );
                             stack.append(State { .FnDef = fn_proto }) catch unreachable;
                             try stack.append(State { .FnProto = fn_proto });
-                            try stack.append(State {
-                                .ExpectTokenSave = ExpectTokenSave {
-                                    .id = Token.Id.Keyword_fn,
-                                    .ptr = &fn_proto.fn_token,
-                                }
-                            });
-                            continue;
-                        },
-                        Token.Id.Keyword_async => {
-                            const async_node = try self.createNode(arena, ast.NodeAsyncAttribute,
-                                ast.NodeAsyncAttribute {
-                                    .base = undefined,
-                                    .async_token = token,
-                                    .allocator_type = null,
-                                    .rangle_bracket = null,
-                                }
-                            );
 
-                            const fn_proto = try self.createAttachNode(arena, ctx.decls, ast.NodeFnProto,
-                                ast.NodeFnProto {
-                                    .base = undefined,
-                                    .visib_token = ctx.visib_token,
-                                    .name_token = null,
-                                    .fn_token = undefined,
-                                    .params = ArrayList(&ast.Node).init(arena),
-                                    .return_type = undefined,
-                                    .var_args_token = null,
-                                    .extern_export_inline_token = ctx.extern_export_inline_token,
-                                    .cc_token = null,
-                                    .async_attr = async_node,
-                                    .body_node = null,
-                                    .lib_name = ctx.lib_name,
-                                    .align_expr = null,
-                                }
-                            );
-                            stack.append(State { .FnDef = fn_proto }) catch unreachable;
-                            try stack.append(State { .FnProto = fn_proto });
-                            try stack.append(State {
-                                .ExpectTokenSave = ExpectTokenSave {
-                                    .id = Token.Id.Keyword_fn,
-                                    .ptr = &fn_proto.fn_token,
-                                }
-                            });
+                            switch (token.id) {
+                                Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => {
+                                    fn_proto.cc_token = token;
+                                    try stack.append(State {
+                                        .ExpectTokenSave = ExpectTokenSave {
+                                            .id = Token.Id.Keyword_fn,
+                                            .ptr = &fn_proto.fn_token,
+                                        }
+                                    });
+                                    continue;
+                                },
+                                Token.Id.Keyword_async => {
+                                    const async_node = try self.createNode(arena, ast.NodeAsyncAttribute,
+                                        ast.NodeAsyncAttribute {
+                                            .base = undefined,
+                                            .async_token = token,
+                                            .allocator_type = null,
+                                            .rangle_bracket = null,
+                                        }
+                                    );
+                                    fn_proto.async_attr = async_node;
 
-                            try stack.append(State { .AsyncAllocator = async_node });
-                            continue;
+                                    try stack.append(State {
+                                        .ExpectTokenSave = ExpectTokenSave {
+                                            .id = Token.Id.Keyword_fn,
+                                            .ptr = &fn_proto.fn_token,
+                                        }
+                                    });
+                                    try stack.append(State { .AsyncAllocator = async_node });
+                                    continue;
+                                },
+                                Token.Id.Keyword_fn => {
+                                    fn_proto.fn_token = token;
+                                    continue;
+                                },
+                                else => unreachable,
+                            }
                         },
                         else => {
                             return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id));
@@ -669,7 +645,7 @@ pub const Parser = struct {
                 },
 
 
-                State.ContainerExtern => |ctx| {
+                State.ContainerKind => |ctx| {
                     const token = self.getNextToken();
                     const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeContainerDecl,
                         ast.NodeContainerDecl {
@@ -697,6 +673,7 @@ pub const Parser = struct {
                     stack.append(State { .ContainerDecl = node }) catch unreachable;
                     try stack.append(State { .ExpectToken = Token.Id.LBrace });
                     try stack.append(State { .ContainerInitArgStart = node });
+                    continue;
                 },
 
                 State.ContainerInitArgStart => |container_decl| {
@@ -706,6 +683,7 @@ pub const Parser = struct {
 
                     stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable;
                     try stack.append(State { .ContainerInitArg = container_decl });
+                    continue;
                 },
 
                 State.ContainerInitArg => |container_decl| {
@@ -781,6 +759,7 @@ pub const Parser = struct {
                                             .container_decl = container_decl,
                                         }
                                     });
+                                    continue;
                                 },
                                 else => {
                                     stack.append(State{ .ContainerDecl = container_decl }) catch unreachable;
@@ -792,6 +771,7 @@ pub const Parser = struct {
                                             .lib_name = null,
                                         }
                                     });
+                                    continue;
                                 }
                             }
                         },
@@ -828,7 +808,25 @@ pub const Parser = struct {
                 },
 
 
-                State.VarDecl => |var_decl| {
+                State.VarDecl => |ctx| {
+                    const var_decl = try self.createAttachNode(arena, ctx.list, ast.NodeVarDecl,
+                        ast.NodeVarDecl {
+                            .base = undefined,
+                            .visib_token = ctx.visib_token,
+                            .mut_token = ctx.mut_token,
+                            .comptime_token = ctx.comptime_token,
+                            .extern_export_token = ctx.extern_export_token,
+                            .type_node = null,
+                            .align_node = null,
+                            .init_node = null,
+                            .lib_name = ctx.lib_name,
+                            // initialized later
+                            .name_token = undefined,
+                            .eq_token = undefined,
+                            .semicolon_token = undefined,
+                        }
+                    );
+
                     stack.append(State { .VarDeclAlign = var_decl }) catch unreachable;
                     try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &var_decl.type_node} });
                     try stack.append(State { .IfToken = Token.Id.Colon });
@@ -907,12 +905,9 @@ pub const Parser = struct {
                     try stack.append(State { .ParamDecl = fn_proto });
                     try stack.append(State { .ExpectToken = Token.Id.LParen });
 
-                    const next_token = self.getNextToken();
-                    if (next_token.id == Token.Id.Identifier) {
-                        fn_proto.name_token = next_token;
-                        continue;
+                    if (self.eatToken(Token.Id.Identifier)) |name_token| {
+                        fn_proto.name_token = name_token;
                     }
-                    self.putBackToken(next_token);
                     continue;
                 },
                 State.FnProtoAlign => |fn_proto| {
@@ -923,7 +918,6 @@ pub const Parser = struct {
                         try stack.append(State { .Expression = OptionalCtx { .RequiredNull = &fn_proto.align_expr } });
                         try stack.append(State { .ExpectToken = Token.Id.LParen });
                     }
-
                     continue;
                 },
                 State.FnProtoReturnType => |fn_proto| {
@@ -987,6 +981,7 @@ pub const Parser = struct {
                     } else if (self.eatToken(Token.Id.Keyword_noalias)) |noalias_token| {
                         param_decl.noalias_token = noalias_token;
                     }
+                    continue;
                 },
                 State.ParamDeclName => |param_decl| {
                     // TODO: Here, we eat two tokens in one state. This means that we can't have
@@ -998,6 +993,7 @@ pub const Parser = struct {
                             self.putBackToken(ident_token);
                         }
                     }
+                    continue;
                 },
                 State.ParamDeclEnd => |ctx| {
                     if (self.eatToken(Token.Id.Ellipsis3)) |ellipsis3| {
@@ -1010,6 +1006,7 @@ pub const Parser = struct {
                     try stack.append(State {
                         .TypeExprBegin = OptionalCtx { .Required = &ctx.param_decl.type_node }
                     });
+                    continue;
                 },
                 State.ParamDeclComma => |fn_proto| {
                     if ((try self.expectCommaOrEnd(Token.Id.RParen)) == null) {
@@ -1018,7 +1015,20 @@ pub const Parser = struct {
                     continue;
                 },
 
+                State.MaybeLabeledExpression => |ctx| {
+                    if (self.eatToken(Token.Id.Colon)) |_| {
+                        stack.append(State {
+                            .LabeledExpression = LabelCtx {
+                                .label = ctx.label,
+                                .opt_ctx = ctx.opt_ctx,
+                            }
+                        }) catch unreachable;
+                        continue;
+                    }
 
+                    _ = try self.createToCtxLiteral(arena, ctx.opt_ctx, ast.NodeIdentifier, ctx.label);
+                    continue;
+                },
                 State.LabeledExpression => |ctx| {
                     const token = self.getNextToken();
                     switch (token.id) {
@@ -1134,11 +1144,13 @@ pub const Parser = struct {
                     try stack.append(State { .ExpectToken = Token.Id.RParen });
                     try stack.append(State { .Expression = OptionalCtx { .Required = &node.condition } });
                     try stack.append(State { .ExpectToken = Token.Id.LParen });
+                    continue;
                 },
                 State.WhileContinueExpr => |dest| {
                     stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable;
                     try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .RequiredNull = dest } });
                     try stack.append(State { .ExpectToken = Token.Id.LParen });
+                    continue;
                 },
                 State.For => |ctx| {
                     const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeFor,
@@ -1159,26 +1171,26 @@ pub const Parser = struct {
                     try stack.append(State { .ExpectToken = Token.Id.RParen });
                     try stack.append(State { .Expression = OptionalCtx { .Required = &node.array_expr } });
                     try stack.append(State { .ExpectToken = Token.Id.LParen });
+                    continue;
                 },
                 State.Else => |dest| {
-                    const else_token = self.getNextToken();
-                    if (else_token.id != Token.Id.Keyword_else) {
-                        self.putBackToken(else_token);
+                    if (self.eatToken(Token.Id.Keyword_else)) |else_token| {
+                        const node = try self.createNode(arena, ast.NodeElse,
+                            ast.NodeElse {
+                                .base = undefined,
+                                .else_token = else_token,
+                                .payload = null,
+                                .body = undefined,
+                            }
+                        );
+                        *dest = node;
+
+                        stack.append(State { .Expression = OptionalCtx { .Required = &node.body } }) catch unreachable;
+                        try stack.append(State { .Payload = OptionalCtx { .Optional = &node.payload } });
+                        continue;
+                    } else {
                         continue;
                     }
-
-                    const node = try self.createNode(arena, ast.NodeElse,
-                        ast.NodeElse {
-                            .base = undefined,
-                            .else_token = else_token,
-                            .payload = null,
-                            .body = undefined,
-                        }
-                    );
-                    *dest = node;
-
-                    stack.append(State { .Expression = OptionalCtx { .Required = &node.body } }) catch unreachable;
-                    try stack.append(State { .Payload = OptionalCtx { .Optional = &node.payload } });
                 },
 
 
@@ -1207,26 +1219,19 @@ pub const Parser = struct {
                                     .block = block,
                                 }
                             }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Keyword_var, Token.Id.Keyword_const => {
-                            const var_decl = try self.createAttachNode(arena, &block.statements, ast.NodeVarDecl,
-                                ast.NodeVarDecl {
-                                    .base = undefined,
+                            stack.append(State {
+                                .VarDecl = VarDeclCtx {
                                     .visib_token = null,
-                                    .mut_token = token,
                                     .comptime_token = null,
                                     .extern_export_token = null,
-                                    .type_node = null,
-                                    .align_node = null,
-                                    .init_node = null,
                                     .lib_name = null,
-                                    // initialized later
-                                    .name_token = undefined,
-                                    .eq_token = undefined,
-                                    .semicolon_token = undefined,
+                                    .mut_token = token,
+                                    .list = &block.statements,
                                 }
-                            );
-                            stack.append(State { .VarDecl = var_decl }) catch unreachable;
+                            }) catch unreachable;
                             continue;
                         },
                         Token.Id.Keyword_defer, Token.Id.Keyword_errdefer => {
@@ -1242,7 +1247,7 @@ pub const Parser = struct {
                                     .expr = undefined,
                                 }
                             );
-                            stack.append(State { .Semicolon = &node.base }) catch unreachable;
+                            stack.append(State { .Semicolon = &&node.base }) catch unreachable;
                             try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = &node.expr } });
                             continue;
                         },
@@ -1270,40 +1275,37 @@ pub const Parser = struct {
                 },
                 State.ComptimeStatement => |ctx| {
                     const token = self.getNextToken();
-                    if (token.id == Token.Id.Keyword_var or token.id == Token.Id.Keyword_const) {
-                        const var_decl = try self.createAttachNode(arena, &ctx.block.statements, ast.NodeVarDecl,
-                            ast.NodeVarDecl {
-                                .base = undefined,
-                                .visib_token = null,
-                                .mut_token = token,
-                                .comptime_token = ctx.comptime_token,
-                                .extern_export_token = null,
-                                .type_node = null,
-                                .align_node = null,
-                                .init_node = null,
-                                .lib_name = null,
-                                // initialized later
-                                .name_token = undefined,
-                                .eq_token = undefined,
-                                .semicolon_token = undefined,
-                            }
-                        );
-                        stack.append(State { .VarDecl = var_decl }) catch unreachable;
-                        continue;
-                    } else {
-                        self.putBackToken(token);
-                        self.putBackToken(ctx.comptime_token);
-                        const statememt = try ctx.block.statements.addOne();
-                        stack.append(State { .Semicolon = statememt }) catch unreachable;
-                        try stack.append(State { .Expression = OptionalCtx { .Required = statememt } });
-                        continue;
+                    switch (token.id) {
+                        Token.Id.Keyword_var, Token.Id.Keyword_const => {
+                            stack.append(State {
+                                .VarDecl = VarDeclCtx {
+                                    .visib_token = null,
+                                    .comptime_token = ctx.comptime_token,
+                                    .extern_export_token = null,
+                                    .lib_name = null,
+                                    .mut_token = token,
+                                    .list = &ctx.block.statements,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
+                        else => {
+                            self.putBackToken(token);
+                            self.putBackToken(ctx.comptime_token);
+                            const statememt = try ctx.block.statements.addOne();
+                            stack.append(State { .Semicolon = statememt }) catch unreachable;
+                            try stack.append(State { .Expression = OptionalCtx { .Required = statememt } });
+                            continue;
+                        }
                     }
                 },
                 State.Semicolon => |node_ptr| {
                     const node = *node_ptr;
                     if (requireSemiColon(node)) {
                         stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable;
+                        continue;
                     }
+                    continue;
                 },
 
 
@@ -1332,6 +1334,7 @@ pub const Parser = struct {
                     try stack.append(State { .StringLiteral = OptionalCtx { .Required = &node.constraint } });
                     try stack.append(State { .ExpectToken = Token.Id.RBracket });
                     try stack.append(State { .Identifier = OptionalCtx { .Required = &node.symbolic_name } });
+                    continue;
                 },
                 State.AsmOutputReturnOrType => |node| {
                     const token = self.getNextToken();
@@ -1377,11 +1380,13 @@ pub const Parser = struct {
                     try stack.append(State { .StringLiteral = OptionalCtx { .Required = &node.constraint } });
                     try stack.append(State { .ExpectToken = Token.Id.RBracket });
                     try stack.append(State { .Identifier = OptionalCtx { .Required = &node.symbolic_name } });
+                    continue;
                 },
                 State.AsmClopperItems => |items| {
                     stack.append(State { .AsmClopperItems = items }) catch unreachable;
                     try stack.append(State { .IfToken = Token.Id.Comma });
                     try stack.append(State { .StringLiteral = OptionalCtx { .Required = try items.addOne() } });
+                    continue;
                 },
 
 
@@ -1393,14 +1398,16 @@ pub const Parser = struct {
 
                     stack.append(State { .ExprListCommaOrEnd = list_state }) catch unreachable;
                     try stack.append(State { .Expression = OptionalCtx { .Required = try list_state.list.addOne() } });
+                    continue;
                 },
                 State.ExprListCommaOrEnd => |list_state| {
                     if (try self.expectCommaOrEnd(list_state.end)) |end| {
                         *list_state.ptr = end;
+                        continue;
                     } else {
                         stack.append(State { .ExprListItemOrEnd = list_state }) catch unreachable;
+                        continue;
                     }
-                    continue;
                 },
                 State.FieldInitListItemOrEnd => |list_state| {
                     if (self.eatToken(Token.Id.RBrace)) |rbrace| {
@@ -1433,22 +1440,25 @@ pub const Parser = struct {
                             .ptr = &node.period_token,
                         }
                     });
+                    continue;
                 },
                 State.FieldInitListCommaOrEnd => |list_state| {
                     if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| {
                         *list_state.ptr = end;
+                        continue;
                     } else {
                         stack.append(State { .FieldInitListItemOrEnd = list_state }) catch unreachable;
+                        continue;
                     }
-                    continue;
                 },
                 State.FieldListCommaOrEnd => |container_decl| {
                     if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| {
                         container_decl.rbrace_token = end;
+                        continue;
                     } else {
                         stack.append(State { .ContainerDecl = container_decl }) catch unreachable;
+                        continue;
                     }
-                    continue;
                 },
                 State.IdentifierListItemOrEnd => |list_state| {
                     if (self.eatToken(Token.Id.RBrace)) |rbrace| {
@@ -1458,14 +1468,16 @@ pub const Parser = struct {
 
                     stack.append(State { .IdentifierListCommaOrEnd = list_state }) catch unreachable;
                     try stack.append(State { .Identifier = OptionalCtx { .Required = try list_state.list.addOne() } });
+                    continue;
                 },
                 State.IdentifierListCommaOrEnd => |list_state| {
                     if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| {
                         *list_state.ptr = end;
+                        continue;
                     } else {
                         stack.append(State { .IdentifierListItemOrEnd = list_state }) catch unreachable;
+                        continue;
                     }
-                    continue;
                 },
                 State.SwitchCaseOrEnd => |list_state| {
                     if (self.eatToken(Token.Id.RBrace)) |rbrace| {
@@ -1486,15 +1498,16 @@ pub const Parser = struct {
                     try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .Required = &node.expr  } });
                     try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } });
                     try stack.append(State { .SwitchCaseFirstItem = &node.items });
-
+                    continue;
                 },
                 State.SwitchCaseCommaOrEnd => |list_state| {
                     if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| {
                         *list_state.ptr = end;
+                        continue;
                     } else {
                         stack.append(State { .SwitchCaseOrEnd = list_state }) catch unreachable;
+                        continue;
                     }
-                    continue;
                 },
                 State.SwitchCaseFirstItem => |case_items| {
                     const token = self.getNextToken();
@@ -1544,6 +1557,7 @@ pub const Parser = struct {
                         }
                     });
                     try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &async_node.allocator_type } });
+                    continue;
                 },
                 State.AsyncEnd => |ctx| {
                     const node = ctx.ctx.get() ?? continue;
@@ -1552,6 +1566,7 @@ pub const Parser = struct {
                         ast.Node.Id.FnProto => {
                             const fn_proto = @fieldParentPtr(ast.NodeFnProto, "base", node);
                             fn_proto.async_attr = ctx.attribute;
+                            continue;
                         },
                         ast.Node.Id.SuffixOp => {
                             const suffix_op = @fieldParentPtr(ast.NodeSuffixOp, "base", node);
@@ -1574,6 +1589,38 @@ pub const Parser = struct {
                 },
 
 
+                State.ExternType => |ctx| {
+                    if (self.eatToken(Token.Id.Keyword_fn)) |fn_token| {
+                        const fn_proto = try self.createToCtxNode(arena, ctx.opt_ctx, ast.NodeFnProto,
+                            ast.NodeFnProto {
+                                .base = undefined,
+                                .visib_token = null,
+                                .name_token = null,
+                                .fn_token = fn_token,
+                                .params = ArrayList(&ast.Node).init(arena),
+                                .return_type = undefined,
+                                .var_args_token = null,
+                                .extern_export_inline_token = ctx.extern_token,
+                                .cc_token = null,
+                                .async_attr = null,
+                                .body_node = null,
+                                .lib_name = null,
+                                .align_expr = null,
+                            }
+                        );
+                        stack.append(State { .FnProto = fn_proto }) catch unreachable;
+                        continue;
+                    }
+
+                    stack.append(State {
+                        .ContainerKind = ContainerKindCtx {
+                            .opt_ctx = ctx.opt_ctx,
+                            .ltoken = ctx.extern_token,
+                            .layout = ast.NodeContainerDecl.Layout.Extern,
+                        },
+                    }) catch unreachable;
+                    continue;
+                },
                 State.SliceOrArrayAccess => |node| {
                     var token = self.getNextToken();
                     switch (token.id) {
@@ -1692,6 +1739,7 @@ pub const Parser = struct {
                         }
                     }) catch unreachable;
                     try stack.append(State { .Identifier = OptionalCtx { .Required = &node.error_symbol } });
+                    continue;
                 },
                 State.PointerPayload => |opt_ctx| {
                     const token = self.getNextToken();
@@ -1729,6 +1777,7 @@ pub const Parser = struct {
                             .ptr = &node.ptr_token,
                         }
                     });
+                    continue;
                 },
                 State.PointerIndexPayload => |opt_ctx| {
                     const token = self.getNextToken();
@@ -1769,26 +1818,14 @@ pub const Parser = struct {
                             .ptr = &node.ptr_token,
                         }
                     });
+                    continue;
                 },
 
 
                 State.Expression => |opt_ctx| {
                     const token = self.getNextToken();
                     switch (token.id) {
-                        Token.Id.Keyword_return => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeControlFlowExpression,
-                                ast.NodeControlFlowExpression {
-                                    .base = undefined,
-                                    .ltoken = token,
-                                    .kind = ast.NodeControlFlowExpression.Kind.Return,
-                                    .rhs = null,
-                                }
-                            );
-
-                            stack.append(State { .Expression = OptionalCtx { .Optional = &node.rhs } }) catch unreachable;
-                            continue;
-                        },
-                        Token.Id.Keyword_break, Token.Id.Keyword_continue => {
+                        Token.Id.Keyword_return, Token.Id.Keyword_break, Token.Id.Keyword_continue => {
                             const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeControlFlowExpression,
                                 ast.NodeControlFlowExpression {
                                     .base = undefined,
@@ -1811,6 +1848,9 @@ pub const Parser = struct {
                                     try stack.append(State { .Identifier = OptionalCtx { .RequiredNull = &node.kind.Continue } });
                                     try stack.append(State { .IfToken = Token.Id.Colon });
                                 },
+                                Token.Id.Keyword_return => {
+                                    node.kind = ast.NodeControlFlowExpression.Kind.Return;
+                                },
                                 else => unreachable,
                             }
                             continue;
@@ -1861,9 +1901,8 @@ pub const Parser = struct {
                             }
                         );
                         stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } }) catch unreachable;
+                        continue;
                     }
-
-                    continue;
                 },
                 State.AssignmentExpressionBegin => |opt_ctx| {
                     stack.append(State { .AssignmentExpressionEnd = opt_ctx }) catch unreachable;
@@ -1904,34 +1943,27 @@ pub const Parser = struct {
                     const lhs = opt_ctx.get() ?? continue;
 
                     const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Keyword_catch, Token.Id.QuestionMarkQuestionMark => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = switch (token.id) {
-                                        Token.Id.Keyword_catch => ast.NodeInfixOp.InfixOp { .Catch = null },
-                                        Token.Id.QuestionMarkQuestionMark => ast.NodeInfixOp.InfixOp { .UnwrapMaybe = void{} },
-                                        else => unreachable,
-                                    },
-                                    .rhs = undefined,
-                                }
-                            );
+                    if (tokenIdToUnwrapExpr(token.id)) |unwrap_id| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = token,
+                                .op = unwrap_id,
+                                .rhs = undefined,
+                            }
+                        );
 
-                            stack.append(State { .UnwrapExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } });
+                        stack.append(State { .UnwrapExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .Expression = OptionalCtx { .Required = &node.rhs } });
 
-                            if (node.op == ast.NodeInfixOp.InfixOp.Catch) {
-                                try stack.append(State { .Payload = OptionalCtx { .Optional = &node.op.Catch } });
-                            }
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                        if (node.op == ast.NodeInfixOp.InfixOp.Catch) {
+                            try stack.append(State { .Payload = OptionalCtx { .Optional = &node.op.Catch } });
+                        }
+                        continue;
+                    } else {
+                        self.putBackToken(token);
+                        continue;
                     }
                 },
 
@@ -1944,26 +1976,19 @@ pub const Parser = struct {
                 State.BoolOrExpressionEnd => |opt_ctx| {
                     const lhs = opt_ctx.get() ?? continue;
 
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Keyword_or => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = ast.NodeInfixOp.InfixOp.BoolOr,
-                                    .rhs = undefined,
-                                }
-                            );
-                            stack.append(State { .BoolOrExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .BoolAndExpressionBegin = OptionalCtx { .Required = &node.rhs } });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                    if (self.eatToken(Token.Id.Keyword_or)) |or_token| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = or_token,
+                                .op = ast.NodeInfixOp.InfixOp.BoolOr,
+                                .rhs = undefined,
+                            }
+                        );
+                        stack.append(State { .BoolOrExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .BoolAndExpressionBegin = OptionalCtx { .Required = &node.rhs } });
+                        continue;
                     }
                 },
 
@@ -1976,26 +2001,19 @@ pub const Parser = struct {
                 State.BoolAndExpressionEnd => |opt_ctx| {
                     const lhs = opt_ctx.get() ?? continue;
 
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Keyword_and => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = ast.NodeInfixOp.InfixOp.BoolAnd,
-                                    .rhs = undefined,
-                                }
-                            );
-                            stack.append(State { .BoolAndExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .ComparisonExpressionBegin = OptionalCtx { .Required = &node.rhs } });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                    if (self.eatToken(Token.Id.Keyword_and)) |and_token| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = and_token,
+                                .op = ast.NodeInfixOp.InfixOp.BoolAnd,
+                                .rhs = undefined,
+                            }
+                        );
+                        stack.append(State { .BoolAndExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .ComparisonExpressionBegin = OptionalCtx { .Required = &node.rhs } });
+                        continue;
                     }
                 },
 
@@ -2037,26 +2055,19 @@ pub const Parser = struct {
                 State.BinaryOrExpressionEnd => |opt_ctx| {
                     const lhs = opt_ctx.get() ?? continue;
 
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Pipe => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = ast.NodeInfixOp.InfixOp.BitOr,
-                                    .rhs = undefined,
-                                }
-                            );
-                            stack.append(State { .BinaryOrExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .BinaryXorExpressionBegin = OptionalCtx { .Required = &node.rhs } });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                    if (self.eatToken(Token.Id.Pipe)) |pipe| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = pipe,
+                                .op = ast.NodeInfixOp.InfixOp.BitOr,
+                                .rhs = undefined,
+                            }
+                        );
+                        stack.append(State { .BinaryOrExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .BinaryXorExpressionBegin = OptionalCtx { .Required = &node.rhs } });
+                        continue;
                     }
                 },
 
@@ -2069,26 +2080,19 @@ pub const Parser = struct {
                 State.BinaryXorExpressionEnd => |opt_ctx| {
                     const lhs = opt_ctx.get() ?? continue;
 
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Caret => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = ast.NodeInfixOp.InfixOp.BitXor,
-                                    .rhs = undefined,
-                                }
-                            );
-                            stack.append(State { .BinaryXorExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .BinaryAndExpressionBegin = OptionalCtx { .Required = &node.rhs } });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                    if (self.eatToken(Token.Id.Caret)) |caret| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = caret,
+                                .op = ast.NodeInfixOp.InfixOp.BitXor,
+                                .rhs = undefined,
+                            }
+                        );
+                        stack.append(State { .BinaryXorExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .BinaryAndExpressionBegin = OptionalCtx { .Required = &node.rhs } });
+                        continue;
                     }
                 },
 
@@ -2101,26 +2105,19 @@ pub const Parser = struct {
                 State.BinaryAndExpressionEnd => |opt_ctx| {
                     const lhs = opt_ctx.get() ?? continue;
 
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Ampersand => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = ast.NodeInfixOp.InfixOp.BitAnd,
-                                    .rhs = undefined,
-                                }
-                            );
-                            stack.append(State { .BinaryAndExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .BitShiftExpressionBegin = OptionalCtx { .Required = &node.rhs } });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                    if (self.eatToken(Token.Id.Ampersand)) |ampersand| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = ampersand,
+                                .op = ast.NodeInfixOp.InfixOp.BitAnd,
+                                .rhs = undefined,
+                            }
+                        );
+                        stack.append(State { .BinaryAndExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .BitShiftExpressionBegin = OptionalCtx { .Required = &node.rhs } });
+                        continue;
                     }
                 },
 
@@ -2241,28 +2238,28 @@ pub const Parser = struct {
                             }
                         });
                         continue;
-                    } else {
-                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeSuffixOp,
-                            ast.NodeSuffixOp {
-                                .base = undefined,
-                                .lhs = lhs,
-                                .op = ast.NodeSuffixOp.SuffixOp {
-                                    .ArrayInitializer = ArrayList(&ast.Node).init(arena),
-                                },
-                                .rtoken = undefined,
-                            }
-                        );
-                        stack.append(State { .CurlySuffixExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
-                        try stack.append(State { .IfToken = Token.Id.LBrace });
-                        try stack.append(State {
-                            .ExprListItemOrEnd = ExprListCtx {
-                                .list = &node.op.ArrayInitializer,
-                                .end = Token.Id.RBrace,
-                                .ptr = &node.rtoken,
-                            }
-                        });
-                        continue;
                     }
+
+                    const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeSuffixOp,
+                        ast.NodeSuffixOp {
+                            .base = undefined,
+                            .lhs = lhs,
+                            .op = ast.NodeSuffixOp.SuffixOp {
+                                .ArrayInitializer = ArrayList(&ast.Node).init(arena),
+                            },
+                            .rtoken = undefined,
+                        }
+                    );
+                    stack.append(State { .CurlySuffixExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                    try stack.append(State { .IfToken = Token.Id.LBrace });
+                    try stack.append(State {
+                        .ExprListItemOrEnd = ExprListCtx {
+                            .list = &node.op.ArrayInitializer,
+                            .end = Token.Id.RBrace,
+                            .ptr = &node.rtoken,
+                        }
+                    });
+                    continue;
                 },
 
                 State.TypeExprBegin => |opt_ctx| {
@@ -2274,26 +2271,19 @@ pub const Parser = struct {
                 State.TypeExprEnd => |opt_ctx| {
                     const lhs = opt_ctx.get() ?? continue;
 
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Bang => {
-                            const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
-                                ast.NodeInfixOp {
-                                    .base = undefined,
-                                    .lhs = lhs,
-                                    .op_token = token,
-                                    .op = ast.NodeInfixOp.InfixOp.ErrorUnion,
-                                    .rhs = undefined,
-                                }
-                            );
-                            stack.append(State { .TypeExprEnd = opt_ctx.toRequired() }) catch unreachable;
-                            try stack.append(State { .PrefixOpExpression = OptionalCtx { .Required = &node.rhs } });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            continue;
-                        },
+                    if (self.eatToken(Token.Id.Bang)) |bang| {
+                        const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeInfixOp,
+                            ast.NodeInfixOp {
+                                .base = undefined,
+                                .lhs = lhs,
+                                .op_token = bang,
+                                .op = ast.NodeInfixOp.InfixOp.ErrorUnion,
+                                .rhs = undefined,
+                            }
+                        );
+                        stack.append(State { .TypeExprEnd = opt_ctx.toRequired() }) catch unreachable;
+                        try stack.append(State { .PrefixOpExpression = OptionalCtx { .Required = &node.rhs } });
+                        continue;
                     }
                 },
 
@@ -2309,6 +2299,7 @@ pub const Parser = struct {
                             }
                         );
 
+                        // Treat '**' token as two derefs
                         if (token.id == Token.Id.AsteriskAsterisk) {
                             const child = try self.createNode(arena, ast.NodePrefixOp,
                                 ast.NodePrefixOp {
@@ -2335,35 +2326,30 @@ pub const Parser = struct {
                 },
 
                 State.SuffixOpExpressionBegin => |opt_ctx| {
-                    const token = self.getNextToken();
-                    switch (token.id) {
-                        Token.Id.Keyword_async => {
-                            const async_node = try self.createNode(arena, ast.NodeAsyncAttribute,
-                                ast.NodeAsyncAttribute {
-                                    .base = undefined,
-                                    .async_token = token,
-                                    .allocator_type = null,
-                                    .rangle_bracket = null,
-                                }
-                            );
-                            stack.append(State {
-                                .AsyncEnd = AsyncEndCtx {
-                                    .ctx = opt_ctx,
-                                    .attribute = async_node,
-                                }
-                            }) catch unreachable;
-                            try stack.append(State { .SuffixOpExpressionEnd = opt_ctx.toRequired() });
-                            try stack.append(State { .PrimaryExpression = opt_ctx.toRequired() });
-                            try stack.append(State { .AsyncAllocator = async_node });
-                            continue;
-                        },
-                        else => {
-                            self.putBackToken(token);
-                            stack.append(State { .SuffixOpExpressionEnd = opt_ctx }) catch unreachable;
-                            try stack.append(State { .PrimaryExpression = opt_ctx });
-                            continue;
-                        }
+                    if (self.eatToken(Token.Id.Keyword_async)) |async_token| {
+                        const async_node = try self.createNode(arena, ast.NodeAsyncAttribute,
+                            ast.NodeAsyncAttribute {
+                                .base = undefined,
+                                .async_token = async_token,
+                                .allocator_type = null,
+                                .rangle_bracket = null,
+                            }
+                        );
+                        stack.append(State {
+                            .AsyncEnd = AsyncEndCtx {
+                                .ctx = opt_ctx,
+                                .attribute = async_node,
+                            }
+                        }) catch unreachable;
+                        try stack.append(State { .SuffixOpExpressionEnd = opt_ctx.toRequired() });
+                        try stack.append(State { .PrimaryExpression = opt_ctx.toRequired() });
+                        try stack.append(State { .AsyncAllocator = async_node });
+                        continue;
                     }
+
+                    stack.append(State { .SuffixOpExpressionEnd = opt_ctx }) catch unreachable;
+                    try stack.append(State { .PrimaryExpression = opt_ctx });
+                    continue;
                 },
 
                 State.SuffixOpExpressionEnd => |opt_ctx| {
@@ -2436,43 +2422,44 @@ pub const Parser = struct {
                     const token = self.getNextToken();
                     switch (token.id) {
                         Token.Id.IntegerLiteral => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeStringLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeStringLiteral, token);
                             continue;
                         },
                         Token.Id.FloatLiteral => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeFloatLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeFloatLiteral, token);
                             continue;
                         },
                         Token.Id.CharLiteral => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeCharLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeCharLiteral, token);
                             continue;
                         },
                         Token.Id.Keyword_undefined => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeUndefinedLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeUndefinedLiteral, token);
                             continue;
                         },
                         Token.Id.Keyword_true, Token.Id.Keyword_false => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeBoolLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeBoolLiteral, token);
                             continue;
                         },
                         Token.Id.Keyword_null => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeNullLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeNullLiteral, token);
                             continue;
                         },
                         Token.Id.Keyword_this => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeThisLiteral, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeThisLiteral, token);
                             continue;
                         },
                         Token.Id.Keyword_var => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeVarType, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeVarType, token);
                             continue;
                         },
                         Token.Id.Keyword_unreachable => {
-                            opt_ctx.store(&(try self.createLiteral(arena, ast.NodeUnreachable, token)).base);
+                            _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeUnreachable, token);
                             continue;
                         },
                         Token.Id.StringLiteral, Token.Id.MultilineStringLiteralLine => {
                             opt_ctx.store((try self.parseStringLiteral(arena, token)) ?? unreachable);
+                            continue;
                         },
                         Token.Id.LParen => {
                             const node = try self.createToCtxNode(arena, opt_ctx, ast.NodeGroupedExpression,
@@ -2521,6 +2508,7 @@ pub const Parser = struct {
                                 }
                             );
                             stack.append(State { .SliceOrArrayType = node }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Keyword_error => {
                             stack.append(State {
@@ -2529,73 +2517,41 @@ pub const Parser = struct {
                                     .opt_ctx = opt_ctx
                                 }
                             }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Keyword_packed => {
                             stack.append(State {
-                                .ContainerExtern = ContainerExternCtx {
+                                .ContainerKind = ContainerKindCtx {
                                     .opt_ctx = opt_ctx,
                                     .ltoken = token,
                                     .layout = ast.NodeContainerDecl.Layout.Packed,
                                 },
                             }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Keyword_extern => {
-                            // TODO: Here, we eat two tokens in the same state. This prevents comments
-                            //       from being between these two tokens.
-                            const next = self.getNextToken();
-                            if (next.id == Token.Id.Keyword_fn) {
-                                const fn_proto = try self.createToCtxNode(arena, opt_ctx, ast.NodeFnProto,
-                                    ast.NodeFnProto {
-                                        .base = undefined,
-                                        .visib_token = null,
-                                        .name_token = null,
-                                        .fn_token = next,
-                                        .params = ArrayList(&ast.Node).init(arena),
-                                        .return_type = undefined,
-                                        .var_args_token = null,
-                                        .extern_export_inline_token = token,
-                                        .cc_token = null,
-                                        .async_attr = null,
-                                        .body_node = null,
-                                        .lib_name = null,
-                                        .align_expr = null,
-                                    }
-                                );
-                                stack.append(State { .FnProto = fn_proto }) catch unreachable;
-                                continue;
-                            }
-
-                            self.putBackToken(next);
                             stack.append(State {
-                                .ContainerExtern = ContainerExternCtx {
+                                .ExternType = ExternTypeCtx {
                                     .opt_ctx = opt_ctx,
-                                    .ltoken = token,
-                                    .layout = ast.NodeContainerDecl.Layout.Extern,
+                                    .extern_token = token,
                                 },
                             }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Keyword_struct, Token.Id.Keyword_union, Token.Id.Keyword_enum => {
                             self.putBackToken(token);
                             stack.append(State {
-                                .ContainerExtern = ContainerExternCtx {
+                                .ContainerKind = ContainerKindCtx {
                                     .opt_ctx = opt_ctx,
                                     .ltoken = token,
                                     .layout = ast.NodeContainerDecl.Layout.Auto,
                                 },
                             }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Identifier => {
-                            // TODO: Here, we eat two tokens in the same state. This prevents comments
-                            //       from being between these two tokens.
-                            const next = self.getNextToken();
-                            if (next.id != Token.Id.Colon) {
-                                self.putBackToken(next);
-                                opt_ctx.store(&(try self.createLiteral(arena, ast.NodeIdentifier, token)).base);
-                                continue;
-                            }
-
                             stack.append(State {
-                                .LabeledExpression = LabelCtx {
+                                .MaybeLabeledExpression = MaybeLabeledExpressionCtx {
                                     .label = token,
                                     .opt_ctx = opt_ctx
                                 }
@@ -2710,7 +2666,7 @@ pub const Parser = struct {
 
                 State.ErrorTypeOrSetDecl => |ctx| {
                     if (self.eatToken(Token.Id.LBrace) == null) {
-                        ctx.opt_ctx.store(&(try self.createLiteral(arena, ast.NodeErrorType, ctx.error_token)).base);
+                        _ = try self.createToCtxLiteral(arena, ctx.opt_ctx, ast.NodeErrorType, ctx.error_token);
                         continue;
                     }
 
@@ -2746,7 +2702,7 @@ pub const Parser = struct {
                 },
                 State.Identifier => |opt_ctx| {
                     if (self.eatToken(Token.Id.Identifier)) |ident_token| {
-                        opt_ctx.store(&(try self.createLiteral(arena, ast.NodeIdentifier, ident_token)).base);
+                        _ = try self.createToCtxLiteral(arena, opt_ctx, ast.NodeIdentifier, ident_token);
                         continue;
                     }
 
@@ -3044,8 +3000,16 @@ pub const Parser = struct {
         };
     }
 
-    fn tokenIdToComparison(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp {
-        return switch (*id) {
+    fn tokenIdToUnwrapExpr(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp {
+        return switch (id) {
+            Token.Id.Keyword_catch => ast.NodeInfixOp.InfixOp { .Catch = null },
+            Token.Id.QuestionMarkQuestionMark => ast.NodeInfixOp.InfixOp { .UnwrapMaybe = void{} },
+            else => null,
+        };
+    }
+
+    fn tokenIdToComparison(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp {
+        return switch (id) {
             Token.Id.BangEqual => ast.NodeInfixOp.InfixOp { .BangEqual = void{} },
             Token.Id.EqualEqual => ast.NodeInfixOp.InfixOp { .EqualEqual = void{} },
             Token.Id.AngleBracketLeft => ast.NodeInfixOp.InfixOp { .LessThan = void{} },
@@ -3056,16 +3020,16 @@ pub const Parser = struct {
         };
     }
 
-    fn tokenIdToBitShift(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp {
-        return switch (*id) {
+    fn tokenIdToBitShift(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp {
+        return switch (id) {
             Token.Id.AngleBracketAngleBracketLeft => ast.NodeInfixOp.InfixOp { .BitShiftLeft = void{} },
             Token.Id.AngleBracketAngleBracketRight => ast.NodeInfixOp.InfixOp { .BitShiftRight = void{} },
             else => null,
         };
     }
 
-    fn tokenIdToAddition(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp {
-        return switch (*id) {
+    fn tokenIdToAddition(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp {
+        return switch (id) {
             Token.Id.Minus => ast.NodeInfixOp.InfixOp { .Sub = void{} },
             Token.Id.MinusPercent => ast.NodeInfixOp.InfixOp { .SubWrap = void{} },
             Token.Id.Plus => ast.NodeInfixOp.InfixOp { .Add = void{} },
@@ -3075,8 +3039,8 @@ pub const Parser = struct {
         };
     }
 
-    fn tokenIdToMultiply(id: &const Token.Id) ?ast.NodeInfixOp.InfixOp {
-        return switch (*id) {
+    fn tokenIdToMultiply(id: @TagType(Token.Id)) ?ast.NodeInfixOp.InfixOp {
+        return switch (id) {
             Token.Id.Slash => ast.NodeInfixOp.InfixOp { .Div = void{} },
             Token.Id.Asterisk => ast.NodeInfixOp.InfixOp { .Mult = void{} },
             Token.Id.AsteriskAsterisk => ast.NodeInfixOp.InfixOp { .ArrayMult = void{} },
@@ -3087,8 +3051,8 @@ pub const Parser = struct {
         };
     }
 
-    fn tokenIdToPrefixOp(id: &const Token.Id) ?ast.NodePrefixOp.PrefixOp {
-        return switch (*id) {
+    fn tokenIdToPrefixOp(id: @TagType(Token.Id)) ?ast.NodePrefixOp.PrefixOp {
+        return switch (id) {
             Token.Id.Bang => ast.NodePrefixOp.PrefixOp { .BoolNot = void{} },
             Token.Id.Tilde => ast.NodePrefixOp.PrefixOp { .BitNot = void{} },
             Token.Id.Minus => ast.NodePrefixOp.PrefixOp { .Negation = void{} },
@@ -3149,6 +3113,13 @@ pub const Parser = struct {
         );
     }
 
+    fn createToCtxLiteral(self: &Parser, arena: &mem.Allocator, opt_ctx: &const OptionalCtx, comptime T: type, token: &const Token) !&T {
+        const node = try self.createLiteral(arena, T, token);
+        opt_ctx.store(&node.base);
+
+        return node;
+    }
+
     fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) (error{ParseError}) {
         const loc = self.tokenizer.getTokenLocation(0, token);
         warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args);