Commit 5e212db29c

Andrew Kelley <superjoe30@gmail.com>
2016-01-21 02:18:50
parsing error value decls and error value literals
and return with '?' or '%' prefix
1 parent 82d1b51
doc/langref.md
@@ -5,9 +5,11 @@
 ```
 Root : many(TopLevelDecl) "EOF"
 
-TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Import | ContainerDecl | VariableDeclaration
+TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Import | ContainerDecl | VariableDeclaration | ErrorValueDecl
 
-VariableDeclaration : option(FnVisibleMod) ("var" | "const") "symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
+ErrorValueDecl : option(FnVisibleMod) "%." "Symbol"
+
+VariableDeclaration : option(FnVisibleMod) ("var" | "const") "Symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
 
 ContainerDecl : many(Directive) option(FnVisibleMod) ("struct" | "enum") "Symbol" "{" many(StructMember) "}"
 
@@ -77,7 +79,7 @@ ForExpression : "for" "(" "Symbol" "," Expression option("," "Symbol") ")" Expre
 
 BoolOrExpression : BoolAndExpression "||" BoolOrExpression | BoolAndExpression
 
-ReturnExpression : "return" option(Expression)
+ReturnExpression : option("%" | "?") "return" option(Expression)
 
 IfExpression : IfVarExpression | IfBoolExpression
 
@@ -133,7 +135,7 @@ StructLiteralField : "." "Symbol" "=" Expression
 
 PrefixOp : "!" | "-" | "~" | "*" | ("&" option("const")) | "?"
 
-PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression
+PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("%." "Symbol")
 
 ArrayType : "[" option(Expression) "]" option("const") PrefixOpExpression
 
@@ -148,7 +150,7 @@ KeywordLiteral : "true" | "false" | "null" | "break" | "continue"
 
 ```
 x() x[] x.y
-!x -x ~x *x &x ?x
+!x -x ~x *x &x ?x %x
 x{}
 * / %
 + -
@@ -199,12 +201,20 @@ c_ulonglong     unsigned long long  for ABI compatibility with C
 ### Boolean Type
 The boolean type has the name `bool` and represents either true or false.
 
-### Function Types
+### Function Type
 TODO
 
-### Array Types
-TODO
-Also, are there slices?
+### Fixed-Size Array Type
+
+Example: The string `"aoeu"` has type `[4]u8`.
+
+The size is known at compile time and is part of the type.
+
+### Slice Type
+
+A slice can be obtained with the slicing syntax: `array[start...end]`
+
+Example: `"aoeu"[0...2]` has type `[]u8`.
 
 ### Struct Types
 TODO
@@ -213,10 +223,13 @@ TODO
 TODO
 
 ### Unreachable Type
+
 The unreachable type has the name `unreachable`. TODO explanation
 
 ### Void Type
-The void type has the name `void`. TODO explanation
+
+The void type has the name `void`. void types are zero bits and are omitted
+from codegen.
 
 
 ## Expressions
example/cat/main.zig
@@ -3,50 +3,50 @@ export executable "cat";
 import "std.zig";
 
 // Things to do to make this work:
-// * isize instead of usize for things
 // * var args printing
-// * update std API
-// * !void type
+// * %void type
 // * defer
-// * !return
-// * !! operator
-// * make main return !void
-// * how to reference error values (!void).Invalid ? !Invalid ?
-// * ~ is bool not, not !
+// * %return
+// * %% operator
+// * make main return %void
+// * how to reference error values %.Invalid
 // * cast err type to string
+// * update std API
+
+pub %.Invalid;
 
