Commit 6db6609df8

Andrew Kelley <superjoe30@gmail.com>
2016-01-25 21:53:40
implement %% operator
See #23
1 parent bcb1833
doc/langref.md
@@ -59,9 +59,13 @@ AsmInputItem : "[" "Symbol" "]" "String" "(" Expression ")"
 
 AsmClobbers: ":" list("String", ",")
 
-UnwrapMaybeExpression : BoolOrExpression "??" BoolOrExpression | BoolOrExpression
+UnwrapExpression : BoolOrExpression (UnwrapMaybe | UnwrapError) | BoolOrExpression
 
-AssignmentExpression : UnwrapMaybeExpression AssignmentOperator UnwrapMaybeExpression | UnwrapMaybeExpression
+UnwrapMaybe : "??" BoolOrExpression
+
+UnwrapError : "%%" option("|" "Symbol" "|") BoolOrExpression
+
+AssignmentExpression : UnwrapExpression AssignmentOperator UnwrapExpression | UnwrapExpression
 
 AssignmentOperator : "=" | "*=" | "/=" | "%=" | "+=" | "-=" | "<<=" | ">>=" | "&=" | "^=" | "|=" | "&&=" | "||="
 
@@ -161,7 +165,7 @@ x{}
 == != < > <= >=
 &&
 ||
