From a3c97081ca37a6f78732c9a5f2244cef5a11cb07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 7 Jan 2016 03:23:38 -0700 Subject: [PATCH] add ?? maybe unwrapping binary operator add null literal fix number literal / maybe interactions --- CMakeLists.txt | 1 + doc/langref.md | 15 +++--- doc/vim/syntax/zig.vim | 10 ++++ example/guess_number/main.zig | 14 +++--- example/maybe_type/main.zig | 16 +++++++ src/analyze.cpp | 63 ++++++++++++++++++++++-- src/codegen.cpp | 90 +++++++++++++++++++++++++++++++++-- src/parser.cpp | 43 +++++++++++++++-- src/parser.hpp | 2 + src/tokenizer.cpp | 9 ++++ src/tokenizer.hpp | 2 + std/std.zig | 21 ++++++++ std/syscall.zig | 5 ++ test/run_tests.cpp | 19 +++++++- 14 files changed, 287 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 143fe66206..a1a4eaace8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,7 @@ set(ZIG_STD_SRC "${CMAKE_SOURCE_DIR}/std/builtin.zig" "${CMAKE_SOURCE_DIR}/std/std.zig" "${CMAKE_SOURCE_DIR}/std/syscall.zig" + "${CMAKE_SOURCE_DIR}/std/errno.zig" "${CMAKE_SOURCE_DIR}/std/rand.zig" ) diff --git a/doc/langref.md b/doc/langref.md index ac857a563a..140fb97d5f 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -98,7 +98,9 @@ AsmInputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token AsmClobbers: token(Colon) list(token(String), token(Comma)) -AssignmentExpression : BoolOrExpression AssignmentOperator BoolOrExpression | BoolOrExpression +UnwrapMaybeExpression : BoolOrExpression token(DoubleQuestion) BoolOrExpression | BoolOrExpression + +AssignmentExpression : UnwrapMaybeExpression AssignmentOperator UnwrapMaybeExpression | UnwrapMaybeExpression AssignmentOperator : token(Eq) | token(TimesEq) | token(DivEq) | token(ModEq) | token(PlusEq) | token(MinusEq) | token(BitShiftLeftEq) | token(BitShiftRightEq) | token(BitAndEq) | token(BitXorEq) | token(BitOrEq) | token(BoolAndEq) | token(BoolOrEq) @@ -166,7 +168,7 @@ Goto: token(Goto) token(Symbol) GroupedExpression : token(LParen) Expression token(RParen) -KeywordLiteral : token(Unreachable) | token(Void) | token(True) | token(False) +KeywordLiteral : token(Unreachable) | token(Void) | token(True) | token(False) | token(Null) ``` ## Operator Precedence @@ -184,6 +186,7 @@ as == != < > <= >= && || +?? = *= /= %= += -= <<= >>= &= ^= |= &&= ||= ``` @@ -192,7 +195,7 @@ as ### Characters and Strings | Example | Characters | Escapes | Null Term | Type ---------------------------------------------------------------------------------- +----------------|----------|-------------|----------------|-----------|---------- Byte | 'H' | All ASCII | Byte | No | u8 UTF-8 Bytes | "hello" | All Unicode | Byte & Unicode | No | [5; u8] UTF-8 C string | c"hello" | All Unicode | Byte & Unicode | Yes | *const u8 @@ -200,7 +203,7 @@ as ### Byte Escapes | Name ------------------------------------------------ +------|---------------------------------------- \x7F | 8-bit character code (exactly 2 digits) \n | Newline \r | Carriage return @@ -213,13 +216,13 @@ as ### Unicode Escapes | Name ----------------------------------------------------------- +----------|----------------------------------------------- \u{7FFF} | 24-bit Unicode character code (up to 6 digits) ### Numbers Number literals | Example | Exponentiation --------------------------------------------------- +--------------------|-------------|--------------- Decimal integer | 98222 | N/A Hex integer | 0xff | N/A Octal integer | 0o77 | N/A diff --git a/doc/vim/syntax/zig.vim b/doc/vim/syntax/zig.vim index c3a72a5068..ffb08fc388 100644 --- a/doc/vim/syntax/zig.vim +++ b/doc/vim/syntax/zig.vim @@ -14,6 +14,7 @@ syn keyword zigStatement goto break return continue asm syn keyword zigConditional if else match syn keyword zigRepeat while for +syn keyword zigConstant null syn keyword zigKeyword fn unreachable use void syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 string @@ -28,6 +29,12 @@ syn match zigHexNumber display "\<0x[a-fA-F0-9_]\+\%([iu]\%(size\|8\|16\|32\|64\ syn match zigOctNumber display "\<0o[0-7_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\=" syn match zigBinNumber display "\<0b[01_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\=" + +syn match zigCharacterInvalid display contained /b\?'\zs[\n\r\t']\ze'/ +syn match zigCharacterInvalidUnicode display contained /b'\zs[^[:cntrl:][:graph:][:alnum:][:space:]]\ze'/ +syn match zigCharacter /b'\([^\\]\|\\\(.\|x\x\{2}\)\)'/ contains=zigEscape,zigEscapeError,zigCharacterInvalid,zigCharacterInvalidUnicode +syn match zigCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u\x\{4}\|U\x\{8}\|u{\x\{1,6}}\)\)'/ contains=zigEscape,zigEscapeUnicode,zigEscapeError,zigCharacterInvalid + syn match zigShebang /\%^#![^[].*/ syn region zigCommentLine start="//" end="$" contains=zigTodo,@Spell @@ -64,6 +71,9 @@ hi def link zigCommentBlockDoc zigCommentLineDoc hi def link zigTodo Todo hi def link zigStringContinuation Special hi def link zigString String +hi def link zigCharacterInvalid Error +hi def link zigCharacterInvalidUnicode zigCharacterInvalid +hi def link zigCharacter Character hi def link zigEscape Special hi def link zigEscapeUnicode zigEscape hi def link zigEscapeError Error diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index 084674fe93..8a96eb1d00 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -14,7 +14,7 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 { var err : isize; if ({err = os_get_random_bytes(&seed as &u8, #sizeof(u32)); err != #sizeof(u32)}) { // TODO full error message - fprint_str(stderr_fileno, "unable to get random bytes"); + fprint_str(stderr_fileno, "unable to get random bytes\n"); return 1; } @@ -27,11 +27,14 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 { print_u64(answer); print_str("\n"); - return 0; - - /* while (true) { - const line = readline("\nGuess a number between 1 and 100: "); + print_str("\nGuess a number between 1 and 100: "); + var line_buf : [20]u8; + const line = readline(line_buf) ?? { + // TODO full error message + fprint_str(stderr_fileno, "unable to read input\n"); + return 1; + }; if (const guess ?= parse_u64(line)) { if (guess > answer) { @@ -46,5 +49,4 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 { print_str("Invalid number format.\n"); } } - */ } diff --git a/example/maybe_type/main.zig b/example/maybe_type/main.zig index f9126f4d3a..df333821bf 100644 --- a/example/maybe_type/main.zig +++ b/example/maybe_type/main.zig @@ -15,5 +15,21 @@ pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 { print_str("x is none\n"); } + const next_x : ?i32 = null; + + const z = next_x ?? 1234; + + if (z != 1234) { + print_str("BAD\n"); + } + + const final_x : ?i32 = 13; + + const num = final_x ?? unreachable; + + if (num != 13) { + print_str("BAD\n"); + } + return 0; } diff --git a/src/analyze.cpp b/src/analyze.cpp index 8174c2c0d6..9d6fc01e77 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -48,6 +48,7 @@ static AstNode *first_executing_node(AstNode *node) { case NodeTypeUse: case NodeTypeVoid: case NodeTypeBoolLiteral: + case NodeTypeNullLiteral: case NodeTypeIfBoolExpr: case NodeTypeIfVarExpr: case NodeTypeLabel: @@ -358,6 +359,7 @@ static TypeTableEntry *eval_const_expr_bin_op(CodeGen *g, BlockContext *context, case BinOpTypeSub: case BinOpTypeMult: case BinOpTypeDiv: + case BinOpTypeUnwrapMaybe: return g->builtin_types.entry_invalid; case BinOpTypeInvalid: case BinOpTypeAssign: @@ -388,6 +390,8 @@ static TypeTableEntry *eval_const_expr(CodeGen *g, BlockContext *context, case NodeTypeBoolLiteral: out_number_literal->data.x_uint = node->data.bool_literal ? 1 : 0; return node->codegen_node->expr_node.type_entry; + case NodeTypeNullLiteral: + return node->codegen_node->expr_node.type_entry; case NodeTypeBinOpExpr: return eval_const_expr_bin_op(g, context, node, out_number_literal); case NodeTypeCompilerFnType: @@ -877,6 +881,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import, case NodeTypeUnreachable: case NodeTypeVoid: case NodeTypeBoolLiteral: + case NodeTypeNullLiteral: case NodeTypeSymbol: case NodeTypeCastExpr: case NodeTypePrefixOpExpr: @@ -951,6 +956,7 @@ static void preview_types(CodeGen *g, ImportTableEntry *import, AstNode *node) { case NodeTypeUnreachable: case NodeTypeVoid: case NodeTypeBoolLiteral: + case NodeTypeNullLiteral: case NodeTypeSymbol: case NodeTypeCastExpr: case NodeTypePrefixOpExpr: @@ -1018,7 +1024,7 @@ static bool num_lit_fits_in_other_type(CodeGen *g, TypeTableEntry *literal_type, return false; } case TypeTableEntryIdMaybe: - return num_lit_fits_in_other_type(g, literal_type, other_type->data.maybe.child_type); + return false; } zig_unreachable(); } @@ -1133,6 +1139,9 @@ static TypeTableEntry *resolve_type_compatibility(CodeGen *g, BlockContext *cont if (actual_type->id == TypeTableEntryIdNumberLiteral && num_lit_fits_in_other_type(g, actual_type, expected_type)) { + assert(!node->codegen_node->data.num_lit_node.resolved_type || + node->codegen_node->data.num_lit_node.resolved_type == expected_type); + node->codegen_node->data.num_lit_node.resolved_type = expected_type; return expected_type; } @@ -1419,6 +1428,7 @@ static bool is_op_allowed(TypeTableEntry *type, BinOpType op) { case BinOpTypeMult: case BinOpTypeDiv: case BinOpTypeMod: + case BinOpTypeUnwrapMaybe: zig_unreachable(); } zig_unreachable(); @@ -1619,6 +1629,24 @@ static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, return resolve_peer_type_compatibility(g, context, node, op1, op2, lhs_type, rhs_type); } + case BinOpTypeUnwrapMaybe: + { + AstNode *op1 = node->data.bin_op_expr.op1; + AstNode *op2 = node->data.bin_op_expr.op2; + TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, op1); + + if (lhs_type->id == TypeTableEntryIdInvalid) { + return lhs_type; + } else if (lhs_type->id == TypeTableEntryIdMaybe) { + TypeTableEntry *child_type = lhs_type->data.maybe.child_type; + analyze_expression(g, import, context, child_type, op2); + return child_type; + } else { + add_node_error(g, op1, + buf_sprintf("expected maybe type, got '%s'", + buf_ptr(&lhs_type->name))); + } + } case BinOpTypeInvalid: zig_unreachable(); } @@ -1695,6 +1723,27 @@ static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableE return analyze_variable_declaration_raw(g, import, context, node, variable_declaration, false); } +static TypeTableEntry *analyze_null_literal_expr(CodeGen *g, ImportTableEntry *import, + BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node) +{ + assert(node->type == NodeTypeNullLiteral); + + if (expected_type) { + assert(expected_type->id == TypeTableEntryIdMaybe); + + assert(node->codegen_node); + node->codegen_node->data.struct_val_expr_node.type_entry = expected_type; + node->codegen_node->data.struct_val_expr_node.source_node = node; + block_context->struct_val_expr_alloca_list.append(&node->codegen_node->data.struct_val_expr_node); + + return expected_type; + } else { + add_node_error(g, node, + buf_sprintf("unable to determine null type")); + return g->builtin_types.entry_invalid; + } +} + static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node) { @@ -1706,8 +1755,11 @@ static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry } else if (expected_type) { NumberLiteralNode *codegen_num_lit = &node->codegen_node->data.num_lit_node; assert(!codegen_num_lit->resolved_type); - codegen_num_lit->resolved_type = resolve_type_compatibility(g, block_context, node, expected_type, num_lit_type); - return codegen_num_lit->resolved_type; + TypeTableEntry *after_implicit_cast_resolved_type = + resolve_type_compatibility(g, block_context, node, expected_type, num_lit_type); + assert(codegen_num_lit->resolved_type || + after_implicit_cast_resolved_type->id == TypeTableEntryIdInvalid); + return after_implicit_cast_resolved_type; } else { return num_lit_type; } @@ -2174,6 +2226,10 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, return_type = g->builtin_types.entry_bool; break; + case NodeTypeNullLiteral: + return_type = analyze_null_literal_expr(g, import, context, expected_type, node); + break; + case NodeTypeSymbol: { return_type = analyze_variable_name(g, import, context, node, &node->data.symbol); @@ -2491,6 +2547,7 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, case NodeTypeUnreachable: case NodeTypeVoid: case NodeTypeBoolLiteral: + case NodeTypeNullLiteral: case NodeTypeSymbol: case NodeTypeCastExpr: case NodeTypePrefixOpExpr: diff --git a/src/codegen.cpp b/src/codegen.cpp index 3f4262364e..0f4499c48d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -613,6 +613,7 @@ static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node, case BinOpTypeAssign: case BinOpTypeAssignBoolAnd: case BinOpTypeAssignBoolOr: + case BinOpTypeUnwrapMaybe: zig_unreachable(); } zig_unreachable(); @@ -814,6 +815,70 @@ static LLVMValueRef gen_assign_expr(CodeGen *g, AstNode *node) { return gen_assign_raw(g, node, node->data.bin_op_expr.bin_op, target_ref, value, op1_type, op2_type); } +static LLVMValueRef gen_unwrap_maybe(CodeGen *g, AstNode *node, LLVMValueRef maybe_struct_ref) { + add_debug_source_node(g, node); + LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, 0, ""); + // TODO if it's a struct we might not want to load the pointer + return LLVMBuildLoad(g->builder, maybe_field_ptr, ""); +} + +static LLVMValueRef gen_unwrap_maybe_expr(CodeGen *g, AstNode *node) { + assert(node->type == NodeTypeBinOpExpr); + assert(node->data.bin_op_expr.bin_op == BinOpTypeUnwrapMaybe); + + AstNode *op1_node = node->data.bin_op_expr.op1; + AstNode *op2_node = node->data.bin_op_expr.op2; + + LLVMValueRef maybe_struct_ref = gen_expr(g, op1_node); + + add_debug_source_node(g, node); + LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_struct_ref, 1, ""); + LLVMValueRef cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, ""); + + LLVMBasicBlockRef non_null_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeNonNull"); + LLVMBasicBlockRef null_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeNull"); + LLVMBasicBlockRef end_block; + + bool non_null_reachable = get_expr_type(op1_node)->id != TypeTableEntryIdUnreachable; + bool null_reachable = get_expr_type(op2_node)->id != TypeTableEntryIdUnreachable; + bool end_reachable = non_null_reachable || null_reachable; + if (end_reachable) { + end_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "MaybeEnd"); + } + + LLVMBuildCondBr(g->builder, cond_value, non_null_block, null_block); + + LLVMPositionBuilderAtEnd(g->builder, non_null_block); + LLVMValueRef non_null_result = gen_unwrap_maybe(g, op1_node, maybe_struct_ref); + if (non_null_reachable) { + add_debug_source_node(g, node); + LLVMBuildBr(g->builder, end_block); + } + + LLVMPositionBuilderAtEnd(g->builder, null_block); + LLVMValueRef null_result = gen_expr(g, op2_node); + if (null_reachable) { + add_debug_source_node(g, node); + LLVMBuildBr(g->builder, end_block); + } + + if (end_reachable) { + LLVMPositionBuilderAtEnd(g->builder, end_block); + if (null_reachable) { + add_debug_source_node(g, node); + LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(non_null_result), ""); + LLVMValueRef incoming_values[2] = {non_null_result, null_result}; + LLVMBasicBlockRef incoming_blocks[2] = {non_null_block, null_block}; + LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2); + return phi; + } else { + return non_null_result; + } + } + + return nullptr; +} + static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) { switch (node->data.bin_op_expr.bin_op) { case BinOpTypeInvalid: @@ -843,6 +908,8 @@ static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) { case BinOpTypeCmpLessOrEq: case BinOpTypeCmpGreaterOrEq: return gen_cmp_expr(g, node); + case BinOpTypeUnwrapMaybe: + return gen_unwrap_maybe_expr(g, node); case BinOpTypeBinOr: case BinOpTypeBinXor: case BinOpTypeBinAnd: @@ -1124,6 +1191,22 @@ static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) { return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, ""); } +static LLVMValueRef gen_null_literal(CodeGen *g, AstNode *node) { + assert(node->type == NodeTypeNullLiteral); + + TypeTableEntry *type_entry = get_expr_type(node); + assert(type_entry->id == TypeTableEntryIdMaybe); + + LLVMValueRef tmp_struct_ptr = node->codegen_node->data.struct_val_expr_node.ptr; + + add_debug_source_node(g, node); + LLVMValueRef field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, 1, ""); + LLVMValueRef null_value = LLVMConstNull(LLVMInt1Type()); + LLVMBuildStore(g->builder, null_value, field_ptr); + + return tmp_struct_ptr; +} + static LLVMValueRef gen_struct_val_expr(CodeGen *g, AstNode *node) { assert(node->type == NodeTypeStructValueExpr); @@ -1242,10 +1325,7 @@ static LLVMValueRef gen_var_decl_raw(CodeGen *g, AstNode *source_node, AstNodeVa LLVMValueRef value; if (unwrap_maybe) { assert(var_decl->expr); - add_debug_source_node(g, source_node); - LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, *init_value, 0, ""); - // TODO if it's a struct we might not want to load the pointer - value = LLVMBuildLoad(g->builder, maybe_field_ptr, ""); + value = gen_unwrap_maybe(g, source_node, *init_value); } else { value = *init_value; } @@ -1375,6 +1455,8 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { return LLVMConstAllOnes(LLVMInt1Type()); else return LLVMConstNull(LLVMInt1Type()); + case NodeTypeNullLiteral: + return gen_null_literal(g, node); case NodeTypeIfBoolExpr: return gen_if_bool_expr(g, node); case NodeTypeIfVarExpr: diff --git a/src/parser.cpp b/src/parser.cpp index fbada22453..1f78424fb1 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -48,6 +48,7 @@ static const char *bin_op_str(BinOpType bin_op) { case BinOpTypeAssignBitOr: return "|="; case BinOpTypeAssignBoolAnd: return "&&="; case BinOpTypeAssignBoolOr: return "||="; + case BinOpTypeUnwrapMaybe: return "??"; } zig_unreachable(); } @@ -117,6 +118,8 @@ const char *node_type_str(NodeType node_type) { return "Void"; case NodeTypeBoolLiteral: return "BoolLiteral"; + case NodeTypeNullLiteral: + return "NullLiteral"; case NodeTypeIfBoolExpr: return "IfBoolExpr"; case NodeTypeIfVarExpr: @@ -349,6 +352,9 @@ void ast_print(AstNode *node, int indent) { case NodeTypeBoolLiteral: fprintf(stderr, "%s '%s'\n", node_type_str(node->type), node->data.bool_literal ? "true" : "false"); break; + case NodeTypeNullLiteral: + fprintf(stderr, "%s\n", node_type_str(node->type)); + break; case NodeTypeIfBoolExpr: fprintf(stderr, "%s\n", node_type_str(node->type)); if (node->data.if_bool_expr.condition) @@ -1280,6 +1286,7 @@ static AstNode *ast_parse_struct_val_expr(ParseContext *pc, int *token_index) { /* PrimaryExpression : token(Number) | token(String) | token(CharLiteral) | KeywordLiteral | GroupedExpression | Goto | token(Break) | token(Continue) | BlockExpression | token(Symbol) | StructValueExpression | CompilerFnType +KeywordLiteral : token(Unreachable) | token(Void) | token(True) | token(False) | token(Null) */ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -1317,6 +1324,10 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool node->data.bool_literal = false; *token_index += 1; return node; + } else if (token->id == TokenIdKeywordNull) { + AstNode *node = ast_create_node(pc, NodeTypeNullLiteral, token); + *token_index += 1; + return node; } else if (token->id == TokenIdSymbol) { Token *next_token = &pc->tokens->at(*token_index + 1); @@ -2045,10 +2056,36 @@ static BinOpType ast_parse_ass_op(ParseContext *pc, int *token_index, bool manda } /* -AssignmentExpression : BoolOrExpression AssignmentOperator BoolOrExpression | BoolOrExpression +UnwrapMaybeExpression : BoolOrExpression token(DoubleQuestion) BoolOrExpression | BoolOrExpression +*/ +static AstNode *ast_parse_unwrap_maybe_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; + } + + *token_index += 1; + + 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; + + return node; +} + +/* +AssignmentExpression : UnwrapMaybeExpression AssignmentOperator UnwrapMaybeExpression | UnwrapMaybeExpression */ static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mandatory) { - AstNode *lhs = ast_parse_bool_or_expr(pc, token_index, mandatory); + AstNode *lhs = ast_parse_unwrap_maybe_expr(pc, token_index, mandatory); if (!lhs) return nullptr; @@ -2057,7 +2094,7 @@ static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mand if (ass_op == BinOpTypeInvalid) return lhs; - AstNode *rhs = ast_parse_bool_or_expr(pc, token_index, true); + AstNode *rhs = ast_parse_unwrap_maybe_expr(pc, token_index, true); AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token); node->data.bin_op_expr.op1 = lhs; diff --git a/src/parser.hpp b/src/parser.hpp index 9f64e777a7..4edeb67ed4 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -45,6 +45,7 @@ enum NodeType { NodeTypeUse, NodeTypeVoid, NodeTypeBoolLiteral, + NodeTypeNullLiteral, NodeTypeIfBoolExpr, NodeTypeIfVarExpr, NodeTypeWhileExpr, @@ -161,6 +162,7 @@ enum BinOpType { BinOpTypeMult, BinOpTypeDiv, BinOpTypeMod, + BinOpTypeUnwrapMaybe, }; struct AstNodeBinOpExpr { diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 7a783d952d..7aafed59ce 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -241,6 +241,8 @@ static void end_token(Tokenize *t) { t->cur_tok->id = TokenIdKeywordContinue; } else if (mem_eql_str(token_mem, token_len, "break")) { t->cur_tok->id = TokenIdKeywordBreak; + } else if (mem_eql_str(token_mem, token_len, "null")) { + t->cur_tok->id = TokenIdKeywordNull; } t->cur_tok = nullptr; @@ -418,6 +420,11 @@ void tokenize(Buf *buf, Tokenization *out) { break; case TokenizeStateSawQuestionMark: switch (c) { + case '?': + t.cur_tok->id = TokenIdDoubleQuestion; + end_token(&t); + t.state = TokenizeStateStart; + break; case '=': t.cur_tok->id = TokenIdMaybeAssign; end_token(&t); @@ -1002,6 +1009,7 @@ static const char * token_name(Token *token) { case TokenIdKeywordWhile: return "While"; case TokenIdKeywordContinue: return "Continue"; case TokenIdKeywordBreak: return "Break"; + case TokenIdKeywordNull: return "Null"; case TokenIdLParen: return "LParen"; case TokenIdRParen: return "RParen"; case TokenIdComma: return "Comma"; @@ -1052,6 +1060,7 @@ static const char * token_name(Token *token) { case TokenIdDot: return "Dot"; case TokenIdEllipsis: return "Ellipsis"; case TokenIdMaybe: return "Maybe"; + case TokenIdDoubleQuestion: return "DoubleQuestion"; case TokenIdMaybeAssign: return "MaybeAssign"; } return "(invalid token)"; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index cf4ba57a71..2169db9c1b 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -35,6 +35,7 @@ enum TokenId { TokenIdKeywordWhile, TokenIdKeywordContinue, TokenIdKeywordBreak, + TokenIdKeywordNull, TokenIdLParen, TokenIdRParen, TokenIdComma, @@ -85,6 +86,7 @@ enum TokenId { TokenIdDot, TokenIdEllipsis, TokenIdMaybe, + TokenIdDoubleQuestion, TokenIdMaybeAssign, }; diff --git a/std/std.zig b/std/std.zig index a53dbb6027..333896cadc 100644 --- a/std/std.zig +++ b/std/std.zig @@ -1,5 +1,6 @@ use "syscall.zig"; +const stdin_fileno : isize = 0; const stdout_fileno : isize = 1; const stderr_fileno : isize = 2; @@ -38,6 +39,26 @@ pub fn print_i64(x: i64) -> isize { return write(stdout_fileno, buf.ptr, len); } +/* +// TODO error handling +pub fn readline(buf: []u8) -> ?[]u8 { + var index = 0; + while (index < buf.len) { + // TODO unknown size array indexing operator + const err = read(stdin_fileno, &buf.ptr[index], 1); + if (err != 0) { + return null; + } + // TODO unknown size array indexing operator + if (buf.ptr[index] == '\n') { + return buf[0...index + 1]; + } + index += 1; + } + return null; +} +*/ + fn digit_to_char(digit: u64) -> u8 { '0' + (digit as u8) } diff --git a/std/syscall.zig b/std/syscall.zig index 1bcbcdbe31..97542363cb 100644 --- a/std/syscall.zig +++ b/std/syscall.zig @@ -1,3 +1,4 @@ +const SYS_read : usize = 0; const SYS_write : usize = 1; const SYS_exit : usize = 60; const SYS_getrandom : usize = 318; @@ -16,6 +17,10 @@ fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) -> usize { : "rcx", "r11") } +pub fn read(fd: isize, buf: &u8, count: usize) -> isize { + syscall3(SYS_read, fd as usize, buf as usize, count) as isize +} + pub fn write(fd: isize, buf: &const u8, count: usize) -> isize { syscall3(SYS_write, fd as usize, buf as usize, count) as isize } diff --git a/test/run_tests.cpp b/test/run_tests.cpp index c41832e47d..1038983d2e 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -710,7 +710,7 @@ pub fn main(argc : isize, argv : &&u8, env : &&u8) -> i32 { add_simple_case("maybe type", R"SOURCE( use "std.zig"; -pub fn main(argc : isize, argv : &&u8, env : &&u8) -> i32 { +pub fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 { const x : ?bool = true; if (const y ?= x) { @@ -722,6 +722,23 @@ pub fn main(argc : isize, argv : &&u8, env : &&u8) -> i32 { } else { print_str("x is none\n"); } + + const next_x : ?i32 = null; + + const z = next_x ?? 1234; + + if (z != 1234) { + print_str("BAD\n"); + } + + const final_x : ?i32 = 13; + + const num = final_x ?? unreachable; + + if (num != 13) { + print_str("BAD\n"); + } + return 0; } )SOURCE", "x is true\n");