Commit c0b37e8514

Andrew Kelley <superjoe30@gmail.com>
2017-02-02 23:09:27
add try expression
See #83
1 parent 8b1c6d8
doc/vim/syntax/zig.vim
@@ -11,7 +11,7 @@ let b:current_syntax = "zig"
 syn keyword zigStorage const var extern export pub noalias inline comptime nakedcc coldcc
 syn keyword zigStructure struct enum union
 syn keyword zigStatement goto break return continue asm defer
-syn keyword zigConditional if else switch
+syn keyword zigConditional if else switch try
 syn keyword zigRepeat while for
 
 syn keyword zigConstant null undefined zeroes this
doc/langref.md
@@ -69,7 +69,7 @@ AssignmentExpression = UnwrapExpression AssignmentOperator UnwrapExpression | Un
 
 AssignmentOperator = "=" | "*=" | "/=" | "%=" | "+=" | "-=" | "<<=" | ">>=" | "&=" | "^=" | "|=" | "&&=" | "||=" | "*%=" | "+%=" | "-%=" | "<<%="
 
-BlockExpression = IfExpression | Block | WhileExpression | ForExpression | SwitchExpression | CompTimeExpression
+BlockExpression = IfExpression | Block | WhileExpression | ForExpression | SwitchExpression | CompTimeExpression | TryExpression
 
 CompTimeExpression = option("comptime") Expression
 
@@ -93,6 +93,8 @@ IfExpression = IfVarExpression | IfBoolExpression
 
 IfBoolExpression = "if" "(" Expression ")" Expression option(Else)
 
+TryExpression = "try" "(" ("const" | "var") option("*") Symbol "=" Expression  ")" Expression option("else" option("|" Symbol "|") Expression)
+
 IfVarExpression = "if" "(" ("const" | "var") option("*") Symbol option(":" TypeExpr) "?=" Expression ")" Expression Option(Else)
 
 Else = "else" Expression
src/all_types.hpp
@@ -299,6 +299,7 @@ enum NodeType {
     NodeTypeErrorType,
     NodeTypeTypeLiteral,
     NodeTypeVarLiteral,
+    NodeTypeTryExpr,
 };
 
 struct AstNodeRoot {
@@ -511,6 +512,16 @@ struct AstNodeIfBoolExpr {
     AstNode *else_node; // null, block node, or other if expr node
 };
 
+struct AstNodeTryExpr {
+    bool var_is_const;
+    Buf *var_symbol;
+    bool var_is_ptr;
+    AstNode *target_node;
+    AstNode *then_node;
+    AstNode *else_node;
+    Buf *err_symbol;
+};
+
 struct AstNodeIfVarExpr {
     AstNodeVariableDeclaration var_decl;
     AstNode *then_block;
@@ -721,6 +732,7 @@ struct AstNode {
         AstNodeUse use;
         AstNodeIfBoolExpr if_bool_expr;
         AstNodeIfVarExpr if_var_expr;
+        AstNodeTryExpr try_expr;
         AstNodeWhileExpr while_expr;
         AstNodeForExpr for_expr;
         AstNodeSwitchExpr switch_expr;
src/analyze.cpp
@@ -1769,6 +1769,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) {
         case NodeTypeErrorType:
         case NodeTypeTypeLiteral:
         case NodeTypeVarLiteral:
+        case NodeTypeTryExpr:
             zig_unreachable();
     }
 }
src/ast_render.cpp
@@ -223,6 +223,8 @@ static const char *node_type_str(NodeType node_type) {
             return "TypeLiteral";
         case NodeTypeVarLiteral:
             return "VarLiteral";
+        case NodeTypeTryExpr:
+            return "TryExpr";
     }
     zig_unreachable();
 }
@@ -769,6 +771,25 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                 }
                 break;
             }
