Table:get() now uses optional values instead of default or failure modes

This commit is contained in:
Bruce Hill 2024-09-12 00:55:43 -04:00
parent fa7e52787f
commit 327d466b95
6 changed files with 65 additions and 93 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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