Commit 8bc523219c

Andrew Kelley <superjoe30@gmail.com>
2017-12-21 04:55:24
add labeled loops, labeled break, labeled continue. remove goto
closes #346 closes #630 regression: translate-c can no longer translate switch statements. after #629 we can ressurect and modify the code to utilize arbitrarily returning from blocks.
1 parent d686113
doc/langref.html.in
@@ -5847,11 +5847,9 @@ ParamDeclList = "(" list(ParamDecl, ",") ")"
 
 ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...")
 
-Block = "{" many(Statement) option(Expression) "}"
+Block = option(Symbol ":") "{" many(Statement) option(Expression) "}"
 
-Statement = Label | LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";"
-
-Label = Symbol ":"
+Statement = LocalVarDecl ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";"
 
 TypeExpr = PrefixOpExpression | "var"
 
@@ -5891,13 +5889,13 @@ SwitchProng = (list(SwitchItem, ",") | "else") "=&gt;" option("|" option("*") Sy
 
 SwitchItem = Expression | (Expression "..." Expression)
 
-ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body))
+ForExpression(body) = option(Symbol ":") option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body))
 
 BoolOrExpression = BoolAndExpression "or" BoolOrExpression | BoolAndExpression
 
 ReturnExpression = option("%") "return" option(Expression)
 
-BreakExpression = "break" option(Expression)
+BreakExpression = "break" option(":" Symbol) option(Expression)
 
 Defer(body) = option("%") "defer" body
 
@@ -5907,7 +5905,7 @@ TryExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|")
 
 TestExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") body option("else" BlockExpression(body))
 
-WhileExpression(body) = option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body))
+WhileExpression(body) = option(Symbol ":") option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body))
 
 BoolAndExpression = ComparisonExpression "and" BoolAndExpression | ComparisonExpression
 
@@ -5955,15 +5953,13 @@ StructLiteralField = "." Symbol "=" Expression
 
 PrefixOp = "!" | "-" | "~" | "*" | ("&amp;" option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "%" | "%%" | "??" | "-%"
 
-PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl
+PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl | ("continue" option(":" Symbol))
 
 ArrayType : "[" option(Expression) "]" option("align" "(" Expression option(":" Integer ":" Integer) ")")) option("const") option("volatile") TypeExpr
 
-GotoExpression = "goto" Symbol
-
 GroupedExpression = "(" Expression ")"
 
-KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable"
+KeywordLiteral = "true" | "false" | "null" | "undefined" | "error" | "this" | "unreachable"
 
 ContainerDecl = option("extern" | "packed")
   ("struct" option(GroupedExpression) | "union" option("enum" option(GroupedExpression) | GroupedExpression) | ("enum" option(GroupedExpression)))
