From 4b831dbdddb92c4dbe32dc7b2a6a647febddf5dc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 4 Jul 2021 12:37:21 +0100 Subject: [PATCH] Try `try` and `or_else` built-in procedures with operators `try` and `try else` --- examples/demo/demo.odin | 34 +++--- src/check_builtin.cpp | 184 -------------------------------- src/check_expr.cpp | 192 ++++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 6 -- src/llvm_backend.cpp | 155 ++++++++++----------------- src/map.cpp | 8 ++ src/parser.cpp | 35 +++++++ src/parser.hpp | 2 + src/parser_pos.cpp | 4 + src/string_map.cpp | 21 ++++ src/tokenizer.cpp | 1 + 11 files changed, 335 insertions(+), 307 deletions(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 5538d5b1c..58e413171 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1999,9 +1999,9 @@ relative_data_types :: proc() { fmt.println(rel_slice[1]); } -try_and_or_else :: proc() { - fmt.println("\n#try(...) and or_else(...)"); - // IMPORTANT NOTE: 'try' and 'or_else' are experimental features and subject to change/removal +try_and_try_else :: proc() { + fmt.println("\n#'try ...'' and 'try ... else ...'"); + // IMPORTANT NOTE: 'try' and 'try else' are experimental features and subject to change/removal Foo :: struct {}; Error :: enum { @@ -2029,11 +2029,11 @@ try_and_or_else :: proc() { // 'try' is a lovely shorthand that does this check automatically // and returns early if necessary - f1 := try(bar(true)); + f1 := try bar(true); fmt.println(#procedure); fmt.println(f1); - f2 := try(bar(false)); + f2 := try bar(false); fmt.println(#procedure); fmt.println(f2); return .None; @@ -2056,13 +2056,13 @@ try_and_or_else :: proc() { // The above can be translated into 'try' i = 1; - f1 := try(bar(true)); + f1 := try bar(true); fmt.println(#procedure); fmt.println(f1); i = 2; - f2 := try(bar(false)); + f2 := try bar(false); fmt.println(#procedure); fmt.println(f2); @@ -2072,7 +2072,7 @@ try_and_or_else :: proc() { } try_return_value4 :: proc() -> (i: int, j: f64, k: bool, err: Error) { - f := try(bar(false)); + f := try bar(false); fmt.println(#procedure); fmt.println(f); return 123, 456, true, .None; @@ -2088,7 +2088,7 @@ try_and_or_else :: proc() { } */ // 'try' equivalent - f2 := try(m["hellope"]); + f2 := try m["hellope"]; fmt.println(f2); return true; } @@ -2109,7 +2109,7 @@ try_and_or_else :: proc() { assert(a == 0 && b == 0 && c == false && err4 == .Something); } { - // 'or_else' does a similar value check as 'try' but instead of doing an + // 'try else' does a similar value check as 'try' but instead of doing an // early return, it will give a default value to be used instead m: map[string]int; @@ -2119,22 +2119,22 @@ try_and_or_else :: proc() { if i, ok = m["hellope"]; !ok { i = 123; } - // The above can be mapped to 'or_else' - i = or_else(m["hellope"], 123); + // The above can be mapped to 'try else' + i = try m["hellope"] else 123; assert(i == 123); } { - // 'or_else' can be used with type assertions too, as they + // 'try else' can be used with type assertions too, as they // have optional ok semantics v: union{int, f64}; i: int; - i = or_else(v.(int), 123); - i = or_else(v.?, 123); // Type inference magic + i = try v.(int) else 123; + i = try v.? else 123; // Type inference magic assert(i == 123); m: Maybe(int); - i = or_else(m.?, 456); + i = try m.? else 456; assert(i == 456); } } @@ -2171,6 +2171,6 @@ main :: proc() { union_maybe(); explicit_context_definition(); relative_data_types(); - try_and_or_else(); + try_and_try_else(); } } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 23c489482..baa5d6ced 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -47,181 +47,6 @@ BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_boolean_end - type_has_nil, }; -void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_) { - switch (x->mode) { - case Addressing_MapIndex: - case Addressing_OptionalOk: - case Addressing_OptionalOkPtr: - if (val_type_) *val_type_ = x->type; - break; - default: - if (ok_type_) *ok_type_ = x->type; - return; - } - - Ast *expr = unparen_expr(x->expr); - - if (expr->kind == Ast_CallExpr) { - Type *pt = base_type(type_of_expr(expr->CallExpr.proc)); - if (is_type_proc(pt)) { - Type *tuple = pt->Proc.results; - add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value); - - if (pt->Proc.result_count >= 2) { - if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; - } - expr->CallExpr.optional_ok_one = false; - x->type = tuple; - return; - } - } - - Type *tuple = make_optional_ok_type(x->type); - if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; - add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value); - x->type = tuple; - GB_ASSERT(is_type_tuple(type_of_expr(x->expr))); -} - -void check_try_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_) { - Type *left_type = nullptr; - Type *right_type = nullptr; - if (x->type->kind == Type_Tuple) { - auto const &vars = x->type->Tuple.variables; - auto lhs = array_slice(vars, 0, vars.count-1); - auto rhs = vars[vars.count-1]; - if (lhs.count == 1) { - left_type = lhs[0]->type; - } else if (lhs.count != 0) { - left_type = alloc_type_tuple(); - left_type->Tuple.variables = array_make_from_ptr(lhs.data, lhs.count, lhs.count); - } - - right_type = rhs->type; - } else { - check_promote_optional_ok(c, x, &left_type, &right_type); - } - - if (left_type_) *left_type_ = left_type; - if (right_type_) *right_type_ = right_type; - - if (!type_has_nil(right_type) && !is_type_boolean(right_type)) { - gbString str = type_to_string(right_type); - error(x->expr, "'%.*s' expects an \"optional ok\" like value, or an n-valued expression where the last value is either a boolean or can be compared against 'nil', got %s", LIT(name), str); - gb_string_free(str); - } -} - -bool check_builtin_try(CheckerContext *c, Operand *operand, String const &name, Ast *call, Type *type_hint) { - ast_node(ce, CallExpr, call); - - Operand x = {}; - check_multi_expr_with_type_hint(c, &x, ce->args[0], type_hint); - if (x.mode == Addressing_Invalid) { - return false; - } - - if (c->in_defer) { - error(call, "'%.*s' cannot be used within a defer statement", LIT(name)); - } - - Type *left_type = nullptr; - Type *right_type = nullptr; - check_try_split_types(c, &x, name, &left_type, &right_type); - add_type_and_value(&c->checker->info, ce->args[0], x.mode, x.type, x.value); - - if (c->curr_proc_sig == nullptr) { - error(call, "'%.*s' can only be used within a procedure", LIT(name)); - } - Type *proc_type = base_type(c->curr_proc_sig); - GB_ASSERT(proc_type->kind == Type_Proc); - Type *result_type = proc_type->Proc.results; - if (result_type == nullptr) { - error(call, "'%.*s' requires the current procedure to have at least one return value", LIT(name)); - } else { - GB_ASSERT(result_type->kind == Type_Tuple); - - auto const &vars = result_type->Tuple.variables; - Type *end_type = vars[vars.count-1]->type; - - if (vars.count > 1) { - if (!proc_type->Proc.has_named_results) { - error(call, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(name)); - } - } - - Operand rhs = {}; - rhs.type = right_type; - rhs.mode = Addressing_Value; - - // TODO(bill): better error message - if (!check_is_assignable_to(c, &rhs, end_type)) { - gbString a = type_to_string(right_type); - gbString b = type_to_string(end_type); - gbString ret_type = type_to_string(result_type); - error(call, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(name)); - if (vars.count == 1) { - error_line("\tProcedure return value type: %s\n", ret_type); - } else { - error_line("\tProcedure return value types: (%s)\n", ret_type); - } - gb_string_free(ret_type); - gb_string_free(b); - gb_string_free(a); - } - } - - - if (left_type != nullptr) { - operand->mode = Addressing_Value; - operand->type = left_type; - } else { - operand->mode = Addressing_NoValue; - operand->type = nullptr; - } - - return true; -} - -bool check_builtin_or_else(CheckerContext *c, Operand *operand, String const &name, Ast *call, Type *type_hint) { - ast_node(ce, CallExpr, call); - - Operand x = {}; - Operand y = {}; - check_multi_expr_with_type_hint(c, &x, ce->args[0], type_hint); - if (x.mode == Addressing_Invalid) { - return false; - } - - check_multi_expr(c, &y, ce->args[1]); - error_operand_no_value(&y); - if (y.mode == Addressing_Invalid) { - return false; - } - - Type *left_type = nullptr; - Type *right_type = nullptr; - check_try_split_types(c, &x, name, &left_type, &right_type); - add_type_and_value(&c->checker->info, ce->args[0], x.mode, x.type, x.value); - - if (left_type != nullptr) { - check_assignment(c, &y, left_type, name); - } else { - // TODO(bill): better error message - error(call, "'%.*s' does not return a value", LIT(name)); - } - - - if (left_type == nullptr) { - left_type = t_invalid; - } - operand->mode = Addressing_Value; - operand->type = left_type; - - return true; -} - - bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { ast_node(ce, CallExpr, call); if (ce->inlining != ProcInlining_none) { @@ -259,10 +84,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 // NOTE(bill): The first arg may be a Type, this will be checked case by case break; - case BuiltinProc_try: - case BuiltinProc_or_else: - // NOTE(bill): The first arg may be a tuple - break; case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); @@ -1891,11 +1712,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } - case BuiltinProc_try: - return check_builtin_try(c, operand, builtin_name, call, type_hint); - case BuiltinProc_or_else: - return check_builtin_or_else(c, operand, builtin_name, call, type_hint); - case BuiltinProc_simd_vector: { Operand x = {}; Operand y = {}; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 57d10f60e..67cf50a75 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -73,6 +73,7 @@ typedef CALL_ARGUMENT_CHECKER(CallArgumentCheckerType); void check_expr (CheckerContext *c, Operand *operand, Ast *expression); void check_multi_expr (CheckerContext *c, Operand *operand, Ast *expression); void check_multi_expr_or_type (CheckerContext *c, Operand *operand, Ast *expression); +void check_multi_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *type_hint); void check_expr_or_type (CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint); ExprKind check_expr_base (CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint); void check_expr_with_type_hint (CheckerContext *c, Operand *o, Ast *e, Type *t); @@ -6195,6 +6196,191 @@ ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, Ast *node, return Expr_Expr; } + +void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_) { + switch (x->mode) { + case Addressing_MapIndex: + case Addressing_OptionalOk: + case Addressing_OptionalOkPtr: + if (val_type_) *val_type_ = x->type; + break; + default: + if (ok_type_) *ok_type_ = x->type; + return; + } + + Ast *expr = unparen_expr(x->expr); + + if (expr->kind == Ast_CallExpr) { + Type *pt = base_type(type_of_expr(expr->CallExpr.proc)); + if (is_type_proc(pt)) { + Type *tuple = pt->Proc.results; + add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value); + + if (pt->Proc.result_count >= 2) { + if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; + } + expr->CallExpr.optional_ok_one = false; + x->type = tuple; + return; + } + } + + Type *tuple = make_optional_ok_type(x->type); + if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; + add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value); + x->type = tuple; + GB_ASSERT(is_type_tuple(type_of_expr(x->expr))); +} + +void check_try_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_) { + Type *left_type = nullptr; + Type *right_type = nullptr; + if (x->type->kind == Type_Tuple) { + auto const &vars = x->type->Tuple.variables; + auto lhs = array_slice(vars, 0, vars.count-1); + auto rhs = vars[vars.count-1]; + if (lhs.count == 1) { + left_type = lhs[0]->type; + } else if (lhs.count != 0) { + left_type = alloc_type_tuple(); + left_type->Tuple.variables = array_make_from_ptr(lhs.data, lhs.count, lhs.count); + } + + right_type = rhs->type; + } else { + check_promote_optional_ok(c, x, &left_type, &right_type); + } + + if (left_type_) *left_type_ = left_type; + if (right_type_) *right_type_ = right_type; + + if (!type_has_nil(right_type) && !is_type_boolean(right_type)) { + gbString str = type_to_string(right_type); + error(x->expr, "'%.*s' expects an \"optional ok\" like value, or an n-valued expression where the last value is either a boolean or can be compared against 'nil', got %s", LIT(name), str); + gb_string_free(str); + } +} + + +ExprKind check_try_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + String name = str_lit("try"); + + ast_node(te, TryExpr, node); + + Operand x = {}; + check_multi_expr_with_type_hint(c, &x, te->expr, type_hint); + if (x.mode == Addressing_Invalid) { + o->mode = Addressing_Value; + o->type = t_invalid; + return Expr_Expr; + } + + if (c->in_defer) { + error(node, "'%.*s' cannot be used within a defer statement", LIT(name)); + } + + Type *left_type = nullptr; + Type *right_type = nullptr; + check_try_split_types(c, &x, name, &left_type, &right_type); + add_type_and_value(&c->checker->info, te->expr, x.mode, x.type, x.value); + + if (c->curr_proc_sig == nullptr) { + error(node, "'%.*s' can only be used within a procedure", LIT(name)); + } + Type *proc_type = base_type(c->curr_proc_sig); + GB_ASSERT(proc_type->kind == Type_Proc); + Type *result_type = proc_type->Proc.results; + if (result_type == nullptr) { + error(node, "'%.*s' requires the current procedure to have at least one return value", LIT(name)); + } else { + GB_ASSERT(result_type->kind == Type_Tuple); + + auto const &vars = result_type->Tuple.variables; + Type *end_type = vars[vars.count-1]->type; + + if (vars.count > 1) { + if (!proc_type->Proc.has_named_results) { + error(node, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(name)); + } + } + + Operand rhs = {}; + rhs.type = right_type; + rhs.mode = Addressing_Value; + + // TODO(bill): better error message + if (!check_is_assignable_to(c, &rhs, end_type)) { + gbString a = type_to_string(right_type); + gbString b = type_to_string(end_type); + gbString ret_type = type_to_string(result_type); + error(node, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(name)); + if (vars.count == 1) { + error_line("\tProcedure return value type: %s\n", ret_type); + } else { + error_line("\tProcedure return value types: (%s)\n", ret_type); + } + gb_string_free(ret_type); + gb_string_free(b); + gb_string_free(a); + } + } + + + if (left_type != nullptr) { + o->mode = Addressing_Value; + o->type = left_type; + } else { + o->mode = Addressing_NoValue; + o->type = nullptr; + } + + return Expr_Expr; +} +ExprKind check_try_else_expr(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { + String name = str_lit("try else"); + + ast_node(te, TryElseExpr, node); + + Operand x = {}; + Operand y = {}; + check_multi_expr_with_type_hint(c, &x, te->expr, type_hint); + if (x.mode == Addressing_Invalid) { + o->mode = Addressing_Value; + o->type = t_invalid; + return Expr_Expr; + } + + check_multi_expr_with_type_hint(c, &y, te->else_expr, x.type); + error_operand_no_value(&y); + if (y.mode == Addressing_Invalid) { + o->mode = Addressing_Value; + o->type = t_invalid; + return Expr_Expr; + } + + Type *left_type = nullptr; + Type *right_type = nullptr; + check_try_split_types(c, &x, name, &left_type, &right_type); + add_type_and_value(&c->checker->info, te->expr, x.mode, x.type, x.value); + + if (left_type != nullptr) { + check_assignment(c, &y, left_type, name); + } else { + // TODO(bill): better error message + error(node, "'%.*s' does not return a value", LIT(name)); + } + + if (left_type == nullptr) { + left_type = t_invalid; + } + o->mode = Addressing_Value; + o->type = left_type; + + return Expr_Expr; +} + + ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { u32 prev_state_flags = c->state_flags; defer (c->state_flags = prev_state_flags); @@ -7564,7 +7750,13 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type } case_end; + case_ast_node(te, TryExpr, node); + return check_try_expr(c, o, node, type_hint); + case_end; + case_ast_node(te, TryElseExpr, node); + return check_try_else_expr(c, o, node, type_hint); + case_end; case_ast_node(se, SelectorExpr, node); check_selector(c, o, node, type_hint); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index a72231f8b..57b5d7eb9 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -33,9 +33,6 @@ enum BuiltinProcId { BuiltinProc_soa_zip, BuiltinProc_soa_unzip, - BuiltinProc_try, - BuiltinProc_or_else, - BuiltinProc_DIRECTIVE, // NOTE(bill): This is used for specialized hash-prefixed procedures // "Intrinsics" @@ -266,9 +263,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("soa_zip"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("soa_unzip"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, - {STR_LIT("try"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, - {STR_LIT("or_else"), 2, false, Expr_Expr, BuiltinProcPkg_builtin}, - {STR_LIT(""), 0, true, Expr_Expr, BuiltinProcPkg_builtin}, // DIRECTIVE diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index a3561056c..4c4c015ba 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2756,9 +2756,7 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) lbValue *found = string_map_get(&m->members, key); if (found) { lb_add_entity(m, entity, *found); - lbProcedure **p_found = string_map_get(&m->procedures, key); - GB_ASSERT(p_found != nullptr); - return *p_found; + return string_map_must_get(&m->procedures, key); } } @@ -5339,7 +5337,10 @@ void lb_build_assignment(lbProcedure *p, Array &lvals, Slice cons } } -void lb_build_return_stmt_internal(lbProcedure *p, bool return_by_pointer, lbValue const &res) { +void lb_build_return_stmt_internal(lbProcedure *p, lbValue const &res) { + lbFunctionType *ft = lb_get_function_type(p->module, p, p->type); + bool return_by_pointer = ft->ret.kind == lbArg_Indirect; + if (return_by_pointer) { if (res.value != nullptr) { LLVMBuildStore(p->builder, res.value, p->return_ptr.addr.value); @@ -5383,9 +5384,8 @@ void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { } else if (return_count == 1) { Entity *e = tuple->variables[0]; if (res_count == 0) { - lbValue *found = map_get(&p->module->values, hash_entity(e)); - GB_ASSERT(found); - res = lb_emit_load(p, *found); + lbValue found = map_must_get(&p->module->values, hash_entity(e)); + res = lb_emit_load(p, found); } else { res = lb_build_expr(p, return_results[0]); res = lb_emit_conv(p, res, e->type); @@ -5393,9 +5393,8 @@ void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { if (p->type->Proc.has_named_results) { // NOTE(bill): store the named values before returning if (e->token.string != "") { - lbValue *found = map_get(&p->module->values, hash_entity(e)); - GB_ASSERT(found != nullptr); - lb_emit_store(p, *found, lb_emit_conv(p, res, e->type)); + lbValue found = map_must_get(&p->module->values, hash_entity(e)); + lb_emit_store(p, found, lb_emit_conv(p, res, e->type)); } } @@ -5419,9 +5418,8 @@ void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { } else { for (isize res_index = 0; res_index < return_count; res_index++) { Entity *e = tuple->variables[res_index]; - lbValue *found = map_get(&p->module->values, hash_entity(e)); - GB_ASSERT(found); - lbValue res = lb_emit_load(p, *found); + lbValue found = map_must_get(&p->module->values, hash_entity(e)); + lbValue res = lb_emit_load(p, found); array_add(&results, res); } } @@ -5442,9 +5440,7 @@ void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { if (e->token.string == "") { continue; } - lbValue *found = map_get(&p->module->values, hash_entity(e)); - GB_ASSERT(found != nullptr); - named_results[i] = *found; + named_results[i] = map_must_get(&p->module->values, hash_entity(e)); values[i] = lb_emit_conv(p, results[i], e->type); } @@ -5483,7 +5479,7 @@ void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { res = lb_emit_load(p, res); } - lb_build_return_stmt_internal(p, return_by_pointer, res); + lb_build_return_stmt_internal(p, res); } void lb_build_if_stmt(lbProcedure *p, Ast *node) { @@ -9554,15 +9550,11 @@ lbValue lb_soa_unzip(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { return lb_addr_load(p, res); } -lbValue lb_emit_try(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { - Ast *arg = ce->args[0]; - +void lb_emit_try_lhs_rhs(lbProcedure *p, Ast *arg, TypeAndValue const &tv, lbValue *lhs_, lbValue *rhs_) { lbValue lhs = {}; lbValue rhs = {}; - TypeAndValue const &arg_tav = type_and_value_of_expr(arg); lbValue value = lb_build_expr(p, arg); - if (is_type_tuple(value.type)) { i32 n = cast(i32)(value.type->Tuple.variables.count-1); if (value.type->Tuple.variables.count == 2) { @@ -9582,52 +9574,54 @@ lbValue lb_emit_try(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { GB_ASSERT(rhs.value != nullptr); - lbValue do_early_return = {}; + if (lhs_) *lhs_ = lhs; + if (rhs_) *rhs_ = rhs; +} + + +lbValue lb_emit_try_has_value(lbProcedure *p, lbValue rhs) { + lbValue has_value = {}; if (is_type_boolean(rhs.type)) { - do_early_return = lb_emit_unary_arith(p, Token_Not, rhs, t_bool); + has_value = rhs; } else { - GB_ASSERT(type_has_nil(rhs.type)); - do_early_return = lb_emit_comp_against_nil(p, Token_NotEq, rhs); + GB_ASSERT_MSG(type_has_nil(rhs.type), "%s", type_to_string(rhs.type)); + has_value = lb_emit_comp_against_nil(p, Token_CmpEq, rhs); } - - GB_ASSERT(do_early_return.value != nullptr); - + GB_ASSERT(has_value.value != nullptr); + return has_value; +} +lbValue lb_emit_try(lbProcedure *p, Ast *arg, TypeAndValue const &tv) { + lbValue lhs = {}; + lbValue rhs = {}; + lb_emit_try_lhs_rhs(p, arg, tv, &lhs, &rhs); lbBlock *return_block = lb_create_block(p, "try.return", false); lbBlock *continue_block = lb_create_block(p, "try.continue", false); - lb_emit_if(p, do_early_return, return_block, continue_block); + lb_emit_if(p, lb_emit_try_has_value(p, rhs), continue_block, return_block); lb_start_block(p, return_block); { Type *proc_type = base_type(p->type); - // TODO(bill): multiple return values Type *results = proc_type->Proc.results; GB_ASSERT(results != nullptr && results->kind == Type_Tuple); TypeTuple *tuple = &results->Tuple; - isize return_count = tuple->variables.count; - // TODO(bill) multiple - GB_ASSERT(return_count != 0); - - lbFunctionType *ft = lb_get_function_type(p->module, p, proc_type); - bool return_by_pointer = ft->ret.kind == lbArg_Indirect; + GB_ASSERT(tuple->variables.count != 0); + Entity *end_entity = tuple->variables[tuple->variables.count-1]; + rhs = lb_emit_conv(p, rhs, end_entity->type); if (p->type->Proc.has_named_results) { - Entity *e = tuple->variables[tuple->variables.count-1]; + GB_ASSERT(end_entity->token.string.len != 0); + // NOTE(bill): store the named values before returning - if (e->token.string != "") { - lbValue *found = map_get(&p->module->values, hash_entity(e)); - GB_ASSERT(found != nullptr); - lb_emit_store(p, *found, lb_emit_conv(p, rhs, e->type)); - } + lbValue found = map_must_get(&p->module->values, hash_entity(end_entity)); + lb_emit_store(p, found, rhs); lb_build_return_stmt(p, {}); } else { - GB_ASSERT(return_count == 1); - Entity *e = tuple->variables[0]; - lb_build_return_stmt_internal(p, return_by_pointer, lb_emit_conv(p, rhs, e->type)); + GB_ASSERT(tuple->variables.count == 1); + lb_build_return_stmt_internal(p, rhs); } - } lb_start_block(p, continue_block); @@ -9639,57 +9633,20 @@ lbValue lb_emit_try(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { } -lbValue lb_emit_or_else(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { - Ast *arg = ce->args[0]; - Ast *else_value = ce->args[1]; - +lbValue lb_emit_try_else(lbProcedure *p, Ast *arg, Ast *else_expr, TypeAndValue const &tv) { lbValue lhs = {}; lbValue rhs = {}; - - TypeAndValue const &arg_tav = type_and_value_of_expr(arg); - if (unparen_expr(arg)->kind == Ast_TypeAssertion) { - GB_ASSERT_MSG(is_type_tuple(arg_tav.type), "%s", type_to_string(arg_tav.type)); - } - lbValue value = lb_build_expr(p, arg); - - if (is_type_tuple(value.type)) { - i32 end_index = cast(i32)(value.type->Tuple.variables.count-1); - if (value.type->Tuple.variables.count == 2) { - lhs = lb_emit_struct_ev(p, value, 0); - } else { - lbAddr lhs_addr = lb_add_local_generated(p, tv.type, false); - lbValue lhs_ptr = lb_addr_get_ptr(p, lhs_addr); - for (i32 i = 0; i < end_index; i++) { - lb_emit_store(p, lb_emit_struct_ep(p, lhs_ptr, i), lb_emit_struct_ev(p, value, i)); - } - lhs = lb_addr_load(p, lhs_addr); - } - rhs = lb_emit_struct_ev(p, value, end_index); - } else { - rhs = value; - } - - GB_ASSERT(rhs.value != nullptr); - - lbValue has_value = {}; - if (is_type_boolean(rhs.type)) { - has_value = rhs; - } else { - GB_ASSERT_MSG(type_has_nil(rhs.type), "%s", type_to_string(rhs.type)); - has_value = lb_emit_comp_against_nil(p, Token_CmpEq, rhs); - } - - GB_ASSERT(has_value.value != nullptr); + lb_emit_try_lhs_rhs(p, arg, tv, &lhs, &rhs); LLVMValueRef incoming_values[2] = {}; LLVMBasicBlockRef incoming_blocks[2] = {}; - GB_ASSERT(else_value != nullptr); + GB_ASSERT(else_expr != nullptr); lbBlock *then = lb_create_block(p, "or_else.then"); lbBlock *done = lb_create_block(p, "or_else.done"); // NOTE(bill): Append later lbBlock *else_ = lb_create_block(p, "or_else.else"); - lb_emit_if(p, has_value, then, else_); + lb_emit_if(p, lb_emit_try_has_value(p, rhs), then, else_); lb_start_block(p, then); Type *type = default_type(tv.type); @@ -9699,7 +9656,7 @@ lbValue lb_emit_or_else(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) lb_emit_jump(p, done); lb_start_block(p, else_); - incoming_values[1] = lb_emit_conv(p, lb_build_expr(p, else_value), type).value; + incoming_values[1] = lb_emit_conv(p, lb_build_expr(p, else_expr), type).value; lb_emit_jump(p, done); lb_start_block(p, done); @@ -10107,13 +10064,6 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, case BuiltinProc_soa_unzip: return lb_soa_unzip(p, ce, tv); - case BuiltinProc_try: - return lb_emit_try(p, ce, tv); - - case BuiltinProc_or_else: - return lb_emit_or_else(p, ce, tv); - - // "Intrinsics" case BuiltinProc_alloca: @@ -12833,6 +12783,14 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { return lb_build_binary_expr(p, expr); case_end; + case_ast_node(te, TryExpr, expr); + return lb_emit_try(p, te->expr, tv); + case_end; + + case_ast_node(te, TryElseExpr, expr); + return lb_emit_try_else(p, te->expr, te->else_expr, tv); + case_end; + case_ast_node(pl, ProcLit, expr); return lb_generate_anonymous_proc_lit(p->module, p->name, expr, p); case_end; @@ -12904,9 +12862,7 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { } lbAddr lb_get_soa_variable_addr(lbProcedure *p, Entity *e) { - lbAddr *found = map_get(&p->module->soa_values, hash_entity(e)); - GB_ASSERT(found != nullptr); - return *found; + return map_must_get(&p->module->soa_values, hash_entity(e)); } lbValue lb_get_using_variable(lbProcedure *p, Entity *e) { GB_ASSERT(e->kind == Entity_Variable && e->flags & EntityFlag_Using); @@ -14406,7 +14362,6 @@ lbValue lb_find_runtime_value(lbModule *m, String const &name) { } lbValue lb_find_package_value(lbModule *m, String const &pkg, String const &name) { Entity *e = find_entity_in_pkg(m->info, pkg, name); - lbValue *found = map_get(&m->values, hash_entity(e)); return lb_find_value_from_entity(m, e); } diff --git a/src/map.cpp b/src/map.cpp index 85836fccb..fc23c9aec 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -69,6 +69,7 @@ struct Map { template void map_init (Map *h, gbAllocator a, isize capacity = 16); template void map_destroy (Map *h); template T * map_get (Map *h, HashKey const &key); +template T & map_must_get (Map *h, HashKey const &key); template void map_set (Map *h, HashKey const &key, T const &value); template void map_remove (Map *h, HashKey const &key); template void map_clear (Map *h); @@ -202,6 +203,13 @@ T *map_get(Map *h, HashKey const &key) { return nullptr; } +template +T &map_must_get(Map *h, HashKey const &key) { + isize index = map__find(h, key).entry_index; + GB_ASSERT(index >= 0); + return h->entries[index].value; +} + template void map_set(Map *h, HashKey const &key, T const &value) { isize index; diff --git a/src/parser.cpp b/src/parser.cpp index 50584eb51..641c4141f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -199,6 +199,15 @@ Ast *clone_ast(Ast *node) { n->AutoCast.expr = clone_ast(n->AutoCast.expr); break; + case Ast_TryExpr: + n->TryExpr.expr = clone_ast(n->TryExpr.expr); + break; + + case Ast_TryElseExpr: + n->TryElseExpr.expr = clone_ast(n->TryElseExpr.expr); + n->TryElseExpr.else_expr = clone_ast(n->TryElseExpr.else_expr); + break; + case Ast_InlineAsmExpr: n->InlineAsmExpr.param_types = clone_ast_array(n->InlineAsmExpr.param_types); n->InlineAsmExpr.return_type = clone_ast(n->InlineAsmExpr.return_type); @@ -680,6 +689,22 @@ Ast *ast_auto_cast(AstFile *f, Token token, Ast *expr) { return result; } +Ast *ast_try_expr(AstFile *f, Token token, Ast *expr) { + Ast *result = alloc_ast_node(f, Ast_TryExpr); + result->TryExpr.token = token; + result->TryExpr.expr = expr; + return result; +} +Ast *ast_try_else_expr(AstFile *f, Token try_token, Ast *expr, Token else_token, Ast *else_expr) { + Ast *result = alloc_ast_node(f, Ast_TryElseExpr); + result->TryElseExpr.try_token = try_token; + result->TryElseExpr.expr = expr; + result->TryElseExpr.else_token = else_token; + result->TryElseExpr.else_expr = else_expr; + return result; +} + + Ast *ast_inline_asm_expr(AstFile *f, Token token, Token open, Token close, Array const ¶m_types, Ast *return_type, @@ -2723,6 +2748,16 @@ Ast *parse_unary_expr(AstFile *f, bool lhs) { return ast_auto_cast(f, token, expr); } + case Token_try: { + Token try_token = advance_token(f); + Ast *expr = parse_unary_expr(f, lhs); + if (f->curr_token.kind == Token_else) { + Token else_token = advance_token(f); + Ast *else_expr = parse_expr(f, lhs); + return ast_try_else_expr(f, try_token, expr, else_token, else_expr); + } + return ast_try_expr(f, try_token, expr); + } case Token_Add: case Token_Sub: diff --git a/src/parser.hpp b/src/parser.hpp index ad2b8c260..b999640a1 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -370,6 +370,8 @@ AST_KIND(_ExprBegin, "", bool) \ }) \ AST_KIND(TypeCast, "type cast", struct { Token token; Ast *type, *expr; }) \ AST_KIND(AutoCast, "auto_cast", struct { Token token; Ast *expr; }) \ + AST_KIND(TryExpr, "try expression", struct { Token token; Ast *expr; }) \ + AST_KIND(TryElseExpr, "try else expression", struct { Token try_token; Ast *expr; Token else_token; Ast *else_expr; }) \ AST_KIND(InlineAsmExpr, "inline asm expression", struct { \ Token token; \ Token open, close; \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 921836afe..8f4fe4527 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -44,6 +44,8 @@ Token ast_token(Ast *node) { case Ast_TypeAssertion: return ast_token(node->TypeAssertion.expr); case Ast_TypeCast: return node->TypeCast.token; case Ast_AutoCast: return node->AutoCast.token; + case Ast_TryExpr: return node->TryExpr.token; + case Ast_TryElseExpr: return node->TryElseExpr.try_token; case Ast_InlineAsmExpr: return node->InlineAsmExpr.token; case Ast_BadStmt: return node->BadStmt.begin; @@ -178,6 +180,8 @@ Token ast_end_token(Ast *node) { case Ast_TypeAssertion: return ast_end_token(node->TypeAssertion.type); case Ast_TypeCast: return ast_end_token(node->TypeCast.expr); case Ast_AutoCast: return ast_end_token(node->AutoCast.expr); + case Ast_TryExpr: return ast_end_token(node->TryExpr.expr); + case Ast_TryElseExpr: return ast_end_token(node->TryElseExpr.else_expr); case Ast_InlineAsmExpr: return node->InlineAsmExpr.close; case Ast_BadStmt: return node->BadStmt.end; diff --git a/src/string_map.cpp b/src/string_map.cpp index 7a446937a..09e125800 100644 --- a/src/string_map.cpp +++ b/src/string_map.cpp @@ -54,6 +54,10 @@ template T * string_map_get (StringMap *h, char co template T * string_map_get (StringMap *h, String const &key); template T * string_map_get (StringMap *h, StringHashKey const &key); +template T & string_map_must_get (StringMap *h, char const *key); +template T & string_map_must_get (StringMap *h, String const &key); +template T & string_map_must_get (StringMap *h, StringHashKey const &key); + template void string_map_set (StringMap *h, StringHashKey const &key, T const &value); template void string_map_set (StringMap *h, String const &key, T const &value); template void string_map_set (StringMap *h, char const *key, T const &value); @@ -187,6 +191,23 @@ gb_inline T *string_map_get(StringMap *h, char const *key) { return string_map_get(h, string_hash_string(make_string_c(key))); } +template +T &string_map_must_get(StringMap *h, StringHashKey const &key) { + isize index = string_map__find(h, key).entry_index; + GB_ASSERT(index >= 0); + return h->entries[index].value; +} + +template +gb_inline T &string_map_must_get(StringMap *h, String const &key) { + return string_map_must_get(h, string_hash_string(key)); +} + +template +gb_inline T &string_map_must_get(StringMap *h, char const *key) { + return string_map_must_get(h, string_hash_string(make_string_c(key))); +} + template void string_map_set(StringMap *h, StringHashKey const &key, T const &value) { isize index; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index edffb7446..130ee742a 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -117,6 +117,7 @@ TOKEN_KIND(Token__KeywordBegin, ""), \ TOKEN_KIND(Token_no_inline, "no_inline"), \ TOKEN_KIND(Token_context, "context"), \ TOKEN_KIND(Token_asm, "asm"), \ + TOKEN_KIND(Token_try, "try"), \ TOKEN_KIND(Token__KeywordEnd, ""), \ TOKEN_KIND(Token_Count, "")