-??
+?? %%
 = *= /= %= += -= <<= >>= &= ^= |= &&= ||=
 ```
 
example/cat/main.zig
@@ -4,9 +4,7 @@ import "std.zig";
 
 // Things to do to make this work:
 // * var args printing
-// * update std API
 // * defer
-// * %return
 // * %% binary operator
 // * %% prefix operator
 // * cast err type to string
example/guess_number/main.zig
@@ -8,7 +8,7 @@ pub fn main(args: [][]u8) %void => {
 
     var seed : u32;
     const seed_bytes = (&u8)(&seed)[0...4];
-    os_get_random_bytes(seed_bytes);
+    os_get_random_bytes(seed_bytes) %% unreachable{};
 
     var rand = rand_new(seed);
 
@@ -18,13 +18,16 @@ pub fn main(args: [][]u8) %void => {
         stderr.print_str("\nGuess a number between 1 and 100: ");
         var line_buf : [20]u8;
 
-        // TODO print error message instead of returning
-        const line_len = %return stdin.readline(line_buf);
+        const line_len = stdin.read(line_buf) %% |err| {
+            stderr.print_str("Unable to read from stdin.\n");
+            return err;
+        };
 
-        var guess : u64;
-        if (parse_u64(line_buf[0...line_len - 1], 10, &guess)) {
-            stderr.print_str("Invalid number format.\n");
-        } else if (guess > answer) {
+        const guess = parse_u64(line_buf[0...line_len - 1], 10) %% {
+            stderr.print_str("Invalid number.\n");
+            continue;
+        };
+        if (guess > answer) {
             stderr.print_str("Guess lower.\n");
         } else if (guess < answer) {
             stderr.print_str("Guess higher.\n");
src/all_types.hpp
@@ -130,6 +130,7 @@ enum NodeType {
     NodeTypeVariableDeclaration,
     NodeTypeErrorValueDecl,
     NodeTypeBinOpExpr,
+    NodeTypeUnwrapErrorExpr,
     NodeTypeNumberLiteral,
     NodeTypeStringLiteral,
     NodeTypeCharLiteral,
@@ -310,6 +311,16 @@ struct AstNodeBinOpExpr {
     Expr resolved_expr;
 };
 
+struct AstNodeUnwrapErrorExpr {
+    AstNode *op1;
+    AstNode *symbol; // can be null
+    AstNode *op2;
+
+    // populated by semantic analyzer:
+    Expr resolved_expr;
+    VariableTableEntry *var;
+};
+
 enum CastOp {
     CastOpNoCast, // signifies the function call expression is not a cast
     CastOpNoop, // fn call expr is a cast, but does nothing
@@ -684,6 +695,7 @@ struct AstNode {
         AstNodeVariableDeclaration variable_declaration;
         AstNodeErrorValueDecl error_value_decl;
         AstNodeBinOpExpr bin_op_expr;
+        AstNodeUnwrapErrorExpr unwrap_err_expr;
         AstNodeExternBlock extern_block;
         AstNodeDirective directive;
         AstNodePrefixOpExpr prefix_op_expr;
src/analyze.cpp
@@ -28,6 +28,8 @@ static AstNode *first_executing_node(AstNode *node) {
             return first_executing_node(node->data.fn_call_expr.fn_ref_expr);
         case NodeTypeBinOpExpr:
             return first_executing_node(node->data.bin_op_expr.op1);
+        case NodeTypeUnwrapErrorExpr:
+            return first_executing_node(node->data.unwrap_err_expr.op1);
         case NodeTypeArrayAccessExpr:
             return first_executing_node(node->data.array_access_expr.array_ref_expr);
         case NodeTypeSliceExpr:
@@ -1076,6 +1078,7 @@ static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeRoot:
         case NodeTypeBlock:
         case NodeTypeBinOpExpr:
+        case NodeTypeUnwrapErrorExpr:
         case NodeTypeFnCallExpr:
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
@@ -2502,6 +2505,38 @@ static VariableTableEntry *add_local_var(CodeGen *g, AstNode *source_node, Block
     return variable_entry;
 }
 
+static TypeTableEntry *analyze_unwrap_error_expr(CodeGen *g, ImportTableEntry *import,
+        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *node)
+{
+    AstNode *op1 = node->data.unwrap_err_expr.op1;
+    AstNode *op2 = node->data.unwrap_err_expr.op2;
+    AstNode *var_node = node->data.unwrap_err_expr.symbol;
+
+    TypeTableEntry *lhs_type = analyze_expression(g, import, parent_context, nullptr, op1);
+    if (lhs_type->id == TypeTableEntryIdInvalid) {
+        return lhs_type;
+    } else if (lhs_type->id == TypeTableEntryIdErrorUnion) {
+        TypeTableEntry *child_type = lhs_type->data.error.child_type;
+        BlockContext *child_context;
+        if (var_node) {
+            child_context = new_block_context(node, parent_context);
+            Buf *var_name = &var_node->data.symbol_expr.symbol;
+            node->data.unwrap_err_expr.var = add_local_var(g, var_node, child_context, var_name,
+                    g->builtin_types.entry_pure_error, true);
+        } else {
+            child_context = parent_context;
+        }
+
+        analyze_expression(g, import, child_context, child_type, op2);
+        return child_type;
+    } else {
+        add_node_error(g, op1,
+            buf_sprintf("expected error type, got '%s'", buf_ptr(&lhs_type->name)));
+        return g->builtin_types.entry_invalid;
+    }
+}
+
+
 static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
         BlockContext *context, AstNode *source_node,
         AstNodeVariableDeclaration *variable_declaration,
@@ -3845,7 +3880,9 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeBinOpExpr:
             return_type = analyze_bin_op_expr(g, import, context, expected_type, node);
             break;
-
+        case NodeTypeUnwrapErrorExpr:
+            return_type = analyze_unwrap_error_expr(g, import, context, expected_type, node);
+            break;
         case NodeTypeFnCallExpr:
             return_type = analyze_fn_call_expr(g, import, context, expected_type, node);
             break;
@@ -4035,6 +4072,7 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeRoot:
         case NodeTypeBlock:
         case NodeTypeBinOpExpr:
+        case NodeTypeUnwrapErrorExpr:
         case NodeTypeFnCallExpr:
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
@@ -4101,6 +4139,10 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
             collect_expr_decl_deps(g, import, node->data.bin_op_expr.op1, decl_node);
             collect_expr_decl_deps(g, import, node->data.bin_op_expr.op2, decl_node);
             break;
+        case NodeTypeUnwrapErrorExpr:
+            collect_expr_decl_deps(g, import, node->data.unwrap_err_expr.op1, decl_node);
+            collect_expr_decl_deps(g, import, node->data.unwrap_err_expr.op2, decl_node);
+            break;
         case NodeTypeReturnExpr:
             collect_expr_decl_deps(g, import, node->data.return_expr.expr, decl_node);
             break;
@@ -4388,6 +4430,7 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
         case NodeTypeRoot:
         case NodeTypeBlock:
         case NodeTypeBinOpExpr:
+        case NodeTypeUnwrapErrorExpr:
         case NodeTypeFnCallExpr:
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
@@ -4582,6 +4625,8 @@ Expr *get_resolved_expr(AstNode *node) {
             return &node->data.return_expr.resolved_expr;
         case NodeTypeBinOpExpr:
             return &node->data.bin_op_expr.resolved_expr;
+        case NodeTypeUnwrapErrorExpr:
+            return &node->data.unwrap_err_expr.resolved_expr;
         case NodeTypePrefixOpExpr:
             return &node->data.prefix_op_expr.resolved_expr;
         case NodeTypeFnCallExpr:
@@ -4669,6 +4714,7 @@ TopLevelDecl *get_resolved_top_level_decl(AstNode *node) {
         case NodeTypeNumberLiteral:
         case NodeTypeReturnExpr:
         case NodeTypeBinOpExpr:
+        case NodeTypeUnwrapErrorExpr:
         case NodeTypePrefixOpExpr:
         case NodeTypeFnCallExpr:
         case NodeTypeArrayAccessExpr:
@@ -4747,10 +4793,30 @@ TypeTableEntry *get_int_type(CodeGen *g, bool is_signed, int size_in_bits) {
 }
 
 bool handle_is_ptr(TypeTableEntry *type_entry) {
-    return type_entry->id == TypeTableEntryIdStruct ||
-            (type_entry->id == TypeTableEntryIdEnum && type_entry->data.enumeration.gen_field_count != 0) ||
-            type_entry->id == TypeTableEntryIdMaybe ||
-            type_entry->id == TypeTableEntryIdArray ||
-            (type_entry->id == TypeTableEntryIdErrorUnion && type_entry->data.error.child_type->size_in_bits > 0);
+    switch (type_entry->id) {
+        case TypeTableEntryIdInvalid:
+        case TypeTableEntryIdMetaType:
+        case TypeTableEntryIdNumLitFloat:
+        case TypeTableEntryIdNumLitInt:
+        case TypeTableEntryIdUndefLit:
+             zig_unreachable();
+        case TypeTableEntryIdUnreachable:
+        case TypeTableEntryIdVoid:
+        case TypeTableEntryIdBool:
+        case TypeTableEntryIdInt:
+        case TypeTableEntryIdFloat:
+        case TypeTableEntryIdPointer:
+        case TypeTableEntryIdPureError:
+        case TypeTableEntryIdFn:
+             return false;
+        case TypeTableEntryIdArray:
+        case TypeTableEntryIdStruct:
+        case TypeTableEntryIdMaybe:
+             return true;
+        case TypeTableEntryIdErrorUnion:
+             return type_entry->data.error.child_type->size_in_bits > 0;
+        case TypeTableEntryIdEnum:
+             return type_entry->data.enumeration.gen_field_count != 0;
+    }
+    zig_unreachable();
 }
-
src/codegen.cpp
@@ -1255,6 +1255,78 @@ static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) {
     zig_unreachable();
 }
 
+static LLVMValueRef gen_unwrap_err_expr(CodeGen *g, AstNode *node) {
+    assert(node->type == NodeTypeUnwrapErrorExpr);
+
+    AstNode *op1 = node->data.unwrap_err_expr.op1;
+    AstNode *op2 = node->data.unwrap_err_expr.op2;
+    VariableTableEntry *var = node->data.unwrap_err_expr.var;
+
+    LLVMValueRef expr_val = gen_expr(g, op1);
+    TypeTableEntry *expr_type = get_expr_type(op1);
+    TypeTableEntry *op2_type = get_expr_type(op2);
+    assert(expr_type->id == TypeTableEntryIdErrorUnion);
+    TypeTableEntry *child_type = expr_type->data.error.child_type;
+    LLVMValueRef err_val;
+    add_debug_source_node(g, node);
+    if (handle_is_ptr(expr_type)) {
+        LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, expr_val, 0, "");
+        err_val = LLVMBuildLoad(g->builder, err_val_ptr, "");
+    } else {
+        err_val = expr_val;
+    }
+    LLVMValueRef zero = LLVMConstNull(g->err_tag_type->type_ref);
+    LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
+
+    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "UnwrapErrOk");
+    LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "UnwrapErrError");
+    LLVMBasicBlockRef end_block;
+    bool err_reachable = op2_type->id != TypeTableEntryIdUnreachable;
+    bool have_end_block = err_reachable && (child_type->size_in_bits > 0);
+    if (have_end_block) {
+        end_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "UnwrapErrEnd");
+    }
+
+    LLVMBuildCondBr(g->builder, cond_val, ok_block, err_block);
+
+    LLVMPositionBuilderAtEnd(g->builder, err_block);
+    if (var) {
+        LLVMBuildStore(g->builder, err_val, var->value_ref);
+    }
+    LLVMValueRef err_result = gen_expr(g, op2);
+    add_debug_source_node(g, node);
+    if (have_end_block) {
+        LLVMBuildBr(g->builder, end_block);
+    } else if (err_reachable) {
+        LLVMBuildBr(g->builder, ok_block);
+    }
+
+    LLVMPositionBuilderAtEnd(g->builder, ok_block);
+    if (child_type->size_in_bits == 0) {
+        return nullptr;
+    }
+    LLVMValueRef child_val_ptr = LLVMBuildStructGEP(g->builder, expr_val, 1, "");
+    LLVMValueRef child_val;
+    if (handle_is_ptr(child_type)) {
+        child_val = child_val_ptr;
+    } else {
+        child_val = LLVMBuildLoad(g->builder, child_val_ptr, "");
+    }
+
+    if (!have_end_block) {
+        return child_val;
+    }
+
+    LLVMBuildBr(g->builder, end_block);
+
+    LLVMPositionBuilderAtEnd(g->builder, end_block);
+    LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(err_result), "");
+    LLVMValueRef incoming_values[2] = {child_val, err_result};
+    LLVMBasicBlockRef incoming_blocks[2] = {ok_block, err_block};
+    LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
+    return phi;
+}
+
 static LLVMValueRef gen_return(CodeGen *g, AstNode *source_node, LLVMValueRef value) {
     TypeTableEntry *return_type = g->cur_fn->type_entry->data.fn.src_return_type;
     if (handle_is_ptr(return_type)) {
@@ -2047,6 +2119,8 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
     switch (node->type) {
         case NodeTypeBinOpExpr:
             return gen_bin_op_expr(g, node);
+        case NodeTypeUnwrapErrorExpr:
+            return gen_unwrap_err_expr(g, node);
         case NodeTypeReturnExpr:
             return gen_return_expr(g, node);
         case NodeTypeVariableDeclaration:
src/parser.cpp
@@ -95,6 +95,8 @@ const char *node_type_str(NodeType node_type) {
             return "Block";
         case NodeTypeBinOpExpr:
             return "BinOpExpr";
+        case NodeTypeUnwrapErrorExpr:
+            return "UnwrapErrorExpr";
         case NodeTypeFnCallExpr:
             return "FnCallExpr";
         case NodeTypeArrayAccessExpr:
@@ -273,6 +275,14 @@ void ast_print(AstNode *node, int indent) {
             ast_print(node->data.bin_op_expr.op1, indent + 2);
             ast_print(node->data.bin_op_expr.op2, indent + 2);
             break;
+        case NodeTypeUnwrapErrorExpr:
+            fprintf(stderr, "%s\n", node_type_str(node->type));
+            ast_print(node->data.unwrap_err_expr.op1, indent + 2);
+            if (node->data.unwrap_err_expr.symbol) {
+                ast_print(node->data.unwrap_err_expr.symbol, indent + 2);
+            }
+            ast_print(node->data.unwrap_err_expr.op2, indent + 2);
+            break;
         case NodeTypeFnCallExpr:
             fprintf(stderr, "%s\n", node_type_str(node->type));
             ast_print(node->data.fn_call_expr.fn_ref_expr, indent + 2);
@@ -964,7 +974,7 @@ static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool ma
 static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandatory);
 static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool mandatory);
 static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool mandatory);
-static AstNode *ast_parse_unwrap_maybe_expr(ParseContext *pc, int *token_index, bool mandatory);
+static AstNode *ast_parse_unwrap_expr(ParseContext *pc, int *token_index, bool mandatory);
 static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, int *token_index, bool mandatory);
 
 static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) {
@@ -1032,7 +1042,7 @@ static void ast_parse_directives(ParseContext *pc, int *token_index,
 }
 
 /*
-ParamDecl : option(token(NoAlias)) token(Symbol) token(Colon) UnwrapMaybeExpression | token(Ellipsis)
+ParamDecl : option("noalias") "Symbol" ":" PrefixOpExpression | "..."
 */
 static AstNode *ast_parse_param_decl(ParseContext *pc, int *token_index) {
     Token *first_token = &pc->tokens->at(*token_index);
@@ -1154,7 +1164,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, int *token_index, bool
 }
 
 /*
-ArrayType : token(LBracket) option(Expression) token(RBracket) option(token(Const)) UnwrapMaybeExpression
+ArrayType : "[" option(Expression) "]" option("const") PrefixOpExpression
 */
 static AstNode *ast_parse_array_type_expr(ParseContext *pc, int *token_index, bool mandatory) {
     Token *l_bracket = &pc->tokens->at(*token_index);
@@ -1207,7 +1217,7 @@ static void ast_parse_asm_input_item(ParseContext *pc, int *token_index, AstNode
 }
 
 /*
-AsmOutputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token(LParen) (token(Symbol) | token(Arrow) UnwrapMaybeExpression token(RParen)
+AsmOutputItem : "[" "Symbol" "]" "String" "(" ("Symbol" | "->" PrefixOpExpression) ")"
 */
 static void ast_parse_asm_output_item(ParseContext *pc, int *token_index, AstNode *node) {
     ast_eat_token(pc, token_index, TokenIdLBracket);
@@ -2132,7 +2142,7 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m
 }
 
 /*
-VariableDeclaration : option(FnVisibleMod) (token(Var) | token(Const)) token(Symbol) (token(Eq) Expression | token(Colon) UnwrapMaybeExpression option(token(Eq) Expression))
+VariableDeclaration : option(FnVisibleMod) ("var" | "const") "Symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
 */
 static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, int *token_index, bool mandatory) {
     Token *first_token = &pc->tokens->at(*token_index);
@@ -2454,38 +2464,55 @@ static BinOpType ast_parse_ass_op(ParseContext *pc, int *token_index, bool manda
 }
 
 /*
-UnwrapMaybeExpression : BoolOrExpression token(DoubleQuestion) BoolOrExpression | BoolOrExpression
+UnwrapExpression : BoolOrExpression (UnwrapMaybe | UnwrapError) | BoolOrExpression
+UnwrapMaybe : "??" BoolOrExpression
+UnwrapError : "%%" option("|" "Symbol" "|") BoolOrExpression
 */
-// this is currently the first child expression of assignment
-static AstNode *ast_parse_unwrap_maybe_expr(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_unwrap_expr(ParseContext *pc, int *token_index, bool mandatory) {
     AstNode *lhs = ast_parse_bool_or_expr(pc, token_index, mandatory);
     if (!lhs)
         return nullptr;
 
     Token *token = &pc->tokens->at(*token_index);
 
-    if (token->id != TokenIdDoubleQuestion) {
-        return lhs;
-    }
+    if (token->id == TokenIdDoubleQuestion) {
+        *token_index += 1;
 
-    *token_index += 1;
+        AstNode *rhs = ast_parse_bool_or_expr(pc, token_index, true);
 
-    AstNode *rhs = ast_parse_bool_or_expr(pc, token_index, true);
+        AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token);
+        node->data.bin_op_expr.op1 = lhs;
+        node->data.bin_op_expr.bin_op = BinOpTypeUnwrapMaybe;
+        node->data.bin_op_expr.op2 = rhs;
 
-    AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token);
-    node->data.bin_op_expr.op1 = lhs;
-    node->data.bin_op_expr.bin_op = BinOpTypeUnwrapMaybe;
-    node->data.bin_op_expr.op2 = rhs;
+        normalize_parent_ptrs(node);
+        return node;
+    } else if (token->id == TokenIdPercentPercent) {
+        *token_index += 1;
 
-    normalize_parent_ptrs(node);
-    return node;
+        AstNode *node = ast_create_node(pc, NodeTypeUnwrapErrorExpr, token);
+        node->data.unwrap_err_expr.op1 = lhs;
+
+        Token *maybe_bar_tok = &pc->tokens->at(*token_index);
+        if (maybe_bar_tok->id == TokenIdBinOr) {
+            *token_index += 1;
+            node->data.unwrap_err_expr.symbol = ast_parse_symbol(pc, token_index);
+            ast_eat_token(pc, token_index, TokenIdBinOr);
+        }
+        node->data.unwrap_err_expr.op2 = ast_parse_expression(pc, token_index, true);
+
+        normalize_parent_ptrs(node);
+        return node;
+    } else {
+        return lhs;
+    }
 }
 
 /*
-AssignmentExpression : UnwrapMaybeExpression AssignmentOperator UnwrapMaybeExpression | UnwrapMaybeExpression
+AssignmentExpression : UnwrapExpression AssignmentOperator UnwrapExpression | UnwrapExpression
 */
 static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mandatory) {
-    AstNode *lhs = ast_parse_unwrap_maybe_expr(pc, token_index, mandatory);
+    AstNode *lhs = ast_parse_unwrap_expr(pc, token_index, mandatory);
     if (!lhs)
         return nullptr;
 
@@ -2494,7 +2521,7 @@ static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mand
     if (ass_op == BinOpTypeInvalid)
         return lhs;
 
-    AstNode *rhs = ast_parse_unwrap_maybe_expr(pc, token_index, true);
+    AstNode *rhs = ast_parse_unwrap_expr(pc, token_index, true);
 
     AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token);
     node->data.bin_op_expr.op1 = lhs;
@@ -2646,7 +2673,7 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato
 }
 
 /*
-FnProto : many(Directive) option(FnVisibleMod) token(Fn) token(Symbol) ParamDeclList option(UnwrapMaybeExpression)
+FnProto : many(Directive) option(FnVisibleMod) "fn" "Symbol" ParamDeclList option(PrefixOpExpression)
 */
 static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mandatory) {
     Token *first_token = &pc->tokens->at(*token_index);
@@ -3164,6 +3191,11 @@ void normalize_parent_ptrs(AstNode *node) {
             set_field(&node->data.bin_op_expr.op1);
             set_field(&node->data.bin_op_expr.op2);
             break;
+        case NodeTypeUnwrapErrorExpr:
+            set_field(&node->data.unwrap_err_expr.op1);
+            set_field(&node->data.unwrap_err_expr.symbol);
+            set_field(&node->data.unwrap_err_expr.op2);
+            break;
         case NodeTypeNumberLiteral:
             // none
             break;
src/tokenizer.cpp
@@ -593,6 +593,11 @@ void tokenize(Buf *buf, Tokenization *out) {
                         end_token(&t);
                         t.state = TokenizeStateStart;
                         break;
+                    case '%':
+                        t.cur_tok->id = TokenIdPercentPercent;
+                        end_token(&t);
+                        t.state = TokenizeStateStart;
+                        break;
                     default:
                         t.pos -= 1;
                         end_token(&t);
@@ -1097,6 +1102,7 @@ const char * token_name(TokenId id) {
         case TokenIdBitShiftRight: return ">>";
         case TokenIdSlash: return "/";
         case TokenIdPercent: return "%";
+        case TokenIdPercentPercent: return "%%";
         case TokenIdDot: return ".";
         case TokenIdEllipsis: return "...";
         case TokenIdMaybe: return "?";
src/tokenizer.hpp
@@ -87,6 +87,7 @@ enum TokenId {
     TokenIdBitShiftRight,
     TokenIdSlash,
     TokenIdPercent,
+    TokenIdPercentPercent,
     TokenIdDot,
     TokenIdEllipsis,
     TokenIdMaybe,
std/std.zig
@@ -130,7 +130,7 @@ pub struct OutStream {
 pub struct InStream {
     fd: isize,
 
-    pub fn readline(is: &InStream, buf: []u8) %isize => {
+    pub fn read(is: &InStream, buf: []u8) %isize => {
         const amt_read = read(is.fd, buf.ptr, buf.len);
         if (amt_read < 0) {
             return switch (-amt_read) {
@@ -144,7 +144,6 @@ pub struct InStream {
         }
         return amt_read;
     }
-
 }
 
 pub fn os_get_random_bytes(buf: []u8) %void => {
@@ -160,30 +159,31 @@ pub fn os_get_random_bytes(buf: []u8) %void => {
 }
 
 
-// TODO return %u64 when we support errors
-pub fn parse_u64(buf: []u8, radix: u8, result: &u64) bool => {
+pub error InvalidChar;
+pub error Overflow;
+
+pub fn parse_u64(buf: []u8, radix: u8) %u64 => {
     var x : u64 = 0;
 
     for (c, buf) {
         const digit = char_to_digit(c);
 
         if (digit > radix) {
-            return true;
+            return error.InvalidChar;
         }
 
         // x *= radix
         if (@mul_with_overflow(u64, x, radix, &x)) {
-            return true;
+            return error.Overflow;
         }
 
         // x += digit
         if (@add_with_overflow(u64, x, digit, &x)) {
-            return true;
+            return error.Overflow;
         }
     }
 
-    *result = x;
-    return false;
+    return x;
 }
 
 fn char_to_digit(c: u8) u8 => {