src/all_types.hpp
@@ -386,8 +386,6 @@ enum NodeType {
     NodeTypeSwitchExpr,
     NodeTypeSwitchProng,
     NodeTypeSwitchRange,
-    NodeTypeLabel,
-    NodeTypeGoto,
     NodeTypeCompTime,
     NodeTypeBreak,
     NodeTypeContinue,
@@ -452,6 +450,7 @@ struct AstNodeParamDecl {
 };
 
 struct AstNodeBlock {
+    Buf *name;
     ZigList<AstNode *> statements;
     bool last_statement_is_result_expression;
 };
@@ -662,6 +661,7 @@ struct AstNodeTestExpr {
 };
 
 struct AstNodeWhileExpr {
+    Buf *name;
     AstNode *condition;
     Buf *var_symbol;
     bool var_is_ptr;
@@ -673,6 +673,7 @@ struct AstNodeWhileExpr {
 };
 
 struct AstNodeForExpr {
+    Buf *name;
     AstNode *array_expr;
     AstNode *elem_node; // always a symbol
     AstNode *index_node; // always a symbol, might be null
@@ -704,11 +705,6 @@ struct AstNodeLabel {
     Buf *name;
 };
 
-struct AstNodeGoto {
-    Buf *name;
-    bool is_inline;
-};
-
 struct AstNodeCompTime {
     AstNode *expr;
 };
@@ -836,11 +832,14 @@ struct AstNodeBoolLiteral {
 };
 
 struct AstNodeBreakExpr {
+    Buf *name;
     AstNode *expr; // may be null
 };
 
 struct AstNodeContinueExpr {
+    Buf *name;
 };
+
 struct AstNodeUnreachableExpr {
 };
 
@@ -886,7 +885,6 @@ struct AstNode {
         AstNodeSwitchProng switch_prong;
         AstNodeSwitchRange switch_range;
         AstNodeLabel label;
-        AstNodeGoto goto_expr;
         AstNodeCompTime comptime_expr;
         AstNodeAsmExpr asm_expr;
         AstNodeFieldAccessExpr field_access_expr;
@@ -1741,6 +1739,7 @@ struct ScopeCImport {
 struct ScopeLoop {
     Scope base;
 
+    Buf *name;
     IrBasicBlock *break_block;
     IrBasicBlock *continue_block;
     IrInstruction *is_comptime;
src/analyze.cpp
@@ -144,9 +144,15 @@ ScopeCImport *create_cimport_scope(AstNode *node, Scope *parent) {
 }
 
 ScopeLoop *create_loop_scope(AstNode *node, Scope *parent) {
-    assert(node->type == NodeTypeWhileExpr || node->type == NodeTypeForExpr);
     ScopeLoop *scope = allocate<ScopeLoop>(1);
     init_scope(&scope->base, ScopeIdLoop, node, parent);
+    if (node->type == NodeTypeWhileExpr) {
+        scope->name = node->data.while_expr.name;
+    } else if (node->type == NodeTypeForExpr) {
+        scope->name = node->data.for_expr.name;
+    } else {
+        zig_unreachable();
+    }
     return scope;
 }
 
@@ -2916,8 +2922,6 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) {
         case NodeTypeSwitchExpr:
         case NodeTypeSwitchProng:
         case NodeTypeSwitchRange:
-        case NodeTypeLabel:
-        case NodeTypeGoto:
         case NodeTypeBreak:
         case NodeTypeContinue:
         case NodeTypeUnreachable:
src/ast_render.cpp
@@ -215,10 +215,6 @@ static const char *node_type_str(NodeType node_type) {
             return "SwitchProng";
         case NodeTypeSwitchRange:
             return "SwitchRange";
-        case NodeTypeLabel:
-            return "Label";
-        case NodeTypeGoto:
-            return "Goto";
         case NodeTypeCompTime:
             return "CompTime";
         case NodeTypeBreak:
@@ -391,7 +387,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
     switch (node->type) {
         case NodeTypeSwitchProng:
         case NodeTypeSwitchRange:
-        case NodeTypeLabel:
         case NodeTypeStructValueField:
             zig_unreachable();
         case NodeTypeRoot:
@@ -470,6 +465,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                 break;
             }
         case NodeTypeBlock:
+            if (node->data.block.name != nullptr) {
+                fprintf(ar->f, "%s: ", buf_ptr(node->data.block.name));
+            }
             if (node->data.block.statements.length == 0) {
                 fprintf(ar->f, "{}");
                 break;
@@ -478,13 +476,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
             ar->indent += ar->indent_size;
             for (size_t i = 0; i < node->data.block.statements.length; i += 1) {
                 AstNode *statement = node->data.block.statements.at(i);
-                if (statement->type == NodeTypeLabel) {
-                    ar->indent -= ar->indent_size;
-                    print_indent(ar);
-                    fprintf(ar->f, "%s:\n", buf_ptr(statement->data.label.name));
-                    ar->indent += ar->indent_size;
-                    continue;
-                }
                 print_indent(ar);
                 render_node_grouped(ar, statement);
                 if (!(i == node->data.block.statements.length - 1 &&
@@ -515,6 +506,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
         case NodeTypeBreak:
             {
                 fprintf(ar->f, "break");
+                if (node->data.break_expr.name != nullptr) {
+                    fprintf(ar->f, " :%s", buf_ptr(node->data.break_expr.name));
+                }
                 if (node->data.break_expr.expr) {
                     fprintf(ar->f, " ");
                     render_node_grouped(ar, node->data.break_expr.expr);
@@ -828,6 +822,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
             }
         case NodeTypeWhileExpr:
             {
+                if (node->data.while_expr.name != nullptr) {
+                    fprintf(ar->f, "%s: ", buf_ptr(node->data.while_expr.name));
+                }
                 const char *inline_str = node->data.while_expr.is_inline ? "inline " : "";
                 fprintf(ar->f, "%swhile (", inline_str);
                 render_node_grouped(ar, node->data.while_expr.condition);
@@ -957,11 +954,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                 fprintf(ar->f, "}");
                 break;
             }
-        case NodeTypeGoto:
-            {
-                fprintf(ar->f, "goto %s", buf_ptr(node->data.goto_expr.name));
-                break;
-            }
         case NodeTypeCompTime:
             {
                 fprintf(ar->f, "comptime ");
@@ -970,6 +962,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
             }
         case NodeTypeForExpr:
             {
+                if (node->data.for_expr.name != nullptr) {
+                    fprintf(ar->f, "%s: ", buf_ptr(node->data.for_expr.name));
+                }
                 const char *inline_str = node->data.for_expr.is_inline ? "inline " : "";
                 fprintf(ar->f, "%sfor (", inline_str);
                 render_node_grouped(ar, node->data.for_expr.array_expr);
@@ -995,6 +990,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
         case NodeTypeContinue:
             {
                 fprintf(ar->f, "continue");
+                if (node->data.continue_expr.name != nullptr) {
+                    fprintf(ar->f, " :%s", buf_ptr(node->data.continue_expr.name));
+                }
                 break;
             }
         case NodeTypeUnreachable:
src/ir.cpp
@@ -3511,29 +3511,6 @@ static VariableTableEntry *ir_create_var(IrBuilder *irb, AstNode *node, Scope *s
     return var;
 }
 
-static LabelTableEntry *find_label(IrExecutable *exec, Scope *scope, Buf *name) {
-    while (scope) {
-        if (scope->id == ScopeIdBlock) {
-            ScopeBlock *block_scope = (ScopeBlock *)scope;
-            auto entry = block_scope->label_table.maybe_get(name);
-            if (entry)
-                return entry->value;
-        }
-        scope = scope->parent;
-    }
-
-    return nullptr;
-}
-
-static ScopeBlock *find_block_scope(IrExecutable *exec, Scope *scope) {
-    while (scope) {
-        if (scope->id == ScopeIdBlock)
-            return (ScopeBlock *)scope;
-        scope = scope->parent;
-    }
-    return nullptr;
-}
-
 static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode *block_node) {
     assert(block_node->type == NodeTypeBlock);
 
@@ -3557,38 +3534,6 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode
     for (size_t i = 0; i < block_node->data.block.statements.length; i += 1) {
         AstNode *statement_node = block_node->data.block.statements.at(i);
 
-        if (statement_node->type == NodeTypeLabel) {
-            Buf *label_name = statement_node->data.label.name;
-            IrBasicBlock *label_block = ir_build_basic_block(irb, child_scope, buf_ptr(label_name));
-            LabelTableEntry *label = allocate<LabelTableEntry>(1);
-            label->decl_node = statement_node;
-            label->bb = label_block;
-            irb->exec->all_labels.append(label);
-
-            LabelTableEntry *existing_label = find_label(irb->exec, child_scope, label_name);
-            if (existing_label) {
-                ErrorMsg *msg = add_node_error(irb->codegen, statement_node,
-                    buf_sprintf("duplicate label name '%s'", buf_ptr(label_name)));
-                add_error_note(irb->codegen, msg, existing_label->decl_node, buf_sprintf("other label here"));
-                return irb->codegen->invalid_instruction;
-            } else {
-                ScopeBlock *scope_block = find_block_scope(irb->exec, child_scope);
-                scope_block->label_table.put(label_name, label);
-            }
-
-            if (!is_continuation_unreachable) {
-                // fall through into new labeled basic block
-                IrInstruction *is_comptime = ir_mark_gen(ir_build_const_bool(irb, child_scope, statement_node,
-                        ir_should_inline(irb->exec, child_scope)));
-                ir_mark_gen(ir_build_br(irb, child_scope, statement_node, label_block, is_comptime));
-            }
-            ir_set_cursor_at_end(irb, label_block);
-
-            // a label is an entry point
-            is_continuation_unreachable = false;
-            continue;
-        }
-
         IrInstruction *statement_value = ir_gen_node(irb, statement_node, child_scope);
         is_continuation_unreachable = instr_is_unreachable(statement_value);
         if (is_continuation_unreachable) {
@@ -6000,22 +5945,6 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
     return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
 }
 
-static IrInstruction *ir_gen_goto(IrBuilder *irb, Scope *scope, AstNode *node) {
-    assert(node->type == NodeTypeGoto);
-
-    // make a placeholder unreachable statement and a note to come back and
-    // replace the instruction with a branch instruction
-    IrGotoItem *goto_item = irb->exec->goto_list.add_one();
-    goto_item->bb = irb->current_basic_block;
-    goto_item->instruction_index = irb->current_basic_block->instruction_list.length;
-    goto_item->source_node = node;
-    goto_item->scope = scope;
-
-    // we don't know if we need to generate defer expressions yet
-    // we do that later when we find out which label we're jumping to.
-    return ir_build_unreachable(irb, scope, node);
-}
-
 static IrInstruction *ir_gen_comptime(IrBuilder *irb, Scope *parent_scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypeCompTime);
 
@@ -6033,16 +5962,28 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *
 
     Scope *search_scope = break_scope;
     ScopeLoop *loop_scope;
+    bool saw_any_loop_scope = false;
     for (;;) {
         if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) {
-            add_node_error(irb->codegen, node, buf_sprintf("break expression outside loop"));
-            return irb->codegen->invalid_instruction;
+            if (saw_any_loop_scope) {
+                add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.break_expr.name)));
+                return irb->codegen->invalid_instruction;
+            } else {
+                add_node_error(irb->codegen, node, buf_sprintf("break expression outside loop"));
+                return irb->codegen->invalid_instruction;
+            }
         } else if (search_scope->id == ScopeIdDeferExpr) {
             add_node_error(irb->codegen, node, buf_sprintf("cannot break out of defer expression"));
             return irb->codegen->invalid_instruction;
         } else if (search_scope->id == ScopeIdLoop) {
-            loop_scope = (ScopeLoop *)search_scope;
-            break;
+            ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope;
+            saw_any_loop_scope = true;
+            if (node->data.break_expr.name == nullptr ||
+                (this_loop_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_loop_scope->name)))
+            {
+                loop_scope = this_loop_scope;
+                break;
+            }
         }
         search_scope = search_scope->parent;
     }
@@ -6081,16 +6022,28 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast
 
     Scope *search_scope = continue_scope;
     ScopeLoop *loop_scope;
+    bool saw_any_loop_scope = false;
     for (;;) {
         if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) {
-            add_node_error(irb->codegen, node, buf_sprintf("continue expression outside loop"));
-            return irb->codegen->invalid_instruction;
+            if (saw_any_loop_scope) {
+                add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.continue_expr.name)));
+                return irb->codegen->invalid_instruction;
+            } else {
+                add_node_error(irb->codegen, node, buf_sprintf("continue expression outside loop"));
+                return irb->codegen->invalid_instruction;
+            }
         } else if (search_scope->id == ScopeIdDeferExpr) {
             add_node_error(irb->codegen, node, buf_sprintf("cannot continue out of defer expression"));
             return irb->codegen->invalid_instruction;
         } else if (search_scope->id == ScopeIdLoop) {
-            loop_scope = (ScopeLoop *)search_scope;
-            break;
+            ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope;
+            saw_any_loop_scope = true;
+            if (node->data.continue_expr.name == nullptr ||
+                (this_loop_scope->name != nullptr && buf_eql_buf(node->data.continue_expr.name, this_loop_scope->name)))
+            {
+                loop_scope = this_loop_scope;
+                break;
+            }
         }
         search_scope = search_scope->parent;
     }
@@ -6332,7 +6285,6 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop
         case NodeTypeSwitchProng:
         case NodeTypeSwitchRange:
         case NodeTypeStructField:
-        case NodeTypeLabel:
         case NodeTypeFnDef:
         case NodeTypeFnDecl:
         case NodeTypeErrorValueDecl:
@@ -6396,8 +6348,6 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop
             return ir_lval_wrap(irb, scope, ir_gen_test_expr(irb, scope, node), lval);
         case NodeTypeSwitchExpr:
             return ir_lval_wrap(irb, scope, ir_gen_switch_expr(irb, scope, node), lval);
-        case NodeTypeGoto:
-            return ir_lval_wrap(irb, scope, ir_gen_goto(irb, scope, node), lval);
         case NodeTypeCompTime:
             return ir_gen_comptime(irb, scope, node, lval);
         case NodeTypeErrorType:
@@ -6432,70 +6382,6 @@ static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope) {
     return ir_gen_node_extra(irb, node, scope, LVAL_NONE);
 }
 
-static bool ir_goto_pass2(IrBuilder *irb) {
-    for (size_t i = 0; i < irb->exec->goto_list.length; i += 1) {
-        IrGotoItem *goto_item = &irb->exec->goto_list.at(i);
-        AstNode *source_node = goto_item->source_node;
-
-        // Since a goto will always end a basic block, we move the "current instruction"
-        // index back to over the placeholder unreachable instruction and begin overwriting
-        irb->current_basic_block = goto_item->bb;
-        irb->current_basic_block->instruction_list.resize(goto_item->instruction_index);
-
-        Buf *label_name = source_node->data.goto_expr.name;
-
-        // Search up the scope until we find one of these things:
-        // * A block scope with the label in it => OK
-        // * A defer expression scope => error, error, cannot leave defer expression
-        // * Top level scope => error, didn't find label
-
-        LabelTableEntry *label;
-        Scope *search_scope = goto_item->scope;
-        for (;;) {
-            if (search_scope == nullptr) {
-                add_node_error(irb->codegen, source_node,
-                    buf_sprintf("no label in scope named '%s'", buf_ptr(label_name)));
-                return false;
-            } else if (search_scope->id == ScopeIdBlock) {
-                ScopeBlock *block_scope = (ScopeBlock *)search_scope;
-                auto entry = block_scope->label_table.maybe_get(label_name);
-                if (entry) {
-                    label = entry->value;
-                    break;
-                }
-            } else if (search_scope->id == ScopeIdDeferExpr) {
-                add_node_error(irb->codegen, source_node,
-                    buf_sprintf("cannot goto out of defer expression"));
-                return false;
-            }
-            search_scope = search_scope->parent;
-        }
-
-        label->used = true;
-
-        IrInstruction *is_comptime = ir_build_const_bool(irb, goto_item->scope, source_node,
-            ir_should_inline(irb->exec, goto_item->scope) || source_node->data.goto_expr.is_inline);
-        if (!ir_gen_defers_for_block(irb, goto_item->scope, label->bb->scope, false)) {
-            add_node_error(irb->codegen, source_node,
-                buf_sprintf("no label in scope named '%s'", buf_ptr(label_name)));
-            return false;
-        }
-        ir_build_br(irb, goto_item->scope, source_node, label->bb, is_comptime);
-    }
-
-    for (size_t i = 0; i < irb->exec->all_labels.length; i += 1) {
-        LabelTableEntry *label = irb->exec->all_labels.at(i);
-        if (!label->used) {
-            add_node_error(irb->codegen, label->decl_node,
-                    buf_sprintf("label '%s' defined but not used",
-                        buf_ptr(label->decl_node->data.label.name)));
-            return false;
-        }
-    }
-
-    return true;
-}
-
 static void invalidate_exec(IrExecutable *exec) {
     if (exec->invalid)
         return;
@@ -6532,11 +6418,6 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
         ir_mark_gen(ir_build_return(irb, scope, result->source_node, result));
     }
 
-    if (!ir_goto_pass2(irb)) {
-        invalidate_exec(ir_executable);
-        return false;
-    }
-
     return true;
 }
 
src/parser.cpp
@@ -632,27 +632,6 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, size_t *token_index, bool m
     return node;
 }
 
-/*
-GotoExpression = "goto" Symbol
-*/
-static AstNode *ast_parse_goto_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
-    Token *goto_token = &pc->tokens->at(*token_index);
-    if (goto_token->id == TokenIdKeywordGoto) {
-        *token_index += 1;
-    } else if (mandatory) {
-        ast_expect_token(pc, goto_token, TokenIdKeywordGoto);
-        zig_unreachable();
-    } else {
-        return nullptr;
-    }
-
-    AstNode *node = ast_create_node(pc, NodeTypeGoto, goto_token);
-
-    Token *dest_symbol = ast_eat_token(pc, token_index, TokenIdSymbol);
-    node->data.goto_expr.name = token_buf(dest_symbol);
-    return node;
-}
-
 /*
 CompTimeExpression(body) = "comptime" body
 */
@@ -676,8 +655,8 @@ static AstNode *ast_parse_comptime_expr(ParseContext *pc, size_t *token_index, b
 }
 
 /*
-PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl
-KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable"
+PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." Symbol) | ContainerDecl | ("continue" option(":" Symbol))
+KeywordLiteral = "true" | "false" | "null" | "undefined" | "error" | "this" | "unreachable"
 */
 static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -721,6 +700,12 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
     } else if (token->id == TokenIdKeywordContinue) {
         AstNode *node = ast_create_node(pc, NodeTypeContinue, token);
         *token_index += 1;
+        Token *maybe_colon_token = &pc->tokens->at(*token_index);
+        if (maybe_colon_token->id == TokenIdColon) {
+            *token_index += 1;
+            Token *name = ast_eat_token(pc, token_index, TokenIdSymbol);
+            node->data.continue_expr.name = token_buf(name);
+        }
         return node;
     } else if (token->id == TokenIdKeywordUndefined) {
         AstNode *node = ast_create_node(pc, NodeTypeUndefinedLiteral, token);
@@ -770,10 +755,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
         return node;
     }
 
