From 327d466b9543aee3983277ca4158e5b7aa06fdf8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 12 Sep 2024 00:55:43 -0400 Subject: [PATCH] Table:get() now uses optional values instead of default or failure modes --- builtins/table.h | 12 +++------ compile.c | 37 ++++++++++------------------ docs/tables.md | 63 +++++++++++++++++++++--------------------------- test/structs.tm | 2 +- test/tables.tm | 34 ++++++++++++++------------ typecheck.c | 10 ++------ 6 files changed, 65 insertions(+), 93 deletions(-) diff --git a/builtins/table.h b/builtins/table.h index 8e6e5de..e589e6e 100644 --- a/builtins/table.h +++ b/builtins/table.h @@ -31,16 +31,10 @@ Table_t Table$from_entries(Array_t entries, const TypeInfo *type); void *Table$get(Table_t t, const void *key, const TypeInfo *type); -#define Table$get_value_or_fail(table_expr, key_t, val_t, key_expr, info_expr, start, end) ({ \ - const Table_t t = table_expr; key_t k = key_expr; const TypeInfo* info = info_expr; \ - val_t *v = Table$get(t, &k, info); \ - if (__builtin_expect(v == NULL, 0)) \ - fail_source(__SOURCE_FILE__, start, end, "The key %k is not in this table\n", (Text_t[1]){generic_as_text(&k, no, info->TableInfo.key)}); \ - *v; }) -#define Table$get_value_or_default(table_expr, key_t, val_t, key_expr, default_val, info_expr) ({ \ +#define Table$get_optional(table_expr, key_t, val_t, key_expr, nonnull_var, nonnull_expr, null_expr, info_expr) ({ \ const Table_t t = table_expr; const key_t k = key_expr; \ - val_t *v = Table$get(t, &k, info_expr); \ - v ? *v : default_val; }) + val_t *nonnull_var = Table$get(t, &k, info_expr); \ + nonnull_var ? nonnull_expr : null_expr; }) #define Table$has_value(table_expr, key_expr, info_expr) ({ \ const Table_t t = table_expr; __typeof(key_expr) k = key_expr; \ (Table$get(t, &k, info_expr) != NULL); }) diff --git a/compile.c b/compile.c index cc334e7..9e5b833 100644 --- a/compile.c +++ b/compile.c @@ -32,14 +32,14 @@ CORD promote_to_optional(type_t *t, CORD code) return code; } else if (t->tag == IntType) { switch (Match(t, IntType)->bits) { - case TYPE_IBITS8: return CORD_all("((OptionalInt8_t){.i=", code, "})"); - case TYPE_IBITS16: return CORD_all("((OptionalInt16_t){.i=", code, "})"); - case TYPE_IBITS32: return CORD_all("((OptionalInt32_t){.i=", code, "})"); - case TYPE_IBITS64: return CORD_all("((OptionalInt64_t){.i=", code, "})"); + case TYPE_IBITS8: return CORD_all("((OptionalInt8_t){", code, "})"); + case TYPE_IBITS16: return CORD_all("((OptionalInt16_t){", code, "})"); + case TYPE_IBITS32: return CORD_all("((OptionalInt32_t){", code, "})"); + case TYPE_IBITS64: return CORD_all("((OptionalInt64_t){", code, "})"); default: errx(1, "Unsupported in type: %T", t); } } else if (t->tag == StructType) { - return CORD_all("((", compile_type(Type(OptionalType, .type=t)), "){.value=", code, "})"); + return CORD_all("((", compile_type(Type(OptionalType, .type=t)), "){", code, "})"); } else { return code; } @@ -1708,6 +1708,9 @@ static bool string_literal_is_all_ascii(CORD literal) CORD compile_null(type_t *t) { + if (t->tag == OptionalType) + t = Match(t, OptionalType)->type; + if (t == THREAD_TYPE) return "NULL"; switch (t->tag) { @@ -2695,26 +2698,12 @@ CORD compile(env_t *env, ast_t *ast) auto table = Match(self_value_t, TableType); if (streq(call->name, "get")) { CORD self = compile_to_pointer_depth(env, call->self, 0, false); - if (call->args->next) { - arg_t *arg_spec = new(arg_t, .name="key", .type=table->key_type, .next=new(arg_t, .name="default", .type=table->value_type)); - return CORD_all("Table$get_value_or_default(", self, ", ", compile_type(table->key_type), ", ", compile_type(table->value_type), ", ", - compile_arguments(env, ast, arg_spec, call->args), ", ", compile_type_info(env, self_value_t), ")"); - } else { - arg_t *arg_spec = new(arg_t, .name="key", .type=table->key_type); - file_t *f = ast->file; - return CORD_all("Table$get_value_or_fail(", self, ", ", compile_type(table->key_type), ", ", compile_type(table->value_type), ", ", - compile_arguments(env, ast, arg_spec, call->args), ", ", compile_type_info(env, self_value_t), ", ", - CORD_asprintf("%ld", (int64_t)(ast->start - f->text)), ", ", - CORD_asprintf("%ld", (int64_t)(ast->end - f->text)), - ")"); - } - } else if (streq(call->name, "get_or_null")) { - if (table->value_type->tag != PointerType) - code_err(ast, "The table method :get_or_null() is only supported for tables whose value type is a pointer, not %T", table->value_type); - CORD self = compile_to_pointer_depth(env, call->self, 0, false); arg_t *arg_spec = new(arg_t, .name="key", .type=table->key_type); - return CORD_all("Table$get_value_or_default(", self, ", ", compile_type(table->key_type), ", ", compile_type(table->value_type), ", ", - compile_arguments(env, ast, arg_spec, call->args), ", NULL, ", compile_type_info(env, self_value_t), ")"); + return CORD_all( + "Table$get_optional(", self, ", ", compile_type(table->key_type), ", ", + compile_type(table->value_type), ", ", compile_arguments(env, ast, arg_spec, call->args), ", ", + "_, ", optional_into_nonnull(table->value_type, "(*_)"), ", ", compile_null(table->value_type), ", ", + compile_type_info(env, self_value_t), ")"); } else if (streq(call->name, "has")) { CORD self = compile_to_pointer_depth(env, call->self, 0, false); arg_t *arg_spec = new(arg_t, .name="key", .type=table->key_type); diff --git a/docs/tables.md b/docs/tables.md index b18b620..9de07b6 100644 --- a/docs/tables.md +++ b/docs/tables.md @@ -38,6 +38,23 @@ t := {i: 10*i for i in 10 if i mod 2 == 0} t := {-1:-10, i: 10*i for i in 10} ``` +### Getting Values + +To get a value from a table, use `:get(key)`, which returns an _optional_ +value, depending on whether it was present in the table or not. For convenience, +you can use the `!` postifx operator to perform a check to ensure that the value +was found or error if it wasn't: + +```tomo +>> t := {"x":1, "y":2} +>> t:get("x") += 1? +>> t:get("????") += !Int +>> t:get("x")! += 1 +``` + ### Fallback Tables Tables can specify a fallback table that is used when looking up a value if it @@ -151,61 +168,35 @@ Nothing. ### `get` **Description:** -Retrieves the value associated with a key, or returns a default value if the key is not present. +Retrieves the value associated with a key, or returns null if the key is not present. **Usage:** ```markdown -t:get(key: K, default: V) -> V +t:get(key: K) -> V? ``` **Parameters:** - `t`: The table. - `key`: The key whose associated value is to be retrieved. -- `default`: The value to return if the key is not present. If this argument is - not provided, a runtime error will be created if the key is not present. **Returns:** -The value associated with the key or the default value if the key is not found. +The value associated with the key or null if the key is not found. **Example:** ```markdown >> t := {"A":1, "B":2} >> t:get("A") -= 1 += 1? : Int? ->> t:get("xxx", 0) -= 0 -``` +>> t:get("????") += !Int : Int? ---- +>> t:get("A")! += 1 : Int -### `get_or_null` - -**Description:** -Retrieves the value associated with a key, or returns `null` if the key is not present. -This method is only available on tables whose values are pointers. - -**Usage:** -```markdown -t:get_or_null(key: K) -> @V? -``` - -**Parameters:** - -- `t`: The table. -- `key`: The key whose associated value is to be retrieved. - -**Returns:** -A mutable reference to the value associated with the key or `null` if the key is not found. - -**Example:** -```markdown ->> t := {"A": @[10]} ->> t:get_or_null("A") -= @[10]? ->> t:get_or_null("xxx") -= !@[Int] +>> t:get("????"):or_else(0) += 0 : Int ``` --- diff --git a/test/structs.tm b/test/structs.tm index c9022ed..53d356f 100644 --- a/test/structs.tm +++ b/test/structs.tm @@ -63,7 +63,7 @@ func main(): = Password(...) >> users_by_password := {my_pass:"User1", Password("xxx"):"User2"} = {Password(...):"User1", Password(...):"User2"} - >> users_by_password:get(my_pass) + >> users_by_password:get(my_pass)! = "User1" >> CorecursiveA(@CorecursiveB()) diff --git a/test/tables.tm b/test/tables.tm index e59283e..50eb5ef 100644 --- a/test/tables.tm +++ b/test/tables.tm @@ -2,12 +2,16 @@ func main(): >> t := {"one":1, "two":2} = {"one":1, "two":2} - >> t:get("one", 999) + >> t:get("one") + = 1? + >> t:get("two") + = 2? + >> t:get("???") + = !Int + >> t:get("one")! = 1 - >> t:get("two", 999) - = 2 - >> t:get("???", 999) - = 999 + >> t:get("???"):or_else(-1) + = -1 t_str := "" for k,v in t: @@ -28,12 +32,12 @@ func main(): >> t2 := {"three":3; fallback=t} = {"three":3; fallback={"one":1, "two":2}} - >> t2:get("one", 999) - = 1 - >> t2:get("three", 999) - = 3 - >> t2:get("???", 999) - = 999 + >> t2:get("one") + = 1? + >> t2:get("three") + = 3? + >> t2:get("???") + = !Int >> t2.length = 1 @@ -60,11 +64,11 @@ func main(): do: >> plain := {1:10, 2:20, 3:30} - >> plain:get(2) + >> plain:get(2)! = 20 - >> plain:get(2, -999) + >> plain:get(2)! = 20 - >> plain:get(456, -999) + >> plain:get(456):or_else(-999) = -999 >> plain:has(2) = yes @@ -74,6 +78,6 @@ func main(): >> fallback := {4:40; fallback=plain} >> fallback:has(1) = yes - >> fallback:get(1, -999) + >> fallback:get(1):or_else(-999) = 10 diff --git a/typecheck.c b/typecheck.c index b70db78..192a335 100644 --- a/typecheck.c +++ b/typecheck.c @@ -832,14 +832,8 @@ type_t *get_type(env_t *env, ast_t *ast) auto table = Match(self_value_t, TableType); if (streq(call->name, "bump")) return Type(VoidType); else if (streq(call->name, "clear")) return Type(VoidType); - else if (streq(call->name, "get")) return table->value_type; - else if (streq(call->name, "get_or_null")) { - if (table->value_type->tag != PointerType) - code_err(ast, "The table method :get_or_null() is only supported for tables whose value type is a pointer, not %T", - table->value_type); - auto ptr = Match(table->value_type, PointerType); - return Type(OptionalType, .type=Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly)); - } else if (streq(call->name, "has")) return Type(BoolType); + else if (streq(call->name, "get")) return Type(OptionalType, .type=table->value_type); + else if (streq(call->name, "has")) return Type(BoolType); else if (streq(call->name, "remove")) return Type(VoidType); else if (streq(call->name, "set")) return Type(VoidType); else if (streq(call->name, "sorted")) return self_value_t;