Commit e260c8ca63

Jimmi Holst Christensen <jhc@liab.dk>
2018-04-09 11:11:18
std.zig.parser now parses while loops and labeled break and continue
1 parent e4d0b46
Changed files (2)
std/zig/ast.zig
@@ -21,6 +21,7 @@ pub const Node = struct {
         ParamDecl,
         Block,
         Payload,
+        Else,
         Switch,
         SwitchCase,
         SwitchElse,
@@ -61,6 +62,7 @@ pub const Node = struct {
             Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).iterate(index),
             Id.Block => @fieldParentPtr(NodeBlock, "base", base).iterate(index),
             Id.Payload => @fieldParentPtr(NodePayload, "base", base).iterate(index),
+            Id.Else => @fieldParentPtr(NodeSwitch, "base", base).iterate(index),
             Id.Switch => @fieldParentPtr(NodeSwitch, "base", base).iterate(index),
             Id.SwitchCase => @fieldParentPtr(NodeSwitchCase, "base", base).iterate(index),
             Id.SwitchElse => @fieldParentPtr(NodeSwitchElse, "base", base).iterate(index),
@@ -102,6 +104,7 @@ pub const Node = struct {
             Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).firstToken(),
             Id.Block => @fieldParentPtr(NodeBlock, "base", base).firstToken(),
             Id.Payload => @fieldParentPtr(NodePayload, "base", base).firstToken(),
+            Id.Else => @fieldParentPtr(NodeSwitch, "base", base).firstToken(),
             Id.Switch => @fieldParentPtr(NodeSwitch, "base", base).firstToken(),
             Id.SwitchCase => @fieldParentPtr(NodeSwitchCase, "base", base).firstToken(),
             Id.SwitchElse => @fieldParentPtr(NodeSwitchElse, "base", base).firstToken(),
@@ -143,6 +146,7 @@ pub const Node = struct {
             Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).lastToken(),
             Id.Block => @fieldParentPtr(NodeBlock, "base", base).lastToken(),
             Id.Payload => @fieldParentPtr(NodePayload, "base", base).lastToken(),
+            Id.Else => @fieldParentPtr(NodeElse, "base", base).lastToken(),
             Id.Switch => @fieldParentPtr(NodeSwitch, "base", base).lastToken(),
             Id.SwitchCase => @fieldParentPtr(NodeSwitchCase, "base", base).lastToken(),
             Id.SwitchElse => @fieldParentPtr(NodeSwitchElse, "base", base).lastToken(),
@@ -578,6 +582,35 @@ pub const NodePayload = struct {
     }
 };
 
