Deprecate :or_else()/:or_fail()/:or_exit() in favor of the or operator

This commit is contained in:
Bruce Hill 2024-09-16 16:06:19 -04:00
parent 821bde156c
commit de49bc5bb3
11 changed files with 47 additions and 76 deletions

View File

@ -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);

View File

@ -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)
```

View File

@ -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
```

View File

@ -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)

View File

@ -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]))$\[]
")

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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)