-    AstNode *goto_node = ast_parse_goto_expr(pc, token_index, false);
-    if (goto_node)
-        return goto_node;
-
     AstNode *grouped_expr_node = ast_parse_grouped_expr(pc, token_index, false);
     if (grouped_expr_node) {
         return grouped_expr_node;
@@ -1488,7 +1469,7 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, size_t *token_index) {
 }
 
 /*
-BreakExpression : "break" option(Expression)
+BreakExpression = "break" option(":" Symbol) option(Expression)
 */
 static AstNode *ast_parse_break_expr(ParseContext *pc, size_t *token_index) {
     Token *token = &pc->tokens->at(*token_index);
@@ -1498,8 +1479,15 @@ static AstNode *ast_parse_break_expr(ParseContext *pc, size_t *token_index) {
     } else {
         return nullptr;
     }
-
     AstNode *node = ast_create_node(pc, NodeTypeBreak, token);
+
+    Token *maybe_colon_token = &pc->tokens->at(*token_index);
+    if (maybe_colon_token->id == TokenIdColon) {
+        *token_index += 1;
+        Token *name = ast_eat_token(pc, token_index, TokenIdSymbol);
+        node->data.break_expr.name = token_buf(name);
+    }
+
     node->data.break_expr.expr = ast_parse_expression(pc, token_index, false);
 
     return node;
@@ -1678,35 +1666,53 @@ static AstNode *ast_parse_bool_or_expr(ParseContext *pc, size_t *token_index, bo
 }
 
 /*
-WhileExpression(body) = option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body))
+WhileExpression(body) = option(Symbol ":") option("inline") "while" "(" Expression ")" option("|" option("*") Symbol "|") option(":" "(" Expression ")") body option("else" option("|" Symbol "|") BlockExpression(body))
 */
 static AstNode *ast_parse_while_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
-    Token *first_token = &pc->tokens->at(*token_index);
-    Token *while_token;
+    size_t orig_token_index = *token_index;
 
-    bool is_inline;
-    if (first_token->id == TokenIdKeywordInline) {
-        while_token = &pc->tokens->at(*token_index + 1);
-        if (while_token->id == TokenIdKeywordWhile) {
-            is_inline = true;
-            *token_index += 2;
+    Token *name_token = nullptr;
+    Token *token = &pc->tokens->at(*token_index);
+
+    if (token->id == TokenIdSymbol) {
+        *token_index += 1;
+        Token *colon_token = &pc->tokens->at(*token_index);
+        if (colon_token->id == TokenIdColon) {
+            *token_index += 1;
+            name_token = token;
+            token = &pc->tokens->at(*token_index);
         } else if (mandatory) {
-            ast_expect_token(pc, while_token, TokenIdKeywordWhile);
+            ast_expect_token(pc, colon_token, TokenIdColon);
             zig_unreachable();
         } else {
+            *token_index = orig_token_index;
             return nullptr;
         }
-    } else if (first_token->id == TokenIdKeywordWhile) {
-        while_token = first_token;
-        is_inline = false;
+    }
+
+    bool is_inline = false;
+    if (token->id == TokenIdKeywordInline) {
+        is_inline = true;
+        *token_index += 1;
+        token = &pc->tokens->at(*token_index);
+    }
+
+    Token *while_token;
+    if (token->id == TokenIdKeywordWhile) {
+        while_token = token;
         *token_index += 1;
     } else if (mandatory) {
-        ast_expect_token(pc, first_token, TokenIdKeywordWhile);
+        ast_expect_token(pc, token, TokenIdKeywordWhile);
         zig_unreachable();
     } else {
+        *token_index = orig_token_index;
         return nullptr;
     }
+
     AstNode *node = ast_create_node(pc, NodeTypeWhileExpr, while_token);
+    if (name_token != nullptr) {
+        node->data.while_expr.name = token_buf(name_token);
+    }
     node->data.while_expr.is_inline = is_inline;
 
     ast_eat_token(pc, token_index, TokenIdLParen);
@@ -1766,36 +1772,53 @@ static AstNode *ast_parse_symbol(ParseContext *pc, size_t *token_index) {
 }
 
 /*
-ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body))
+ForExpression(body) = option(Symbol ":") option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body))
 */
 static AstNode *ast_parse_for_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
-    Token *first_token = &pc->tokens->at(*token_index);
-    Token *for_token;
+    size_t orig_token_index = *token_index;
 
-    bool is_inline;
-    if (first_token->id == TokenIdKeywordInline) {
-        is_inline = true;
-        for_token = &pc->tokens->at(*token_index + 1);
-        if (for_token->id == TokenIdKeywordFor) {
-            *token_index += 2;
+    Token *name_token = nullptr;
+    Token *token = &pc->tokens->at(*token_index);
+
+    if (token->id == TokenIdSymbol) {
+        *token_index += 1;
+        Token *colon_token = &pc->tokens->at(*token_index);
+        if (colon_token->id == TokenIdColon) {
+            *token_index += 1;
+            name_token = token;
+            token = &pc->tokens->at(*token_index);
         } else if (mandatory) {
-            ast_expect_token(pc, first_token, TokenIdKeywordFor);
+            ast_expect_token(pc, colon_token, TokenIdColon);
             zig_unreachable();
         } else {
+            *token_index = orig_token_index;
             return nullptr;
         }
-    } else if (first_token->id == TokenIdKeywordFor) {
-        for_token = first_token;
-        is_inline = false;
+    }
+
+    bool is_inline = false;
+    if (token->id == TokenIdKeywordInline) {
+        is_inline = true;
+        *token_index += 1;
+        token = &pc->tokens->at(*token_index);
+    }
+
+    Token *for_token;
+    if (token->id == TokenIdKeywordFor) {
+        for_token = token;
         *token_index += 1;
     } else if (mandatory) {
-        ast_expect_token(pc, first_token, TokenIdKeywordFor);
+        ast_expect_token(pc, token, TokenIdKeywordFor);
         zig_unreachable();
     } else {
+        *token_index = orig_token_index;
         return nullptr;
     }
 
     AstNode *node = ast_create_node(pc, NodeTypeForExpr, for_token);
+    if (name_token != nullptr) {
+        node->data.for_expr.name = token_buf(name_token);
+    }
     node->data.for_expr.is_inline = is_inline;
 
     ast_eat_token(pc, token_index, TokenIdLParen);
@@ -2125,32 +2148,6 @@ static AstNode *ast_parse_expression(ParseContext *pc, size_t *token_index, bool
 /*
 Label: token(Symbol) token(Colon)
 */
-static AstNode *ast_parse_label(ParseContext *pc, size_t *token_index, bool mandatory) {
-    Token *symbol_token = &pc->tokens->at(*token_index);
-    if (symbol_token->id != TokenIdSymbol) {
-        if (mandatory) {
-            ast_expect_token(pc, symbol_token, TokenIdSymbol);
-        } else {
-            return nullptr;
-        }
-    }
-
-    Token *colon_token = &pc->tokens->at(*token_index + 1);
-    if (colon_token->id != TokenIdColon) {
-        if (mandatory) {
-            ast_expect_token(pc, colon_token, TokenIdColon);
-        } else {
-            return nullptr;
-        }
-    }
-
-    *token_index += 2;
-
-    AstNode *node = ast_create_node(pc, NodeTypeLabel, symbol_token);
-    node->data.label.name = token_buf(symbol_token);
-    return node;
-}
-
 static bool statement_terminates_without_semicolon(AstNode *node) {
     switch (node->type) {
         case NodeTypeIfBoolExpr:
@@ -2175,7 +2172,6 @@ static bool statement_terminates_without_semicolon(AstNode *node) {
             return node->data.defer.expr->type == NodeTypeBlock;
         case NodeTypeSwitchExpr:
         case NodeTypeBlock:
-        case NodeTypeLabel:
             return true;
         default:
             return false;
@@ -2183,27 +2179,48 @@ static bool statement_terminates_without_semicolon(AstNode *node) {
 }
 
 /*
-Block = "{" many(Statement) option(Expression) "}"
+Block = option(Symbol ":") "{" many(Statement) option(Expression) "}"
 Statement = Label | VariableDeclaration ";" | Defer(Block) | Defer(Expression) ";" | BlockExpression(Block) | Expression ";" | ";" | ExportDecl
 */
 static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mandatory) {
+    size_t orig_token_index = *token_index;
+
+    Token *name_token = nullptr;
     Token *last_token = &pc->tokens->at(*token_index);
 
+    if (last_token->id == TokenIdSymbol) {
+        *token_index += 1;
+        Token *colon_token = &pc->tokens->at(*token_index);
+        if (colon_token->id == TokenIdColon) {
+            *token_index += 1;
+            name_token = last_token;
+            last_token = &pc->tokens->at(*token_index);
+        } else if (mandatory) {
+            ast_expect_token(pc, colon_token, TokenIdColon);
+            zig_unreachable();
+        } else {
+            *token_index = orig_token_index;
+            return nullptr;
+        }
+    }
+
     if (last_token->id != TokenIdLBrace) {
         if (mandatory) {
             ast_expect_token(pc, last_token, TokenIdLBrace);
         } else {
+            *token_index = orig_token_index;
             return nullptr;
         }
     }
     *token_index += 1;
 
     AstNode *node = ast_create_node(pc, NodeTypeBlock, last_token);
+    if (name_token != nullptr) {
+        node->data.block.name = token_buf(name_token);
+    }
 
     for (;;) {
-        AstNode *statement_node = ast_parse_label(pc, token_index, false);
-        if (!statement_node)
-            statement_node = ast_parse_local_var_decl(pc, token_index);
+        AstNode *statement_node = ast_parse_local_var_decl(pc, token_index);
         if (!statement_node)
             statement_node = ast_parse_defer_expr(pc, token_index);
         if (!statement_node)
@@ -2225,9 +2242,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand
             }
         }
 
-        node->data.block.last_statement_is_result_expression = statement_node && !(
-            statement_node->type == NodeTypeLabel ||
-            statement_node->type == NodeTypeDefer);
+        node->data.block.last_statement_is_result_expression = statement_node && statement_node->type != NodeTypeDefer;
 
         last_token = &pc->tokens->at(*token_index);
         if (last_token->id == TokenIdRBrace) {
@@ -2860,12 +2875,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
             visit_field(&node->data.switch_range.start, visit, context);
             visit_field(&node->data.switch_range.end, visit, context);
             break;
-        case NodeTypeLabel:
-            // none
-            break;
-        case NodeTypeGoto:
-            // none
-            break;
         case NodeTypeCompTime:
             visit_field(&node->data.comptime_expr.expr, visit, context);
             break;
src/translate_c.cpp
@@ -104,10 +104,8 @@ static TransScopeRoot *trans_scope_root_create(Context *c);
 static TransScopeWhile *trans_scope_while_create(Context *c, TransScope *parent_scope);
 static TransScopeBlock *trans_scope_block_create(Context *c, TransScope *parent_scope);
 static TransScopeVar *trans_scope_var_create(Context *c, TransScope *parent_scope, Buf *wanted_name);
-static TransScopeSwitch *trans_scope_switch_create(Context *c, TransScope *parent_scope);
 
 static TransScopeBlock *trans_scope_block_find(TransScope *scope);
-static TransScopeSwitch *trans_scope_switch_find(TransScope *scope);
 
 static AstNode *resolve_record_decl(Context *c, const RecordDecl *record_decl);
 static AstNode *resolve_enum_decl(Context *c, const EnumDecl *enum_decl);
@@ -265,18 +263,6 @@ static AstNode *trans_create_node_addr_of(Context *c, bool is_const, bool is_vol
     return node;
 }
 
-static AstNode *trans_create_node_goto(Context *c, Buf *label_name) {
-    AstNode *goto_node = trans_create_node(c, NodeTypeGoto);
-    goto_node->data.goto_expr.name = label_name;
-    return goto_node;
-}
-
-static AstNode *trans_create_node_label(Context *c, Buf *label_name) {
-    AstNode *label_node = trans_create_node(c, NodeTypeLabel);
-    label_node->data.label.name = label_name;
-    return label_node;
-}
-
 static AstNode *trans_create_node_bool(Context *c, bool value) {
     AstNode *bool_node = trans_create_node(c, NodeTypeBoolLiteral);
     bool_node->data.bool_literal.value = value;
@@ -2379,145 +2365,6 @@ static AstNode *trans_do_loop(Context *c, TransScope *parent_scope, const DoStmt
     return while_scope->node;
 }
 
-static AstNode *trans_switch_stmt(Context *c, TransScope *parent_scope, const SwitchStmt *stmt) {
-    TransScopeBlock *block_scope = trans_scope_block_create(c, parent_scope);
-
-    TransScopeSwitch *switch_scope;
-
-    const DeclStmt *var_decl_stmt = stmt->getConditionVariableDeclStmt();
-    if (var_decl_stmt == nullptr) {
-        switch_scope = trans_scope_switch_create(c, &block_scope->base);
-    } else {
-        AstNode *vars_node;
-        TransScope *var_scope = trans_stmt(c, &block_scope->base, var_decl_stmt, &vars_node);
-        if (var_scope == nullptr)
-            return nullptr;
-        if (vars_node != nullptr)
-            block_scope->node->data.block.statements.append(vars_node);
-        switch_scope = trans_scope_switch_create(c, var_scope);
-    }
-    block_scope->node->data.block.statements.append(switch_scope->switch_node);
-
-    // TODO avoid name collisions
-    Buf *end_label_name = buf_create_from_str("end");
-    switch_scope->end_label_name = end_label_name;
-
-    const Expr *cond_expr = stmt->getCond();
-    assert(cond_expr != nullptr);
-
-    AstNode *expr_node = trans_expr(c, ResultUsedYes, &block_scope->base, cond_expr, TransRValue);
-    if (expr_node == nullptr)
-        return nullptr;
-    switch_scope->switch_node->data.switch_expr.expr = expr_node;
-
-    AstNode *body_node;
-    const Stmt *body_stmt = stmt->getBody();
-    if (body_stmt->getStmtClass() == Stmt::CompoundStmtClass) {
-        if (trans_compound_stmt_inline(c, &switch_scope->base, (const CompoundStmt *)body_stmt,
-            block_scope->node, nullptr))
-        {
-            return nullptr;
-        }
-    } else {
-        TransScope *body_scope = trans_stmt(c, &switch_scope->base, body_stmt, &body_node);
-        if (body_scope == nullptr)
-            return nullptr;
-        if (body_node != nullptr)
-            block_scope->node->data.block.statements.append(body_node);
-    }
-
-    if (!switch_scope->found_default && !stmt->isAllEnumCasesCovered()) {
-        AstNode *prong_node = trans_create_node(c, NodeTypeSwitchProng);
-        prong_node->data.switch_prong.expr = trans_create_node_goto(c, end_label_name);
-        switch_scope->switch_node->data.switch_expr.prongs.append(prong_node);
-    }
-
-    // This is necessary if the last switch case "falls through" the end of the switch block
-    block_scope->node->data.block.statements.append(trans_create_node_goto(c, end_label_name));
-
-    block_scope->node->data.block.statements.append(trans_create_node_label(c, end_label_name));
-
-    return block_scope->node;
-}
-
-static int trans_switch_case(Context *c, TransScope *parent_scope, const CaseStmt *stmt, AstNode **out_node,
-        TransScope **out_scope)
-{
-    *out_node = nullptr;
-
-    if (stmt->getRHS() != nullptr) {
-        emit_warning(c, stmt->getLocStart(), "TODO support GNU switch case a ... b extension");
-        return ErrorUnexpected;
-    }
-
-    TransScopeSwitch *switch_scope = trans_scope_switch_find(parent_scope);
-    assert(switch_scope != nullptr);
-
-    Buf *label_name = buf_sprintf("case_%" PRIu32, switch_scope->case_index);
-    switch_scope->case_index += 1;
-
-    {
-        // Add the prong
-        AstNode *prong_node = trans_create_node(c, NodeTypeSwitchProng);
-        AstNode *item_node = trans_expr(c, ResultUsedYes, &switch_scope->base, stmt->getLHS(), TransRValue);
-        if (item_node == nullptr)
-            return ErrorUnexpected;
-        prong_node->data.switch_prong.items.append(item_node);
-
-        prong_node->data.switch_prong.expr = trans_create_node_goto(c, label_name);
-
-        switch_scope->switch_node->data.switch_expr.prongs.append(prong_node);
-    }
-
-    TransScopeBlock *scope_block = trans_scope_block_find(parent_scope);
-    scope_block->node->data.block.statements.append(trans_create_node_label(c, label_name));
-
-    AstNode *sub_stmt_node;
-    TransScope *new_scope = trans_stmt(c, parent_scope, stmt->getSubStmt(), &sub_stmt_node);
-    if (new_scope == nullptr)
-        return ErrorUnexpected;
-    if (sub_stmt_node != nullptr)
-        scope_block->node->data.block.statements.append(sub_stmt_node);
-
-    *out_scope = new_scope;
-    return ErrorNone;
-}
-
-static int trans_switch_default(Context *c, TransScope *parent_scope, const DefaultStmt *stmt, AstNode **out_node,
-        TransScope **out_scope)
-{
-    *out_node = nullptr;
-
-    TransScopeSwitch *switch_scope = trans_scope_switch_find(parent_scope);
-    assert(switch_scope != nullptr);
-
-    Buf *label_name = buf_sprintf("default");
-
-    {
-        // Add the prong
-        AstNode *prong_node = trans_create_node(c, NodeTypeSwitchProng);
-
-        prong_node->data.switch_prong.expr = trans_create_node_goto(c, label_name);
-
-        switch_scope->switch_node->data.switch_expr.prongs.append(prong_node);
-        switch_scope->found_default = true;
-    }
-
-    TransScopeBlock *scope_block = trans_scope_block_find(parent_scope);
-    scope_block->node->data.block.statements.append(trans_create_node_label(c, label_name));
-
-
-    AstNode *sub_stmt_node;
-    TransScope *new_scope = trans_stmt(c, parent_scope, stmt->getSubStmt(), &sub_stmt_node);
-    if (new_scope == nullptr)
-        return ErrorUnexpected;
-    if (sub_stmt_node != nullptr)
-        scope_block->node->data.block.statements.append(sub_stmt_node);
-
-    *out_scope = new_scope;
-    return ErrorNone;
-}
-
 static AstNode *trans_for_loop(Context *c, TransScope *parent_scope, const ForStmt *stmt) {
     AstNode *loop_block_node;
     TransScopeWhile *while_scope;
@@ -2595,8 +2442,7 @@ static AstNode *trans_break_stmt(Context *c, TransScope *scope, const BreakStmt
         if (cur_scope->id == TransScopeIdWhile) {
             return trans_create_node(c, NodeTypeBreak);
         } else if (cur_scope->id == TransScopeIdSwitch) {
-            TransScopeSwitch *switch_scope = (TransScopeSwitch *)cur_scope;
-            return trans_create_node_goto(c, switch_scope->end_label_name);
+            zig_panic("TODO");
         }
         cur_scope = cur_scope->parent;
     }
@@ -2696,12 +2542,14 @@ static int trans_stmt_extra(Context *c, TransScope *scope, const Stmt *stmt,
             return wrap_stmt(out_node, out_child_scope, scope,
                     trans_expr(c, result_used, scope, ((const ParenExpr*)stmt)->getSubExpr(), lrvalue));
         case Stmt::SwitchStmtClass:
-            return wrap_stmt(out_node, out_child_scope, scope,
-                    trans_switch_stmt(c, scope, (const SwitchStmt *)stmt));
+            emit_warning(c, stmt->getLocStart(), "TODO handle C SwitchStmtClass");
+            return ErrorUnexpected;
         case Stmt::CaseStmtClass:
-            return trans_switch_case(c, scope, (const CaseStmt *)stmt, out_node, out_child_scope);
+            emit_warning(c, stmt->getLocStart(), "TODO handle C CaseStmtClass");
+            return ErrorUnexpected;
         case Stmt::DefaultStmtClass:
-            return trans_switch_default(c, scope, (const DefaultStmt *)stmt, out_node, out_child_scope);
+            emit_warning(c, stmt->getLocStart(), "TODO handle C DefaultStmtClass");
+            return ErrorUnexpected;
         case Stmt::NoStmtClass:
             emit_warning(c, stmt->getLocStart(), "TODO handle C NoStmtClass");
             return ErrorUnexpected;
@@ -3871,14 +3719,6 @@ static TransScopeVar *trans_scope_var_create(Context *c, TransScope *parent_scop
     return result;
 }
 
-static TransScopeSwitch *trans_scope_switch_create(Context *c, TransScope *parent_scope) {
-    TransScopeSwitch *result = allocate<TransScopeSwitch>(1);
-    result->base.id = TransScopeIdSwitch;
-    result->base.parent = parent_scope;
-    result->switch_node = trans_create_node(c, NodeTypeSwitchExpr);
-    return result;
-}
-
 static TransScopeBlock *trans_scope_block_find(TransScope *scope) {
     while (scope != nullptr) {
         if (scope->id == TransScopeIdBlock) {
@@ -3889,16 +3729,6 @@ static TransScopeBlock *trans_scope_block_find(TransScope *scope) {
     return nullptr;
 }
 
-static TransScopeSwitch *trans_scope_switch_find(TransScope *scope) {
-    while (scope != nullptr) {
-        if (scope->id == TransScopeIdSwitch) {
-            return (TransScopeSwitch *)scope;
-        }
-        scope = scope->parent;
-    }
-    return nullptr;
-}
-
 static void render_aliases(Context *c) {
     for (size_t i = 0; i < c->aliases.length; i += 1) {
         Alias *alias = &c->aliases.at(i);
std/os/index.zig
@@ -902,40 +902,41 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) -> %void {
 /// this function recursively removes its entries and then tries again.
 // TODO non-recursive implementation
 pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void {
-start_over:
-    // First, try deleting the item as a file. This way we don't follow sym links.
-    if (deleteFile(allocator, full_path)) {
-        return;
-    } else |err| {
-        if (err == error.FileNotFound)
+    start_over: while (true) {
+        // First, try deleting the item as a file. This way we don't follow sym links.
+        if (deleteFile(allocator, full_path)) {
             return;
-        if (err != error.IsDir)
-            return err;
-    }
-    {
-        var dir = Dir.open(allocator, full_path) %% |err| {
+        } else |err| {
             if (err == error.FileNotFound)
                 return;
-            if (err == error.NotDir)
-                goto start_over;
-            return err;
-        };
-        defer dir.close();
+            if (err != error.IsDir)
+                return err;
+        }
+        {
+            var dir = Dir.open(allocator, full_path) %% |err| {
+                if (err == error.FileNotFound)
+                    return;
+                if (err == error.NotDir)
+                    continue :start_over;
+                return err;
+            };
+            defer dir.close();
 
-        var full_entry_buf = ArrayList(u8).init(allocator);
-        defer full_entry_buf.deinit();
+            var full_entry_buf = ArrayList(u8).init(allocator);
+            defer full_entry_buf.deinit();
 
-        while (%return dir.next()) |entry| {
-            %return full_entry_buf.resize(full_path.len + entry.name.len + 1);
-            const full_entry_path = full_entry_buf.toSlice();
-            mem.copy(u8, full_entry_path, full_path);
-            full_entry_path[full_path.len] = '/';
-            mem.copy(u8, full_entry_path[full_path.len + 1..], entry.name);
+            while (%return dir.next()) |entry| {
+                %return full_entry_buf.resize(full_path.len + entry.name.len + 1);
+                const full_entry_path = full_entry_buf.toSlice();
+                mem.copy(u8, full_entry_path, full_path);
+                full_entry_path[full_path.len] = '/';
+                mem.copy(u8, full_entry_path[full_path.len + 1..], entry.name);
 
-            %return deleteTree(allocator, full_entry_path);
+                %return deleteTree(allocator, full_entry_path);
+            }
         }
+        return deleteDir(allocator, full_path);
     }
-    return deleteDir(allocator, full_path);
 }
 
 pub const Dir = struct {
@@ -988,58 +989,59 @@ pub const Dir = struct {
     /// Memory such as file names referenced in this returned entry becomes invalid
     /// with subsequent calls to next, as well as when this ::Dir is deinitialized.
     pub fn next(self: &Dir) -> %?Entry {
-    start_over:
-        if (self.index >= self.end_index) {
-            if (self.buf.len == 0) {
-                self.buf = %return self.allocator.alloc(u8, page_size);
-            }
+        start_over: while (true) {
+            if (self.index >= self.end_index) {
+                if (self.buf.len == 0) {
+                    self.buf = %return self.allocator.alloc(u8, page_size);
+                }
 
-            while (true) {
-                const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
-                const err = linux.getErrno(result);
-                if (err > 0) {
-                    switch (err) {
-                        posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
-                        posix.EINVAL => {
-                            self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2);
-                            continue;
-                        },
-                        else => return unexpectedErrorPosix(err),
-                    };
+                while (true) {
+                    const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
+                    const err = linux.getErrno(result);
+                    if (err > 0) {
+                        switch (err) {
+                            posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
+                            posix.EINVAL => {
+                                self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2);
+                                continue;
+                            },
+                            else => return unexpectedErrorPosix(err),
+                        };
+                    }
+                    if (result == 0)
+                        return null;
+                    self.index = 0;
+                    self.end_index = result;
+                    break;
                 }
-                if (result == 0)
-                    return null;
-                self.index = 0;
-                self.end_index = result;
-                break;
             }
-        }
-        const linux_entry = @ptrCast(& align(1) LinuxEntry, &self.buf[self.index]);
-        const next_index = self.index + linux_entry.d_reclen;
-        self.index = next_index;
+            const linux_entry = @ptrCast(& align(1) LinuxEntry, &self.buf[self.index]);
+            const next_index = self.index + linux_entry.d_reclen;
+            self.index = next_index;
 
-        const name = cstr.toSlice(&linux_entry.d_name);
+            const name = cstr.toSlice(&linux_entry.d_name);
 
-        // skip . and .. entries
-        if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
-            goto start_over;
-        }
+            // skip . and .. entries
+            if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
+                continue :start_over;
+            }
 
-        const type_char = self.buf[next_index - 1];
-        const entry_kind = switch (type_char) {
-            posix.DT_BLK => Entry.Kind.BlockDevice,
-            posix.DT_CHR => Entry.Kind.CharacterDevice,
-            posix.DT_DIR => Entry.Kind.Directory,
-            posix.DT_FIFO => Entry.Kind.NamedPipe,
-            posix.DT_LNK => Entry.Kind.SymLink,
-            posix.DT_REG => Entry.Kind.File,
-            posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
-            else => Entry.Kind.Unknown,
-        };
-        return Entry {
-            .name = name,
-            .kind = entry_kind,
-        };
+            const type_char = self.buf[next_index - 1];
+            const entry_kind = switch (type_char) {
+                posix.DT_BLK => Entry.Kind.BlockDevice,
+                posix.DT_CHR => Entry.Kind.CharacterDevice,
+                posix.DT_DIR => Entry.Kind.Directory,
+                posix.DT_FIFO => Entry.Kind.NamedPipe,
+                posix.DT_LNK => Entry.Kind.SymLink,
+                posix.DT_REG => Entry.Kind.File,
+                posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
+                else => Entry.Kind.Unknown,
+            };
+            return Entry {
+                .name = name,
+                .kind = entry_kind,
+            };
+        }
     }
 };
 
std/elf.zig
@@ -243,7 +243,7 @@ pub const Elf = struct {
         var file_stream = io.FileInStream.init(elf.in_file);
         const in = &file_stream.stream;
 
-        for (elf.section_headers) |*elf_section| {
+        section_loop: for (elf.section_headers) |*elf_section| {
             if (elf_section.sh_type == SHT_NULL) continue;
 
             const name_offset = elf.string_section.offset + elf_section.name;
@@ -251,15 +251,13 @@ pub const Elf = struct {
 
             for (name) |expected_c| {
                 const target_c = %return in.readByte();
-                if (target_c == 0 or expected_c != target_c) goto next_section;
+                if (target_c == 0 or expected_c != target_c) continue :section_loop;
             }
 
             {
                 const null_byte = %return in.readByte();
                 if (null_byte == 0) return elf_section;
             }
-
-            next_section:
         }
 
         return null;
test/cases/for.zig
@@ -55,3 +55,37 @@ test "basic for loop" {
 
     assert(mem.eql(u8, buffer[0..buf_index], expected_result));
 }
+
+test "break from outer for loop" {
+    testBreakOuter();
+    comptime testBreakOuter();
+}
+
+fn testBreakOuter() {
+    var array = "aoeu";
+    var count: usize = 0;
+    outer: for (array) |_| {
+        for (array) |_2| { // TODO shouldn't get error for redeclaring "_"
+            count += 1;
+            break :outer;
+        }
+    }
+    assert(count == 1);
+}
+
+test "continue outer for loop" {
+    testContinueOuter();
+    comptime testContinueOuter();
+}
+
+fn testContinueOuter() {
+    var array = "aoeu";
+    var counter: usize = 0;
+    outer: for (array) |_| {
+        for (array) |_2| { // TODO shouldn't get error for redeclaring "_"
+            counter += 1;
+            continue :outer;
+        }
+    }
+    assert(counter == array.len);
+}
test/cases/goto.zig
@@ -1,37 +0,0 @@
-const assert = @import("std").debug.assert;
-
-test "goto and labels" {
-    gotoLoop();
-    assert(goto_counter == 10);
-}
-fn gotoLoop() {
-    var i: i32 = 0;
-    goto cond;
-loop:
-    i += 1;
-cond:
-    if (!(i < 10)) goto end;
-    goto_counter += 1;
-    goto loop;
-end:
-}
-var goto_counter: i32 = 0;
-
-
-
-test "goto leave defer scope" {
-    testGotoLeaveDeferScope(true);
-}
-fn testGotoLeaveDeferScope(b: bool) {
-    var it_worked = false;
-
-    goto entry;
-exit:
-    if (it_worked) {
-        return;
-    }
-    unreachable;
-entry:
-    defer it_worked = true;
-    if (b) goto exit;
-}
test/cases/while.zig
@@ -188,6 +188,33 @@ test "while on bool with else result follow break prong" {
     assert(result == 10);
 }
 
+test "break from outer while loop" {
+    testBreakOuter();
+    comptime testBreakOuter();
+}
+
+fn testBreakOuter() {
+    outer: while (true) {
+        while (true) {
+            break :outer;
+        }
+    }
+}
+
+test "continue outer while loop" {
+    testContinueOuter();
+    comptime testContinueOuter();
+}
+
+fn testContinueOuter() {
+    var i: usize = 0;
+    outer: while (i < 10) : (i += 1) {
+        while (true) {
+            continue :outer;
+        }
+    }
+}
+
 fn returnNull() -> ?i32 { null }
 fn returnMaybe(x: i32) -> ?i32 { x }
 error YouWantedAnError;
test/behavior.zig
@@ -20,7 +20,6 @@ comptime {
     _ = @import("cases/fn.zig");
     _ = @import("cases/for.zig");
     _ = @import("cases/generics.zig");
-    _ = @import("cases/goto.zig");
     _ = @import("cases/if.zig");
     _ = @import("cases/import.zig");
     _ = @import("cases/incomplete_struct_param_tld.zig");
test/compile_errors.zig
@@ -1,6 +1,27 @@
 const tests = @import("tests.zig");
 
 pub fn addCases(cases: &tests.CompileErrorContext) {
+    cases.add("labeled break not found",
+        \\export fn entry() {
+        \\    blah: while (true) {
+        \\        while (true) {
+        \\            break :outer;
+        \\        }
+        \\    }
+        \\}
+    , ".tmp_source.zig:4:13: error: labeled loop not found: 'outer'");
+
+    cases.add("labeled continue not found",
+        \\export fn entry() {
+        \\    var i: usize = 0;
+        \\    blah: while (i < 10) : (i += 1) {
+        \\        while (true) {
+        \\            continue :outer;
+        \\        }
+        \\    }
+        \\}
+    , ".tmp_source.zig:5:13: error: labeled loop not found: 'outer'");
+
     cases.add("attempt to use 0 bit type in extern fn",
         \\extern fn foo(ptr: extern fn(&void));
         \\
@@ -833,26 +854,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
         \\export fn entry() -> usize { @sizeOf(@typeOf(test1)) }
     , ".tmp_source.zig:3:16: error: unable to evaluate constant expression");
 
-    cases.add("goto jumping into block",
-        \\export fn f() {
-        \\    {
-        \\a_label:
-        \\    }
-        \\    goto a_label;
-        \\}
-    , ".tmp_source.zig:5:5: error: no label in scope named 'a_label'");
-
-    cases.add("goto jumping past a defer",
-        \\fn f(b: bool) {
-        \\    if (b) goto label;
-        \\    defer derp();
-        \\label:
-        \\}
-        \\fn derp(){}
-        \\
-        \\export fn entry() -> usize { @sizeOf(@typeOf(f)) }
-    , ".tmp_source.zig:2:12: error: no label in scope named 'label'");
-
     cases.add("assign null to non-nullable pointer",
         \\const a: &u8 = null;
         \\
@@ -1854,16 +1855,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
     ,
         ".tmp_source.zig:4:13: error: cannot continue out of defer expression");
 
-    cases.add("cannot goto out of defer expression",
-        \\export fn foo() {
-        \\    defer {
-        \\        goto label;
-        \\    };
-        \\label:
-        \\}
-    ,
-        ".tmp_source.zig:3:9: error: cannot goto out of defer expression");
-
     cases.add("calling a var args function only known at runtime",
         \\var foos = []fn(...) { foo1, foo2 };
         \\
test/translate_c.zig
@@ -1005,48 +1005,6 @@ pub fn addCases(cases: &tests.TranslateCContext) {
         \\}
     );
 
-    cases.add("switch statement",
-        \\int foo(int x) {
-        \\    switch (x) {
-        \\        case 1:
-        \\            x += 1;
-        \\        case 2:
-        \\            break;
-        \\        case 3:
-        \\        case 4:
-        \\            return x + 1;
-        \\        default:
-        \\            return 10;
-        \\    }
-        \\    return x + 13;
-        \\}
-    ,
-        \\fn foo(_arg_x: c_int) -> c_int {
-        \\    var x = _arg_x;
-        \\    {
-        \\        switch (x) {
-        \\            1 => goto case_0,
-        \\            2 => goto case_1,
-        \\            3 => goto case_2,
-        \\            4 => goto case_3,
-        \\            else => goto default,
-        \\        };
-        \\    case_0:
-        \\        x += 1;
-        \\    case_1:
-        \\        goto end;
-        \\    case_2:
-        \\    case_3:
-        \\        return x + 1;
-        \\    default:
-        \\        return 10;
-        \\        goto end;
-        \\    end:
-        \\    };
-        \\    return x + 13;
-        \\}
-    );
-    
     cases.add("macros with field targets",
         \\typedef unsigned int GLbitfield;
         \\typedef void (*PFNGLCLEARPROC) (GLbitfield mask);
@@ -1085,44 +1043,6 @@ pub fn addCases(cases: &tests.TranslateCContext) {
         \\pub const OpenGLProcs = union_OpenGLProcs;
     );
 
-    cases.add("switch statement with no default",
-        \\int foo(int x) {
-        \\    switch (x) {
-        \\        case 1:
-        \\            x += 1;
-        \\        case 2:
-        \\            break;
-        \\        case 3:
-        \\        case 4:
-        \\            return x + 1;
-        \\    }
-        \\    return x + 13;
-        \\}
-    ,
-        \\fn foo(_arg_x: c_int) -> c_int {
-        \\    var x = _arg_x;
-        \\    {
-        \\        switch (x) {
-        \\            1 => goto case_0,
-        \\            2 => goto case_1,
-        \\            3 => goto case_2,
-        \\            4 => goto case_3,
-        \\            else => goto end,
-        \\        };
-        \\    case_0:
-        \\        x += 1;
-        \\    case_1:
-        \\        goto end;
-        \\    case_2:
-        \\    case_3:
-        \\        return x + 1;
-        \\        goto end;
-        \\    end:
-        \\    };
-        \\    return x + 13;
-        \\}
-    );
-
     cases.add("variable name shadowing",
         \\int foo(void) {
         \\    int x = 1;