From 40c33987fa0a91a8525d960f8494ca9ddf12806d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Nov 2024 15:50:54 -0500 Subject: [PATCH] Bring back `table[key]` syntax --- compile.c | 21 +++++++++++++ docs/functions.md | 6 ++-- docs/tables.md | 59 ++++++++++++++++++++++++----------- examples/ini/ini.tm | 8 ++--- examples/learnxiny.tm | 16 +++++----- examples/tomodeps/tomodeps.tm | 8 ++--- test/structs.tm | 2 +- test/tables.tm | 31 +++++++++++------- typecheck.c | 2 +- 9 files changed, 102 insertions(+), 51 deletions(-) diff --git a/compile.c b/compile.c index 8356fbb..8244992 100644 --- a/compile.c +++ b/compile.c @@ -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->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 { 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; 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); + 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); CORD val = compile_to_type(val_env, value->ast, lhs_t); 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->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 { code_err(ast, "Indexing is not supported for type: %T", container_t); } diff --git a/docs/functions.md b/docs/functions.md index 05d84e4..5ea2dec 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -86,10 +86,10 @@ add_cache := @{:add_args:Int} func add(x, y:Int -> Int): args := add_args(x, y) - if add_cache:has(args): - return add_cache:get(args) + if cached := add_cache[args]: + return cached ret := _add(x, y) - add_cache:set(args, ret) + add_cache[args] = ret return ret ``` diff --git a/docs/tables.md b/docs/tables.md index 6cbe859..ef2333a 100644 --- a/docs/tables.md +++ b/docs/tables.md @@ -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 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 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} ``` -### Getting Values +## Accessing 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: +Table values can be accessed with square bracket indexing. The result is an +optional value: ```tomo ->> t := {"x":1, "y":2} ->> t:get("x") +table := {"A": 1, "B": 2} +>> table["A"] = 1 : Int? ->> t:get("????") +>> table["missing"] = 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 @@ -63,12 +66,32 @@ is not found in the table itself: ```tomo t := {"A": 10} t2 := {"B": 20; fallback=t} ->> t2:get("A") -= 10 +>> t2["A"] += 10 : Int? ``` 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 diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm index 5952bb0..9575b9f 100644 --- a/examples/ini/ini.tm +++ b/examples/ini/ini.tm @@ -20,11 +20,11 @@ func parse_ini(path:Path -> {Text:{Text:Text}}): if line:matches($/[?]/): section_name := line:replace($/[?]/, "\1"):trim():lower() current_section = @{:Text:Text} - sections:set(section_name, current_section) + sections[section_name] = current_section else if line:matches($/{..}={..}/): key := line:replace($/{..}={..}/, "\1"):trim():lower() value := line:replace($/{..}={..}/, "\2"):trim() - current_section:set(key, value) + current_section[key] = value return {k:v[] for k,v in sections[]} @@ -42,7 +42,7 @@ func main(path:Path, key:Text?): return section := keys[1]:lower() - section_data := data:get(section) or exit(" + section_data := data[section] or exit(" Invalid section name: $\[31;1]$section$\[] Valid names: $\[1]$(", ":join([k:quoted() for k in data.keys]))$\[] ") @@ -51,7 +51,7 @@ func main(path:Path, key:Text?): return 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$\[] Valid keys: $\[1]$(", ":join([s:quoted() for s in section_data.keys]))$\[] ") diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index e31bab7..af7a55f 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -105,21 +105,21 @@ func main(): # Tables are efficient hash maps table := {"one": 1, "two": 2} - >> table:get("two") + >> table["two"] = 2 : Int? # The value returned is optional because NONE will be returned if the key # is not in the table: - >> table:get("xxx")! + >> table["xxx"]! = NONE : Int? # Optional values can be converted to regular values using `!` (which will # create a runtime error if the value is null): - >> table:get("two")! + >> table["two"]! = 2 : Int # You can also use `or` to provide a fallback value to replace NONE: - >> table:get("xxx") or 0 + >> table["xxx"] or 0 = 0 : Int # 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 # isn't found in the table itself: table2 := {"three": 3; fallback=table} - >> table2:get("two")! + >> table2["two"]! = 2 - >> table2:get("three")! + >> table2["three"]! = 3 # 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 # strings: table3 := {[10, 20]: "one", [30, 40, 50]: "two"} - >> table3:get([10, 20])! + >> table3[[10, 20]]! = "one" # Sets are similar to tables, but they represent an unordered collection of @@ -294,7 +294,7 @@ func demo_structs(): = yes table := {alice: "first", bob: "second"} - >> table:get(alice)! + >> table[alice]! = "first" diff --git a/examples/tomodeps/tomodeps.tm b/examples/tomodeps/tomodeps.tm index fbe297e..1f42850 100644 --- a/examples/tomodeps/tomodeps.tm +++ b/examples/tomodeps/tomodeps.tm @@ -28,7 +28,7 @@ func _get_file_dependencies(file:Path -> {Dependency}): func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency:{Dependency}}): return if dependencies:has(dep) - dependencies:set(dep, {:Dependency}) # Placeholder + dependencies[dep] = {:Dependency} # Placeholder dep_deps := when dep is File(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[] - dependencies:set(dep, dep_deps) + dependencies[dep] = dep_deps for dep2 in dep_deps: _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: "│ ") - children := dependencies:get(dep) or {:Dependency} + children := dependencies[dep] or {:Dependency} for i,child in children.items: is_child_last := (i == children.length) _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} say(_printable_name(dep)) printed:add(dep) - deps := dependencies:get(dep) or {:Dependency} + deps := dependencies[dep] or {:Dependency} for i,child in deps.items: is_child_last := (i == deps.length) _draw_tree(child, dependencies, already_printed=printed, is_last=is_child_last) diff --git a/test/structs.tm b/test/structs.tm index ae5bec0..afbb1a8 100644 --- a/test/structs.tm +++ b/test/structs.tm @@ -74,7 +74,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[my_pass]! = "User1" >> CorecursiveA(@CorecursiveB()) diff --git a/test/tables.tm b/test/tables.tm index acda0eb..0d8954a 100644 --- a/test/tables.tm +++ b/test/tables.tm @@ -2,15 +2,15 @@ func main(): >> t := {"one":1, "two":2} = {"one":1, "two":2} - >> t:get("one") + >> t["one"] = 1 : Int? - >> t:get("two") + >> t["two"] = 2 : Int? - >> t:get("???") + >> t["???"] = NONE : Int? - >> t:get("one")! + >> t["one"]! = 1 - >> t:get("???") or -1 + >> t["???"] or -1 = -1 t_str := "" @@ -32,11 +32,11 @@ func main(): >> t2 := {"three":3; fallback=t} = {"three":3; fallback={"one":1, "two":2}} - >> t2:get("one") + >> t2["one"] = 1 : Int? - >> t2:get("three") + >> t2["three"] = 3 : Int? - >> t2:get("???") + >> t2["???"] = NONE : Int? >> t2.length @@ -64,11 +64,11 @@ func main(): do: >> plain := {1:10, 2:20, 3:30} - >> plain:get(2)! + >> plain[2]! = 20 - >> plain:get(2)! + >> plain[2]! = 20 - >> plain:get(456) or -999 + >> plain[456] or -999 = -999 >> plain:has(2) = yes @@ -78,6 +78,13 @@ func main(): >> fallback := {4:40; fallback=plain} >> fallback:has(1) = yes - >> fallback:get(1) or -999 + >> fallback[1] or -999 = 10 + do: + >> t4 := {"one": 1} + >> t4["one"] = 999 + >> t4["two"] = 222 + >> t4 + = {"one":999, "two":222} + diff --git a/typecheck.c b/typecheck.c index eb186df..9284da5 100644 --- a/typecheck.c +++ b/typecheck.c @@ -718,7 +718,7 @@ type_t *get_type(env_t *env, ast_t *ast) return Match(value_t, ArrayType)->item_type; code_err(indexing->index, "I only know how to index lists using integers, not %T", index_t); } 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 { code_err(ast, "I don't know how to index %T values", indexed_t); }