+        case NodeTypeTryExpr:
+            {
+                const char *var_str = node->data.try_expr.var_is_const ? "const" : "var";
+                const char *var_name = buf_ptr(node->data.try_expr.var_symbol);
+                const char *ptr_str = node->data.try_expr.var_is_ptr ? "*" : "";
+                fprintf(ar->f, "try (%s %s%s", var_str, ptr_str, var_name);
+                fprintf(ar->f, " = ");
+                render_node_grouped(ar, node->data.try_expr.target_node);
+                fprintf(ar->f, ") ");
+                render_node_grouped(ar, node->data.try_expr.then_node);
+                if (node->data.try_expr.else_node) {
+                    fprintf(ar->f, " else ");
+                    if (node->data.try_expr.err_symbol) {
+                        fprintf(ar->f, "|%s| ", buf_ptr(node->data.try_expr.err_symbol));
+                    }
+                    render_node_grouped(ar, node->data.try_expr.else_node);
+                }
+                break;
+            }
         case NodeTypeSwitchExpr:
             {
                 AstNodeSwitchExpr *switch_expr = &node->data.switch_expr;
src/ir.cpp
@@ -4748,6 +4748,98 @@ static IrInstruction *ir_gen_if_var_expr(IrBuilder *irb, Scope *scope, AstNode *
     return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
 }
 
+static IrInstruction *ir_gen_try_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
+    assert(node->type == NodeTypeTryExpr);
+
+    AstNode *target_node = node->data.try_expr.target_node;
+    AstNode *then_node = node->data.try_expr.then_node;
+    AstNode *else_node = node->data.try_expr.else_node;
+    bool var_is_ptr = node->data.try_expr.var_is_ptr;
+    bool var_is_const = node->data.try_expr.var_is_const;
+    Buf *var_symbol = node->data.try_expr.var_symbol;
+    Buf *err_symbol = node->data.try_expr.err_symbol;
+
+    IrInstruction *err_val_ptr = ir_gen_node_extra(irb, target_node, scope, LValPurposeAddressOf);
+    if (err_val_ptr == irb->codegen->invalid_instruction)
+        return err_val_ptr;
+
+    IrInstruction *err_val = ir_build_load_ptr(irb, scope, node, err_val_ptr);
+    IrInstruction *is_err = ir_build_test_err(irb, scope, node, err_val);
+
+    IrBasicBlock *ok_block = ir_build_basic_block(irb, scope, "TryOk");
+    IrBasicBlock *else_block = ir_build_basic_block(irb, scope, "TryElse");
+    IrBasicBlock *endif_block = ir_build_basic_block(irb, scope, "TryEnd");
+
+    IrInstruction *is_comptime;
+    if (ir_should_inline(irb->exec, scope)) {
+        is_comptime = ir_build_const_bool(irb, scope, node, true);
+    } else {
+        is_comptime = ir_build_test_comptime(irb, scope, node, is_err);
+    }
+    ir_build_cond_br(irb, scope, node, is_err, else_block, ok_block, is_comptime);
+
+    ir_set_cursor_at_end(irb, ok_block);
+
+    Scope *var_scope;
+    if (var_symbol) {
+        IrInstruction *var_type = nullptr;
+        bool is_shadowable = false;
+        VariableTableEntry *var = ir_create_var(irb, node, scope,
+                var_symbol, var_is_const, var_is_const, is_shadowable, is_comptime);
+
+        IrInstruction *var_ptr_value = ir_build_unwrap_err_payload(irb, scope, node, err_val_ptr, false);
+        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, scope, node, var_ptr_value);
+        ir_build_var_decl(irb, scope, node, var, var_type, var_value);
+        var_scope = var->child_scope;
+    } else {
+        var_scope = scope;
+    }
+    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, var_scope);
+    if (then_expr_result == irb->codegen->invalid_instruction)
+        return then_expr_result;
+    IrBasicBlock *after_then_block = irb->current_basic_block;
+    if (!instr_is_unreachable(then_expr_result))
+        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));
+
+    ir_set_cursor_at_end(irb, else_block);
+
+    IrInstruction *else_expr_result;
+    if (else_node) {
+        Scope *err_var_scope;
+        if (err_symbol) {
+            IrInstruction *var_type = nullptr;
+            bool is_shadowable = false;
+            bool is_const = true;
+            VariableTableEntry *var = ir_create_var(irb, node, scope,
+                    err_symbol, is_const, is_const, is_shadowable, is_comptime);
+
+            IrInstruction *var_value = ir_build_unwrap_err_code(irb, scope, node, err_val_ptr);
+            ir_build_var_decl(irb, scope, node, var, var_type, var_value);
+            err_var_scope = var->child_scope;
+        } else {
+            err_var_scope = scope;
+        }
+        else_expr_result = ir_gen_node(irb, else_node, err_var_scope);
+        if (else_expr_result == irb->codegen->invalid_instruction)
+            return else_expr_result;
+    } else {
+        else_expr_result = ir_build_const_void(irb, scope, node);
+    }
+    IrBasicBlock *after_else_block = irb->current_basic_block;
+    if (!instr_is_unreachable(else_expr_result))
+        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));
+
+    ir_set_cursor_at_end(irb, endif_block);
+    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
+    incoming_values[0] = then_expr_result;
+    incoming_values[1] = else_expr_result;
+    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
+    incoming_blocks[0] = after_then_block;
+    incoming_blocks[1] = after_else_block;
+
+    return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
+}
+
 static bool ir_gen_switch_prong_expr(IrBuilder *irb, Scope *scope, AstNode *switch_node, AstNode *prong_node,
         IrBasicBlock *end_block, IrInstruction *is_comptime, IrInstruction *target_value_ptr, IrInstruction *prong_value,
         ZigList<IrBasicBlock *> *incoming_blocks, ZigList<IrInstruction *> *incoming_values)
