aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compile.c21
-rw-r--r--docs/functions.md6
-rw-r--r--docs/tables.md59
-rw-r--r--examples/ini/ini.tm8
-rw-r--r--examples/learnxiny.tm16
-rw-r--r--examples/tomodeps/tomodeps.tm8
-rw-r--r--test/structs.tm2
-rw-r--r--test/tables.tm31
-rw-r--r--typecheck.c2
9 files changed, 102 insertions, 51 deletions
diff --git a/compile.c b/compile.c
index 8356fbb7..82449927 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 05d84e4a..5ea2decd 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 6cbe859c..ef2333a7 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 5952bb00..9575b9fd 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 e31bab70..af7a55f6 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 fbe297ec..1f42850b 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 ae5bec0f..afbb1a86 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 acda0eb5..0d8954a2 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 eb186dff..9284da57 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);
}