From 436d2e02debe058e9968935e742e397c19de628a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 11 Oct 2025 15:50:19 -0400 Subject: Improvements to set support and updating docs --- CHANGES.md | 9 +++++++-- api/api.md | 8 ++++---- api/lists.md | 8 ++++---- api/lists.yaml | 8 ++++---- man/man3/tomo-Byte.to.3 | 18 +++++++++--------- man/man3/tomo-List.clear.3 | 4 ++-- man/man3/tomo-List.heap_push.3 | 4 ++-- man/man3/tomo-List.heapify.3 | 6 +++--- man/man3/tomo-List.shuffle.3 | 4 ++-- man/man3/tomo-List.unique.3 | 10 +++++----- man/man3/tomo-Path.expand_home.3 | 10 +++++----- man/man3/tomo-Text.from_codepoint_names.3 | 12 ++++++------ man/man3/tomo-Text.translate.3 | 20 ++++++++++---------- man/man3/tomo-getenv.3 | 6 +++--- src/compile/lists.c | 2 +- src/stdlib/tables.c | 13 ++++++++----- src/typecheck.c | 3 ++- test/lists.tm | 2 +- 18 files changed, 78 insertions(+), 69 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fd51056e..f84c3af8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,8 +12,11 @@ - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). - Deprecated: - - Sets - - Instead of sets, use tables. + - Sets are no longer a separate type with separate methods. + - Instead of sets, use tables with a value type of `{KeyType:Empty}`. + - As a shorthand, you can use `{a,b,c}` instead of `{a:Empty(), + b:Empty(), c:Empty()}` and the type annotation `{K}` as shorthand for + `{K:Empty}`. - Tables now have `and`, `or`, `xor`, and `-` (minus) metamethods. - `extern` keyword for declaring external symbols from C. - Use `C_code` instead. @@ -31,6 +34,8 @@ `table.intersection(other)`, and `table.difference(other)`. - Added `Empty` for a built-in empty struct type and `EMPTY` for an instance of the empty struct. +- Changed `list.unique()` to return a table with `Empty()` values for each + unique list item. - Added a `--format` flag to the `tomo` binary that autoformats your code (currently unstable, do not rely on it just yet). - Standardized text methods for Unicode encodings: diff --git a/api/api.md b/api/api.md index 49ca6a5b..eaf8da17 100644 --- a/api/api.md +++ b/api/api.md @@ -1266,21 +1266,21 @@ assert [10, 20, 30, 40, 50].to(-2) == [10, 20, 30, 40] ## List.unique ```tomo -List.unique : func(list: [T] -> [T]) +List.unique : func(list: [T] -> {T}) ``` -Returns a list of the unique elements of the list. +Returns a set of the unique elements of the list. Argument | Type | Description | Default ---------|------|-------------|--------- list | `[T]` | The list to process. | - -**Return:** A list of the unique elements from the list. +**Return:** A set of the unique elements from the list. **Example:** ```tomo -assert [10, 20, 10, 10, 30].unique() == [10, 20, 30] +assert [10, 20, 10, 10, 30].unique() == {10, 20, 30} ``` ## List.where diff --git a/api/lists.md b/api/lists.md index 3ad61805..1c741989 100644 --- a/api/lists.md +++ b/api/lists.md @@ -569,21 +569,21 @@ assert [10, 20, 30, 40, 50].to(-2) == [10, 20, 30, 40] ## List.unique ```tomo -List.unique : func(list: [T] -> [T]) +List.unique : func(list: [T] -> {T}) ``` -Returns a list of the unique elements of the list. +Returns a set of the unique elements of the list. Argument | Type | Description | Default ---------|------|-------------|--------- list | `[T]` | The list to process. | - -**Return:** A list of the unique elements from the list. +**Return:** A set of the unique elements from the list. **Example:** ```tomo -assert [10, 20, 10, 10, 30].unique() == [10, 20, 30] +assert [10, 20, 10, 10, 30].unique() == {10, 20, 30} ``` ## List.where diff --git a/api/lists.yaml b/api/lists.yaml index 89769064..5a2bc2a1 100644 --- a/api/lists.yaml +++ b/api/lists.yaml @@ -607,18 +607,18 @@ List.to: List.unique: short: get the unique items in a list description: > - Returns a list of the unique elements of the list. + Returns a set of the unique elements of the list. return: - type: '[T]' + type: '{T}' description: > - A list of the unique elements from the list. + A set of the unique elements from the list. args: list: type: '[T]' description: > The list to process. example: | - assert [10, 20, 10, 10, 30].unique() == [10, 20, 30] + assert [10, 20, 10, 10, 30].unique() == {10, 20, 30} List.where: short: find an index where a predicate matches diff --git a/man/man3/tomo-Byte.to.3 b/man/man3/tomo-Byte.to.3 index 9982f5da..15774ba7 100644 --- a/man/man3/tomo-Byte.to.3 +++ b/man/man3/tomo-Byte.to.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Byte.to 3 2025-04-30 "Tomo man-pages" +.TH Byte.to 3 2025-10-11 "Tomo man-pages" .SH NAME Byte.to \- iterate over a range of bytes .SH LIBRARY @@ -31,13 +31,13 @@ An iterator function that returns each byte in the given range (inclusive). .SH EXAMPLES .EX ->> Byte(2).to(5) -= func(->Byte?) ->> [x for x in Byte(2).to(5)] -= [Byte(2), Byte(3), Byte(4), Byte(5)] ->> [x for x in Byte(5).to(2)] -= [Byte(5), Byte(4), Byte(3), Byte(2)] +iter := Byte(2).to(4) +assert iter() == 2 +assert iter() == 3 +assert iter() == 4 +assert iter() == none ->> [x for x in Byte(2).to(5, step=2)] -= [Byte(2), Byte(4)] +assert [x for x in Byte(2).to(5)] == [Byte(2), Byte(3), Byte(4), Byte(5)] +assert [x for x in Byte(5).to(2)] == [Byte(5), Byte(4), Byte(3), Byte(2)] +assert [x for x in Byte(2).to(5, step=2)] == [Byte(2), Byte(4)] .EE diff --git a/man/man3/tomo-List.clear.3 b/man/man3/tomo-List.clear.3 index e912c983..bece5524 100644 --- a/man/man3/tomo-List.clear.3 +++ b/man/man3/tomo-List.clear.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.clear 3 2025-04-30 "Tomo man-pages" +.TH List.clear 3 2025-10-11 "Tomo man-pages" .SH NAME List.clear \- clear a list .SH LIBRARY @@ -29,5 +29,5 @@ Nothing. .SH EXAMPLES .EX ->> my_list.clear() +my_list.clear() .EE diff --git a/man/man3/tomo-List.heap_push.3 b/man/man3/tomo-List.heap_push.3 index 5da4a40a..3c1804bd 100644 --- a/man/man3/tomo-List.heap_push.3 +++ b/man/man3/tomo-List.heap_push.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.heap_push 3 2025-04-30 "Tomo man-pages" +.TH List.heap_push 3 2025-10-11 "Tomo man-pages" .SH NAME List.heap_push \- heap push .SH LIBRARY @@ -31,5 +31,5 @@ Nothing. .SH EXAMPLES .EX ->> my_heap.heap_push(10) +my_heap.heap_push(10) .EE diff --git a/man/man3/tomo-List.heapify.3 b/man/man3/tomo-List.heapify.3 index af7fafd1..345a47b9 100644 --- a/man/man3/tomo-List.heapify.3 +++ b/man/man3/tomo-List.heapify.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.heapify 3 2025-04-30 "Tomo man-pages" +.TH List.heapify 3 2025-10-11 "Tomo man-pages" .SH NAME List.heapify \- convert a list into a heap .SH LIBRARY @@ -30,6 +30,6 @@ Nothing. .SH EXAMPLES .EX ->> my_heap := [30, 10, 20] ->> my_heap.heapify() +my_heap := [30, 10, 20] +my_heap.heapify() .EE diff --git a/man/man3/tomo-List.shuffle.3 b/man/man3/tomo-List.shuffle.3 index 7d7e1def..875a73c6 100644 --- a/man/man3/tomo-List.shuffle.3 +++ b/man/man3/tomo-List.shuffle.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.shuffle 3 2025-04-30 "Tomo man-pages" +.TH List.shuffle 3 2025-10-11 "Tomo man-pages" .SH NAME List.shuffle \- shuffle a list in place .SH LIBRARY @@ -30,5 +30,5 @@ Nothing. .SH EXAMPLES .EX ->> list.shuffle() +list.shuffle() .EE diff --git a/man/man3/tomo-List.unique.3 b/man/man3/tomo-List.unique.3 index cab945b8..9ed81687 100644 --- a/man/man3/tomo-List.unique.3 +++ b/man/man3/tomo-List.unique.3 @@ -2,17 +2,17 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.unique 3 2025-09-21 "Tomo man-pages" +.TH List.unique 3 2025-10-11 "Tomo man-pages" .SH NAME List.unique \- get the unique items in a list .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI List.unique\ :\ func(list:\ [T]\ ->\ [T]) +.BI List.unique\ :\ func(list:\ [T]\ ->\ {T}) .fi .SH DESCRIPTION -Returns a list of the unique elements of the list. +Returns a set of the unique elements of the list. .SH ARGUMENTS @@ -25,9 +25,9 @@ Name Type Description Default list [T] The list to process. - .TE .SH RETURN -A list of the unique elements from the list. +A set of the unique elements from the list. .SH EXAMPLES .EX -assert [10, 20, 10, 10, 30].unique() == [10, 20, 30] +assert [10, 20, 10, 10, 30].unique() == {10, 20, 30} .EE diff --git a/man/man3/tomo-Path.expand_home.3 b/man/man3/tomo-Path.expand_home.3 index 5dcf6a77..7114b65b 100644 --- a/man/man3/tomo-Path.expand_home.3 +++ b/man/man3/tomo-Path.expand_home.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.expand_home 3 2025-05-17 "Tomo man-pages" +.TH Path.expand_home 3 2025-10-11 "Tomo man-pages" .SH NAME Path.expand_home \- expand ~ to $HOME .SH LIBRARY @@ -29,8 +29,8 @@ If the path does not start with a `~`, then return it unmodified. Otherwise, rep .SH EXAMPLES .EX ->> (~/foo).expand_home() # Assume current user is 'user' -= /home/user/foo ->> (/foo).expand_home() # No change -= /foo +# Assume current user is 'user' +assert (~/foo).expand_home() == (/home/user/foo) +# No change +assert (/foo).expand_home() == (/foo) .EE diff --git a/man/man3/tomo-Text.from_codepoint_names.3 b/man/man3/tomo-Text.from_codepoint_names.3 index 4d9dc59d..4a1f9f56 100644 --- a/man/man3/tomo-Text.from_codepoint_names.3 +++ b/man/man3/tomo-Text.from_codepoint_names.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.from_codepoint_names 3 2025-04-30 "Tomo man-pages" +.TH Text.from_codepoint_names 3 2025-10-11 "Tomo man-pages" .SH NAME Text.from_codepoint_names \- convert list of unicode codepoint names to text .SH LIBRARY @@ -32,10 +32,10 @@ The text will be normalized, so the resulting text's codepoints may not exactly .SH EXAMPLES .EX ->> Text.from_codepoint_names([ -"LATIN CAPITAL LETTER A WITH RING ABOVE", -"LATIN SMALL LETTER K", -"LATIN SMALL LETTER E", +text := Text.from_codepoint_names([ + "LATIN CAPITAL LETTER A WITH RING ABOVE", + "LATIN SMALL LETTER K", + "LATIN SMALL LETTER E", ] -= "Åke" +assert text == "Åke" .EE diff --git a/man/man3/tomo-Text.translate.3 b/man/man3/tomo-Text.translate.3 index e8b36ec4..8cfeb3f7 100644 --- a/man/man3/tomo-Text.translate.3 +++ b/man/man3/tomo-Text.translate.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.translate 3 2025-04-30 "Tomo man-pages" +.TH Text.translate 3 2025-10-11 "Tomo man-pages" .SH NAME Text.translate \- perform multiple replacements .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Text.translate\ :\ func(text:\ Text,\ translations:\ {Text=Text}\ ->\ Text) +.BI Text.translate\ :\ func(text:\ Text,\ translations:\ {Text:Text}\ ->\ Text) .fi .SH DESCRIPTION Takes a table mapping target texts to their replacements and performs all the replacements in the table on the whole text. At each position, the first matching replacement is applied and the matching moves on to *after* the replacement text, so replacement text is not recursively modified. See Text.replace() for more information about replacement behavior. @@ -23,19 +23,19 @@ lb lb lbx lb l l l l. Name Type Description Default text Text The text to be translated. - -translations {Text=Text} A table mapping from target text to its replacement. - +translations {Text:Text} A table mapping from target text to its replacement. - .TE .SH RETURN The text with all occurrences of the targets replaced with their corresponding replacement text. .SH EXAMPLES .EX ->> "A & an amperand".translate({ - "&" = "&", - "<" = "<", - ">" = ">", - '"" = """, - "'" = "'", +text := "A & an amperand".translate({ + "&": "&", + "<": "<", + ">": ">", + '"": """, + "'": "'", }) -= "A <tag> & an ampersand" +assert text == "A <tag> & an ampersand" .EE diff --git a/man/man3/tomo-getenv.3 b/man/man3/tomo-getenv.3 index e06e35ff..84bef6be 100644 --- a/man/man3/tomo-getenv.3 +++ b/man/man3/tomo-getenv.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH getenv 3 2025-05-17 "Tomo man-pages" +.TH getenv 3 2025-10-11 "Tomo man-pages" .SH NAME getenv \- get an environment variable .SH LIBRARY @@ -29,6 +29,6 @@ If set, the environment variable's value, otherwise, `none`. .SH EXAMPLES .EX ->> getenv("TERM") -= "xterm-256color"? +assert getenv("TERM") == "xterm-256color" +assert getenv("not_a_variable") == none .EE diff --git a/src/compile/lists.c b/src/compile/lists.c index d9fb46a3..bb94eb1d 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -253,7 +253,7 @@ Text_t compile_list_method_call(env_t *env, ast_t *ast) { } else if (streq(call->name, "unique")) { self = compile_to_pointer_depth(env, call->self, 0, false); (void)compile_arguments(env, ast, NULL, call->args); - return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Void$info)).entries"); + return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Empty$$info))"); } else if (streq(call->name, "pop")) { EXPECT_POINTER(); arg_t *arg_spec = new (arg_t, .name = "index", .type = INT_TYPE, .default_val = FakeAST(Int, "-1")); diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index fdd85b56..3c78b770 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -390,8 +390,10 @@ PUREFUNC public bool Table$equal(const void *vx, const void *vy, const TypeInfo_ void *x_key = x->entries.data + i * x->entries.stride; void *y_value = Table$get_raw(*y, x_key, type); if (!y_value) return false; - void *x_value = x_key + offset; - if (!generic_equal(y_value, x_value, value_type)) return false; + if (value_type->size > 0) { + void *x_value = x_key + offset; + if (!generic_equal(y_value, x_value, value_type)) return false; + } } return true; } @@ -439,7 +441,7 @@ PUREFUNC public int32_t Table$compare(const void *vx, const void *vy, const Type void *y_value = key + value_offset(type); void *x_value = Table$get_raw(*x, key, type); - if (!x_value || !generic_equal(x_value, y_value, table.value)) { + if (!x_value || (table.value->size > 0 && !generic_equal(x_value, y_value, table.value))) { if (mismatched_key == NULL || generic_compare(key, mismatched_key, table.key) < 0) mismatched_key = key; } } @@ -589,7 +591,7 @@ Table_t Table$intersection(Table_t a, Table_t b, const TypeInfo_t *type) { void *key = GET_ENTRY(*t, i); void *a_value = key + offset; void *b_value = Table$get(b, key, type); - if (b_value && generic_equal(a_value, b_value, type->TableInfo.value)) + if (b_value && (type->TableInfo.value->size == 0 || generic_equal(a_value, b_value, type->TableInfo.value))) Table$set(&result, key, a_value, type); } } @@ -650,7 +652,8 @@ Table_t Table$without(Table_t a, Table_t b, const TypeInfo_t *type) { void *key = GET_ENTRY(*t, i); void *a_value = key + offset; void *b_value = Table$get(b, key, type); - if (!b_value || !generic_equal(a_value, b_value, type->TableInfo.value)) + if (!b_value + || (type->TableInfo.value->size > 0 && !generic_equal(a_value, b_value, type->TableInfo.value))) Table$set(&result, key, a_value, type); } } diff --git a/src/typecheck.c b/src/typecheck.c index 9342f069..67fb1168 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -969,7 +969,8 @@ type_t *get_type(env_t *env, ast_t *ast) { else if (streq(call->name, "sort")) return Type(VoidType); else if (streq(call->name, "sorted")) return self_value_t; else if (streq(call->name, "to")) return self_value_t; - else if (streq(call->name, "unique")) return Type(ListType, .item_type = item_type); + else if (streq(call->name, "unique")) + return Type(TableType, .key_type = item_type, .value_type = EMPTY_TYPE); else code_err(ast, "There is no '", call->name, "' method for lists"); } case TableType: { diff --git a/test/lists.tm b/test/lists.tm index 176c3d6d..4e9f769b 100644 --- a/test/lists.tm +++ b/test/lists.tm @@ -144,4 +144,4 @@ func main() assert nums[] == [] assert nums.pop() == none - assert [1,2,1,2,3].unique() == [1,2,3] + assert [1,2,1,2,3].unique() == {1,2,3} -- cgit v1.2.3