@@ -5291,6 +5383,8 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop
             return ir_lval_wrap(irb, scope, ir_gen_var_literal(irb, scope, node), lval);
         case NodeTypeIfVarExpr:
             return ir_lval_wrap(irb, scope, ir_gen_if_var_expr(irb, scope, node), lval);
+        case NodeTypeTryExpr:
+            return ir_lval_wrap(irb, scope, ir_gen_try_expr(irb, scope, node), lval);
         case NodeTypeSwitchExpr:
             return ir_lval_wrap(irb, scope, ir_gen_switch_expr(irb, scope, node), lval);
         case NodeTypeGoto:
src/parser.cpp
@@ -623,6 +623,71 @@ static AstNode *ast_parse_comptime_expr(ParseContext *pc, size_t *token_index, b
     return node;
 }
 
+/*
+TryExpression = "try" "(" ("const" | "var") option("*") Symbol "=" Expression  ")" Expression option("else" option("|" Symbol "|") Expression)
+*/
+static AstNode *ast_parse_try_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
+    Token *try_token = &pc->tokens->at(*token_index);
+    if (try_token->id == TokenIdKeywordTry) {
+        *token_index += 1;
+    } else if (mandatory) {
+        ast_expect_token(pc, try_token, TokenIdKeywordTry);
+        zig_unreachable();
+    } else {
+        return nullptr;
+    }
+
+    AstNode *node = ast_create_node(pc, NodeTypeTryExpr, try_token);
+
+    ast_eat_token(pc, token_index, TokenIdLParen);
+
+    Token *var_token = &pc->tokens->at(*token_index);
+    if (var_token->id == TokenIdKeywordVar) {
+        node->data.try_expr.var_is_const = false;
+        *token_index += 1;
+    } else if (var_token->id == TokenIdKeywordConst) {
+        node->data.try_expr.var_is_const = true;
+        *token_index += 1;
+    } else {
+        ast_invalid_token_error(pc, var_token);
+    }
+
+    Token *star_token = &pc->tokens->at(*token_index);
+    if (star_token->id == TokenIdStar) {
+        node->data.try_expr.var_is_ptr = true;
+        *token_index += 1;
+    }
+
+    Token *var_name_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
+    node->data.try_expr.var_symbol = token_buf(var_name_tok);
+
+    ast_eat_token(pc, token_index, TokenIdEq);
+
+    node->data.try_expr.target_node = ast_parse_expression(pc, token_index, true);
+
+    ast_eat_token(pc, token_index, TokenIdRParen);
+
+    node->data.try_expr.then_node = ast_parse_expression(pc, token_index, true);
+
+    Token *else_token = &pc->tokens->at(*token_index);
+    if (else_token->id != TokenIdKeywordElse)
+        return node;
+
+    *token_index += 1;
+    Token *open_bar_tok = &pc->tokens->at(*token_index);
+    if (open_bar_tok->id == TokenIdBinOr) {
+        *token_index += 1;
+
+        Token *err_name_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
+        node->data.try_expr.err_symbol = token_buf(err_name_tok);
+
+        ast_eat_token(pc, token_index, TokenIdBinOr);
+    }
+
+    node->data.try_expr.else_node = ast_parse_expression(pc, token_index, true);
+    return node;
+}
+
 /*
 PrimaryExpression = Number | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | Symbol | ("@" Symbol FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." Symbol) | ContainerDecl
 KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "error" | "type" | "this"
@@ -1775,7 +1840,7 @@ static AstNode *ast_parse_switch_expr(ParseContext *pc, size_t *token_index, boo
 }
 
 /*
-BlockExpression = IfExpression | Block | WhileExpression | ForExpression | SwitchExpression | CompTimeExpression
+BlockExpression = IfExpression | Block | WhileExpression | ForExpression | SwitchExpression | CompTimeExpression | TryExpression
 */
 static AstNode *ast_parse_block_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -1804,6 +1869,10 @@ static AstNode *ast_parse_block_expr(ParseContext *pc, size_t *token_index, bool
     if (comptime_node)
         return comptime_node;
 
+    AstNode *try_node = ast_parse_try_expr(pc, token_index, false);
+    if (try_node)
+        return try_node;
+
     if (mandatory)
         ast_invalid_token_error(pc, token);
 
@@ -2555,6 +2624,11 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
             visit_field(&node->data.if_var_expr.then_block, visit, context);
             visit_field(&node->data.if_var_expr.else_node, visit, context);
             break;
+        case NodeTypeTryExpr:
+            visit_field(&node->data.try_expr.target_node, visit, context);
+            visit_field(&node->data.try_expr.then_node, visit, context);
+            visit_field(&node->data.try_expr.else_node, visit, context);
+            break;
         case NodeTypeWhileExpr:
             visit_field(&node->data.while_expr.condition, visit, context);
             visit_field(&node->data.while_expr.body, visit, context);
src/tokenizer.cpp
@@ -134,6 +134,7 @@ static const struct ZigKeyword zig_keywords[] = {
     {"switch", TokenIdKeywordSwitch},
     {"this", TokenIdKeywordThis},
     {"true", TokenIdKeywordTrue},
+    {"try", TokenIdKeywordTry},
     {"type", TokenIdKeywordType},
     {"undefined", TokenIdKeywordUndefined},
     {"union", TokenIdKeywordUnion},
@@ -1444,108 +1445,109 @@ void tokenize(Buf *buf, Tokenization *out) {
 
 const char * token_name(TokenId id) {
     switch (id) {
-        case TokenIdEof: return "EOF";
-        case TokenIdSymbol: return "Symbol";
-        case TokenIdKeywordFn: return "fn";
-        case TokenIdKeywordConst: return "const";
-        case TokenIdKeywordVar: return "var";
-        case TokenIdKeywordReturn: return "return";
-        case TokenIdKeywordExtern: return "extern";
-        case TokenIdKeywordPub: return "pub";
-        case TokenIdKeywordExport: return "export";
-        case TokenIdKeywordUse: return "use";
-        case TokenIdKeywordTrue: return "true";
-        case TokenIdKeywordFalse: return "false";
-        case TokenIdKeywordIf: return "if";
-        case TokenIdKeywordElse: return "else";
-        case TokenIdKeywordGoto: return "goto";
-        case TokenIdKeywordVolatile: return "volatile";
-        case TokenIdKeywordAsm: return "asm";
-        case TokenIdKeywordStruct: return "struct";
-        case TokenIdKeywordEnum: return "enum";
-        case TokenIdKeywordUnion: return "union";
-        case TokenIdKeywordWhile: return "while";
-        case TokenIdKeywordFor: return "for";
-        case TokenIdKeywordContinue: return "continue";
-        case TokenIdKeywordBreak: return "break";
-        case TokenIdKeywordNull: return "null";
-        case TokenIdKeywordNoAlias: return "noalias";
-        case TokenIdKeywordSwitch: return "switch";
-        case TokenIdKeywordUndefined: return "undefined";
-        case TokenIdKeywordThis: return "this";
-        case TokenIdKeywordError: return "error";
-        case TokenIdKeywordType: return "type";
-        case TokenIdKeywordInline: return "inline";
-        case TokenIdKeywordCompTime: return "comptime";
-        case TokenIdKeywordDefer: return "defer";
-        case TokenIdKeywordColdCC: return "coldcc";
-        case TokenIdKeywordNakedCC: return "nakedcc";
-        case TokenIdLParen: return "(";
-        case TokenIdRParen: return ")";
-        case TokenIdComma: return ",";
-        case TokenIdStar: return "*";
-        case TokenIdStarStar: return "**";
-        case TokenIdLBrace: return "{";
-        case TokenIdRBrace: return "}";
-        case TokenIdLBracket: return "[";
-        case TokenIdRBracket: return "]";
-        case TokenIdStringLiteral: return "StringLiteral";
-        case TokenIdCharLiteral: return "CharLiteral";
-        case TokenIdSemicolon: return ";";
-        case TokenIdNumberLiteral: return "NumberLiteral";
-        case TokenIdPlus: return "+";
-        case TokenIdPlusPlus: return "++";
-        case TokenIdColon: return ":";
+        case TokenIdAmpersand: return "&";
         case TokenIdArrow: return "->";
-        case TokenIdFatArrow: return "=>";
-        case TokenIdDash: return "-";
-        case TokenIdNumberSign: return "#";
+        case TokenIdAtSign: return "@";
+        case TokenIdBang: return "!";
         case TokenIdBinOr: return "|";
-        case TokenIdAmpersand: return "&";
         case TokenIdBinXor: return "^";
-        case TokenIdBoolOr: return "||";
-        case TokenIdBoolAnd: return "&&";
-        case TokenIdEq: return "=";
-        case TokenIdTimesEq: return "*=";
-        case TokenIdDivEq: return "/=";
-        case TokenIdModEq: return "%=";
-        case TokenIdPlusEq: return "+=";
-        case TokenIdMinusEq: return "-=";
+        case TokenIdBitAndEq: return "&=";
+        case TokenIdBitOrEq: return "|=";
+        case TokenIdBitShiftLeft: return "<<";
         case TokenIdBitShiftLeftEq: return "<<=";
+        case TokenIdBitShiftLeftPercent: return "<<%";
+        case TokenIdBitShiftLeftPercentEq: return "<<%=";
+        case TokenIdBitShiftRight: return ">>";
         case TokenIdBitShiftRightEq: return ">>=";
-        case TokenIdBitAndEq: return "&=";
         case TokenIdBitXorEq: return "^=";
-        case TokenIdBitOrEq: return "|=";
+        case TokenIdBoolAnd: return "&&";
         case TokenIdBoolAndEq: return "&&=";
+        case TokenIdBoolOr: return "||";
         case TokenIdBoolOrEq: return "||=";
-        case TokenIdBang: return "!";
-        case TokenIdTilde: return "~";
+        case TokenIdCharLiteral: return "CharLiteral";
         case TokenIdCmpEq: return "==";
-        case TokenIdCmpNotEq: return "!=";
-        case TokenIdCmpLessThan: return "<";
+        case TokenIdCmpGreaterOrEq: return ">=";
         case TokenIdCmpGreaterThan: return ">";
         case TokenIdCmpLessOrEq: return "<=";
-        case TokenIdCmpGreaterOrEq: return ">=";
-        case TokenIdBitShiftLeft: return "<<";
-        case TokenIdBitShiftRight: return ">>";
-        case TokenIdSlash: return "/";
-        case TokenIdPercent: return "%";
-        case TokenIdPercentPercent: return "%%";
+        case TokenIdCmpLessThan: return "<";
+        case TokenIdCmpNotEq: return "!=";
+        case TokenIdColon: return ":";
+        case TokenIdComma: return ",";
+        case TokenIdDash: return "-";
+        case TokenIdDivEq: return "/=";
         case TokenIdDot: return ".";
+        case TokenIdDoubleQuestion: return "??";
         case TokenIdEllipsis: return "...";
+        case TokenIdEof: return "EOF";
+        case TokenIdEq: return "=";
+        case TokenIdFatArrow: return "=>";
+        case TokenIdKeywordAsm: return "asm";
+        case TokenIdKeywordBreak: return "break";
+        case TokenIdKeywordColdCC: return "coldcc";
+        case TokenIdKeywordCompTime: return "comptime";
+        case TokenIdKeywordConst: return "const";
+        case TokenIdKeywordContinue: return "continue";
+        case TokenIdKeywordDefer: return "defer";
+        case TokenIdKeywordElse: return "else";
+        case TokenIdKeywordEnum: return "enum";
+        case TokenIdKeywordError: return "error";
+        case TokenIdKeywordExport: return "export";
+        case TokenIdKeywordExtern: return "extern";
+        case TokenIdKeywordFalse: return "false";
+        case TokenIdKeywordFn: return "fn";
+        case TokenIdKeywordFor: return "for";
+        case TokenIdKeywordGoto: return "goto";
+        case TokenIdKeywordIf: return "if";
+        case TokenIdKeywordInline: return "inline";
+        case TokenIdKeywordNakedCC: return "nakedcc";
+        case TokenIdKeywordNoAlias: return "noalias";
+        case TokenIdKeywordNull: return "null";
+        case TokenIdKeywordPub: return "pub";
+        case TokenIdKeywordReturn: return "return";
+        case TokenIdKeywordStruct: return "struct";
+        case TokenIdKeywordSwitch: return "switch";
+        case TokenIdKeywordThis: return "this";
+        case TokenIdKeywordTrue: return "true";
+        case TokenIdKeywordTry: return "try";
+        case TokenIdKeywordType: return "type";
+        case TokenIdKeywordUndefined: return "undefined";
+        case TokenIdKeywordUnion: return "union";
+        case TokenIdKeywordUse: return "use";
+        case TokenIdKeywordVar: return "var";
+        case TokenIdKeywordVolatile: return "volatile";
+        case TokenIdKeywordWhile: return "while";
+        case TokenIdLBrace: return "{";
+        case TokenIdLBracket: return "[";
+        case TokenIdLParen: return "(";
         case TokenIdMaybe: return "?";
-        case TokenIdDoubleQuestion: return "??";
         case TokenIdMaybeAssign: return "?=";
-        case TokenIdAtSign: return "@";
+        case TokenIdMinusEq: return "-=";
+        case TokenIdMinusPercent: return "-%";
+        case TokenIdMinusPercentEq: return "-%=";
+        case TokenIdModEq: return "%=";
+        case TokenIdNumberLiteral: return "NumberLiteral";
+        case TokenIdNumberSign: return "#";
+        case TokenIdPercent: return "%";
         case TokenIdPercentDot: return "%.";
-        case TokenIdTimesPercent: return "*%";
-        case TokenIdTimesPercentEq: return "*%=";
+        case TokenIdPercentPercent: return "%%";
+        case TokenIdPlus: return "+";
+        case TokenIdPlusEq: return "+=";
         case TokenIdPlusPercent: return "+%";
         case TokenIdPlusPercentEq: return "+%=";
-        case TokenIdMinusPercent: return "-%";
-        case TokenIdMinusPercentEq: return "-%=";
-        case TokenIdBitShiftLeftPercent: return "<<%";
-        case TokenIdBitShiftLeftPercentEq: return "<<%=";
+        case TokenIdPlusPlus: return "++";
+        case TokenIdRBrace: return "}";
+        case TokenIdRBracket: return "]";
+        case TokenIdRParen: return ")";
+        case TokenIdSemicolon: return ";";
+        case TokenIdSlash: return "/";
+        case TokenIdStar: return "*";
+        case TokenIdStarStar: return "**";
+        case TokenIdStringLiteral: return "StringLiteral";
+        case TokenIdSymbol: return "Symbol";
+        case TokenIdTilde: return "~";
+        case TokenIdTimesEq: return "*=";
+        case TokenIdTimesPercent: return "*%";
+        case TokenIdTimesPercentEq: return "*%=";
     }
     return "(invalid token)";
 }
src/tokenizer.hpp
@@ -25,6 +25,7 @@ enum TokenId {
     TokenIdKeywordTrue,
     TokenIdKeywordFalse,
     TokenIdKeywordIf,
+    TokenIdKeywordTry,
     TokenIdKeywordElse,
     TokenIdKeywordGoto,
     TokenIdKeywordAsm,
std/debug.zig
@@ -9,8 +9,8 @@ error MissingDebugInfo;
 error InvalidDebugInfo;
 error UnsupportedDebugInfo;
 
-pub fn assert(b: bool) {
-    if (!b) @unreachable()
+pub fn assert(ok: bool) {
+    if (!ok) @unreachable()
 }
 
 pub fn printStackTrace() -> %void {
test/cases/try.zig
@@ -0,0 +1,33 @@
+const assert = @import("std").debug.assert;
+
+fn tryOnErrorUnion() {
+    @setFnTest(this);
+
+    const x = try (const val = returnsTen()) {
+        val + 1
+    } else |err| switch (err) {
+        error.ItBroke, error.NoMem => 1,
+        error.CrappedOut => i32(2),
+    };
+    assert(x == 11);
+}
+
+fn tryOnErrorUnionComptime() {
+    @setFnTest(this);
+
+    comptime {
+        const x = try (const val = returnsTen()) {
+            val + 1
+        } else |err| switch (err) {
+            error.ItBroke, error.NoMem => 1,
+            error.CrappedOut => i32(2),
+        };
+        assert(x == 11);
+    }
+}
+error ItBroke;
+error NoMem;
+error CrappedOut;
+fn returnsTen() -> %i32 {
+    10
+}
test/self_hosted.zig
@@ -28,6 +28,7 @@ const test_switch = @import("cases/switch.zig");
 const test_switch_prong_err_enum = @import("cases/switch_prong_err_enum.zig");
 const test_switch_prong_implicit_cast = @import("cases/switch_prong_implicit_cast.zig");
 const test_this = @import("cases/this.zig");
+const test_try = @import("cases/try.zig");
 const test_undefined = @import("cases/undefined.zig");
 const test_var_args = @import("cases/var_args.zig");
 const test_while = @import("cases/while.zig");