Table:get() now uses optional values instead of default or failure modes
This commit is contained in:
parent
fa7e52787f
commit
327d466b95
@ -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); })
|
||||
|
37
compile.c
37
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);
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
||||
|
10
typecheck.c
10
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;
|
||||
|
Loading…
Reference in New Issue
Block a user