Bring back table[key] syntax

This commit is contained in:
Bruce Hill 2024-11-30 15:50:54 -05:00
parent f3fc7558bb
commit 40c33987fa
9 changed files with 102 additions and 51 deletions

View File

@ -356,6 +356,15 @@ static CORD compile_lvalue(env_t *env, ast_t *ast)
", ", heap_strf("%ld", ast->start - ast->file->text), ", ", heap_strf("%ld", ast->start - ast->file->text),
", ", heap_strf("%ld", ast->end - ast->file->text), ")"); ", ", heap_strf("%ld", ast->end - ast->file->text), ")");
} }
} else if (container_t->tag == TableType) {
type_t *key_type = Match(container_t, TableType)->key_type;
type_t *value_type = Match(container_t, TableType)->value_type;
if (index->unchecked)
code_err(ast, "Table indexes cannot be unchecked");
return CORD_all("*(", compile_type(Type(PointerType, value_type)), ")Table$reserve(",
compile_to_pointer_depth(env, index->indexed, 1, false), ", ",
compile_to_type(env, index->index, Type(PointerType, key_type, .is_view=true)), ", NULL,",
compile_type_info(env, container_t), ")");
} else { } else {
code_err(ast, "I don't know how to assign to this target"); code_err(ast, "I don't know how to assign to this target");
} }
@ -669,6 +678,8 @@ CORD compile_statement(env_t *env, ast_t *ast)
int64_t i = 1; int64_t i = 1;
for (ast_list_t *value = assign->values, *target = assign->targets; value && target; value = value->next, target = target->next) { for (ast_list_t *value = assign->values, *target = assign->targets; value && target; value = value->next, target = target->next) {
type_t *lhs_t = get_type(env, target->ast); type_t *lhs_t = get_type(env, target->ast);
if (target->ast->tag == Index && get_type(env, Match(target->ast, Index)->indexed)->tag == TableType)
lhs_t = Match(lhs_t, OptionalType)->type;
env_t *val_env = with_enum_scope(env, lhs_t); env_t *val_env = with_enum_scope(env, lhs_t);
CORD val = compile_to_type(val_env, value->ast, lhs_t); CORD val = compile_to_type(val_env, value->ast, lhs_t);
CORD_appendf(&code, "%r $%ld = %r;\n", compile_type(lhs_t), i++, val); CORD_appendf(&code, "%r $%ld = %r;\n", compile_type(lhs_t), i++, val);
@ -3566,6 +3577,16 @@ CORD compile(env_t *env, ast_t *ast)
CORD_asprintf("%ld", (int64_t)(indexing->index->start - f->text)), ", ", CORD_asprintf("%ld", (int64_t)(indexing->index->start - f->text)), ", ",
CORD_asprintf("%ld", (int64_t)(indexing->index->end - f->text)), CORD_asprintf("%ld", (int64_t)(indexing->index->end - f->text)),
")"); ")");
} else if (container_t->tag == TableType) {
type_t *key_type = Match(container_t, TableType)->key_type;
type_t *value_type = Match(container_t, TableType)->value_type;
if (indexing->unchecked)
code_err(ast, "Table indexes cannot be unchecked");
return CORD_all("({ ", compile_declaration(Type(PointerType, value_type, .is_view=true), "value"),
" = Table$get(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ",
compile_to_type(env, indexing->index, Type(PointerType, key_type, .is_view=true)), ", ",
compile_type_info(env, container_t), "); \n"
"value ? ", promote_to_optional(value_type, "*value"), " : ", compile_null(value_type), "; })");
} else { } else {
code_err(ast, "Indexing is not supported for type: %T", container_t); code_err(ast, "Indexing is not supported for type: %T", container_t);
} }

View File

@ -86,10 +86,10 @@ add_cache := @{:add_args:Int}
func add(x, y:Int -> Int): func add(x, y:Int -> Int):
args := add_args(x, y) args := add_args(x, y)
if add_cache:has(args): if cached := add_cache[args]:
return add_cache:get(args) return cached
ret := _add(x, y) ret := _add(x, y)
add_cache:set(args, ret) add_cache[args] = ret
return ret return ret
``` ```

View File

@ -5,11 +5,6 @@ Map. Tables are efficiently implemented as a hash table that preserves
insertion order and has fast access to keys and values as array slices. Tables insertion order and has fast access to keys and values as array slices. Tables
support *all* types as both keys and values. support *all* types as both keys and values.
Tables do not support square bracket indexing (`t[key]`), but instead rely on
the methods `:get(key)` and `:set(key, value)`. This is explicit to avoid
hiding the fact that table lookups and table insertion are performing function
calls and have edge conditions like a failure to find an entry.
## Syntax ## Syntax
Tables are written using `{}` curly braces with `:` colons associating key Tables are written using `{}` curly braces with `:` colons associating key
@ -38,21 +33,29 @@ t := {i: 10*i for i in 10 if i mod 2 == 0}
t := {-1:-10, i: 10*i for i in 10} t := {-1:-10, i: 10*i for i in 10}
``` ```
### Getting Values ## Accessing Values
To get a value from a table, use `:get(key)`, which returns an _optional_ Table values can be accessed with square bracket indexing. The result is an
value, depending on whether it was present in the table or not. For convenience, optional value:
you can use the `!` postifx operator to perform a check to ensure that the value
was found or error if it wasn't:
```tomo ```tomo
>> t := {"x":1, "y":2} table := {"A": 1, "B": 2}
>> t:get("x") >> table["A"]
= 1 : Int? = 1 : Int?
>> t:get("????") >> table["missing"]
= NONE : Int? = NONE : Int?
>> t:get("x")! ```
= 1
As with all optional values, you can use the `!` postfix operator to assert
that the value is non-NONE (and create a runtime error if it is), or you can
use the `or` operator to provide a fallback value in the case that it's NONE:
```tomo
>> table["A"]!
= 1 : Int
>> table["missing"] or -1
= -1 : Int
``` ```
### Fallback Tables ### Fallback Tables
@ -63,12 +66,32 @@ is not found in the table itself:
```tomo ```tomo
t := {"A": 10} t := {"A": 10}
t2 := {"B": 20; fallback=t} t2 := {"B": 20; fallback=t}
>> t2:get("A") >> t2["A"]
= 10 = 10 : Int?
``` ```
The fallback is available by the `.fallback` field, which returns an optional The fallback is available by the `.fallback` field, which returns an optional
table value. table value:
```tomo
>> t2.fallback
= {"A":10} : {Text:Int}?
>> t.fallback
= NONE : {Text:Int}?
```
## Setting Values
You can assign a new key/value mapping or overwrite an existing one using
`:set(key, value)` or an `=` assignment statement:
```tomo
t := {"A": 1, "B": 2}
t["B"] = 222
t["C"] = 333
>> t
= {"A":1, "B":222, "C":333}
```
## Length ## Length

View File

@ -20,11 +20,11 @@ func parse_ini(path:Path -> {Text:{Text:Text}}):
if line:matches($/[?]/): if line:matches($/[?]/):
section_name := line:replace($/[?]/, "\1"):trim():lower() section_name := line:replace($/[?]/, "\1"):trim():lower()
current_section = @{:Text:Text} current_section = @{:Text:Text}
sections:set(section_name, current_section) sections[section_name] = current_section
else if line:matches($/{..}={..}/): else if line:matches($/{..}={..}/):
key := line:replace($/{..}={..}/, "\1"):trim():lower() key := line:replace($/{..}={..}/, "\1"):trim():lower()
value := line:replace($/{..}={..}/, "\2"):trim() value := line:replace($/{..}={..}/, "\2"):trim()
current_section:set(key, value) current_section[key] = value
return {k:v[] for k,v in sections[]} return {k:v[] for k,v in sections[]}
@ -42,7 +42,7 @@ func main(path:Path, key:Text?):
return return
section := keys[1]:lower() section := keys[1]:lower()
section_data := data:get(section) or exit(" section_data := data[section] or exit("
Invalid section name: $\[31;1]$section$\[] Invalid section name: $\[31;1]$section$\[]
Valid names: $\[1]$(", ":join([k:quoted() for k in data.keys]))$\[] Valid names: $\[1]$(", ":join([k:quoted() for k in data.keys]))$\[]
") ")
@ -51,7 +51,7 @@ func main(path:Path, key:Text?):
return return
section_key := keys[2]:lower() section_key := keys[2]:lower()
value := section_data:get(section_key) or exit(" value := section_data[section_key] or exit("
Invalid key: $\[31;1]$section_key$\[] Invalid key: $\[31;1]$section_key$\[]
Valid keys: $\[1]$(", ":join([s:quoted() for s in section_data.keys]))$\[] Valid keys: $\[1]$(", ":join([s:quoted() for s in section_data.keys]))$\[]
") ")

View File

@ -105,21 +105,21 @@ func main():
# Tables are efficient hash maps # Tables are efficient hash maps
table := {"one": 1, "two": 2} table := {"one": 1, "two": 2}
>> table:get("two") >> table["two"]
= 2 : Int? = 2 : Int?
# The value returned is optional because NONE will be returned if the key # The value returned is optional because NONE will be returned if the key
# is not in the table: # is not in the table:
>> table:get("xxx")! >> table["xxx"]!
= NONE : Int? = NONE : Int?
# Optional values can be converted to regular values using `!` (which will # Optional values can be converted to regular values using `!` (which will
# create a runtime error if the value is null): # create a runtime error if the value is null):
>> table:get("two")! >> table["two"]!
= 2 : Int = 2 : Int
# You can also use `or` to provide a fallback value to replace NONE: # You can also use `or` to provide a fallback value to replace NONE:
>> table:get("xxx") or 0 >> table["xxx"] or 0
= 0 : Int = 0 : Int
# Empty tables require specifying the key and value types: # Empty tables require specifying the key and value types:
@ -142,9 +142,9 @@ func main():
# Tables can have a fallback table that's used as a fallback when the key # Tables can have a fallback table that's used as a fallback when the key
# isn't found in the table itself: # isn't found in the table itself:
table2 := {"three": 3; fallback=table} table2 := {"three": 3; fallback=table}
>> table2:get("two")! >> table2["two"]!
= 2 = 2
>> table2:get("three")! >> table2["three"]!
= 3 = 3
# Tables can also be created with comprehension loops: # Tables can also be created with comprehension loops:
@ -157,7 +157,7 @@ func main():
# Any types can be used in tables, for example, a table mapping arrays to # Any types can be used in tables, for example, a table mapping arrays to
# strings: # strings:
table3 := {[10, 20]: "one", [30, 40, 50]: "two"} table3 := {[10, 20]: "one", [30, 40, 50]: "two"}
>> table3:get([10, 20])! >> table3[[10, 20]]!
= "one" = "one"
# Sets are similar to tables, but they represent an unordered collection of # Sets are similar to tables, but they represent an unordered collection of
@ -294,7 +294,7 @@ func demo_structs():
= yes = yes
table := {alice: "first", bob: "second"} table := {alice: "first", bob: "second"}
>> table:get(alice)! >> table[alice]!
= "first" = "first"

View File

@ -28,7 +28,7 @@ func _get_file_dependencies(file:Path -> {Dependency}):
func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency:{Dependency}}): func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency:{Dependency}}):
return if dependencies:has(dep) return if dependencies:has(dep)
dependencies:set(dep, {:Dependency}) # Placeholder dependencies[dep] = {:Dependency} # Placeholder
dep_deps := when dep is File(path): dep_deps := when dep is File(path):
_get_file_dependencies(path) _get_file_dependencies(path)
@ -50,7 +50,7 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency:{Dependen
module_deps:add(file_dep) module_deps:add(file_dep)
module_deps[] module_deps[]
dependencies:set(dep, dep_deps) dependencies[dep] = dep_deps
for dep2 in dep_deps: for dep2 in dep_deps:
_build_dependency_graph(dep2, dependencies) _build_dependency_graph(dep2, dependencies)
@ -80,7 +80,7 @@ func _draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}, already_
child_prefix := prefix ++ (if is_last: " " else: "│ ") child_prefix := prefix ++ (if is_last: " " else: "│ ")
children := dependencies:get(dep) or {:Dependency} children := dependencies[dep] or {:Dependency}
for i,child in children.items: for i,child in children.items:
is_child_last := (i == children.length) is_child_last := (i == children.length)
_draw_tree(child, dependencies, already_printed, child_prefix, is_child_last) _draw_tree(child, dependencies, already_printed, child_prefix, is_child_last)
@ -89,7 +89,7 @@ func draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}):
printed := @{:Dependency} printed := @{:Dependency}
say(_printable_name(dep)) say(_printable_name(dep))
printed:add(dep) printed:add(dep)
deps := dependencies:get(dep) or {:Dependency} deps := dependencies[dep] or {:Dependency}
for i,child in deps.items: for i,child in deps.items:
is_child_last := (i == deps.length) is_child_last := (i == deps.length)
_draw_tree(child, dependencies, already_printed=printed, is_last=is_child_last) _draw_tree(child, dependencies, already_printed=printed, is_last=is_child_last)

View File

@ -74,7 +74,7 @@ func main():
= Password(...) = Password(...)
>> users_by_password := {my_pass:"User1", Password("xxx"):"User2"} >> users_by_password := {my_pass:"User1", Password("xxx"):"User2"}
= {Password(...):"User1", Password(...):"User2"} = {Password(...):"User1", Password(...):"User2"}
>> users_by_password:get(my_pass)! >> users_by_password[my_pass]!
= "User1" = "User1"
>> CorecursiveA(@CorecursiveB()) >> CorecursiveA(@CorecursiveB())

View File

@ -2,15 +2,15 @@ func main():
>> t := {"one":1, "two":2} >> t := {"one":1, "two":2}
= {"one":1, "two":2} = {"one":1, "two":2}
>> t:get("one") >> t["one"]
= 1 : Int? = 1 : Int?
>> t:get("two") >> t["two"]
= 2 : Int? = 2 : Int?
>> t:get("???") >> t["???"]
= NONE : Int? = NONE : Int?
>> t:get("one")! >> t["one"]!
= 1 = 1
>> t:get("???") or -1 >> t["???"] or -1
= -1 = -1
t_str := "" t_str := ""
@ -32,11 +32,11 @@ func main():
>> t2 := {"three":3; fallback=t} >> t2 := {"three":3; fallback=t}
= {"three":3; fallback={"one":1, "two":2}} = {"three":3; fallback={"one":1, "two":2}}
>> t2:get("one") >> t2["one"]
= 1 : Int? = 1 : Int?
>> t2:get("three") >> t2["three"]
= 3 : Int? = 3 : Int?
>> t2:get("???") >> t2["???"]
= NONE : Int? = NONE : Int?
>> t2.length >> t2.length
@ -64,11 +64,11 @@ func main():
do: do:
>> plain := {1:10, 2:20, 3:30} >> plain := {1:10, 2:20, 3:30}
>> plain:get(2)! >> plain[2]!
= 20 = 20
>> plain:get(2)! >> plain[2]!
= 20 = 20
>> plain:get(456) or -999 >> plain[456] or -999
= -999 = -999
>> plain:has(2) >> plain:has(2)
= yes = yes
@ -78,6 +78,13 @@ func main():
>> fallback := {4:40; fallback=plain} >> fallback := {4:40; fallback=plain}
>> fallback:has(1) >> fallback:has(1)
= yes = yes
>> fallback:get(1) or -999 >> fallback[1] or -999
= 10 = 10
do:
>> t4 := {"one": 1}
>> t4["one"] = 999
>> t4["two"] = 222
>> t4
= {"one":999, "two":222}

View File

@ -718,7 +718,7 @@ type_t *get_type(env_t *env, ast_t *ast)
return Match(value_t, ArrayType)->item_type; return Match(value_t, ArrayType)->item_type;
code_err(indexing->index, "I only know how to index lists using integers, not %T", index_t); code_err(indexing->index, "I only know how to index lists using integers, not %T", index_t);
} else if (value_t->tag == TableType) { } else if (value_t->tag == TableType) {
code_err(ast, "Tables use the table:get(key) method, not square bracket indexing like table[key]"); return Type(OptionalType, Match(value_t, TableType)->value_type);
} else { } else {
code_err(ast, "I don't know how to index %T values", indexed_t); code_err(ast, "I don't know how to index %T values", indexed_t);
} }