From c034175ae1cbe53bc0e7e1ae71b51c64dcc3402a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 11 Sep 2024 23:17:03 -0400 Subject: [PATCH] Add optional:or_else(fallback) and optional:or_fail(message) --- compile.c | 68 ++++++++++++++++++++++++++++++++--------------- docs/optionals.md | 17 ++++++++++++ test/optionals.tm | 9 +++++++ typecheck.c | 6 +++++ 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/compile.c b/compile.c index 8da5eba..a72d447 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 445a968..1414ca6 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 e2cdf9a..26e825a 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 e500dfa..9ad151a 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)