Merge branch 'main' into no-colons

This commit is contained in:
Bruce Hill 2025-04-06 16:36:40 -04:00
commit 6d12f1dce7
13 changed files with 102 additions and 107 deletions

View File

@ -4,37 +4,36 @@ Sets represent an unordered collection of unique elements. These are
implemented using hash tables.
```tomo
a := {10, 20, 30}
b := {20, 30}
a := |10, 20, 30|
b := |20, 30|
>> a.overlap(b)
= {20}
= |20|
```
## Syntax
Sets are written using `{...}` curly braces with comma-separated items:
Sets are written using `|...|` vertical pipes with comma-separated items:
```tomo
nums := {10, 20, 30}
nums := |10, 20, 30|
```
Empty sets must specify the set type explicitly and use `{/}` for an empty set
(because `{}` is an empty table).
Empty sets must specify the set type explicitly:
```tomo
empty : {Int} = {/}
empty : |Int| = ||
```
For type annotations, a set that holds items with type `T` is written as `{T}`.
For type annotations, a set that holds items with type `T` is written as `|T|`.
### Comprehensions
Similar to arrays, sets can use comprehensions:
```tomo
set := {10*i for i in 10}
set2 := {10*i for i in 10 if i mod 2 == 0}
set3 := {-10, 10*i for i in 10}
set := |10*i for i in 10|
set2 := |10*i for i in 10 if i mod 2 == 0|
set3 := |-10, 10*i for i in 10|
```
## Accessing Items
@ -44,7 +43,7 @@ Sets internally store their items in an array, which you can access with the
view:
```tomo
set := {10, 20, 30}
set := |10, 20, 30|
>> set.items
= [10, 20, 30]
```
@ -54,7 +53,7 @@ set := {10, 20, 30}
Set length can be accessed by the `.length` field:
```tomo
>> {10, 20, 30}.length
>> |10, 20, 30|.length
= 3
```
@ -76,23 +75,23 @@ iterating over any of the new values.
## Set Methods
- [`func add(set:{T}, item: T -> Void)`](#add)
- [`func add_all(set:@{T}, items: [T] -> Void)`](#add_all)
- [`func clear(set:@{T} -> Void)`](#clear)
- [`func has(set:{T}, item:T -> Bool)`](#has)
- [`func (set: {T}, other: {T}, strict: Bool = no -> Bool)`](#is_subset_of)
- [`func is_superset_of(set:{T}, other: {T}, strict: Bool = no -> Bool)`](#is_superset_of)
- [`func overlap(set:{T}, other: {T} -> {T})`](#overlap)
- [`func remove(set:@{T}, item: T -> Void)`](#remove)
- [`func remove_all(set:@{T}, items: [T] -> Void)`](#remove_all)
- [`func with(set:{T}, other: {T} -> {T})`](#with)
- [`func without(set:{T}, other: {T} -> {T})`](#without)
- [`func add(set:|T|, item: T -> Void)`](#add)
- [`func add_all(set:@|T|, items: [T] -> Void)`](#add_all)
- [`func clear(set:@|T| -> Void)`](#clear)
- [`func has(set:|T|, item:T -> Bool)`](#has)
- [`func (set: |T|, other: |T|, strict: Bool = no -> Bool)`](#is_subset_of)
- [`func is_superset_of(set:|T|, other: |T|, strict: Bool = no -> Bool)`](#is_superset_of)
- [`func overlap(set:|T|, other: |T| -> |T|)`](#overlap)
- [`func remove(set:@|T|, item: T -> Void)`](#remove)
- [`func remove_all(set:@|T|, items: [T] -> Void)`](#remove_all)
- [`func with(set:|T|, other: |T| -> |T|)`](#with)
- [`func without(set:|T|, other: |T| -> |T|)`](#without)
### `add`
Adds an item to the set.
```tomo
func add(set:{T}, item: T -> Void)
func add(set:|T|, item: T -> Void)
```
- `set`: The mutable reference to the set.
@ -112,7 +111,7 @@ Nothing.
Adds multiple items to the set.
```tomo
func add_all(set:@{T}, items: [T] -> Void)
func add_all(set:@|T|, items: [T] -> Void)
```
- `set`: The mutable reference to the set.
@ -132,7 +131,7 @@ Nothing.
Removes all items from the set.
```tomo
func clear(set:@{T} -> Void)
func clear(set:@|T| -> Void)
```
- `set`: The mutable reference to the set.
@ -151,7 +150,7 @@ Nothing.
Checks if the set contains a specified item.
```tomo
func has(set:{T}, item:T -> Bool)
func has(set:|T|, item:T -> Bool)
```
- `set`: The set to check.
@ -162,7 +161,7 @@ func has(set:{T}, item:T -> Bool)
**Example:**
```tomo
>> {10, 20}.has(20)
>> |10, 20|.has(20)
= yes
```
@ -172,7 +171,7 @@ func has(set:{T}, item:T -> Bool)
Checks if the set is a subset of another set.
```tomo
func (set: {T}, other: {T}, strict: Bool = no -> Bool)
func (set: |T|, other: |T|, strict: Bool = no -> Bool)
```
- `set`: The set to check.
@ -184,7 +183,7 @@ func (set: {T}, other: {T}, strict: Bool = no -> Bool)
**Example:**
```tomo
>> {1, 2}.is_subset_of({1, 2, 3})
>> |1, 2|.is_subset_of(|1, 2, 3|)
= yes
```
@ -194,7 +193,7 @@ func (set: {T}, other: {T}, strict: Bool = no -> Bool)
Checks if the set is a superset of another set.
```tomo
func is_superset_of(set:{T}, other: {T}, strict: Bool = no -> Bool)
func is_superset_of(set:|T|, other: |T|, strict: Bool = no -> Bool)
```
- `set`: The set to check.
@ -206,14 +205,14 @@ func is_superset_of(set:{T}, other: {T}, strict: Bool = no -> Bool)
**Example:**
```tomo
>> {1, 2, 3}.is_superset_of({1, 2})
>> |1, 2, 3|.is_superset_of(|1, 2|)
= yes
```
### `overlap`
Creates a new set with items that are in both the original set and another set.
```tomo
func overlap(set:{T}, other: {T} -> {T})
func overlap(set:|T|, other: |T| -> |T|)
```
- `set`: The original set.
@ -224,8 +223,8 @@ A new set containing only items present in both sets.
**Example:**
```tomo
>> {1, 2}.overlap({2, 3})
= {2}
>> |1, 2|.overlap(|2, 3|)
= |2|
```
---
@ -234,7 +233,7 @@ A new set containing only items present in both sets.
Removes an item from the set.
```tomo
func remove(set:@{T}, item: T -> Void)
func remove(set:@|T|, item: T -> Void)
```
- `set`: The mutable reference to the set.
@ -254,7 +253,7 @@ Nothing.
Removes multiple items from the set.
```tomo
func remove_all(set:@{T}, items: [T] -> Void)
func remove_all(set:@|T|, items: [T] -> Void)
```
- `set`: The mutable reference to the set.
@ -274,7 +273,7 @@ Nothing.
Creates a new set that is the union of the original set and another set.
```tomo
func with(set:{T}, other: {T} -> {T})
func with(set:|T|, other: |T| -> |T|)
```
- `set`: The original set.
@ -285,8 +284,8 @@ A new set containing all items from both sets.
**Example:**
```tomo
>> {1, 2}.with({2, 3})
= {1, 2, 3}
>> |1, 2|.with(|2, 3|)
= |1, 2, 3|
```
---
@ -295,7 +294,7 @@ A new set containing all items from both sets.
Creates a new set with items from the original set but without items from another set.
```tomo
func without(set:{T}, other: {T} -> {T})
func without(set:|T|, other: |T| -> |T|)
```
- `set`: The original set.
@ -306,6 +305,6 @@ A new set containing items from the original set excluding those in the other se
**Example:**
```tomo
>> {1, 2}.without({2, 3})
= {1}
>> |1, 2|.without(|2, 3|)
= |1|
```

View File

@ -159,20 +159,20 @@ func main()
# Sets are similar to tables, but they represent an unordered collection of
# unique values:
set := {10, 20, 30}
set := |10, 20, 30|
>> set.has(20)
= yes
>> set.has(999)
= no
# You can do some operations on sets:
other_set := {30, 40, 50}
other_set := |30, 40, 50|
>> set.with(other_set)
= {10, 20, 30, 40, 50}
= |10, 20, 30, 40, 50|
>> set.without(other_set)
= {10, 20}
= |10, 20|
>> set.overlap(other_set)
= {30}
= |30|
# So far, the datastructures that have been discussed are all *immutable*,
# meaning you can't add, remove, or change their contents. If you want to
@ -241,6 +241,7 @@ func takes_many_types(
text_aka_string:Text,
array_of_ints:[Int],
table_of_text_to_bools:{Text=Bool},
set_of_ints:|Int|,
pointer_to_mutable_array_of_ints:@[Int],
optional_int:Int?,
function_from_int_to_text:func(x:Int -> Text),

View File

@ -3,7 +3,7 @@ use <stdio.h>
timestamp_format := CString("%F %T")
logfiles : @{Path} = @{/}
logfiles : @|Path| = @||
func _timestamp(->Text)
c_str := inline C:CString {

View File

@ -11,12 +11,12 @@ _HELP := "
enum Dependency(File(path:Path), Module(name:Text))
func _get_file_dependencies(file:Path -> {Dependency})
func _get_file_dependencies(file:Path -> |Dependency|)
if not file.is_file()
say("Could not read file: $file")
return {/}
return ||
deps : @{Dependency} = @{/}
deps : @|Dependency| = @||
if lines := file.by_line()
for line in lines
if line.matches_pattern($Pat/use {..}.tm/)
@ -27,18 +27,18 @@ func _get_file_dependencies(file:Path -> {Dependency})
deps.add(Dependency.Module(module_name))
return deps[]
func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency={Dependency}})
func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency=|Dependency|})
return if dependencies.has(dep)
dependencies[dep] = {/} # Placeholder
dependencies[dep] = || # Placeholder
dep_deps := when dep is File(path)
_get_file_dependencies(path)
is Module(module)
dir := (~/.local/share/tomo/installed/$module)
module_deps : @{Dependency} = @{/}
visited : @{Path} = @{/}
unvisited := @{f.resolved() for f in dir.files() if f.extension() == ".tm"}
module_deps : @|Dependency| = @||
visited : @|Path| = @||
unvisited := @|f.resolved() for f in dir.files() if f.extension() == ".tm"|
while unvisited.length > 0
file := unvisited.items[-1]
unvisited.remove(file)
@ -57,8 +57,8 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency={Dependen
for dep2 in dep_deps
_build_dependency_graph(dep2, dependencies)
func get_dependency_graph(dep:Dependency -> {Dependency={Dependency}})
graph : @{Dependency={Dependency}} = @{}
func get_dependency_graph(dep:Dependency -> {Dependency=|Dependency|})
graph : @{Dependency=|Dependency|} = @{}
_build_dependency_graph(dep, graph)
return graph
@ -72,7 +72,7 @@ func _printable_name(dep:Dependency -> Text)
else
return "$(\x1b)[31;1m$(f) (not found)$(\x1b)[m"
func _draw_tree(dep:Dependency, dependencies:{Dependency={Dependency}}, already_printed:@{Dependency}, prefix="", is_last=yes)
func _draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|}, already_printed:@|Dependency|, prefix="", is_last=yes)
if already_printed.has(dep)
say(prefix ++ (if is_last "└── " else "├── ") ++ _printable_name(dep) ++ " $\x1b[2m(recursive)$\x1b[m")
return
@ -82,16 +82,16 @@ func _draw_tree(dep:Dependency, dependencies:{Dependency={Dependency}}, already_
child_prefix := prefix ++ (if is_last " " else "│ ")
children := dependencies[dep] or {/}
children := dependencies[dep] or ||
for i,child in children.items
is_child_last := (i == children.length)
_draw_tree(child, dependencies, already_printed, child_prefix, is_child_last)
func draw_tree(dep:Dependency, dependencies:{Dependency={Dependency}})
printed : @{Dependency} = @{/}
func draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|})
printed : @|Dependency| = @||
say(_printable_name(dep))
printed.add(dep)
deps := dependencies[dep] or {/}
deps := dependencies[dep] or ||
for i,child in deps.items
is_child_last := (i == deps.length)
_draw_tree(child, dependencies, already_printed=printed, is_last=is_child_last)

View File

@ -520,14 +520,13 @@ type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) {
type_ast_t *parse_set_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "{")) return NULL;
if (!match(&pos, "|")) return NULL;
whitespace(&pos);
type_ast_t *item_type = parse_type(ctx, pos);
if (!item_type) return NULL;
pos = item_type->end;
whitespace(&pos);
if (match(&pos, ",")) return NULL;
expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this set type");
expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this set type");
return NewTypeAST(ctx->file, start, pos, SetTypeAST, .item=item_type);
}
@ -706,7 +705,6 @@ PARSER(parse_array) {
PARSER(parse_table) {
const char *start = pos;
if (match(&pos, "{/}")) return NULL;
if (!match(&pos, "{")) return NULL;
whitespace(&pos);
@ -768,10 +766,10 @@ PARSER(parse_table) {
PARSER(parse_set) {
const char *start = pos;
if (match(&pos, "{/}"))
if (match(&pos, "||"))
return NewAST(ctx->file, start, pos, Set);
if (!match(&pos, "{")) return NULL;
if (!match(&pos, "|")) return NULL;
whitespace(&pos);
@ -780,7 +778,6 @@ PARSER(parse_set) {
ast_t *item = optional(ctx, &pos, parse_extended_expr);
if (!item) break;
whitespace(&pos);
if (match(&pos, "=")) return NULL;
ast_t *suffixed = parse_comprehension_suffix(ctx, item);
while (suffixed) {
item = suffixed;
@ -795,7 +792,7 @@ PARSER(parse_set) {
REVERSE_LIST(items);
whitespace(&pos);
expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this set");
expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this set");
return NewAST(ctx->file, start, pos, Set, .items=items);
}

View File

@ -588,13 +588,13 @@ public Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *ty
Text("}"));
else
return Text$concat(
Text("{"),
Text("|"),
generic_as_text(NULL, false, table.key),
Text("}"));
Text("|"));
}
int64_t val_off = (int64_t)value_offset(type);
Text_t text = Text("{");
Text_t text = table.value == &Void$info ? Text("|") : Text("{");
for (int64_t i = 0, length = Table$length(*t); i < length; i++) {
if (i > 0)
text = Text$concat(text, Text(", "));
@ -604,14 +604,11 @@ public Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *ty
text = Text$concat(text, Text("="), generic_as_text(entry + val_off, colorize, table.value));
}
if (table.value == &Void$info)
text = Text$concat(text, Text("/"));
if (t->fallback) {
text = Text$concat(text, Text("; fallback="), Table$as_text(t->fallback, colorize, type));
}
text = Text$concat(text, Text("}"));
text = Text$concat(text, table.value == &Void$info ? Text("|") : Text("}"));
return text;
}

View File

@ -370,9 +370,7 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed)
if (actual->tag == ArrayType && needed->tag == ArrayType && Match(actual, ArrayType)->item_type == NULL)
return true; // [] -> [T]
if (actual->tag == SetType && needed->tag == SetType && Match(actual, SetType)->item_type == NULL)
return true; // {/} -> {T}
if (actual->tag == TableType && needed->tag == SetType && Match(actual, TableType)->key_type == NULL && Match(actual, TableType)->value_type == NULL)
return true; // {} -> {T}
return true; // || -> |T|
if (actual->tag == TableType && needed->tag == TableType && Match(actual, TableType)->key_type == NULL && Match(actual, TableType)->value_type == NULL)
return true; // {} -> {K=V}

View File

@ -42,7 +42,7 @@ func main()
= yes
>> x := Foo.One(123)
>> t := {x}
>> t := |x|
>> t.has(x)
= yes
>> t.has(Foo.Zero)

View File

@ -245,8 +245,8 @@ func main()
= no
>> (5? == 5?)
= yes
>> nones : {Int?} = {none, none}
>> also_nones : {Int?} = {none}
>> nones : |Int?| = |none, none|
>> also_nones : |Int?| = |none|
>> nones == also_nones
>> [5?, none, none, 6?].sorted()
= [none, none, 5, 6]

View File

@ -76,9 +76,9 @@ func main()
= yes
do
>> obj := {10, 20, 30}
>> obj := |10, 20, 30|
>> bytes := obj.serialized()
>> deserialize(bytes -> {Int}) == obj
>> deserialize(bytes -> |Int|) == obj
= yes
do

View File

@ -1,39 +1,42 @@
func main()
>> t1 := @{10, 20, 30, 10}
= @{10, 20, 30}
>> t1 := @|10, 20, 30, 10|
= @|10, 20, 30|
>> t1.has(10)
= yes
>> t1.has(-999)
= no
>> t2 := {30, 40}
>> t2 := |30, 40|
>> t1.with(t2)
>> {10, 20, 30, 40}
>> |10, 20, 30, 40|
>> t1.without(t2)
>> {10, 20}
>> |10, 20|
>> t1.overlap(t2)
>> {30}
>> |30|
>> {1,2}.is_subset_of({2,3})
>> |1,2|.is_subset_of(|2,3|)
= no
>> {1,2}.is_subset_of({1,2,3})
>> |1,2|.is_subset_of(|1,2,3|)
= yes
>> {1,2}.is_subset_of({1,2})
>> |1,2|.is_subset_of(|1,2|)
= yes
>> {1,2}.is_subset_of({1,2}, strict=yes)
>> |1,2|.is_subset_of(|1,2|, strict=yes)
= no
>> t1.add_all(t2)
>> t1
= @{10, 20, 30, 40}
= @|10, 20, 30, 40|
>> t1.remove_all(t2)
>> t1
= @{10, 20}
= @|10, 20|
>> {3, i for i in 5}
= {3, 1, 2, 4, 5}
>> |3, i for i in 5|
= |3, 1, 2, 4, 5|
>> empty : |Int| = ||
= ||

View File

@ -32,7 +32,7 @@ func test_metamethods()
>> x < Pair(11, 20)
= yes
>> set := {x}
>> set := |x|
>> set.has(x)
= yes
>> set.has(y)
@ -49,7 +49,7 @@ func test_mixed()
= no
>> x < Mixed(11, "Hello")
= yes
>> set := {x}
>> set := |x|
>> set.has(x)
= yes
>> set.has(y)

View File

@ -99,8 +99,8 @@ func main()
>> ints : [{Int=Int}] = [{}, {0=0}, {99=99}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 2=-99, 3=3}, {1=1, 99=-99, 3=4}].sorted()
= [{}, {0=0}, {1=1, 2=-99, 3=3}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 99=-99, 3=4}, {99=99}]
>> other_ints : [{Int}] = [{/}, {1}, {2}, {99}, {0, 3}, {1, 2}, {99}].sorted()
= [{/}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}]
>> other_ints : [|Int|] = [||, |1|, |2|, |99|, |0, 3|, |1, 2|, |99|].sorted()
= [||, |0, 3|, |1|, |1, 2|, |2|, |99|, |99|]
do
# Default values: