diff --git a/lib/std/collections/map.c3 b/lib/std/collections/map.c3 index 7b9f43942..03c66e4ee 100644 --- a/lib/std/collections/map.c3 +++ b/lib/std/collections/map.c3 @@ -112,7 +112,7 @@ fn Value! HashMap.get(HashMap* map, Key key) @operator([]) fn bool HashMap.has_key(HashMap* map, Key key) { - return try(map.get_ref(key)); + return try? map.get_ref(key); } fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=) diff --git a/lib/std/core/private/main_stub.c3 b/lib/std/core/private/main_stub.c3 index 7635ce198..569b02ed6 100644 --- a/lib/std/core/private/main_stub.c3 +++ b/lib/std/core/private/main_stub.c3 @@ -7,7 +7,7 @@ macro usz _strlen(ptr) @private return len; } -macro int @main_to_err_main(#m, int, char**) => catch(#m()) ? 1 : 0; +macro int @main_to_err_main(#m, int, char**) => catch? #m() ? 1 : 0; macro int @main_to_int_main(#m, int, char**) => #m(); macro int @main_to_void_main(#m, int, char**) { @@ -32,7 +32,7 @@ macro int @main_to_err_main_args(#m, int argc, char** argv) { String[] list = args_to_strings(argc, argv); defer free(list.ptr); - return catch(#m(list)) ? 1 : 0; + return catch? #m(list) ? 1 : 0; } macro int @main_to_int_main_args(#m, int argc, char** argv) @@ -79,7 +79,7 @@ macro void release_wargs(String[] list) @private free(list.ptr); } -macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => catch(#m()) ? 1 : 0; +macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => catch? #m() ? 1 : 0; macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m(); macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) { @@ -91,7 +91,7 @@ macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd { String[] args = win_command_line_to_strings(cmd_line); defer release_wargs(args); - return catch(#m(args)) ? 1 : 0; + return catch? #m(args) ? 1 : 0; } macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd) @@ -113,7 +113,7 @@ macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd) { String[] args = win_command_line_to_strings(cmd_line); defer release_wargs(args); - return catch(#m(handle, args, show_cmd)) ? 1 : 0; + return catch? #m(handle, args, show_cmd) ? 1 : 0; } macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd) @@ -135,7 +135,7 @@ macro int @wmain_to_err_main_args(#m, int argc, Char16** argv) { String[] args = wargs_strings(argc, argv); defer release_wargs(args); - return catch(#m(args)) ? 1 : 0; + return catch? #m(args) ? 1 : 0; } macro int @wmain_to_int_main_args(#m, int argc, Char16** argv) diff --git a/lib/std/io/io.c3 b/lib/std/io/io.c3 index 0a09ac671..f61aeb4fd 100644 --- a/lib/std/io/io.c3 +++ b/lib/std/io/io.c3 @@ -55,18 +55,18 @@ macro void print(x) var $Type = $typeof(x); $switch ($Type): $case String: - catch(stdout().print(x)); + catch? stdout().print(x); $case ZString: - catch(stdout().print(x.as_str())); + catch? stdout().print(x.as_str()); $case DString: - catch(stdout().print(x.str())); + catch? stdout().print(x.str()); $case VarString: - catch(stdout().print(x.str())); + catch? stdout().print(x.str()); $default: $if (@convertible(x, String)): - catch(stdout().print((String)x)); + catch? stdout().print((String)x); $else: - catch(stdout().printf("%s", x)); + catch? stdout().printf("%s", x); $endif; $endswitch; } @@ -76,18 +76,18 @@ macro void printn(x = "") var $Type = $typeof(x); $switch ($Type): $case String: - catch(stdout().printn(x)); + catch? stdout().printn(x); $case ZString: - catch(stdout().printn(x.as_str())); + catch? stdout().printn(x.as_str()); $case DString: - catch(stdout().printn(x.str())); + catch? stdout().printn(x.str()); $case VarString: - catch(stdout().printn(x.str())); + catch? stdout().printn(x.str()); $default: $if (@convertible(x, String)): - catch(stdout().printn((String)x)); + catch? stdout().printn((String)x); $else: - catch(stdout().printfn("%s", x)); + catch? stdout().printfn("%s", x); $endif; $endswitch; } diff --git a/lib/std/io/os/fileinfo_darwin.c3 b/lib/std/io/os/fileinfo_darwin.c3 index 24fa7f836..0a00144f7 100644 --- a/lib/std/io/os/fileinfo_darwin.c3 +++ b/lib/std/io/os/fileinfo_darwin.c3 @@ -51,19 +51,19 @@ fn usz! native_file_size(String path) fn bool native_file_or_dir_exists(String path) { Darwin64Stat stat; - return try(read_stat(&stat, path)); + return try? read_stat(&stat, path); } fn bool native_is_file(String path) { Darwin64Stat stat; - return try(read_stat(&stat, path)) && (stat.st_mode & S_IFREG); + return try? read_stat(&stat, path) && (stat.st_mode & S_IFREG); } fn bool native_is_dir(String path) { Darwin64Stat stat; - return try(read_stat(&stat, path)) && (stat.st_mode & S_IFDIR); + return try? read_stat(&stat, path) && (stat.st_mode & S_IFDIR); } fn void! read_stat(Darwin64Stat* stat, String path) @local diff --git a/lib/std/io/os/fileinfo_other.c3 b/lib/std/io/os/fileinfo_other.c3 index 419ccc1c0..a42aa1f93 100644 --- a/lib/std/io/os/fileinfo_other.c3 +++ b/lib/std/io/os/fileinfo_other.c3 @@ -75,7 +75,7 @@ fn bool native_is_file(String path) { File! f = file::open(path, "r"); defer (void)f.close(); - return try(f); + return try? f; } fn bool native_is_dir(String path) diff --git a/lib/std/io/os/fileinfo_win32.c3 b/lib/std/io/os/fileinfo_win32.c3 index 241292b3c..d0948df7c 100644 --- a/lib/std/io/os/fileinfo_win32.c3 +++ b/lib/std/io/os/fileinfo_win32.c3 @@ -30,7 +30,7 @@ fn bool native_is_file(String path) { File! f = file::open(path, "r"); defer (void)f.close(); - return try(f); + return try? f; } fn bool native_is_dir(String path) diff --git a/resources/examples/contextfree/boolerr.c3 b/resources/examples/contextfree/boolerr.c3 index be4f56619..1e44d6f51 100644 --- a/resources/examples/contextfree/boolerr.c3 +++ b/resources/examples/contextfree/boolerr.c3 @@ -133,7 +133,7 @@ fn void main() bool! has_title = readWhetherTitleNonEmpty(url); // This looks a bit less than elegant, but as you see it's mostly due to having to // use printf here. - io::printf(" Has title: %s vs %s\n", bool_to_string(has_title) ?? catch(has_title).nameof, has_title ?? false); + io::printf(" Has title: %s vs %s\n", bool_to_string(has_title) ?? (catch? has_title).nameof, has_title ?? false); }; dynamic_arena.reset(); } diff --git a/resources/examples/contextfree/guess_number.c3 b/resources/examples/contextfree/guess_number.c3 index 1f4c28b55..6a1565a55 100644 --- a/resources/examples/contextfree/guess_number.c3 +++ b/resources/examples/contextfree/guess_number.c3 @@ -44,7 +44,7 @@ fn int! askGuessMulti(int high) while (true) { int! result = askGuess(high); - if (catch(result) == InputResult.NOT_AN_INT) + if (catch? result == InputResult.NOT_AN_INT) { libc::printf("I didn't understand that.\n"); err_count++; diff --git a/src/compiler/ast.c b/src/compiler/ast.c index de14260e2..362e321f0 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -16,7 +16,7 @@ Decl *poisoned_decl = &poison_decl; Expr *poisoned_expr = &poison_expr; Ast *poisoned_ast = &poison_ast; - +// Standard decl creation, used by compile time constructs, since they have no need for neither type nor name. Decl *decl_new_ct(DeclKind kind, SourceSpan span) { Decl *decl = decl_calloc(); @@ -25,6 +25,7 @@ Decl *decl_new_ct(DeclKind kind, SourceSpan span) return decl; } +// Named declaration without type. Decl *decl_new(DeclKind decl_kind, const char *name, SourceSpan span) { Decl *decl = decl_calloc(); @@ -34,11 +35,11 @@ Decl *decl_new(DeclKind decl_kind, const char *name, SourceSpan span) return decl; } +// Check if local or parameter $foo/$Foo bool decl_is_ct_var(Decl *decl) { if (decl->decl_kind != DECL_VAR) return false; return decl_var_kind_is_ct(decl->var.kind); - UNREACHABLE; } Decl *decl_new_with_type(const char *name, SourceSpan loc, DeclKind decl_type) diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index dcbb57f50..097991e27 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1874,6 +1874,7 @@ extern const char *kw_at_param; extern const char *kw_at_pure; extern const char *kw_at_require; extern const char *kw_at_return; +extern const char *kw_catch_question; extern const char *kw_check_assign; extern const char *kw_deprecated; extern const char *kw_distinct; @@ -1897,6 +1898,7 @@ extern const char *kw_ptr; extern const char *kw_pure; extern const char *kw_return; extern const char *kw_std; +extern const char *kw_try_question; extern const char *kw_type; extern const char *kw_winmain; extern const char *kw_wmain; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 983f991b6..cdd14cfee 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -345,7 +345,7 @@ typedef enum PREC_BIT, // ^ | & PREC_SHIFT, // << >> PREC_MULTIPLICATIVE, // * / % - PREC_UNARY, // ! - + ~ * & prefix ++/-- try catch (type) + PREC_UNARY, // ! - + ~ * & prefix ++/-- try? catch? (type) PREC_CALL, // . () [] postfix ++/-- PREC_MACRO, PREC_FIRST = PREC_MACRO @@ -455,31 +455,6 @@ typedef enum TOKEN_SHL_ASSIGN, // <<= TOKEN_SHR_ASSIGN, // >>= - // Basic types names - TOKEN_VOID, - TOKEN_BOOL, - TOKEN_CHAR, - TOKEN_DOUBLE, - TOKEN_FLOAT, - TOKEN_FLOAT16, - TOKEN_INT128, - TOKEN_ICHAR, - TOKEN_INT, - TOKEN_IPTR, - TOKEN_ISZ, - TOKEN_LONG, - TOKEN_SHORT, - TOKEN_UINT128, - TOKEN_UINT, - TOKEN_ULONG, - TOKEN_UPTR, - TOKEN_USHORT, - TOKEN_USZ, - TOKEN_FLOAT128, - TOKEN_VARIANT, - TOKEN_ANYERR, - TOKEN_TYPEID, - // Literals. TOKEN_IDENT, // Any normal ident. TOKEN_CONST_IDENT, // Any purely uppercase ident, @@ -508,6 +483,32 @@ typedef enum TOKEN_DOC_COMMENT, // Doc Comment start + // Basic types names + TOKEN_VOID, + TOKEN_FIRST_KEYWORD = TOKEN_VOID, + TOKEN_BOOL, + TOKEN_CHAR, + TOKEN_DOUBLE, + TOKEN_FLOAT, + TOKEN_FLOAT16, + TOKEN_INT128, + TOKEN_ICHAR, + TOKEN_INT, + TOKEN_IPTR, + TOKEN_ISZ, + TOKEN_LONG, + TOKEN_SHORT, + TOKEN_UINT128, + TOKEN_UINT, + TOKEN_ULONG, + TOKEN_UPTR, + TOKEN_USHORT, + TOKEN_USZ, + TOKEN_FLOAT128, + TOKEN_VARIANT, + TOKEN_ANYERR, + TOKEN_TYPEID, + // Keywords TOKEN_ALIAS, // Reserved TOKEN_AS, @@ -517,6 +518,7 @@ typedef enum TOKEN_BREAK, TOKEN_CASE, TOKEN_CATCH, + TOKEN_CATCH_QUESTION, TOKEN_CONST, TOKEN_CONTINUE, TOKEN_DEFINE, @@ -546,6 +548,7 @@ typedef enum TOKEN_SWITCH, TOKEN_TRUE, TOKEN_TRY, + TOKEN_TRY_QUESTION, TOKEN_TYPEDEF, TOKEN_UNION, TOKEN_VAR, @@ -586,6 +589,7 @@ typedef enum TOKEN_CT_VAARG, // $vaarg, TOKEN_CT_VAEXPR, // $vaexpr, TOKEN_CT_VASPLAT, // $vasplat, + TOKEN_LAST_KEYWORD = TOKEN_CT_VASPLAT, TOKEN_DOCS_START, // /** TOKEN_DOCS_END, // */ (may start with an arbitrary number of `*` TOKEN_DOC_DIRECTIVE, // Any doc directive diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index 5b71d2e42..51bfb083d 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -367,7 +367,27 @@ static inline bool scan_ident(Lexer *lexer, TokenType normal, TokenType const_to return add_error_token(lexer, "An identifier may not consist of only '_' characters."); } const char* interned_string = symtab_add(lexer->lexing_start, len, hash, &type); - if (type == TOKEN_RETURN && lexer->mode == LEX_DOCS) type = TOKEN_IDENT; + switch (type) + { + case TOKEN_RETURN: + if (lexer->mode == LEX_DOCS) type = TOKEN_IDENT; + break; + case TOKEN_TRY: + if (peek(lexer) == '?') + { + next(lexer); + return return_token(lexer, TOKEN_TRY_QUESTION, kw_try_question); + } + break; + case TOKEN_CATCH: + if (peek(lexer) == '?') + { + next(lexer); + return return_token(lexer, TOKEN_CATCH_QUESTION, kw_catch_question); + } + default: + break; + } return return_token(lexer, type, interned_string); } diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 5753f5d7b..192fff415 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -87,7 +87,7 @@ typedef struct GenContext_ const char *ir_filename; const char *object_filename; const char *asm_filename; - // The recipient of the error value in a catch(err = ...) expression. + // The recipient of the error value in a catch err = ... expression. LLVMValueRef opt_var; LLVMTypeRef bool_type; LLVMTypeRef byte_type; diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 6c61ee684..f8e825001 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -19,9 +19,15 @@ typedef struct extern ParseRule rules[TOKEN_EOF + 1]; +/** + * maybe_range ::= range | ('^'? INTEGER) + * range ::= dot_range | colon_range + * dot_range ::= ('^'? INTEGER)? .. ('^'? INTEGER)? + * colon_range ::= ('^'? INTEGER)? : ('^'? INTEGER)? + */ bool parse_range(ParseContext *c, Range *range) { - // Not range with missing entry + // Insert zero if missing if (tok_is(c, TOKEN_DOTDOT) || tok_is(c, TOKEN_COLON)) { // ..123 and :123 @@ -30,17 +36,20 @@ bool parse_range(ParseContext *c, Range *range) } else { - // Might be ^ prefix + // Parse ^123 and 123 range->start_from_end = try_consume(c, TOKEN_BIT_XOR); ASSIGN_EXPRID_OR_RET(range->start, parse_expr(c), false); } + // Check if .. or : bool is_len_range = range->is_len = try_consume(c, TOKEN_COLON); if (!is_len_range && !try_consume(c, TOKEN_DOTDOT)) { + // Otherwise this is not a range. range->is_range = false; return true; } range->is_range = true; + // ] ) are the possible ways to end a range. if (tok_is(c, TOKEN_RBRACKET) || tok_is(c, TOKEN_RPAREN)) { @@ -54,61 +63,67 @@ bool parse_range(ParseContext *c, Range *range) return true; } +/** + * Parse lhs [op] [rhs] + * This will return lhs if no candidate is found. + */ inline Expr *parse_precedence_with_left_side(ParseContext *c, Expr *left_side, Precedence precedence) { while (1) { TokenType tok = c->tok; Precedence token_precedence = rules[tok].precedence; + // See if the operator precedence is greater than the last, if so exit. + // Note that if the token is not an operator then token_precedence = 0 if (precedence > token_precedence) break; + // LHS may be poison. if (!expr_ok(left_side)) return left_side; + // See if there is a rule for infix. ParseFn infix_rule = rules[tok].infix; + // Otherwise we ran into a symbol that can't appear in this position. if (!infix_rule) { - SEMA_ERROR_HERE("An expression was expected."); + SEMA_ERROR_HERE("'%s' can't appear in this position, did you forget something before the operator?", token_type_to_string(tok)); return poisoned_expr; } + // The rule exists, so run it. left_side = infix_rule(c, left_side); } return left_side; } +/** + * Parse an expression in any position. + */ static Expr *parse_precedence(ParseContext *c, Precedence precedence) { // Get the rule for the previous token. ParseFn prefix_rule = rules[c->tok].prefix; - if (prefix_rule == NULL) + // No prefix rule => either it's not an expression + // or it is a token that can't be in a prefix position. + if (!prefix_rule) { SEMA_ERROR_HERE("An expression was expected."); return poisoned_expr; } - + // Get the expression Expr *left_side = prefix_rule(c, NULL); + // Exit if it's an error. if (!expr_ok(left_side)) return left_side; + // Now parse the (optional) right hand side. return parse_precedence_with_left_side(c, left_side, precedence); } +/** + * Since generic parameters can't be longer expressions, we require they have + * ADDITIVE precedence or higher. This excludes <> and we can use those safely. + */ Expr *parse_generic_parameter(ParseContext *c) { return parse_precedence(c, PREC_ADDITIVE); } -Expr *parse_expr_or_initializer_list(ParseContext *c) -{ - return parse_expr(c); -} - -static inline bool next_is_try_unwrap(ParseContext *c) -{ - return tok_is(c, TOKEN_TRY) && peek(c) != TOKEN_LPAREN; -} - -static inline bool next_is_catch_unwrap(ParseContext *c) -{ - return tok_is(c, TOKEN_CATCH) && peek(c) != TOKEN_LPAREN; -} - static inline Expr *parse_for_try_expr(ParseContext *c) { return parse_precedence(c, PREC_AND + 1); @@ -204,7 +219,7 @@ static inline Expr *parse_try_unwrap_chain(ParseContext *c) vec_add(unwraps, first_unwrap); while (try_consume(c, TOKEN_AND)) { - if (next_is_try_unwrap(c)) + if (tok_is(c, TOKEN_TRY)) { ASSIGN_EXPR_OR_RET(Expr * expr, parse_try_unwrap(c), poisoned_expr); vec_add(unwraps, expr); @@ -221,7 +236,7 @@ static inline Expr *parse_try_unwrap_chain(ParseContext *c) Expr *parse_assert_expr(ParseContext *c) { - if (next_is_try_unwrap(c)) + if (tok_is(c, TOKEN_TRY)) { return parse_try_unwrap_chain(c); } @@ -239,7 +254,7 @@ Expr *parse_cond(ParseContext *c) decl_expr->cond_expr = NULL; while (1) { - if (next_is_try_unwrap(c)) + if (tok_is(c, TOKEN_TRY)) { ASSIGN_EXPR_OR_RET(Expr * try_unwrap, parse_try_unwrap_chain(c), poisoned_expr); vec_add(decl_expr->cond_expr, try_unwrap); @@ -250,9 +265,9 @@ Expr *parse_cond(ParseContext *c) } break; } - if (next_is_catch_unwrap(c)) + if (tok_is(c, TOKEN_CATCH)) { - ASSIGN_EXPR_OR_RET(Expr * catch_unwrap, parse_catch_unwrap(c), poisoned_expr); + ASSIGN_EXPR_OR_RET(Expr* catch_unwrap, parse_catch_unwrap(c), poisoned_expr); vec_add(decl_expr->cond_expr, catch_unwrap); if (tok_is(c, TOKEN_COMMA)) { @@ -417,7 +432,7 @@ bool parse_arg_list(ParseContext *c, Expr ***result, TokenType param_end, bool * CONSUME_OR_RET(TOKEN_EQ, false); // Now parse the rest - ASSIGN_EXPR_OR_RET(expr->designator_expr.value, parse_expr_or_initializer_list(c), false); + ASSIGN_EXPR_OR_RET(expr->designator_expr.value, parse_expr(c), false); RANGE_EXTEND_PREV(expr); } @@ -431,7 +446,7 @@ bool parse_arg_list(ParseContext *c, Expr ***result, TokenType param_end, bool * { *splat = try_consume(c, TOKEN_ELLIPSIS); } - ASSIGN_EXPR_OR_RET(expr, parse_expr_or_initializer_list(c), false); + ASSIGN_EXPR_OR_RET(expr, parse_expr(c), false); } vec_add(*result, expr); if (!try_consume(c, TOKEN_COMMA)) @@ -1059,24 +1074,10 @@ static Expr *parse_identifier_starting_expression(ParseContext *c, Expr *left) static Expr *parse_try_expr(ParseContext *c, Expr *left) { assert(!left && "Unexpected left hand side"); - bool is_try = tok_is(c, TOKEN_TRY); + bool is_try = tok_is(c, TOKEN_TRY_QUESTION); Expr *try_expr = EXPR_NEW_TOKEN(is_try ? EXPR_TRY : EXPR_CATCH); advance(c); - if (!try_consume(c, TOKEN_LPAREN)) - { - if (is_try) - { - SEMA_ERROR(try_expr, "An unwrapping 'try' can only occur as the last element of a conditional, did you want 'try(expr)'?"); - return poisoned_expr; - } - else - { - SEMA_ERROR(try_expr, "An unwrapping 'catch' can only occur as the last element of a conditional, did you want 'catch(expr)'?"); - return poisoned_expr; - } - } - ASSIGN_EXPR_OR_RET(try_expr->inner_expr, parse_expr(c), poisoned_expr); - CONSUME_OR_RET(TOKEN_RPAREN, poisoned_expr); + ASSIGN_EXPR_OR_RET(try_expr->inner_expr, parse_precedence(c, PREC_UNARY), poisoned_expr); RANGE_EXTEND_PREV(try_expr); return try_expr; } @@ -1764,8 +1765,8 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_MINUSMINUS] = { parse_unary_expr, parse_post_unary, PREC_CALL }, [TOKEN_LPAREN] = { parse_grouping_expr, parse_call_expr, PREC_CALL }, [TOKEN_LBRAPIPE] = { parse_expr_block, NULL, PREC_NONE }, - [TOKEN_TRY] = { parse_try_expr, NULL, PREC_NONE }, - [TOKEN_CATCH] = { parse_try_expr, NULL, PREC_NONE }, + [TOKEN_TRY_QUESTION] = { parse_try_expr, NULL, PREC_NONE }, + [TOKEN_CATCH_QUESTION] = { parse_try_expr, NULL, PREC_NONE }, [TOKEN_BANGBANG] = { NULL, parse_force_unwrap_expr, PREC_CALL }, [TOKEN_LBRACKET] = { NULL, parse_subscript_expr, PREC_CALL }, [TOKEN_MINUS] = { parse_unary_expr, parse_binary, PREC_ADDITIVE }, diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 04d9c67e1..67215a233 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -470,20 +470,10 @@ static inline Ast* parse_defer_stmt(ParseContext *c) if (try_consume(c, TOKEN_TRY)) { defer_stmt->defer_stmt.is_try = true; - if (tok_is(c, TOKEN_LPAREN)) - { - SEMA_ERROR_HERE("Expected a '{' or a non-'try' statement after 'defer try'."); - return poisoned_ast; - } } else if (try_consume(c, TOKEN_CATCH)) { defer_stmt->defer_stmt.is_catch = true; - if (tok_is(c, TOKEN_LPAREN)) - { - SEMA_ERROR_HERE("Expected a '{' or a non-'catch' statement after 'defer catch'."); - return poisoned_ast; - } } ASSIGN_ASTID_OR_RET(defer_stmt->defer_stmt.body, parse_stmt(c), poisoned_ast); return defer_stmt; @@ -1319,8 +1309,8 @@ Ast *parse_stmt(ParseContext *c) case TOKEN_CT_CHECKS: case TOKEN_CT_STRINGIFY: case TOKEN_CT_EVAL: - case TOKEN_TRY: - case TOKEN_CATCH: + case TOKEN_TRY_QUESTION: + case TOKEN_CATCH_QUESTION: case TOKEN_BYTES: case TOKEN_BUILTIN: case TOKEN_CT_VACOUNT: @@ -1414,6 +1404,11 @@ Ast *parse_stmt(ParseContext *c) SEMA_ERROR_HERE("Mismatched '%s' found.", token_type_to_string(c->tok)); advance(c); return poisoned_ast; + case TOKEN_TRY: + case TOKEN_CATCH: + SEMA_ERROR_HERE("'%s' can only be used when unwrapping an optional, did you mean '%s?'?", token_type_to_string(c->tok), token_type_to_string(c->tok)); + advance(c); + return poisoned_ast; case TOKEN_EOS: advance(c); return ast_new_curr(c, AST_NOP_STMT); diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index f15a0b641..d9aa81076 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -229,7 +229,7 @@ static bool bool_to_float(Expr *expr, Type *canonical, Type *type) /** * Insert a cast from `void!` to some fault type by inserting a `catch`, - * so "anyerr a = returns_voidfail()" => "anyerr a = catch(returns_voidfail())" + * so "anyerr a = returns_voidfail()" => "anyerr a = catch? returns_voidfail()" */ static bool voidfail_to_error(Expr *expr, Type *type) { diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 8a47861b4..cad815c5a 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -7336,8 +7336,8 @@ bool sema_analyse_cond_expr(SemaContext *context, Expr *expr) if (!sema_analyse_expr(context, expr)) return false; if (IS_OPTIONAL(expr)) { - SEMA_ERROR(expr, "An optional %s cannot be implicitly converted to a regular boolean value, use 'try()' " - "and 'catch()' to conditionally execute on success or failure.", + SEMA_ERROR(expr, "An optional %s cannot be implicitly converted to a regular boolean value, use 'try? ' " + "and 'catch? ' to conditionally execute on success or failure.", type_quoted_error_string(expr->type)); return false; } diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index f36adfa90..87c086100 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -810,7 +810,7 @@ static inline bool sema_analyse_last_cond(SemaContext *context, Expr *expr, Cond case EXPR_CATCH_UNWRAP: if (cond_type != COND_TYPE_UNWRAP_BOOL && cond_type != COND_TYPE_UNWRAP) { - SEMA_ERROR(expr, "Catch unwrapping is only allowed inside of a 'while' or 'if' conditional, maybe catch(...) will do what you need?"); + SEMA_ERROR(expr, "Catch unwrapping is only allowed inside of a 'while' or 'if' conditional, maybe catch? will do what you need?"); return false; } return sema_analyse_catch_unwrap(context, expr); diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index 6abe71fcd..e3eafc47b 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -50,6 +50,7 @@ const char *kw_at_param; const char *kw_at_pure; const char *kw_at_require; const char *kw_at_return; +const char *kw_catch_question; const char *kw_check_assign; const char *kw_deprecated; const char *kw_distinct; @@ -76,6 +77,7 @@ const char *kw_std; const char *kw_std__core; const char *kw_std__core__types; const char *kw___run_default_test_runner; +const char *kw_try_question; const char *kw_type; const char *kw_typekind; const char *kw_winmain; @@ -101,19 +103,27 @@ void symtab_init(uint32_t capacity) memset(symtab.bucket, 0, size); // Add keywords. - for (int i = 0; i < TOKEN_LAST; i++) + for (TokenType i = TOKEN_FIRST_KEYWORD; i <= TOKEN_LAST_KEYWORD; i++) { - const char* name = token_type_to_string((TokenType)i); - // Skip non-keywords - if (!char_is_lower(name[0])) - { - if ((name[0] != '@' && name[0] != '$') || !char_is_lower(name[1])) continue; - } + TokenType type = i; + const char* name = token_type_to_string(type); uint32_t len = (uint32_t)strlen(name); - TokenType type = (TokenType)i; const char* interned = symtab_add(name, (uint32_t)strlen(name), fnv1a(name, len), &type); - if (type == TOKEN_RETURN) kw_return = interned; - assert(type == (TokenType)i); + switch (type) + { + case TOKEN_RETURN: + kw_return = interned; + break; + case TOKEN_TRY_QUESTION: + kw_try_question = interned; + break; + case TOKEN_CATCH_QUESTION: + kw_catch_question = interned; + break; + default: + break; + } + assert(type == i); assert(symtab_add(name, (uint32_t)strlen(name), fnv1a(name, len), &type) == interned); } diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index a62297cbd..1ac648d37 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -201,6 +201,8 @@ const char *token_type_to_string(TokenType type) return "case"; case TOKEN_CATCH: return "catch"; + case TOKEN_CATCH_QUESTION: + return "catch?"; case TOKEN_CONST: return "const"; case TOKEN_CONTINUE: @@ -259,6 +261,8 @@ const char *token_type_to_string(TokenType type) return "true"; case TOKEN_TRY: return "try"; + case TOKEN_TRY_QUESTION: + return "try?"; case TOKEN_TYPEDEF: return "typedef"; case TOKEN_TYPEID: diff --git a/src/version.h b/src/version.h index ca719259d..ac1fcca81 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.110" \ No newline at end of file +#define COMPILER_VERSION "0.4.111" \ No newline at end of file diff --git a/test/test_suite/errors/error_regression_2.c3t b/test/test_suite/errors/error_regression_2.c3t index 96d18ee3b..285ab6cbc 100644 --- a/test/test_suite/errors/error_regression_2.c3t +++ b/test/test_suite/errors/error_regression_2.c3t @@ -144,7 +144,7 @@ fn void main() bool! has_title = readWhetherTitleNonEmpty(url); // This looks a bit less than elegant, but as you see it's mostly due to having to // use printf here. - libc::printf(" Has title: %s vs %s\n", bool_to_string(has_title) ?? nameFromError(catch(has_title)), (has_title ?? false) ? (char*)"true" : (char*)"false"); + libc::printf(" Has title: %s vs %s\n", bool_to_string(has_title) ?? nameFromError(catch? has_title), (has_title ?? false) ? (char*)"true" : (char*)"false"); } } diff --git a/test/test_suite/errors/failable_catch.c3t b/test/test_suite/errors/failable_catch.c3t index a9d447afe..b884d797c 100644 --- a/test/test_suite/errors/failable_catch.c3t +++ b/test/test_suite/errors/failable_catch.c3t @@ -22,7 +22,7 @@ fn int main() printf("a = %d\n", a); printf("b = %d\n", b); printf("c = %d\n", c); - if (catch(c)) printf("c had error\n"); + if (catch? c) printf("c had error\n"); c = 3; printf("c = %d\n", c); return 0; diff --git a/test/test_suite/errors/lone_try.c3 b/test/test_suite/errors/lone_try.c3 new file mode 100644 index 000000000..e1cd036f3 --- /dev/null +++ b/test/test_suite/errors/lone_try.c3 @@ -0,0 +1,11 @@ +module test; + +fn void main() +{ + try a; // #error: try? +} + +fn void hello() +{ + catch a; // #error: catch? +} diff --git a/test/test_suite/errors/optional_with_optional.c3t b/test/test_suite/errors/optional_with_optional.c3t index d9b752f65..719885ced 100644 --- a/test/test_suite/errors/optional_with_optional.c3t +++ b/test/test_suite/errors/optional_with_optional.c3t @@ -9,10 +9,10 @@ fn void main() io::printfn("1:%d", get_a(1) ?? get_b(4) ?? -1); io::printfn("2:%d", get_a(2) ?? get_b(4) ?? -1); io::printfn("3:%d", get_a(1) ?? get_b(5) ?? -1); - io::printfn("4:%s", catch(Foo.ABC! ?? Foo.DEF!)); + io::printfn("4:%s", catch? (Foo.ABC! ?? Foo.DEF!)); io::printfn("5:%s", Foo.ABC! ?? 3); - io::printfn("6:%s", catch((3 > 2 ? Foo.ABC! : 4) ?? Foo.DEF!)); - io::printfn("7:%s", catch((3 < 2 ? Foo.ABC! : 4) ?? Foo.DEF!)); + io::printfn("6:%s", catch? ((3 > 2 ? Foo.ABC! : 4) ?? Foo.DEF!)); + io::printfn("7:%s", catch? ((3 < 2 ? Foo.ABC! : 4) ?? Foo.DEF!)); long x = Foo.DEF! ?? 3; io::printfn("8:%s", x); int! xy = Foo.ABC! ?? Foo.DEF!; diff --git a/test/test_suite/errors/try_with_assign_to_failable.c3 b/test/test_suite/errors/try_with_assign_to_failable.c3 index 191006004..57c054fcd 100644 --- a/test/test_suite/errors/try_with_assign_to_failable.c3 +++ b/test/test_suite/errors/try_with_assign_to_failable.c3 @@ -2,5 +2,5 @@ fn void test() { int! z; int! w; - try w = z; // #error: An unwrapping 'try' can only occur as the last element of a conditional, did you want 'try(expr) + try w = z; // #error: can only be used when unwrapping an optional } diff --git a/test/test_suite/expressions/negate_int.c3 b/test/test_suite/expressions/negate_int.c3 index da6a0702a..b9aa196ea 100644 --- a/test/test_suite/expressions/negate_int.c3 +++ b/test/test_suite/expressions/negate_int.c3 @@ -1,41 +1,41 @@ fn void test1() { short! a = 1; - try(-a); + try? -a; short b = -a; // #error: 'int!' to 'short'. } fn void test2() { int! a = 1; - try(-a); + try? -a; int b = -a; // #error: 'int!' to 'int' } fn void test3() { long! a = 1; - try(-a); + try? -a; long b = -a; // #error: 'long!' to 'long' } fn void test4() { short! a = 1; - try(~a); + try? ~a; short b = ~a; // #error: 'short!' to 'short' } fn void test5() { int! a = 1; - try(~a); + try? ~a; int b = ~a; // #error: 'int!' to 'int' } fn void test6() { long! a = 1; - try(~a); + try? ~a; long b = ~a; // #error: 'long!' to 'long' } diff --git a/test/test_suite/expressions/not_in_wrong_position.c3 b/test/test_suite/expressions/not_in_wrong_position.c3 new file mode 100644 index 000000000..a0e68c976 --- /dev/null +++ b/test/test_suite/expressions/not_in_wrong_position.c3 @@ -0,0 +1,4 @@ +fn void main() +{ + x = (a ~ a); // #error: can't appear +} diff --git a/test/unit/stdlib/collections/linkedlist.c3 b/test/unit/stdlib/collections/linkedlist.c3 index 0595cb6bb..1acc45f0d 100644 --- a/test/unit/stdlib/collections/linkedlist.c3 +++ b/test/unit/stdlib/collections/linkedlist.c3 @@ -158,7 +158,7 @@ fn void! test_pop() @test assert(list.first()? == 23); assert(list.pop()? == 23); assert(list.len() == 0); - assert(catch(list.pop())); + assert(catch? list.pop()); assert(list.len() == 0); list.push(55); assert(list.len() == 1); @@ -173,16 +173,16 @@ fn void! test_remove_first() @test assert(list.len() == 3); assert(list.last()? == 23); assert(list.first()? == -3); - assert(try(list.remove_first())); + assert(try? list.remove_first()); assert(list.len() == 2); assert(list.last()? == 23); assert(list.first()? == 55); - assert(try(list.remove_first())); + assert(try? list.remove_first()); assert(list.last()? == 23); assert(list.first()? == 23); - assert(try(list.remove_first())); + assert(try? list.remove_first()); assert(list.len() == 0); - assert(catch(list.pop())); + assert(catch? list.pop()); assert(list.len() == 0); list.push(55); assert(list.len() == 1); @@ -197,16 +197,16 @@ fn void! test_remove_last() @test assert(list.len() == 3); assert(list.last()? == 23); assert(list.first()? == -3); - assert(try(list.remove_last())); + assert(try? list.remove_last()); assert(list.len() == 2); assert(list.first()? == -3); assert(list.last()? == 55); - assert(try(list.remove_last())); + assert(try? list.remove_last()); assert(list.first()? == -3); assert(list.last()? == -3); - assert(try(list.remove_last())); + assert(try? list.remove_last()); assert(list.len() == 0); - assert(catch(list.remove_last())); + assert(catch? list.remove_last()); assert(list.len() == 0); list.push(55); assert(list.len() == 1); diff --git a/test/unit/stdlib/conv_tests.c3 b/test/unit/stdlib/conv_tests.c3 index 782e625be..7fe1ae4e6 100644 --- a/test/unit/stdlib/conv_tests.c3 +++ b/test/unit/stdlib/conv_tests.c3 @@ -23,7 +23,7 @@ fn void! comparison_helper_8_to_32(String in, Char32 c32) fn void assert_utf8_is_error(String in) { usz len = in.len; - assert(catch(conv::utf8_to_char32(in.ptr, &len)), "Expected error"); + assert(catch? conv::utf8_to_char32(in.ptr, &len), "Expected error"); } fn void! test_char32_ut8_boundary() @test @@ -33,7 +33,7 @@ fn void! test_char32_ut8_boundary() @test comparison_helper_32_to_8(0x00000080, { 0xc2, 0x80 })?; comparison_helper_32_to_8(0x00000800, { 0xe0, 0xa0, 0x80 })?; comparison_helper_32_to_8(0x00010000, { 0xf0, 0x90, 0x80, 0x80 })?; - assert(catch(comparison_helper_32_to_8(0x10ffff + 1, { 0 })), "Expected error"); + assert(catch? comparison_helper_32_to_8(0x10ffff + 1, { 0 }), "Expected error"); // Last seq per len comparison_helper_32_to_8(0x0000007f, { 0x7f })?; comparison_helper_32_to_8(0x000007ff, { 0xdf, 0xbf })?; diff --git a/test/unit/stdlib/core/string.c3 b/test/unit/stdlib/core/string.c3 index 89d28f22a..c66d56436 100644 --- a/test/unit/stdlib/core/string.c3 +++ b/test/unit/stdlib/core/string.c3 @@ -80,7 +80,7 @@ fn void! test_index_of() String test = "hello world hello"; assert(test.index_of("o")? == 4); assert(test.index_of("ll")? == 2); - assert(catch(test.index_of("wi"))); + assert(catch? test.index_of("wi")); } fn void! test_rindex_of() @@ -90,5 +90,5 @@ fn void! test_rindex_of() assert(test.rindex_of("ll")? == 14); assert(test.rindex_of("he")? == 12); assert(test.rindex_of("world")? == 6); - assert(catch(test.rindex_of("wi"))); + assert(catch? test.rindex_of("wi")); } \ No newline at end of file diff --git a/test/unit/stdlib/io/path.c3 b/test/unit/stdlib/io/path.c3 index 43bbb929c..2424ad432 100644 --- a/test/unit/stdlib/io/path.c3 +++ b/test/unit/stdlib/io/path.c3 @@ -4,9 +4,9 @@ module std::io::path @test; fn void! test_parent() { Path p = path::new("")?; - assert(catch(p.parent())); + assert(catch? p.parent()); p = path::new("/", .path_env = PathEnv.POSIX)?; - assert(catch(p.parent())); + assert(catch? p.parent()); p = path::new("/a/b/c", .path_env = PathEnv.POSIX)?; assert(p.parent().as_str()? == "/a/b"); p = path::new("/a/b/c", .path_env = PathEnv.WIN32)?; @@ -16,25 +16,25 @@ fn void! test_parent() fn void! test_path_normalized() { assert(path::new("", .path_env = PathEnv.WIN32).as_str()? == ""); - assert(catch(path::new("1:\\a\\b\\c.txt", .path_env = PathEnv.WIN32))); - assert(catch(path::new(":", .path_env = PathEnv.WIN32))); - assert(catch(path::new("1:", .path_env = PathEnv.WIN32))); - assert(catch(path::new("1:a", .path_env = PathEnv.WIN32))); -// assert(catch(path::new(`\\\a\b\c.txt`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`\\server\a\b\..\..\..\c`, .path_env = PathEnv.WIN32))); + assert(catch? path::new("1:\\a\\b\\c.txt", .path_env = PathEnv.WIN32)); + assert(catch? path::new(":", .path_env = PathEnv.WIN32)); + assert(catch? path::new("1:", .path_env = PathEnv.WIN32)); + assert(catch? path::new("1:a", .path_env = PathEnv.WIN32)); +// assert(catch? path::new(`\\\a\b\c.txt`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`\\server\a\b\..\..\..\c`, .path_env = PathEnv.WIN32)); - assert(catch(path::new(`\\a`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`/a/b/../../../c`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`/a/b/../../../c`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`/a/b/../../..`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`/a/b/../../..`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`/../a`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`/../a`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`/..`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`/..`, .path_env = PathEnv.POSIX))); - assert(catch(path::new(`C:/a/b/../../../c`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`C:/../a`, .path_env = PathEnv.WIN32))); - assert(catch(path::new(`C:/..`, .path_env = PathEnv.WIN32))); + assert(catch? path::new(`\\a`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`/a/b/../../../c`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`/a/b/../../../c`, .path_env = PathEnv.POSIX)); + assert(catch? path::new(`/a/b/../../..`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`/a/b/../../..`, .path_env = PathEnv.POSIX)); + assert(catch? path::new(`/../a`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`/../a`, .path_env = PathEnv.POSIX)); + assert(catch? path::new(`/..`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`/..`, .path_env = PathEnv.POSIX)); + assert(catch? path::new(`C:/a/b/../../../c`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`C:/../a`, .path_env = PathEnv.WIN32)); + assert(catch? path::new(`C:/..`, .path_env = PathEnv.WIN32)); assert(path::new("/", .path_env = PathEnv.POSIX).as_str()? == "/"); assert(path::new("/./", .path_env = PathEnv.POSIX).as_str()? == "/"); @@ -189,11 +189,11 @@ fn void! test_path_normalized() fn void! test_extension() { - assert(catch(path::new(`C:`, .path_env = PathEnv.WIN32).extension())); - assert(catch(path::new(`C:`, .path_env = PathEnv.POSIX).extension())); - assert(catch(path::new(`file`, .path_env = PathEnv.WIN32).extension())); - assert(catch(path::new(`file`, .path_env = PathEnv.POSIX).extension())); - assert(catch(path::new(`C:\temp\foo.bar\README`, .path_env = PathEnv.WIN32).extension())); + assert(catch? path::new(`C:`, .path_env = PathEnv.WIN32).extension()); + assert(catch? path::new(`C:`, .path_env = PathEnv.POSIX).extension()); + assert(catch? path::new(`file`, .path_env = PathEnv.WIN32).extension()); + assert(catch? path::new(`file`, .path_env = PathEnv.POSIX).extension()); + assert(catch? path::new(`C:\temp\foo.bar\README`, .path_env = PathEnv.WIN32).extension()); assert(path::new_windows("file.txt").extension()? == "txt"); assert(path::new_posix("file.txt").extension()? == "txt"); diff --git a/test/unit/stdlib/net/inetaddr.c3 b/test/unit/stdlib/net/inetaddr.c3 index 8357b848b..e96e29cdf 100644 --- a/test/unit/stdlib/net/inetaddr.c3 +++ b/test/unit/stdlib/net/inetaddr.c3 @@ -34,11 +34,11 @@ fn void! test_ipv4_parse() assert(a.ipv4.a == 127 && a.ipv4.b == 0 && a.ipv4.c == 0 && a.ipv4.d == 1); a = net::ipv4_from_str("255.254.253.255")?; assert(a.ipv4.a == 255 && a.ipv4.b == 254 && a.ipv4.c == 253 && a.ipv4.d == 255); - assert(catch(net::ipv4_from_str(".1.1.1.1"))); - assert(catch(net::ipv4_from_str("1..1.1"))); - assert(catch(net::ipv4_from_str("1..1.1.1"))); - assert(catch(net::ipv4_from_str("1.1.1.256"))); - assert(catch(net::ipv4_from_str("256.1.1.1"))); + assert(catch? net::ipv4_from_str(".1.1.1.1")); + assert(catch? net::ipv4_from_str("1..1.1")); + assert(catch? net::ipv4_from_str("1..1.1.1")); + assert(catch? net::ipv4_from_str("1.1.1.256")); + assert(catch? net::ipv4_from_str("256.1.1.1")); } fn void test_ipv6()