+pub const NodeElse = struct {
+    base: Node,
+    else_token: Token,
+    payload: ?&NodePayload,
+    body: &Node,
+
+    pub fn iterate(self: &NodeElse, index: usize) ?&Node {
+        var i = index;
+
+        if (self.payload) |payload| {
+            if (i < 1) return &payload.base;
+            i -= 1;
+        }
+
+        if (i < 1) return self.body;
+        i -= 1;
+
+        return null;
+    }
+
+    pub fn firstToken(self: &NodeElse) Token {
+        return self.else_token;
+    }
+
+    pub fn lastToken(self: &NodeElse) Token {
+        return self.body.lastToken();
+    }
+};
+
 pub const NodeSwitch = struct {
     base: Node,
     switch_token: Token,
@@ -609,7 +642,7 @@ pub const NodeSwitch = struct {
 pub const NodeSwitchCase = struct {
     base: Node,
     items: ArrayList(&Node),
-    payload: ?&Node,
+    payload: ?&NodePayload,
     expr: &Node,
 
     pub fn iterate(self: &NodeSwitchCase, index: usize) ?&Node {
@@ -657,18 +690,14 @@ pub const NodeSwitchElse = struct {
 
 pub const NodeWhile = struct {
     base: Node,
+    label: ?Token,
+    inline_token: ?Token,
     while_token: Token,
     condition: &Node,
     payload: ?&NodePayload,
     continue_expr: ?&Node,
     body: &Node,
-    @"else": ?Else,
-
-    const Else = struct {
-        capture: ?&NodeIdentifier,
-        body: &Node,
-    };
-
+    @"else": ?&NodeElse,
 
     pub fn iterate(self: &NodeWhile, index: usize) ?&Node {
         var i = index;
@@ -690,12 +719,7 @@ pub const NodeWhile = struct {
         i -= 1;
 
         if (self.@"else") |@"else"| {
-            if (@"else".capture) |capture| {
-                if (i < 1) return &capture.base;
-                i -= 1;
-            }
-
-            if (i < 1) return @"else".body;
+            if (i < 1) return &@"else".base;
             i -= 1;
         }
 
@@ -703,6 +727,14 @@ pub const NodeWhile = struct {
     }
 
     pub fn firstToken(self: &NodeWhile) Token {
+        if (self.label) |label| {
+            return label;
+        }
+
+        if (self.inline_token) |inline_token| {
+            return inline_token;
+        }
+
         return self.while_token;
     }
 
@@ -749,7 +781,7 @@ pub const NodeInfixOp = struct {
         BitXor,
         BoolAnd,
         BoolOr,
-        Catch: ?&Node,
+        Catch: ?&NodePayload,
         Div,
         EqualEqual,
         ErrorUnion,
std/zig/parser.zig
@@ -116,6 +116,24 @@ pub const Parser = struct {
         };
     }
 
+    const LabelCtx = struct {
+        label: ?Token,
+        dest_ptr: DestPtr,
+    };
+
+    const InlineCtx = struct {
+        label: ?Token,
+        inline_token: ?Token,
+        dest_ptr: DestPtr,
+    };
+
+    const LoopCtx = struct {
+        label: ?Token,
+        inline_token: ?Token,
+        loop_token: Token,
+        dest_ptr: DestPtr,
+    };
+
     const State = union(enum) {
         TopLevel,
         TopLevelExtern: TopLevelDeclCtx,
@@ -137,15 +155,22 @@ pub const Parser = struct {
         ParamDecl: &ast.NodeFnProto,
         ParamDeclComma,
         FnDef: &ast.NodeFnProto,
+        LabeledExpression: LabelCtx,
+        Inline: InlineCtx,
+        While: LoopCtx,
+        For: LoopCtx,
         Block: &ast.NodeBlock,
+        Else: &?&ast.NodeElse,
+        WhileContinueExpr: &?&ast.Node,
         Statement: &ast.NodeBlock,
+        Semicolon: &const &const ast.Node,
         ExprListItemOrEnd: ExprListCtx,
         ExprListCommaOrEnd: ExprListCtx,
         FieldInitListItemOrEnd: ListSave(&ast.NodeFieldInitializer),
         FieldInitListCommaOrEnd: ListSave(&ast.NodeFieldInitializer),
         FieldListCommaOrEnd: &ast.NodeContainerDecl,
         SwitchCaseOrEnd: ListSave(&ast.NodeSwitchCase),
-        Payload: DestPtr,
+        Payload: &?&ast.NodePayload,
         SwitchCaseCommaOrEnd: ListSave(&ast.NodeSwitchCase),
         SwitchCaseItem: &ArrayList(&ast.Node),
         SwitchCaseItemCommaOrEnd: &ArrayList(&ast.Node),
@@ -157,9 +182,6 @@ pub const Parser = struct {
         /// "leaked" nodes. TODO: Figure out if it's nessesary to handle leaked nodes.
         Optional: RevertState,
 
-        /// Optional can be reverted by adding the Required state to the stack.
-        Required,
-
         Expression: DestPtr,
         RangeExpressionBegin: DestPtr,
         RangeExpressionEnd: DestPtr,
@@ -598,8 +620,7 @@ pub const Parser = struct {
                     continue;
                 },
 
-                State.Optional,
-                State.Required => { },
+                State.Optional => { },
 
                 State.Expression => |dest_ptr| {
                     const token = self.getNextToken();
@@ -626,10 +647,51 @@ pub const Parser = struct {
                             continue;
                         },
                         Token.Id.Keyword_break => {
-                            @panic("TODO: break");
+                            const label = blk: {
+                                const colon = self.getNextToken();
+                                if (colon.id != Token.Id.Colon) {
+                                    self.putBackToken(colon);
+                                    break :blk null;
+                                }
+
+                                break :blk (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue;
+                            };
+
+                            const node = try self.createControlFlowExpr(arena, token,
+                                ast.NodeControlFlowExpression.Kind {
+                                    .Break = label,
+                                }
+                            );
+                            dest_ptr.store(&node.base);
+
+                            stack.append(State {
+                                .Optional = RevertState {
+                                    .parser = *self,
+                                    .tokenizer = *self.tokenizer,
+                                    .ptr = &node.rhs,
+                                }
+                            }) catch unreachable;
+                            try stack.append(State { .Expression = DestPtr { .NullableField = &node.rhs } });
+                            continue;
                         },
                         Token.Id.Keyword_continue => {
-                            @panic("TODO: break");
+                            const label = blk: {
+                                const colon = self.getNextToken();
+                                if (colon.id != Token.Id.Colon) {
+                                    self.putBackToken(colon);
+                                    break :blk null;
+                                }
+
+                                break :blk (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue;
+                            };
+
+                            const node = try self.createControlFlowExpr(arena, token,
+                                ast.NodeControlFlowExpression.Kind {
+                                    .Continue = label,
+                                }
+                            );
+                            dest_ptr.store(&node.base);
+                            continue;
                         },
                         Token.Id.Keyword_cancel => {
                             @panic("TODO: cancel");
@@ -707,14 +769,7 @@ pub const Parser = struct {
 
                             stack.append(State { .UnwrapExpressionEnd = dest_ptr }) catch unreachable;
                             try stack.append(State { .Expression = DestPtr { .Field = &node.rhs } });
-                            try stack.append(State {
-                                .Optional = RevertState {
-                                    .tokenizer = *self.tokenizer,
-                                    .parser = *self,
-                                    .ptr = &node.op.Catch,
-                                }
-                            });
-                            try stack.append(State { .Payload = DestPtr { .NullableField = &node.op.Catch } });
+                            try stack.append(State { .Payload = &node.op.Catch });
                             continue;
                         },
                         Token.Id.QuestionMarkQuestionMark => {
@@ -1370,16 +1425,12 @@ pub const Parser = struct {
                                 continue;
                             }
 
-                            const block = try self.createBlock(arena, (?Token)(token), Token(undefined));
-                            dest_ptr.store(&block.base);
-
-                            stack.append(State { .Block = block }) catch unreachable;
-                            try stack.append(State {
-                                .ExpectTokenSave = ExpectTokenSave {
-                                    .id = Token.Id.LBrace,
-                                    .ptr = &block.lbrace,
+                            stack.append(State {
+                                .LabeledExpression = LabelCtx {
+                                    .label = token,
+                                    .dest_ptr = dest_ptr
                                 }
-                            });
+                            }) catch unreachable;
                             continue;
                         },
                         Token.Id.LBrace => {
@@ -1398,26 +1449,31 @@ pub const Parser = struct {
                         Token.Id.Keyword_if => {
                             @panic("TODO: inline if");
                         },
+                        Token.Id.Keyword_inline => {
+                            stack.append(State {
+                                .Inline = InlineCtx {
+                                    .label = null,
+                                    .inline_token = token,
+                                    .dest_ptr = dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
                         Token.Id.Keyword_while => {
-                            const node = try arena.create(ast.NodeWhile);
-                            *node = ast.NodeWhile {
-                                .base = self.initNode(ast.Node.Id.While),
-                                .while_token = token,
-                                .condition = undefined,
-                                .payload = null,
-                                .continue_expr = null,
-                                .body = undefined,
-                                .@"else" = null,
-                            };
-                            dest_ptr.store(&node.base);
-
-                            @panic("TODO: inline while");
+                            stack.append(State {
+                                .While = LoopCtx {
+                                    .label = null,
+                                    .inline_token = null,
+                                    .loop_token = token,
+                                    .dest_ptr = dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
                         },
                         Token.Id.Keyword_for => {
                             @panic("TODO: inline for");
                         },
                         Token.Id.Keyword_switch => {
-                            @breakpoint();
                             const node = try arena.create(ast.NodeSwitch);
                             *node = ast.NodeSwitch {
                                 .base = self.initNode(ast.Node.Id.Switch),
@@ -1558,15 +1614,7 @@ pub const Parser = struct {
                     try list_state.list.append(node);
                     stack.append(State { .SwitchCaseCommaOrEnd = list_state }) catch unreachable;
                     try stack.append(State { .Expression = DestPtr{ .Field = &node.expr  } });
-                    try stack.append(State {
-                        .Optional = RevertState {
-                            .tokenizer = *self.tokenizer,
-                            .parser = *self,
-                            .ptr = &node.payload,
-                        }
-                    });
-                    try stack.append(State { .Payload = DestPtr { .NullableField = &node.payload } });
-                    try stack.append(State.Required);
+                    try stack.append(State { .Payload = &node.payload });
 
                     const maybe_else = self.getNextToken();
                     if (maybe_else.id == Token.Id.Keyword_else) {
@@ -1585,32 +1633,6 @@ pub const Parser = struct {
                     }
                 },
 
-                State.Payload => |dest_ptr| {
-                    const lpipe = (try self.eatToken(&stack, Token.Id.Pipe)) ?? continue;
-
-                    const is_ptr = blk: {
-                        const asterik = self.getNextToken();
-                        if (asterik.id == Token.Id.Asterisk) {
-                            break :blk true;
-                        } else {
-                            self.putBackToken(asterik);
-                            break :blk false;
-                        }
-                    };
-
-                    const ident = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue;
-                    const rpipe = (try self.eatToken(&stack, Token.Id.Pipe)) ?? continue;
-                    const node = try arena.create(ast.NodePayload);
-                    *node = ast.NodePayload {
-                        .base = self.initNode(ast.Node.Id.Payload),
-                        .lpipe = lpipe,
-                        .is_ptr = is_ptr,
-                        .symbol = try self.createIdentifier(arena, ident),
-                        .rpipe = rpipe
-                    };
-                    dest_ptr.store(&node.base);
-                },
-
                 State.SwitchCaseItem => |case_items| {
                     stack.append(State { .SwitchCaseItemCommaOrEnd = case_items }) catch unreachable;
                     try stack.append(State { .RangeExpressionBegin = DestPtr{ .Field = try case_items.addOne() } });
@@ -1642,6 +1664,68 @@ pub const Parser = struct {
                     continue;
                 },
 
+                State.Else => |dest| {
+                    const else_token = self.getNextToken();
+                    if (else_token.id != Token.Id.Keyword_else) {
+                        self.putBackToken(else_token);
+                        continue;
+                    }
+
+                    const node = try arena.create(ast.NodeElse);
+                    *node = ast.NodeElse {
+                        .base = self.initNode(ast.Node.Id.Else),
+                        .else_token = else_token,
+                        .payload = null,
+                        .body = undefined,
+                    };
+                    *dest = node;
+
+                    stack.append(State { .Expression = DestPtr { .Field = &node.body } }) catch unreachable;
+                    try stack.append(State { .Payload = &node.payload });
+                },
+
+                State.WhileContinueExpr => |dest| {
+                    const colon = self.getNextToken();
+                    if (colon.id != Token.Id.Colon) {
+                        self.putBackToken(colon);
+                        continue;
+                    }
+
+                    _ = (try self.eatToken(&stack, Token.Id.LParen)) ?? continue;
+                    stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable;
+                    try stack.append(State { .Expression = DestPtr { .NullableField = dest } });
+                },
+
+                State.Payload => |dest| {
+                    const lpipe = self.getNextToken();
+                    if (lpipe.id != Token.Id.Pipe) {
+                        self.putBackToken(lpipe);
+                        continue;
+                    }
+
+                    const is_ptr = blk: {
+                        const asterik = self.getNextToken();
+                        if (asterik.id == Token.Id.Asterisk) {
+                            break :blk true;
+                        } else {
+                            self.putBackToken(asterik);
+                            break :blk false;
+                        }
+                    };
+
+                    const ident = (try self.eatToken(&stack, Token.Id.Identifier)) ?? continue;
+                    const rpipe = (try self.eatToken(&stack, Token.Id.Pipe)) ?? continue;
+                    const node = try arena.create(ast.NodePayload);
+                    *node = ast.NodePayload {
+                        .base = self.initNode(ast.Node.Id.Payload),
+                        .lpipe = lpipe,
+                        .is_ptr = is_ptr,
+                        .symbol = try self.createIdentifier(arena, ident),
+                        .rpipe = rpipe
+                    };
+                    *dest = node;
+                },
+
                 State.AddrOfModifiers => |addr_of_info| {
                     var token = self.getNextToken();
                     switch (token.id) {
@@ -1803,6 +1887,114 @@ pub const Parser = struct {
                     }
                 },
 
+                State.LabeledExpression => |ctx| {
+                    const token = self.getNextToken();
+                    switch (token.id) {
+                        Token.Id.LBrace => {
+                            const block = try self.createBlock(arena, (?Token)(ctx.label), token);
+                            ctx.dest_ptr.store(&block.base);
+
+                            stack.append(State { .Block = block }) catch unreachable;
+                            continue;
+                        },
+                        Token.Id.Keyword_while => {
+                            stack.append(State {
+                                .While = LoopCtx {
+                                    .label = ctx.label,
+                                    .inline_token = null,
+                                    .loop_token = token,
+                                    .dest_ptr = ctx.dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
+                        Token.Id.Keyword_for => {
+                            stack.append(State {
+                                .For = LoopCtx {
+                                    .label = ctx.label,
+                                    .inline_token = null,
+                                    .loop_token = token,
+                                    .dest_ptr = ctx.dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
+                        Token.Id.Keyword_inline => {
+                            stack.append(State {
+                                .Inline = InlineCtx {
+                                    .label = ctx.label,
+                                    .inline_token = token,
+                                    .dest_ptr = ctx.dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
+                        else => {
+                            try self.parseError(&stack, token, "expected 'while', 'for', 'inline' or '{{', found {}", @tagName(token.id));
+                            continue;
+                        },
+                    }
+                },
+
+                State.Inline => |ctx| {
+                    const token = self.getNextToken();
+                    switch (token.id) {
+                        Token.Id.Keyword_while => {
+                            stack.append(State {
+                                .While = LoopCtx {
+                                    .inline_token = ctx.inline_token,
+                                    .label = ctx.label,
+                                    .loop_token = token,
+                                    .dest_ptr = ctx.dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
+                        Token.Id.Keyword_for => {
+                            stack.append(State {
+                                .For = LoopCtx {
+                                    .inline_token = ctx.inline_token,
+                                    .label = ctx.label,
+                                    .loop_token = token,
+                                    .dest_ptr = ctx.dest_ptr,
+                                }
+                            }) catch unreachable;
+                            continue;
+                        },
+                        else => {
+                            try self.parseError(&stack, token, "expected 'while' or 'for', found {}", @tagName(token.id));
+                            continue;
+                        },
+                    }
+                },
+
+                State.While => |ctx| {
+                    const node = try arena.create(ast.NodeWhile);
+                    *node = ast.NodeWhile {
+                        .base = self.initNode(ast.Node.Id.While),
+                        .label = ctx.label,
+                        .inline_token = ctx.inline_token,
+                        .while_token = ctx.loop_token,
+                        .condition = undefined,
+                        .payload = null,
+                        .continue_expr = null,
+                        .body = undefined,
+                        .@"else" = null,
+                    };
+                    ctx.dest_ptr.store(&node.base);
+
+                    stack.append(State { .Else = &node.@"else" }) catch unreachable;
+                    try stack.append(State { .Expression = DestPtr { .Field = &node.body } });
+                    try stack.append(State { .WhileContinueExpr = &node.continue_expr });
+                    try stack.append(State { .Payload = &node.payload });
+                    try stack.append(State { .ExpectToken = Token.Id.RParen });
+                    try stack.append(State { .Expression = DestPtr { .Field = &node.condition } });
+                    try stack.append(State { .ExpectToken = Token.Id.LParen });
+                },
+
+                State.For => |ctx| {
+                },
+
                 State.Block => |block| {
                     const token = self.getNextToken();
                     switch (token.id) {
@@ -1841,28 +2033,6 @@ pub const Parser = struct {
                             stack.append(State { .VarDecl = var_decl }) catch unreachable;
                             continue;
                         },
-                        Token.Id.Identifier => {
-                            const maybe_colon = self.getNextToken();
-                            if (maybe_colon.id != Token.Id.Colon) {
-                                self.putBackToken(maybe_colon);
-                                self.putBackToken(next);
-                                stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable;
-                                try stack.append(State { .Expression = DestPtr{.Field = try block.statements.addOne() } });
-                                continue;
-                            }
-
-                            const inner_block = try self.createBlock(arena, (?Token)(next), Token(undefined));
-                            try block.statements.append(&inner_block.base);
-
-                            stack.append(State { .Block = inner_block }) catch unreachable;
-                            try stack.append(State {
-                                .ExpectTokenSave = ExpectTokenSave {
-                                    .id = Token.Id.LBrace,
-                                    .ptr = &inner_block.lbrace,
-                                }
-                            });
-                            continue;
-                        },
                         Token.Id.LBrace => {
                             const inner_block = try self.createBlock(arena, (?Token)(null), next);
                             try block.statements.append(&inner_block.base);
@@ -1870,22 +2040,53 @@ pub const Parser = struct {
                             stack.append(State { .Block = inner_block }) catch unreachable;
                             continue;
                         },
-                        Token.Id.Keyword_suspend, Token.Id.Keyword_if,
-                        Token.Id.Keyword_while, Token.Id.Keyword_for,
-                        Token.Id.Keyword_switch => {
-                            self.putBackToken(next);
-                            stack.append(State { .Expression = DestPtr{.Field = try block.statements.addOne() } }) catch unreachable;
-                            continue;
-                        },
                         else => {
                             self.putBackToken(next);
-                            stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable;
-                            try stack.append(State { .Expression = DestPtr{.Field = try block.statements.addOne() } });
+                            const statememt = try block.statements.addOne();
+                            stack.append(State { .Semicolon = statememt }) catch unreachable;
+                            try stack.append(State { .Expression = DestPtr{.Field = statememt } });
                             continue;
                         }
                     }
 
                 },
+
+                State.Semicolon => |node_ptr| {
+                    const node = *node_ptr;
+                    switch (node.id) {
+                        ast.Node.Id.Root,
+                        ast.Node.Id.StructField,
+                        ast.Node.Id.UnionTag,
+                        ast.Node.Id.EnumTag,
+                        ast.Node.Id.ParamDecl,
+                        ast.Node.Id.Block,
+                        ast.Node.Id.Payload,
+                        ast.Node.Id.Switch,
+                        ast.Node.Id.SwitchCase,
+                        ast.Node.Id.SwitchElse,
+                        ast.Node.Id.FieldInitializer,
+                        ast.Node.Id.LineComment,
+                        ast.Node.Id.TestDecl => continue,
+                        ast.Node.Id.While => {
+                            const while_node = @fieldParentPtr(ast.NodeWhile, "base", node);
+                            if (while_node.@"else") |@"else"| {
+                                stack.append(State { .Semicolon = &@"else".base }) catch unreachable;
+                                continue;
+                            }
+
+                            stack.append(State { .Semicolon = &while_node.body }) catch unreachable;
+                            continue;
+                        },
+                        ast.Node.Id.Else => {
+                            const else_node = @fieldParentPtr(ast.NodeElse, "base", node);
+                            stack.append(State { .Semicolon = &else_node.body }) catch unreachable;
+                            continue;
+                        },
+                        else => {
+                            _ = (try self.eatToken(&stack, Token.Id.Semicolon)) ?? continue;
+                        }
+                    }
+                }
             }
         }
     }
@@ -2295,9 +2496,6 @@ pub const Parser = struct {
                     *revert.ptr = null;
                     return;
                 },
-                State.Required => {
-                    return error.StateRequired;
-                },
                 else => { }
             }
         }
@@ -2361,6 +2559,7 @@ pub const Parser = struct {
         Expression: &ast.Node,
         VarDecl: &ast.NodeVarDecl,
         Statement: &ast.Node,
+        Semicolon: &ast.Node,
         FieldInitializer: &ast.NodeFieldInitializer,
         PrintIndent,
         Indent: usize,
@@ -2589,7 +2788,7 @@ pub const Parser = struct {
                         if (prefix_op_node.op == ast.NodeInfixOp.InfixOp.Catch) {
                             if (prefix_op_node.op.Catch) |payload| {
                             try stack.append(RenderState { .Text = " " });
-                                try stack.append(RenderState { .Expression = payload });
+                                try stack.append(RenderState { .Expression = &payload.base });
                             }
                             try stack.append(RenderState { .Text = " catch " });
                         } else {
@@ -3013,7 +3212,7 @@ pub const Parser = struct {
                         try stack.append(RenderState { .Expression = switch_case.expr });
                         if (switch_case.payload) |payload| {
                             try stack.append(RenderState { .Text = " " });
-                            try stack.append(RenderState { .Expression = payload });
+                            try stack.append(RenderState { .Expression = &payload.base });
                         }
                         try stack.append(RenderState { .Text = " => "});
 
@@ -3032,7 +3231,74 @@ pub const Parser = struct {
                         const switch_else = @fieldParentPtr(ast.NodeSwitchElse, "base", base);
                         try stream.print("{}", self.tokenizer.getTokenSlice(switch_else.token));
                     },
-                    ast.Node.Id.While => @panic("TODO: Render while"),
+                    ast.Node.Id.Else => {
+                        const else_node = @fieldParentPtr(ast.NodeElse, "base", base);
+                        try stream.print("{} ", self.tokenizer.getTokenSlice(else_node.else_token));
+
+                        if (else_node.body.id == ast.Node.Id.Block) {
+                            try stack.append(RenderState { .Expression = else_node.body });
+                        } else {
+                            try stack.append(RenderState { .Indent = indent });
+                            try stack.append(RenderState { .Expression = else_node.body });
+                            try stack.append(RenderState.PrintIndent);
+                            try stack.append(RenderState { .Indent = indent + indent_delta });
+                            try stack.append(RenderState { .Text = "\n" });
+                        }
+
+                        if (else_node.payload) |payload| {
+                            try stack.append(RenderState { .Text = " " });
+                            try stack.append(RenderState { .Expression = &payload.base });
+                        }
+                    },
+                    ast.Node.Id.While => {
+                        const while_node = @fieldParentPtr(ast.NodeWhile, "base", base);
+                        if (while_node.label) |label| {
+                            try stream.print("{}: ", self.tokenizer.getTokenSlice(label));
+                        }
+
+                        if (while_node.inline_token) |inline_token| {
+                            try stream.print("{} ", self.tokenizer.getTokenSlice(inline_token));
+                        }
+
+                        try stream.print("{} ", self.tokenizer.getTokenSlice(while_node.while_token));
+
+                        if (while_node.@"else") |@"else"| {
+                            try stack.append(RenderState { .Expression = &@"else".base });
+
+                            if (while_node.body.id == ast.Node.Id.Block) {
+                                try stack.append(RenderState { .Text = " " });
+                            } else {
+                                try stack.append(RenderState { .Text = "\n" });
+                            }
+                        }
+
+                        if (while_node.body.id == ast.Node.Id.Block) {
+                            try stack.append(RenderState { .Expression = while_node.body });
+                            try stack.append(RenderState { .Text = " " });
+                        } else {
+                            try stack.append(RenderState { .Indent = indent });
+                            try stack.append(RenderState { .Expression = while_node.body });
+                            try stack.append(RenderState.PrintIndent);
+                            try stack.append(RenderState { .Indent = indent + indent_delta });
+                            try stack.append(RenderState { .Text = "\n" });
+                        }
+
+                        if (while_node.continue_expr) |continue_expr| {
+                            try stack.append(RenderState { .Text = ")" });
+                            try stack.append(RenderState { .Expression = continue_expr });
+                            try stack.append(RenderState { .Text = ": (" });
+                            try stack.append(RenderState { .Text = " " });
+                        }
+
+                        if (while_node.payload) |payload| {
+                            try stack.append(RenderState { .Expression = &payload.base });
+                            try stack.append(RenderState { .Text = " " });
+                        }
+
+                        try stack.append(RenderState { .Text = ")" });
+                        try stack.append(RenderState { .Expression = while_node.condition });
+                        try stack.append(RenderState { .Text = "(" });
+                    },
 
                     ast.Node.Id.StructField,
                     ast.Node.Id.UnionTag,
@@ -3077,13 +3343,45 @@ pub const Parser = struct {
                             const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", base);
                             try stack.append(RenderState { .VarDecl = var_decl});
                         },
-                        ast.Node.Id.Block, ast.Node.Id.Switch => {
-                            try stack.append(RenderState { .Expression = base});
-                        },
                         else => {
-                            try stack.append(RenderState { .Text = ";"});
-                            try stack.append(RenderState { .Expression = base});
+                            try stack.append(RenderState { .Semicolon = base });
+                            try stack.append(RenderState { .Expression = base });
+                        },
+                    }
+                },
+                RenderState.Semicolon => |base| {
+                    switch (base.id) {
+                        ast.Node.Id.Root,
+                        ast.Node.Id.StructField,
+                        ast.Node.Id.UnionTag,
+                        ast.Node.Id.EnumTag,
+                        ast.Node.Id.ParamDecl,
+                        ast.Node.Id.Block,
+                        ast.Node.Id.Payload,
+                        ast.Node.Id.Switch,
+                        ast.Node.Id.SwitchCase,
+                        ast.Node.Id.SwitchElse,
+                        ast.Node.Id.FieldInitializer,
+                        ast.Node.Id.LineComment,
+                        ast.Node.Id.TestDecl => {},
+                        ast.Node.Id.While => {
+                            const while_node = @fieldParentPtr(ast.NodeWhile, "base", base);
+                            if (while_node.@"else") |@"else"| {
+                                stack.append(RenderState { .Semicolon = &@"else".base }) catch unreachable;
+                                continue;
+                            }
+
+                            stack.append(RenderState { .Semicolon = while_node.body }) catch unreachable;
+                            continue;
+                        },
+                        ast.Node.Id.Else => {
+                            const else_node = @fieldParentPtr(ast.NodeElse, "base", base);
+                            stack.append(RenderState { .Semicolon = else_node.body }) catch unreachable;
+                            continue;
                         },
+                        else => {
+                            try stack.append(RenderState { .Text = ";" });
+                        }
                     }
                 },
                 RenderState.Indent => |new_indent| indent = new_indent,
@@ -3690,8 +3988,11 @@ test "zig fmt: while" {
         \\        continue;
         \\
         \\    i = 0;
-        \\    var j usize = 0;
-        \\    while (i < 10) : ({ i += 1; j += 1; }) {
+        \\    var j: usize = 0;
+        \\    while (i < 10) : ({
+        \\        i += 1;
+        \\        j += 1;
+        \\    }) {
         \\        continue;
         \\    }
         \\
@@ -3711,7 +4012,7 @@ test "zig fmt: while" {
         \\        break 7;
         \\    } else {
         \\        unreachable;
-        \\    }
+        \\    };
         \\
         \\    var a: error!u8 = 0;
         \\    while (a) |v| {
@@ -3721,7 +4022,7 @@ test "zig fmt: while" {
         \\    }
         \\
         \\    comptime var k: usize = 0;
-        \\    inline while (i < 10) (i += 1)
+        \\    inline while (i < 10) : (i += 1)
         \\        j += 2;
         \\}
         \\