diff --git a/compile.c b/compile.c index 6904519..9f9b0e9 100644 --- a/compile.c +++ b/compile.c @@ -1939,14 +1939,24 @@ CORD compile(env_t *env, ast_t *ast) type_t *lhs_t = get_type(env, binop->lhs); type_t *rhs_t = get_type(env, binop->rhs); - if (binop->op == BINOP_OR && lhs_t->tag == OptionalType - && (rhs_t->tag == AbortType || rhs_t->tag == ReturnType)) { - return CORD_all("({ ", compile_declaration(lhs_t, "lhs"), " = ", compile(env, binop->lhs), "; ", - "if (", check_null(lhs_t, "lhs"), ") ", compile_statement(env, binop->rhs), " ", - optional_into_nonnull(lhs_t, "lhs"), "; })"); + if (binop->op == BINOP_OR && lhs_t->tag == OptionalType) { + if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) { + return CORD_all("({ ", compile_declaration(lhs_t, "lhs"), " = ", compile(env, binop->lhs), "; ", + "if (", check_null(lhs_t, "lhs"), ") ", compile_statement(env, binop->rhs), " ", + optional_into_nonnull(lhs_t, "lhs"), "; })"); + } else if (rhs_t->tag == OptionalType && type_eq(lhs_t, rhs_t)) { + return CORD_all("({ ", compile_declaration(lhs_t, "lhs"), " = ", compile(env, binop->lhs), "; ", + check_null(lhs_t, "lhs"), " ? ", compile(env, binop->rhs), " : lhs; })"); + } else if (rhs_t->tag != OptionalType && type_eq(Match(lhs_t, OptionalType)->type, rhs_t)) { + return CORD_all("({ ", compile_declaration(lhs_t, "lhs"), " = ", compile(env, binop->lhs), "; ", + check_null(lhs_t, "lhs"), " ? ", compile(env, binop->rhs), " : ", + optional_into_nonnull(lhs_t, "lhs"), "; })"); + } else { + code_err(ast, "I don't know how to do an 'or' operation between %T and %T", lhs_t, rhs_t); + } } - CORD lhs= compile(env, binop->lhs); + CORD lhs = compile(env, binop->lhs); CORD rhs = compile(env, binop->rhs); type_t *operand_t; if (promote(env, &rhs, rhs_t, lhs_t)) @@ -2813,40 +2823,6 @@ CORD compile(env_t *env, ast_t *ast) return CORD_all("Table$sorted(", self, ", ", compile_type_info(env, self_value_t), ")"); } else code_err(ast, "There is no '%s' method for tables", call->name); } - case OptionalType: { - type_t *nonnull = Match(self_value_t, OptionalType)->type; - if (streq(call->name, "or_else")) { - CORD self = compile_to_pointer_depth(env, call->self, 0, false); - arg_t *arg_spec = new(arg_t, .name="fallback", .type=nonnull); - return CORD_all("({ ", compile_declaration(self_value_t, "opt"), " = ", self, "; ", - check_null(self_value_t, "opt"), " ? ", - compile_arguments(env, ast, arg_spec, call->args), - " : ", optional_into_nonnull(self_value_t, "opt"), "; })"); - } else if (streq(call->name, "or_fail")) { - CORD self = compile_to_pointer_depth(env, call->self, 0, false); - arg_t *arg_spec = new(arg_t, .name="message", .type=TEXT_TYPE, - .default_val=FakeAST(TextLiteral, .cord="This value was expected to be non-null, but it's null!")); - return CORD_all("({ ", compile_declaration(self_value_t, "opt"), " = ", self, "; ", - "if (", check_null(self_value_t, "opt"), ")\n", - CORD_asprintf("fail_source(%r, %ld, %ld, Text$as_c_string(%r));\n", - CORD_quoted(ast->file->filename), - (long)(call->self->start - call->self->file->text), - (long)(call->self->end - call->self->file->text), - compile_arguments(env, ast, arg_spec, call->args)), - optional_into_nonnull(self_value_t, "opt"), "; })"); - } else if (streq(call->name, "or_exit")) { - CORD self = compile_to_pointer_depth(env, call->self, 0, false); - arg_t *arg_spec = new(arg_t, .name="message", .type=Type(OptionalType, .type=TEXT_TYPE), - .default_val=FakeAST(Null, .type=new(type_ast_t, .tag=VarTypeAST, .__data.VarTypeAST.name="Text")), - .next=new(arg_t, .name="code", .type=Type(IntType, .bits=TYPE_IBITS32), - .default_val=FakeAST(Int, .bits=IBITS32, .str="1"))); - return CORD_all("({ ", compile_declaration(self_value_t, "opt"), " = ", self, "; ", - "if (", check_null(self_value_t, "opt"), ")\n", - "tomo_exit(", compile_arguments(env, ast, arg_spec, call->args), ");\n", - optional_into_nonnull(self_value_t, "opt"), "; })"); - } - code_err(ast, "There is no '%s' method for optional %T values", call->name, nonnull); - } default: { auto methodcall = Match(ast, MethodCall); type_t *fn_t = get_method_type(env, methodcall->self, methodcall->name); diff --git a/docs/optionals.md b/docs/optionals.md index 54fe5f5..2bc747f 100644 --- a/docs/optionals.md +++ b/docs/optionals.md @@ -30,25 +30,28 @@ having to use a more generalized form of `enum` which may have different naming conventions and which would generate a lot of unnecessary code. In addition to using conditionals to check for null values, you can also use -`:or_else(fallback)` or `:or_fail()` or `:or_exit()`: +`or` to get a non-null value by either providing an alternative non-null value +or by providing an early out statement like `return`/`skip`/`stop` or a function +with an `Abort` type like `fail()` or `exit()`: ```tomo maybe_x := 5? ->> maybe_x:or_else(-1) +>> maybe_x or -1 = 5 : Int ->> maybe_x:or_fail() +>> maybe_x or fail("No value!") = 5 : Int maybe_x = !Int ->> maybe_x:or_else(-1) +>> maybe_x or -1 = -1 : Int ->> maybe_x:or_fail("No value!") +>> maybe_x or fail("No value!") # Failure! +func do_stuff(matches:[Text]): + pass -maybe_x = !Int ->> maybe_x:or_exit() -= -1 : Int ->> maybe_x:or_exit("No value!") -# Exit! +for line in lines: + matches := line:matches($/{..},{..}/) or skip + # The `or skip` above means that if we're here, `matches` is non-null: + do_stuff(matches) ``` diff --git a/docs/tables.md b/docs/tables.md index 9de07b6..f589eb2 100644 --- a/docs/tables.md +++ b/docs/tables.md @@ -195,7 +195,7 @@ The value associated with the key or null if the key is not found. >> t:get("A")! = 1 : Int ->> t:get("????"):or_else(0) +>> t:get("????") or 0 = 0 : Int ``` diff --git a/examples/game/game.tm b/examples/game/game.tm index 8c5124d..c7d843e 100644 --- a/examples/game/game.tm +++ b/examples/game/game.tm @@ -9,7 +9,7 @@ func main(map=(./map.txt)): extern InitWindow:func(w:Int32, h:Int32, title:CString)->Void InitWindow(1600, 900, "raylib [core] example - 2d camera") - map_contents := map:read():or_exit("Could not find the game map: $map") + map_contents := map:read() or exit("Could not find the game map: $map") World.CURRENT:load_map(map_contents) diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm index 8b91d3f..37692ca 100644 --- a/examples/ini/ini.tm +++ b/examples/ini/ini.tm @@ -7,7 +7,7 @@ _HELP := " " func parse_ini(path:Path)->{Text:{Text:Text}}: - text := path:read():or_exit("Could not read INI file: $\[31;1]$(path.text_content)$\[]") + text := path:read() or exit("Could not read INI file: $\[31;1]$(path.text_content)$\[]") sections := {:Text:@{Text:Text}} current_section := @{:Text:Text} @@ -29,7 +29,7 @@ func parse_ini(path:Path)->{Text:{Text:Text}}: return {k:v[] for k,v in sections} func main(path:Path, key:Text?): - keys := key:or_else(""):split($|/|) + keys := (key or ""):split($|/|) if keys.length > 2: exit(" Too many arguments! @@ -42,7 +42,7 @@ func main(path:Path, key:Text?): return section := keys[1]:lower() - section_data := data:get(section):or_exit(" + section_data := data:get(section) or exit(" Invalid section name: $\[31;1]$section$\[] Valid names: $\[1]$(", ":join([k:quoted() for k in data.keys]))$\[] ") @@ -51,7 +51,7 @@ func main(path:Path, key:Text?): return section_key := keys[2]:lower() - value := section_data:get(section_key):or_exit(" + value := section_data:get(section_key) or exit(" Invalid key: $\[31;1]$section_key$\[] Valid keys: $\[1]$(", ":join([s:quoted() for s in section_data.keys]))$\[] ") diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index 7feca6c..f003167 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -110,12 +110,11 @@ func main(): # The value returned is optional (because the key might not be in the table). # Optional values can be converted to regular values using `!` (which will - # create a runtime error if the value is null) or :or_else() which uses a - # fallback value if it's null. + # create a runtime error if the value is null) or the `or` operator: >> table:get("two")! = 2 - >> table:get("xxx"):or_else(0) + >> table:get("xxx") or 0 = 0 # Empty tables require specifying the key and value types: diff --git a/examples/tomodeps/tomodeps.tm b/examples/tomodeps/tomodeps.tm index b2dc82d..1cd2ee5 100644 --- a/examples/tomodeps/tomodeps.tm +++ b/examples/tomodeps/tomodeps.tm @@ -80,7 +80,7 @@ func _draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}, already_ child_prefix := prefix ++ (if is_last: " " else: "│ ") - children := dependencies:get(dep):or_else({:Dependency}) + children := dependencies:get(dep) or {:Dependency} for i,child in children.items: is_child_last := (i == children.length) _draw_tree(child, dependencies, already_printed, child_prefix, is_child_last) @@ -89,7 +89,7 @@ func draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}): printed := {:Dependency} say(_printable_name(dep)) printed:add(dep) - deps := dependencies:get(dep):or_else({:Dependency}) + deps := dependencies:get(dep) or {:Dependency} for i,child in deps.items: is_child_last := (i == deps.length) _draw_tree(child, dependencies, already_printed=&printed, is_last=is_child_last) diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm index 5419819..a89d8b4 100644 --- a/examples/wrap/wrap.tm +++ b/examples/wrap/wrap.tm @@ -82,7 +82,7 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN files = [(/dev/stdin)] for file in files: - text := file:read():or_exit("Could not read file: $(file.text_content)") + text := file:read() or exit("Could not read file: $(file.text_content)") if rewrap: text = unwrap(text) diff --git a/test/optionals.tm b/test/optionals.tm index 05008e2..2c87d6c 100644 --- a/test/optionals.tm +++ b/test/optionals.tm @@ -83,16 +83,16 @@ func main(): 5 = 5? : Int? - >> (5?):or_else(-1) + >> 5? or -1 = 5 : Int - >> (5?):or_fail() + >> 5? or fail("Non-null is falsey") = 5 : Int - >> (5?):or_exit() + >> 5? or exit("Non-null is falsey") = 5 : Int - >> (!Int):or_else(-1) + >> (!Int) or -1 = -1 : Int do: diff --git a/test/tables.tm b/test/tables.tm index 50eb5ef..3dd27b0 100644 --- a/test/tables.tm +++ b/test/tables.tm @@ -10,7 +10,7 @@ func main(): = !Int >> t:get("one")! = 1 - >> t:get("???"):or_else(-1) + >> t:get("???") or -1 = -1 t_str := "" @@ -68,7 +68,7 @@ func main(): = 20 >> plain:get(2)! = 20 - >> plain:get(456):or_else(-999) + >> plain:get(456) or -999 = -999 >> plain:has(2) = yes @@ -78,6 +78,6 @@ func main(): >> fallback := {4:40; fallback=plain} >> fallback:has(1) = yes - >> fallback:get(1):or_else(-999) + >> fallback:get(1) or -999 = 10 diff --git a/typecheck.c b/typecheck.c index f5e130f..eefd92b 100644 --- a/typecheck.c +++ b/typecheck.c @@ -838,13 +838,6 @@ type_t *get_type(env_t *env, ast_t *ast) else if (streq(call->name, "sorted")) return self_value_t; code_err(ast, "There is no '%s' method for %T tables", call->name, self_value_t); } - case OptionalType: { - type_t *nonnull = Match(self_value_t, OptionalType)->type; - if (streq(call->name, "or_else")) return nonnull; - else if (streq(call->name, "or_fail")) return nonnull; - else if (streq(call->name, "or_exit")) return nonnull; - code_err(ast, "There is no '%s' method for optional %T values", call->name, nonnull); - } default: { type_t *fn_type_t = get_method_type(env, call->self, call->name); if (!fn_type_t)