-pub fn main(args: [][]u8) !void => {
+pub fn main(args: [][]u8) %void => {
     const exe = args[0];
     var catted_anything = false;
     for (arg, args[1...]) {
         if (arg == "-") {
             catted_anything = true;
-            !return cat_stream(stdin);
+            %return cat_stream(stdin);
         } else if (arg[0] == '-') {
             return usage(exe);
         } else {
             var is: InputStream;
-            is.open(arg, OpenReadOnly) !! (err) => {
+            is.open(arg, OpenReadOnly) %% (err) => {
                 stderr.print("Unable to open file: {}", ([]u8])(err));
                 return err;
             }
             defer is.close();
 
             catted_anything = true;
-            !return cat_stream(is);
+            %return cat_stream(is);
         }
     }
-    if (~catted_anything) {
-        !return cat_stream(stdin)
+    if (!catted_anything) {
+        %return cat_stream(stdin)
     }
 }
 
-fn usage(exe: []u8) !void => {
+fn usage(exe: []u8) %void => {
     stderr.print("Usage: {} [FILE]...\n", exe);
-    return !Invalid;
+    return %.Invalid;
 }
 
-fn cat_stream(is: InputStream) !void => {
+fn cat_stream(is: InputStream) %void => {
     var buf: [1024 * 4]u8;
 
     while (true) {
example/hello_world/hello.zig
@@ -3,6 +3,7 @@ export executable "hello";
 import "std.zig";
 
 pub fn main(args: [][]u8) i32 => {
+    //stderr.print_str("Hello, world!\n");
     print_str("Hello, world!\n");
     return 0;
 }
src/all_types.hpp
@@ -129,10 +129,12 @@ enum NodeType {
     NodeTypeDirective,
     NodeTypeReturnExpr,
     NodeTypeVariableDeclaration,
+    NodeTypeErrorValueDecl,
     NodeTypeBinOpExpr,
     NodeTypeNumberLiteral,
     NodeTypeStringLiteral,
     NodeTypeCharLiteral,
+    NodeTypeErrorLiteral,
     NodeTypeSymbol,
     NodeTypePrefixOpExpr,
     NodeTypeFnCallExpr,
@@ -222,7 +224,14 @@ struct AstNodeBlock {
     Expr resolved_expr;
 };
 
+enum ReturnKind {
+    ReturnKindUnconditional,
+    ReturnKindMaybe,
+    ReturnKindError,
+};
+
 struct AstNodeReturnExpr {
+    ReturnKind kind;
     // might be null in case of return void;
     AstNode *expr;
 
@@ -243,6 +252,14 @@ struct AstNodeVariableDeclaration {
     Expr resolved_expr;
 };
 
+struct AstNodeErrorValueDecl {
+    VisibMod visib_mod;
+    Buf name;
+
+    // populated by semantic analyzer
+    TopLevelDecl top_level_decl;
+};
+
 enum BinOpType {
     BinOpTypeInvalid,
     BinOpTypeAssign,
@@ -358,6 +375,7 @@ enum PrefixOp {
     PrefixOpConstAddressOf,
     PrefixOpDereference,
     PrefixOpMaybe,
+    PrefixOpError,
 };
 
 struct AstNodePrefixOpExpr {
@@ -564,6 +582,14 @@ struct AstNodeNumberLiteral {
     Expr resolved_expr;
 };
 
+struct AstNodeErrorLiteral {
+    Buf symbol;
+
+    // populated by semantic analyzer
+    NumLitCodeGen codegen;
+    Expr resolved_expr;
+};
+
 struct AstNodeStructValueField {
     Buf name;
     AstNode *expr;
@@ -644,6 +670,7 @@ struct AstNode {
         AstNodeBlock block;
         AstNodeReturnExpr return_expr;
         AstNodeVariableDeclaration variable_declaration;
+        AstNodeErrorValueDecl error_value_decl;
         AstNodeBinOpExpr bin_op_expr;
         AstNodeExternBlock extern_block;
         AstNodeDirective directive;
@@ -668,6 +695,7 @@ struct AstNode {
         AstNodeStringLiteral string_literal;
         AstNodeCharLiteral char_literal;
         AstNodeNumberLiteral number_literal;
+        AstNodeErrorLiteral error_literal;
         AstNodeContainerInitExpr container_init_expr;
         AstNodeStructValueField struct_val_field;
         AstNodeNullLiteral null_literal;
@@ -738,6 +766,10 @@ struct TypeTableEntryMaybe {
     TypeTableEntry *child_type;
 };
 
+struct TypeTableEntryError {
+    TypeTableEntry *child_type;
+};
+
 struct TypeTableEntryEnum {
     AstNode *decl_node;
     uint32_t field_count;
@@ -778,6 +810,7 @@ enum TypeTableEntryId {
     TypeTableEntryIdStruct,
     TypeTableEntryIdNumberLiteral,
     TypeTableEntryIdMaybe,
+    TypeTableEntryIdError,
     TypeTableEntryIdEnum,
     TypeTableEntryIdFn,
 };
@@ -799,6 +832,7 @@ struct TypeTableEntry {
         TypeTableEntryStruct structure;
         TypeTableEntryNumLit num_lit;
         TypeTableEntryMaybe maybe;
+        TypeTableEntryError error;
         TypeTableEntryEnum enumeration;
         TypeTableEntryFn fn;
     } data;
@@ -808,6 +842,7 @@ struct TypeTableEntry {
     TypeTableEntry *unknown_size_array_parent[2];
     HashMap<uint64_t, TypeTableEntry *, uint64_hash, uint64_eq> arrays_by_size;
     TypeTableEntry *maybe_parent;
+    TypeTableEntry *error_parent;
 };
 
 struct ImporterInfo {
src/analyze.cpp
@@ -43,7 +43,9 @@ static AstNode *first_executing_node(AstNode *node) {
         case NodeTypeDirective:
         case NodeTypeReturnExpr:
         case NodeTypeVariableDeclaration:
+        case NodeTypeErrorValueDecl:
         case NodeTypeNumberLiteral:
+        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeSymbol:
@@ -104,6 +106,7 @@ TypeTableEntry *new_type_table_entry(TypeTableEntryId id) {
         case TypeTableEntryIdNumberLiteral:
         case TypeTableEntryIdMaybe:
         case TypeTableEntryIdFn:
+        case TypeTableEntryIdError:
             // nothing to init
             break;
         case TypeTableEntryIdStruct:
@@ -215,6 +218,57 @@ static TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) {
     }
 }
 
+static TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) {
+    if (child_type->error_parent) {
+        return child_type->error_parent;
+    } else {
+        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdError);
+        zig_panic("TODO get_error_type");
+        // create a struct with a boolean whether this is the null value
+        assert(child_type->type_ref);
+        LLVMTypeRef elem_types[] = {
+            child_type->type_ref,
+            LLVMInt1Type(),
+        };
+        entry->type_ref = LLVMStructType(elem_types, 2, false);
+        buf_resize(&entry->name, 0);
+        buf_appendf(&entry->name, "?%s", buf_ptr(&child_type->name));
+        entry->size_in_bits = child_type->size_in_bits + 8;
+        entry->align_in_bits = child_type->align_in_bits;
+        assert(child_type->di_type);
+
+
+        LLVMZigDIScope *compile_unit_scope = LLVMZigCompileUnitToScope(g->compile_unit);
+        LLVMZigDIFile *di_file = nullptr;
+        unsigned line = 0;
+        entry->di_type = LLVMZigCreateReplaceableCompositeType(g->dbuilder,
+            LLVMZigTag_DW_structure_type(), buf_ptr(&entry->name),
+            compile_unit_scope, di_file, line);
+
+        LLVMZigDIType *di_element_types[] = {
+            LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
+                    "val", di_file, line, child_type->size_in_bits, child_type->align_in_bits, 0, 0,
+                    child_type->di_type),
+            LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
+                    "maybe", di_file, line, 8, 8, 8, 0,
+                    child_type->di_type),
+        };
+        LLVMZigDIType *replacement_di_type = LLVMZigCreateDebugStructType(g->dbuilder,
+                compile_unit_scope,
+                buf_ptr(&entry->name),
+                di_file, line, entry->size_in_bits, entry->align_in_bits, 0,
+                nullptr, di_element_types, 2, 0, nullptr, "");
+
+        LLVMZigReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
+        entry->di_type = replacement_di_type;
+
+        entry->data.maybe.child_type = child_type;
+
+        child_type->maybe_parent = entry;
+        return entry;
+    }
+}
+
 static TypeTableEntry *get_array_type(CodeGen *g, TypeTableEntry *child_type, uint64_t array_size)
 {
     auto existing_entry = child_type->arrays_by_size.maybe_get(array_size);
@@ -922,6 +976,11 @@ static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
                 g->global_vars.append(var);
                 break;
             }
+        case NodeTypeErrorValueDecl:
+            {
+                zig_panic("TODO resolve_top_level_decl NodeTypeErrorValueDecl");
+                break;
+            }
         case NodeTypeUse:
             // nothing to do here
             break;
@@ -937,6 +996,7 @@ static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeNumberLiteral:
+        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -1005,6 +1065,7 @@ static bool num_lit_fits_in_other_type(CodeGen *g, TypeTableEntry *literal_type,
         case TypeTableEntryIdEnum:
         case TypeTableEntryIdMetaType:
         case TypeTableEntryIdFn:
+        case TypeTableEntryIdError:
             return false;
         case TypeTableEntryIdInt:
             if (is_num_lit_unsigned(num_lit)) {
@@ -2263,6 +2324,12 @@ static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry
     }
 }
 
+static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
+        BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
+{
+    zig_panic("TODO analyze_error_literal_expr");
+}
+
 static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
@@ -3021,7 +3088,7 @@ static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *impo
                     if (meta_type->id == TypeTableEntryIdInvalid) {
                         return g->builtin_types.entry_invalid;
                     } else if (meta_type->id == TypeTableEntryIdUnreachable) {
-                        add_node_error(g, node, buf_create_from_str("maybe unreachable type not allowed"));
+                        add_node_error(g, node, buf_create_from_str("unable to wrap unreachable in maybe type"));
                         return g->builtin_types.entry_invalid;
                     } else {
                         return resolve_expr_const_val_as_type(g, node, get_maybe_type(g, meta_type));
@@ -3034,6 +3101,31 @@ static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *impo
                     return get_maybe_type(g, type_entry);
                 }
             }
+        case PrefixOpError:
+            {
+                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, expr_node);
+
+                if (type_entry->id == TypeTableEntryIdInvalid) {
+                    return type_entry;
+                } else if (type_entry->id == TypeTableEntryIdMetaType) {
+                    TypeTableEntry *meta_type = resolve_type(g, expr_node);
+                    if (meta_type->id == TypeTableEntryIdInvalid) {
+                        return meta_type;
+                    } else if (meta_type->id == TypeTableEntryIdUnreachable) {
+                        add_node_error(g, node, buf_create_from_str("unable to wrap unreachable in error type"));
+                        return g->builtin_types.entry_invalid;
+                    } else {
+                        return resolve_expr_const_val_as_type(g, node, get_error_type(g, meta_type));
+                    }
+                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+                    add_node_error(g, expr_node, buf_sprintf("unable to wrap unreachable in error type"));
+                    return g->builtin_types.entry_invalid;
+                } else {
+                    // TODO eval const expr
+                    return get_error_type(g, type_entry);
+                }
+
+            }
     }
     zig_unreachable();
 }
@@ -3099,6 +3191,37 @@ static TypeTableEntry *analyze_switch_expr(CodeGen *g, ImportTableEntry *import,
     return expected_type;
 }
 
+static TypeTableEntry *analyze_return_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        TypeTableEntry *expected_type, AstNode *node)
+{
+    if (!context->fn_entry) {
+        add_node_error(g, node, buf_sprintf("return expression outside function definition"));
+        return g->builtin_types.entry_invalid;
+    }
+
+    if (node->data.return_expr.kind != ReturnKindUnconditional) {
+        zig_panic("TODO analyze_return_expr conditional");
+    }
+
+    TypeTableEntry *expected_return_type = get_return_type(context);
+    TypeTableEntry *actual_return_type;
+    if (node->data.return_expr.expr) {
+        actual_return_type = analyze_expression(g, import, context, expected_return_type, node->data.return_expr.expr);
+    } else {
+        actual_return_type = g->builtin_types.entry_void;
+    }
+
+    if (actual_return_type->id == TypeTableEntryIdUnreachable) {
+        // "return exit(0)" should just be "exit(0)".
+        add_node_error(g, node, buf_sprintf("returning is unreachable"));
+        actual_return_type = g->builtin_types.entry_invalid;
+    }
+
+    resolve_type_compatibility(g, context, node, expected_return_type, actual_return_type);
+
+    return g->builtin_types.entry_unreachable;
+}
+
 static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
@@ -3140,29 +3263,8 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
             }
 
         case NodeTypeReturnExpr:
-            {
-                if (context->fn_entry) {
-                    TypeTableEntry *expected_return_type = get_return_type(context);
-                    TypeTableEntry *actual_return_type;
-                    if (node->data.return_expr.expr) {
-                        actual_return_type = analyze_expression(g, import, context, expected_return_type, node->data.return_expr.expr);
-                    } else {
-                        actual_return_type = g->builtin_types.entry_void;
-                    }
-
-                    if (actual_return_type->id == TypeTableEntryIdUnreachable) {
-                        // "return exit(0)" should just be "exit(0)".
-                        add_node_error(g, node, buf_sprintf("returning is unreachable"));
-                        actual_return_type = g->builtin_types.entry_invalid;
-                    }
-
-                    resolve_type_compatibility(g, context, node, expected_return_type, actual_return_type);
-                } else {
-                    add_node_error(g, node, buf_sprintf("return expression outside function definition"));
-                }
-                return_type = g->builtin_types.entry_unreachable;
-                break;
-            }
+            return_type = analyze_return_expr(g, import, context, expected_type, node);
+            break;
         case NodeTypeVariableDeclaration:
             analyze_variable_declaration(g, import, context, expected_type, node);
             return_type = g->builtin_types.entry_void;
@@ -3236,6 +3338,9 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeNumberLiteral:
             return_type = analyze_number_literal_expr(g, import, context, expected_type, node);
             break;
+        case NodeTypeErrorLiteral:
+            return_type = analyze_error_literal_expr(g, import, context, expected_type, node);
+            break;
         case NodeTypeStringLiteral:
             if (node->data.string_literal.c) {
                 return_type = g->builtin_types.entry_c_string_literal;
@@ -3294,6 +3399,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeStructDecl:
         case NodeTypeStructField:
         case NodeTypeStructValueField:
+        case NodeTypeErrorValueDecl:
             zig_unreachable();
     }
     assert(return_type);
@@ -3411,6 +3517,7 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeExternBlock:
         case NodeTypeUse:
         case NodeTypeVariableDeclaration:
+        case NodeTypeErrorValueDecl:
             // already took care of these
             break;
         case NodeTypeDirective:
@@ -3425,6 +3532,7 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeNumberLiteral:
+        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -3457,6 +3565,7 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
 {
     switch (node->type) {
         case NodeTypeNumberLiteral:
+        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -3464,6 +3573,7 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
         case NodeTypeGoto:
         case NodeTypeBreak:
         case NodeTypeContinue:
+        case NodeTypeErrorValueDecl:
             // no dependencies on other top level declarations
             break;
         case NodeTypeSymbol:
@@ -3758,6 +3868,10 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
         case NodeTypeUse:
             // already taken care of
             break;
+        case NodeTypeErrorValueDecl:
+            // error value declarations do not depend on other top level decls
+            resolve_top_level_decl(g, import, node);
+            break;
         case NodeTypeDirective:
         case NodeTypeParamDecl:
         case NodeTypeFnDecl:
@@ -3769,6 +3883,7 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeNumberLiteral:
+        case NodeTypeErrorLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeBoolLiteral:
@@ -3966,6 +4081,8 @@ Expr *get_resolved_expr(AstNode *node) {
             return &node->data.container_init_expr.resolved_expr;
         case NodeTypeNumberLiteral:
             return &node->data.number_literal.resolved_expr;
+        case NodeTypeErrorLiteral:
+            return &node->data.error_literal.resolved_expr;
         case NodeTypeStringLiteral:
             return &node->data.string_literal.resolved_expr;
         case NodeTypeBlock:
@@ -4006,6 +4123,7 @@ Expr *get_resolved_expr(AstNode *node) {
         case NodeTypeStructDecl:
         case NodeTypeStructField:
         case NodeTypeStructValueField:
+        case NodeTypeErrorValueDecl:
             zig_unreachable();
     }
     zig_unreachable();
@@ -4015,6 +4133,8 @@ NumLitCodeGen *get_resolved_num_lit(AstNode *node) {
     switch (node->type) {
         case NodeTypeNumberLiteral:
             return &node->data.number_literal.codegen;
+        case NodeTypeErrorLiteral:
+            return &node->data.error_literal.codegen;
         case NodeTypeFnCallExpr:
             return &node->data.fn_call_expr.resolved_num_lit;
         case NodeTypeReturnExpr:
@@ -4056,6 +4176,7 @@ NumLitCodeGen *get_resolved_num_lit(AstNode *node) {
         case NodeTypeStructField:
         case NodeTypeStructValueField:
         case NodeTypeArrayType:
+        case NodeTypeErrorValueDecl:
             zig_unreachable();
     }
     zig_unreachable();
@@ -4069,7 +4190,10 @@ TopLevelDecl *get_resolved_top_level_decl(AstNode *node) {
             return &node->data.fn_proto.top_level_decl;
         case NodeTypeStructDecl:
             return &node->data.struct_decl.top_level_decl;
+        case NodeTypeErrorValueDecl:
+            return &node->data.error_value_decl.top_level_decl;
         case NodeTypeNumberLiteral:
+        case NodeTypeErrorLiteral:
         case NodeTypeReturnExpr:
         case NodeTypeBinOpExpr:
         case NodeTypePrefixOpExpr:
src/codegen.cpp
@@ -829,6 +829,10 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) {
             {
                 zig_panic("TODO codegen PrefixOpMaybe");
             }
+        case PrefixOpError:
+            {
+                zig_panic("TODO codegen PrefixOpError");
+            }
     }
     zig_unreachable();
 }
@@ -1937,6 +1941,12 @@ static LLVMValueRef gen_number_literal(CodeGen *g, AstNode *node) {
     return gen_number_literal_raw(g, node, codegen_num_lit, &node->data.number_literal);
 }
 
+static LLVMValueRef gen_error_literal(CodeGen *g, AstNode *node) {
+    assert(node->type == NodeTypeErrorLiteral);
+
+    zig_panic("TODO gen_error_literal");
+}
+
 static LLVMValueRef gen_symbol(CodeGen *g, AstNode *node) {
     assert(node->type == NodeTypeSymbol);
     VariableTableEntry *variable = node->data.symbol_expr.variable;
@@ -2070,6 +2080,8 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) {
             return gen_asm_expr(g, node);
         case NodeTypeNumberLiteral:
             return gen_number_literal(g, node);
+        case NodeTypeErrorLiteral:
+            return gen_error_literal(g, node);
         case NodeTypeStringLiteral:
             {
                 Buf *str = &node->data.string_literal.buf;
@@ -2125,6 +2137,7 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) {
         case NodeTypeArrayType:
         case NodeTypeSwitchProng:
         case NodeTypeSwitchRange:
+        case NodeTypeErrorValueDecl:
             zig_unreachable();
     }
     zig_unreachable();
src/parser.cpp
@@ -63,6 +63,16 @@ static const char *prefix_op_str(PrefixOp prefix_op) {
         case PrefixOpConstAddressOf: return "&const";
         case PrefixOpDereference: return "*";
         case PrefixOpMaybe: return "?";
+        case PrefixOpError: return "%";
+    }
+    zig_unreachable();
+}
+
+static const char *return_prefix_str(ReturnKind kind) {
+    switch (kind) {
+        case ReturnKindError: return "%";
+        case ReturnKindMaybe: return "?";
+        case ReturnKindUnconditional: return "";
     }
     zig_unreachable();
 }
@@ -99,8 +109,12 @@ const char *node_type_str(NodeType node_type) {
             return "ReturnExpr";
         case NodeTypeVariableDeclaration:
             return "VariableDeclaration";
+        case NodeTypeErrorValueDecl:
+            return "ErrorValueDecl";
         case NodeTypeNumberLiteral:
             return "NumberLiteral";
+        case NodeTypeErrorLiteral:
+            return "ErrorLiteral";
         case NodeTypeStringLiteral:
             return "StringLiteral";
         case NodeTypeCharLiteral:
@@ -214,10 +228,13 @@ void ast_print(AstNode *node, int indent) {
                 break;
             }
         case NodeTypeReturnExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            if (node->data.return_expr.expr)
-                ast_print(node->data.return_expr.expr, indent + 2);
-            break;
+            {
+                const char *prefix_str = return_prefix_str(node->data.return_expr.kind);
+                fprintf(stderr, "%s%s\n", prefix_str, node_type_str(node->type));
+                if (node->data.return_expr.expr)
+                    ast_print(node->data.return_expr.expr, indent + 2);
+                break;
+            }
         case NodeTypeVariableDeclaration:
             {
                 Buf *name_buf = &node->data.variable_declaration.symbol;
@@ -228,6 +245,12 @@ void ast_print(AstNode *node, int indent) {
                     ast_print(node->data.variable_declaration.expr, indent + 2);
                 break;
             }
+        case NodeTypeErrorValueDecl:
+            {
+                Buf *name_buf = &node->data.error_value_decl.name;
+                fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
+                break;
+            }
         case NodeTypeExternBlock:
             {
                 fprintf(stderr, "%s\n", node_type_str(node->type));
@@ -288,6 +311,11 @@ void ast_print(AstNode *node, int indent) {
                 }
                 break;
             }
+        case NodeTypeErrorLiteral:
+            {
+                fprintf(stderr, "%s '%s'", node_type_str(node->type), buf_ptr(&node->data.error_literal.symbol));
+                break;
+            }
         case NodeTypeStringLiteral:
             {
                 const char *c = node->data.string_literal.c ? "c" : "";
@@ -1345,7 +1373,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand
 }
 
 /*
-PrimaryExpression : token(Number) | token(String) | token(CharLiteral) | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | token(Symbol) | (token(AtSign) token(Symbol) FnCallExpression) | ArrayType | AsmExpression
+PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("%." "Symbol")
 KeywordLiteral : token(True) | token(False) | token(Null) | token(Break) | token(Continue)
 */
 static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) {
@@ -1415,6 +1443,12 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
 
         ast_buf_from_token(pc, dest_symbol, &node->data.goto_expr.name);
         return node;
+    } else if (token->id == TokenIdPercentDot) {
+        *token_index += 1;
+        Token *symbol_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
+        AstNode *node = ast_create_node(pc, NodeTypeErrorLiteral, token);
+        ast_buf_from_token(pc, symbol_tok, &node->data.error_literal.symbol);
+        return node;
     }
 
     AstNode *grouped_expr_node = ast_parse_grouped_expr(pc, token_index, false);
@@ -1612,6 +1646,7 @@ static PrefixOp tok_to_prefix_op(Token *token) {
         case TokenIdAmpersand: return PrefixOpAddressOf;
         case TokenIdStar: return PrefixOpDereference;
         case TokenIdMaybe: return PrefixOpMaybe;
+        case TokenIdPercent: return PrefixOpError;
         case TokenIdBoolAnd: return PrefixOpAddressOf;
         default: return PrefixOpInvalid;
     }
@@ -2031,20 +2066,46 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda
 }
 
 /*
-ReturnExpression : token(Return) option(Expression)
+ReturnExpression : option("%" | "?") "return" option(Expression)
 */
 static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool mandatory) {
-    Token *return_tok = &pc->tokens->at(*token_index);
-    if (return_tok->id == TokenIdKeywordReturn) {
+    Token *token = &pc->tokens->at(*token_index);
+
+    ReturnKind kind;
+
+    if (token->id == TokenIdPercent) {
+        Token *next_token = &pc->tokens->at(*token_index + 1);
+        if (next_token->id == TokenIdKeywordReturn) {
+            kind = ReturnKindError;
+            *token_index += 2;
+        } else if (mandatory) {
+            ast_invalid_token_error(pc, token);
+        } else {
+            return nullptr;
+        }
+    } else if (token->id == TokenIdMaybe) {
+        Token *next_token = &pc->tokens->at(*token_index + 1);
+        if (next_token->id == TokenIdKeywordReturn) {
+            kind = ReturnKindMaybe;
+            *token_index += 2;
+        } else if (mandatory) {
+            ast_invalid_token_error(pc, token);
+        } else {
+            return nullptr;
+        }
+    } else if (token->id == TokenIdKeywordReturn) {
+        kind = ReturnKindUnconditional;
         *token_index += 1;
-        AstNode *node = ast_create_node(pc, NodeTypeReturnExpr, return_tok);
-        node->data.return_expr.expr = ast_parse_expression(pc, token_index, false);
-        return node;
     } else if (mandatory) {
-        ast_invalid_token_error(pc, return_tok);
+        ast_invalid_token_error(pc, token);
     } else {
         return nullptr;
     }
+
+    AstNode *node = ast_create_node(pc, NodeTypeReturnExpr, token);
+    node->data.return_expr.kind = kind;
+    node->data.return_expr.expr = ast_parse_expression(pc, token_index, false);
+    return node;
 }
 
 /*
@@ -2054,27 +2115,46 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, int *token
     Token *first_token = &pc->tokens->at(*token_index);
 
     VisibMod visib_mod;
+    bool is_const;
 
     if (first_token->id == TokenIdKeywordPub) {
-        *token_index += 1;
-        visib_mod = VisibModPub;
+        Token *next_token = &pc->tokens->at(*token_index + 1);
+        if (next_token->id == TokenIdKeywordVar ||
+            next_token->id == TokenIdKeywordConst)
+        {
+            visib_mod = VisibModPub;
+            is_const = (next_token->id == TokenIdKeywordConst);
+            *token_index += 2;
+        } else if (mandatory) {
+            ast_invalid_token_error(pc, next_token);
+        } else {
+            return nullptr;
+        }
     } else if (first_token->id == TokenIdKeywordExport) {
-        *token_index += 1;
-        visib_mod = VisibModExport;
+        Token *next_token = &pc->tokens->at(*token_index + 1);
+        if (next_token->id == TokenIdKeywordVar ||
+            next_token->id == TokenIdKeywordConst)
+        {
+            visib_mod = VisibModExport;
+            is_const = (next_token->id == TokenIdKeywordConst);
+            *token_index += 2;
+        } else if (mandatory) {
+            ast_invalid_token_error(pc, next_token);
+        } else {
+            return nullptr;
+        }
     } else if (first_token->id == TokenIdKeywordVar ||
                first_token->id == TokenIdKeywordConst)
     {
         visib_mod = VisibModPrivate;
+        is_const = (first_token->id == TokenIdKeywordConst);
+        *token_index += 1;
     } else if (mandatory) {
         ast_invalid_token_error(pc, first_token);
     } else {
         return nullptr;
     }
 
-    Token *var_or_const_tok = &pc->tokens->at(*token_index);
-    bool is_const = (var_or_const_tok->id == TokenIdKeywordConst);
-    *token_index += 1;
-
     AstNode *node = ast_create_node(pc, NodeTypeVariableDeclaration, first_token);
 
     node->data.variable_declaration.is_const = is_const;
@@ -2836,7 +2916,54 @@ static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
 }
 
 /*
-TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Use | StructDecl | VariableDeclaration | EnumDecl
+ErrorValueDecl : option(FnVisibleMod) "%." "Symbol"
+*/
+static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, bool mandatory) {
+    Token *first_token = &pc->tokens->at(*token_index);
+
+    VisibMod visib_mod;
+
+    if (first_token->id == TokenIdKeywordPub) {
+        Token *next_token = &pc->tokens->at(*token_index + 1);
+        if (next_token->id == TokenIdPercentDot) {
+            visib_mod = VisibModPub;
+            *token_index += 2;
+        } else if (mandatory) {
+            ast_invalid_token_error(pc, next_token);
+        } else {
+            return nullptr;
+        }
+    } else if (first_token->id == TokenIdKeywordExport) {
+        Token *next_token = &pc->tokens->at(*token_index + 1);
+        if (next_token->id == TokenIdPercentDot) {
+            visib_mod = VisibModExport;
+            *token_index += 2;
+        } else if (mandatory) {
+            ast_invalid_token_error(pc, next_token);
+        } else {
+            return nullptr;
+        }
+    } else if (first_token->id == TokenIdPercentDot) {
+        visib_mod = VisibModPrivate;
+        *token_index += 1;
+    } else if (mandatory) {
+        ast_invalid_token_error(pc, first_token);
+    } else {
+        return nullptr;
+    }
+
+    Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
+    ast_eat_token(pc, token_index, TokenIdSemicolon);
+
+    AstNode *node = ast_create_node(pc, NodeTypeErrorValueDecl, first_token);
+    node->data.error_value_decl.visib_mod = visib_mod;
+    ast_buf_from_token(pc, name_tok, &node->data.error_value_decl.name);
+
+    return node;
+}
+
+/*
+TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Import | ContainerDecl | VariableDeclaration | ErrorValueDecl
 */
 static void ast_parse_top_level_decls(ParseContext *pc, int *token_index, ZigList<AstNode *> *top_level_decls) {
     for (;;) {
@@ -2887,6 +3014,12 @@ static void ast_parse_top_level_decls(ParseContext *pc, int *token_index, ZigLis
             continue;
         }
 
+        AstNode *error_value_node = ast_parse_error_value_decl(pc, token_index, false);
+        if (error_value_node) {
+            top_level_decls->append(error_value_node);
+            continue;
+        }
+
         return;
     }
     zig_unreachable();
src/tokenizer.cpp
@@ -584,6 +584,11 @@ void tokenize(Buf *buf, Tokenization *out) {
                         end_token(&t);
                         t.state = TokenizeStateStart;
                         break;
+                    case '.':
+                        t.cur_tok->id = TokenIdPercentDot;
+                        end_token(&t);
+                        t.state = TokenizeStateStart;
+                        break;
                     default:
                         t.pos -= 1;
                         end_token(&t);
@@ -1092,6 +1097,7 @@ const char * token_name(TokenId id) {
         case TokenIdDoubleQuestion: return "??";
         case TokenIdMaybeAssign: return "?=";
         case TokenIdAtSign: return "@";
+        case TokenIdPercentDot: return "%.";
     }
     return "(invalid token)";
 }
src/tokenizer.hpp
@@ -91,6 +91,7 @@ enum TokenId {
     TokenIdDoubleQuestion,
     TokenIdMaybeAssign,
     TokenIdAtSign,
+    TokenIdPercentDot,
 };
 
 struct Token {
std/std.zig
@@ -1,43 +1,177 @@
 import "syscall.zig";
+//import "errno.zig";
 
 pub const stdin_fileno : isize = 0;
 pub const stdout_fileno : isize = 1;
 pub const stderr_fileno : isize = 2;
 
-// TODO error handling
-pub fn os_get_random_bytes(buf: []u8) isize => {
-    getrandom(buf.ptr, buf.len, 0)
+/*
+pub var stdin = InStream {
+    .fd = stdin_fileno,
+};
+
+pub var stdout = OutStream {
+    .fd = stdout_fileno,
+    .buffer = uninitialized,
+    .index = 0,
+    .buffered = true,
+};
+
+pub var stderr = OutStream {
+    .fd = stderr_fileno,
+    .buffer = uninitialized,
+    .index = 0,
+    .buffered = false,
+};
+
+pub %.Unexpected;
+pub %.DiskQuota;
+pub %.FileTooBig;
+pub %.SigInterrupt;
+pub %.Io;
+pub %.NoSpaceLeft;
+pub %.BadPerm;
+pub %.PipeFail;
+*/
+
+const buffer_size: u16 = 4 * 1024;
+const max_u64_base10_digits: isize = 20;
+
+/*
+pub struct OutStream {
+    fd: isize,
+    buffer: [buffer_size]u8,
+    index: @typeof(buffer_size),
+    buffered: bool,
+
+    pub fn print_str(os: &OutStream, str: []const u8) %isize => {
+        var src_bytes_left = str.len;
+        var src_index: @typeof(str.len) = 0;
+        const dest_space_left = os.buffer.len - index;
+
+        while (src_bytes_left > 0) {
+            const copy_amt = min_isize(dest_space_left, src_bytes_left);
+            @memcpy(&buffer[os.index], &str[src_index], copy_amt);
+            os.index += copy_amt;
+            if (os.index == os.buffer.len) {
+                %return os.flush();
+            }
+            src_bytes_left -= copy_amt;
+        }
+        if (!os.buffered) {
+            %return os.flush();
+        }
+        return str.len;
+    }
+
+    pub fn print_u64(os: &OutStream, x: u64) %isize => {
+        if (os.index + max_u64_base10_digits >= os.buffer.len) {
+            %return os.flush();
+        }
+        const amt_printed = buf_print_u64(buf[os.index...], x);
+        os.index += amt_printed;
+
+        if (!os.buffered) {
+            %return os.flush();
+        }
+
+        return amt_printed;
+    }
+
+
+    pub fn print_i64(os: &OutStream, x: i64) %isize => {
+        if (os.index + max_u64_base10_digits >= os.buffer.len) {
+            %return os.flush();
+        }
+        const amt_printed = buf_print_i64(buf[os.index...], x);
+        os.index += amt_printed;
+
+        if (!os.buffered) {
+            %return os.flush();
+        }
+
+        return amt_printed;
+    }
+
+
+    pub fn flush(os: &OutStream) %void => {
+        const amt_to_write = os.index;
+        os.index = 0;
+        switch (write(fd, os.buffer.ptr, amt_to_write)) {
+            EINVAL => unreachable{},
+            EDQUOT => %.DiskQuota,
+            EFBIG  => %.FileTooBig,
+            EINTR  => %.SigInterrupt,
+            EIO    => %.Io,
+            ENOSPC => %.NoSpaceLeft,
+            EPERM  => %.BadPerm,
+            EPIPE  => %.PipeFail,
+            else   => %.Unexpected,
+        }
+    }
+}
+
+pub struct InStream {
+    fd: isize,
+
+    pub fn readline(buf: []u8) %isize => {
+        const amt_read = read(stdin_fileno, buf.ptr, buf.len);
+        if (amt_read < 0) {
+            switch (-amt_read) {
+                EINVAL => unreachable{},
+                EFAULT => unreachable{},
+                EBADF  => %.BadFd,
+                EINTR  => %.SigInterrupt,
+                EIO    => %.Io,
+                else   => %.Unexpected,
+            }
+        }
+        return amt_read;
+    }
+
+}
+
+pub fn os_get_random_bytes(buf: []u8) %void => {
+    switch (getrandom(buf.ptr, buf.len, 0)) {
+        EINVAL => unreachable{},
+        EFAULT => unreachable{},
+        EINTR  => %.SigInterrupt,
+        else   => %.Unexpected,
+    }
 }
+*/
+
 
-// TODO error handling
-// TODO handle buffering and flushing (mutex protected)
+// TODO remove this
 pub fn print_str(str: []const u8) isize => {
     fprint_str(stdout_fileno, str)
 }
 
-// TODO error handling
-// TODO handle buffering and flushing (mutex protected)
+// TODO remove this
 pub fn fprint_str(fd: isize, str: []const u8) isize => {
     write(fd, str.ptr, str.len)
 }
 
-// TODO handle buffering and flushing (mutex protected)
-// TODO error handling
+// TODO remove this
+pub fn os_get_random_bytes(buf: []u8) isize => {
+    getrandom(buf.ptr, buf.len, 0)
+}
+
+// TODO remove this
 pub fn print_u64(x: u64) isize => {
     var buf: [max_u64_base10_digits]u8;
     const len = buf_print_u64(buf, x);
     return write(stdout_fileno, buf.ptr, len);
 }
 
-// TODO handle buffering and flushing (mutex protected)
-// TODO error handling
+// TODO remove this
 pub fn print_i64(x: i64) isize => {
     var buf: [max_u64_base10_digits]u8;
     const len = buf_print_i64(buf, x);
     return write(stdout_fileno, buf.ptr, len);
 }
 
-// TODO error handling
+// TODO remove this
 pub fn readline(buf: []u8, out_len: &isize) bool => {
     const amt_read = read(stdin_fileno, buf.ptr, buf.len);
     if (amt_read < 0) {
@@ -47,7 +181,8 @@ pub fn readline(buf: []u8, out_len: &isize) bool => {
     return false;
 }
 
-// TODO return ?u64 when we support returning struct byval
+
+// TODO return %u64 when we support errors
 pub fn parse_u64(buf: []u8, radix: u8, result: &u64) bool => {
     var x : u64 = 0;
 
@@ -74,6 +209,7 @@ pub fn parse_u64(buf: []u8, radix: u8, result: &u64) bool => {
 }
 
 fn char_to_digit(c: u8) u8 => {
+    // TODO use switch with range
     if ('0' <= c && c <= '9') {
         c - '0'
     } else if ('A' <= c && c <= 'Z') {
@@ -85,8 +221,6 @@ fn char_to_digit(c: u8) u8 => {
     }
 }
 
-const max_u64_base10_digits: isize = 20;
-
 fn buf_print_i64(out_buf: []u8, x: i64) isize => {
     if (x < 0) {
         out_buf[0] = '-';
@@ -112,7 +246,7 @@ fn buf_print_u64(out_buf: []u8, x: u64) isize => {
 
     const len = buf.len - index;
 
-    @memcpy(out_buf.ptr, &buf[index], len);
+    @memcpy(&out_buf[0], &buf[index], len);
 
     return len;
 }