aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-11 23:17:03 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-11 23:17:03 -0400
commitc034175ae1cbe53bc0e7e1ae71b51c64dcc3402a (patch)
tree8a6b1754c7530399a563d7b80a669d6d08d8af37
parentcfef667a899339a0fd5b79214d581db6ede10748 (diff)
Add optional:or_else(fallback) and optional:or_fail(message)
-rw-r--r--compile.c68
-rw-r--r--docs/optionals.md17
-rw-r--r--test/optionals.tm9
-rw-r--r--typecheck.c6
4 files changed, 78 insertions, 22 deletions
diff --git a/compile.c b/compile.c
index 8da5eba4..a72d4476 100644
--- a/compile.c
+++ b/compile.c
@@ -333,33 +333,32 @@ static CORD optional_var_into_nonnull(binding_t *b)
}
}
-static CORD compile_optional_check(env_t *env, ast_t *ast)
+static CORD compile_optional_check(type_t *t, CORD value)
{
- type_t *t = get_type(env, ast);
t = Match(t, OptionalType)->type;
if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType
|| t->tag == ChannelType || t == THREAD_TYPE)
- return CORD_all("(", compile(env, ast), " != NULL)");
+ return CORD_all("(", value, " != NULL)");
else if (t->tag == BigIntType)
- return CORD_all("((", compile(env, ast), ").small != 0)");
+ return CORD_all("((", value, ").small != 0)");
else if (t->tag == ClosureType)
- return CORD_all("((", compile(env, ast), ").fn != NULL)");
+ return CORD_all("((", value, ").fn != NULL)");
else if (t->tag == NumType)
- return CORD_all("!isnan(", compile(env, ast), ")");
+ return CORD_all("!isnan(", value, ")");
else if (t->tag == ArrayType)
- return CORD_all("((", compile(env, ast), ").length >= 0)");
+ return CORD_all("((", value, ").length >= 0)");
else if (t->tag == TableType || t->tag == SetType)
- return CORD_all("((", compile(env, ast), ").entries.length >= 0)");
+ return CORD_all("((", value, ").entries.length >= 0)");
else if (t->tag == BoolType)
- return CORD_all("((", compile(env, ast), ") != NULL_BOOL)");
+ return CORD_all("((", value, ") != NULL_BOOL)");
else if (t->tag == TextType)
- return CORD_all("((", compile(env, ast), ").length >= 0)");
+ return CORD_all("((", value, ").length >= 0)");
else if (t->tag == IntType)
- return CORD_all("!(", compile(env, ast), ").is_null");
+ return CORD_all("!(", value, ").is_null");
else if (t->tag == StructType)
- return CORD_all("!(", compile(env, ast), ").is_null");
+ return CORD_all("!(", value, ").is_null");
else if (t->tag == EnumType)
- return CORD_all("((", compile(env, ast), ").tag != 0)");
+ return CORD_all("((", value, ").tag != 0)");
errx(1, "Optional check not implemented for: %T", t);
}
@@ -1234,10 +1233,8 @@ CORD compile_statement(env_t *env, ast_t *ast)
next_fn = "next";
}
- env_t *tmp_env = fresh_scope(env);
- set_binding(tmp_env, "cur", new(binding_t, .type=fn->ret, .code="cur"));
next_fn = CORD_all("(cur=", next_fn, iter_t->tag == ClosureType ? "(next.userdata)" : "()", ", ",
- compile_optional_check(tmp_env, FakeAST(Var, "cur")), ")");
+ compile_optional_check(fn->ret, "cur"), ")");
if (for_->vars) {
naked_body = CORD_all(
@@ -1292,7 +1289,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
nonnull_b->code = optional_var_into_nonnull(b);
set_binding(truthy_scope, varname, nonnull_b);
}
- condition_code = compile_optional_check(env, condition);
+ condition_code = compile_optional_check(cond_t, compile(env, condition));
} else if (cond_t->tag == BoolType) {
condition_code = compile(env, condition);
} else {
@@ -1828,7 +1825,7 @@ CORD compile(env_t *env, ast_t *ast)
else if (t->tag == TextType)
return CORD_all("(", compile(env, value), " == CORD_EMPTY)");
else if (t->tag == OptionalType)
- return CORD_all("!(", compile_optional_check(env, value), ")");
+ return CORD_all("!(", compile_optional_check(t, compile(env, value)), ")");
code_err(ast, "I don't know how to negate values of type %T", t);
}
@@ -2745,6 +2742,30 @@ 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, "; ",
+ compile_optional_check(self_value_t, "opt"), " ? ",
+ optional_var_into_nonnull(new(binding_t, .type=self_value_t, .code="opt")),
+ " : ", compile_arguments(env, ast, arg_spec, call->args), "; })");
+ } 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 (!", compile_optional_check(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_var_into_nonnull(new(binding_t, .type=self_value_t, .code="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);
@@ -3377,10 +3398,11 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type)
CORD flag = CORD_replace(arg->name, "_", "-");
switch (non_optional->tag) {
case BoolType: {
+ CORD optional_check = compile_optional_check(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name));
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n"
"if (flag.length != 0) {\n",
"$", arg->name, " = Bool$from_text(flag);\n"
- "if (!", compile_optional_check(main_env, FakeAST(Var, arg->name)), ") \n"
+ "if (!", optional_check, ") \n"
"USAGE_ERR(\"Invalid argument for '--", flag, "'\");\n",
"} else {\n",
"$", arg->name, " = yes;\n",
@@ -3408,12 +3430,13 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type)
break;
}
case BigIntType: case IntType: case NumType: {
+ CORD optional_check = compile_optional_check(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name));
CORD type_name = type_to_cord(non_optional);
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n",
"if (flag.length == 0)\n"
"USAGE_ERR(\"No value provided for '--", flag, "'\");\n"
"$", arg->name, " = ", type_name, "$from_text(flag);\n"
- "if (!", compile_optional_check(main_env, FakeAST(Var, arg->name)), ")\n"
+ "if (!", optional_check, ")\n"
"USAGE_ERR(\"Invalid value provided for '--", flag, "'\");\n",
"}\n");
break;
@@ -3455,7 +3478,8 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type)
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
type_t *t = get_arg_type(main_env, arg);
type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t;
- code = CORD_all(code, "if (!", compile_optional_check(main_env, FakeAST(Var, arg->name)), ") {\n");
+ CORD optional_check = compile_optional_check(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name));
+ code = CORD_all(code, "if (!", optional_check, ") {\n");
if (non_optional->tag == ArrayType) {
if (Match(non_optional, ArrayType)->item_type->tag != TextType)
compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T (only arrays of Text are supported)", non_optional);
@@ -3503,7 +3527,7 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type)
code = CORD_all(
code,
"$", arg->name, " = ", type_to_cord(non_optional), "$from_text(Text$from_str(argv[i]))", ";\n"
- "if (!", compile_optional_check(main_env, FakeAST(Var, arg->name)), ")\n"
+ "if (!", optional_check, ")\n"
"USAGE_ERR(\"Unable to parse this argument as a ", type_to_cord(non_optional), ": %s\", argv[i]);\n");
}
code = CORD_all(
diff --git a/docs/optionals.md b/docs/optionals.md
index 445a9681..1414ca6a 100644
--- a/docs/optionals.md
+++ b/docs/optionals.md
@@ -28,3 +28,20 @@ func maybe_takes_int(x:Int?):
This establishes a common language for talking about optional values without
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()`:
+
+```tomo
+maybe_x := 5?
+>> maybe_x:or_else(-1)
+= 5 : Int
+>> maybe_x:or_fail()
+= 5 : Int
+
+maybe_x = !Int
+>> maybe_x:or_else(-1)
+= -1 : Int
+>> maybe_x:or_fail()
+# Failure!
+```
diff --git a/test/optionals.tm b/test/optionals.tm
index e2cdf9a6..26e825ae 100644
--- a/test/optionals.tm
+++ b/test/optionals.tm
@@ -83,6 +83,15 @@ func main():
5
= 5? : Int?
+ >> (5?):or_else(-1)
+ = 5 : Int
+
+ >> (5?):or_fail()
+ = 5 : Int
+
+ >> (!Int):or_else(-1)
+ = -1 : Int
+
do:
!! Ints:
>> yep := maybe_int(yes)
diff --git a/typecheck.c b/typecheck.c
index e500dfa7..9ad151a3 100644
--- a/typecheck.c
+++ b/typecheck.c
@@ -838,6 +838,12 @@ 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;
+ 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)