From af1bd79fd91d1a1efde3cf084643f065c61d330a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 14:10:28 -0500 Subject: Make EMPTY_TEXT into a macro --- src/stdlib/text.c | 7 ------- src/stdlib/text.h | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/stdlib/text.c b/src/stdlib/text.c index d40e1306..f323d88d 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -146,13 +146,6 @@ static int32_t num_synthetic_graphemes = 0; static Text_t simple_concatenation(Text_t a, Text_t b); -public -Text_t EMPTY_TEXT = { - .length = 0, - .tag = TEXT_ASCII, - .ascii = 0, -}; - PUREFUNC static bool graphemes_equal(const void *va, const void *vb, const TypeInfo_t *info) { (void)info; ucs4_t *a = *(ucs4_t **)va; diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 821325a9..12b4bc20 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -27,6 +27,8 @@ typedef struct { #define Text(str) ((Text_t){.length = sizeof(str) - 1, .tag = TEXT_ASCII, .ascii = "" str}) +#define EMPTY_TEXT ((Text_t){.length = 0, .tag = TEXT_ASCII, .ascii = 0}) + static inline Text_t Text_from_str_literal(const char *str) { return (Text_t){.length = strlen(str), .tag = TEXT_ASCII, .ascii = str}; } @@ -121,7 +123,6 @@ MACROLIKE int32_t Text$get_grapheme(Text_t text, int64_t index) { } extern const TypeInfo_t Text$info; -extern Text_t EMPTY_TEXT; #define Text$metamethods \ { \ -- cgit v1.2.3 From 38ea5c4753e13b04e7c41c89bf0b58b5d47cc86d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:12:16 -0500 Subject: Bugfix for int parsing --- src/stdlib/bigint.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c index be01a001..46957c2d 100644 --- a/src/stdlib/bigint.c +++ b/src/stdlib/bigint.c @@ -415,25 +415,28 @@ OptionalInt_t Int$parse(Text_t text, Text_t *remainder) { mpz_t i; int result; if (strncmp(str, "0x", 2) == 0) { - const char *end = str + 2 + strspn(str + 2, "0123456789abcdefABCDEF"); + str += 2; + const char *end = str + strspn(str, "0123456789abcdefABCDEF"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str + 2, 16); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 16); } else if (strncmp(str, "0o", 2) == 0) { - const char *end = str + 2 + strspn(str + 2, "01234567"); + str += 2; + const char *end = str + strspn(str, "01234567"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str + 2, 8); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 8); } else if (strncmp(str, "0b", 2) == 0) { - const char *end = str + 2 + strspn(str + 2, "01"); + str += 2; + const char *end = str + strspn(str, "01"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str + 2, 2); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 2); } else { const char *end = str + strspn(str, "0123456789"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, str, 10); + result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 10); } if (result != 0) { if (remainder) *remainder = text; -- cgit v1.2.3 From a1884f7a85cbee5a67cf48c9e7b088fdea8b8b38 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:12:57 -0500 Subject: Fix for potential issue with codepoint names --- src/stdlib/text.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stdlib/text.c b/src/stdlib/text.c index f323d88d..8e800c8a 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1605,6 +1605,7 @@ static INLINE const char *codepoint_name(ucs4_t c) { char *found_name = unicode_character_name(c, name); if (found_name) return found_name; const uc_block_t *block = uc_block(c); + if (!block) return "???"; assert(block); return String(block->name, "-", hex(c, .no_prefix = true, .uppercase = true)); } -- cgit v1.2.3 From 290c72732f21f1cddb3a0f8ec3213e4ec321da14 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:13:44 -0500 Subject: Add Path.lines() --- CHANGES.md | 1 + api/api.md | 71 +++++++++++++++++++++++++++++++++++++++++--- api/paths.md | 28 ++++++++++++++--- api/paths.yaml | 25 +++++++++++++--- man/man3/tomo-Path.by_line.3 | 10 +++---- src/environment.c | 1 + src/stdlib/paths.c | 22 ++++++++++++++ src/stdlib/paths.h | 1 + 8 files changed, 142 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1e1e56d7..08fb2396 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ - Syntax for text literals and inline C code has been simplified somewhat. - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). +- Added `Path.lines()` - Deprecated: - Sets are no longer a separate type with separate methods. - Instead of sets, use tables with a value type of `{KeyType:Empty}`. diff --git a/api/api.md b/api/api.md index eaf8da17..3af79d37 100644 --- a/api/api.md +++ b/api/api.md @@ -342,6 +342,49 @@ assert [x for x in Byte(2).to(5, step=2)] == [Byte(2), Byte(4)] ``` +# CString +## CString.as_text + +```tomo +CString.as_text : func(str: CString -> Text) +``` + +Convert a C string to Text. + +Argument | Type | Description | Default +---------|------|-------------|--------- +str | `CString` | The C string. | - + +**Return:** The C string as a Text. + + +**Example:** +```tomo +assert CString("Hello").as_text() == "Hello" + +``` +## CString.join + +```tomo +CString.join : func(glue: CString, pieces: [CString] -> CString) +``` + +Join a list of C strings together with a separator. + +Argument | Type | Description | Default +---------|------|-------------|--------- +glue | `CString` | The C joiner used to between elements. | - +pieces | `[CString]` | A list of C strings to join. | - + +**Return:** A C string of the joined together bits. + + +**Example:** +```tomo +assert CString(",").join([CString("a"), CString("b")]) == CString("a,b") + +``` + # Int ## Int.abs @@ -2565,14 +2608,14 @@ path | `Path` | The path of the file. | - ```tomo # Safely handle file not being readable: if lines := (./file.txt).by_line() -for line in lines -say(line.upper()) + for line in lines + say(line.upper()) else -say("Couldn't read file!") + say("Couldn't read file!") # Assume the file is readable and error if that's not the case: for line in (/dev/stdin).by_line()! -say(line.upper()) + say(line.upper()) ``` ## Path.can_execute @@ -3018,6 +3061,26 @@ path | `Path` | The path to check. | - ```tomo assert (./link).is_symlink() == yes +``` +## Path.lines + +```tomo +Path.lines : func(path: Path -> [Text]?) +``` + +Returns a list with the lines of text in a file or returns none if the file could not be opened. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file. | - + +**Return:** A list of the lines in a file or none if the file couldn't be read. + + +**Example:** +```tomo +lines := (./file.txt).lines()! + ``` ## Path.modified diff --git a/api/paths.md b/api/paths.md index c69e91d9..07f0560b 100644 --- a/api/paths.md +++ b/api/paths.md @@ -108,14 +108,14 @@ path | `Path` | The path of the file. | - ```tomo # Safely handle file not being readable: if lines := (./file.txt).by_line() -for line in lines -say(line.upper()) + for line in lines + say(line.upper()) else -say("Couldn't read file!") + say("Couldn't read file!") # Assume the file is readable and error if that's not the case: for line in (/dev/stdin).by_line()! -say(line.upper()) + say(line.upper()) ``` ## Path.can_execute @@ -561,6 +561,26 @@ path | `Path` | The path to check. | - ```tomo assert (./link).is_symlink() == yes +``` +## Path.lines + +```tomo +Path.lines : func(path: Path -> [Text]?) +``` + +Returns a list with the lines of text in a file or returns none if the file could not be opened. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file. | - + +**Return:** A list of the lines in a file or none if the file couldn't be read. + + +**Example:** +```tomo +lines := (./file.txt).lines()! + ``` ## Path.modified diff --git a/api/paths.yaml b/api/paths.yaml index 532d9c71..8fbd18dc 100644 --- a/api/paths.yaml +++ b/api/paths.yaml @@ -107,14 +107,31 @@ Path.by_line: example: | # Safely handle file not being readable: if lines := (./file.txt).by_line() - for line in lines - say(line.upper()) + for line in lines + say(line.upper()) else - say("Couldn't read file!") + say("Couldn't read file!") # Assume the file is readable and error if that's not the case: for line in (/dev/stdin).by_line()! - say(line.upper()) + say(line.upper()) + +Path.lines: + short: return the lines in a file + description: > + Returns a list with the lines of text in a file or returns none if the file + could not be opened. + return: + type: '[Text]?' + description: > + A list of the lines in a file or none if the file couldn't be read. + args: + path: + type: 'Path' + description: > + The path of the file. + example: | + lines := (./file.txt).lines()! Path.can_execute: short: check execute permissions diff --git a/man/man3/tomo-Path.by_line.3 b/man/man3/tomo-Path.by_line.3 index ff7a737c..02a75c5a 100644 --- a/man/man3/tomo-Path.by_line.3 +++ b/man/man3/tomo-Path.by_line.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.by_line 3 2025-05-17 "Tomo man-pages" +.TH Path.by_line 3 2025-11-15 "Tomo man-pages" .SH NAME Path.by_line \- iterate by line .SH LIBRARY @@ -31,12 +31,12 @@ An iterator that can be used to get lines from a file one at a time or none if t .EX # Safely handle file not being readable: if lines := (./file.txt).by_line() -for line in lines -say(line.upper()) + for line in lines + say(line.upper()) else -say("Couldn't read file!") + say("Couldn't read file!") # Assume the file is readable and error if that's not the case: for line in (/dev/stdin).by_line()! -say(line.upper()) + say(line.upper()) .EE diff --git a/src/environment.c b/src/environment.c index 96595ac7..8f49f86e 100644 --- a/src/environment.c +++ b/src/environment.c @@ -325,6 +325,7 @@ env_t *global_env(bool source_mapping) { {"is_pipe", "Path$is_pipe", "func(path:Path, follow_symlinks=yes -> Bool)"}, // {"is_socket", "Path$is_socket", "func(path:Path, follow_symlinks=yes -> Bool)"}, // {"is_symlink", "Path$is_symlink", "func(path:Path -> Bool)"}, // + {"lines", "Path$lines", "func(path:Path -> [Text]?)"}, // {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // {"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, // {"parent", "Path$parent", "func(path:Path -> Path)"}, // diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 810f98b1..704421c2 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -703,6 +703,28 @@ OptionalClosure_t Path$by_line(Path_t path) { return (Closure_t){.fn = (void *)_next_line, .userdata = wrapper}; } +public +OptionalList_t Path$lines(Path_t path) { + const char *path_str = Path$as_c_string(path); + FILE *f = fopen(path_str, "r"); + if (f == NULL) { + if (errno == EMFILE || errno == ENFILE) { + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will + // be closed by GC finalizers. + GC_gcollect(); + f = fopen(path_str, "r"); + } + } + + if (f == NULL) return NONE_LIST; + + List_t lines = {}; + for (OptionalText_t line; (line = _next_line(&f)).tag != TEXT_NONE;) { + List$insert(&lines, &line, I(0), sizeof(line)); + } + return lines; +} + public List_t Path$glob(Path_t path) { glob_t glob_result; diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index ce6de1c8..3b1f3ce6 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -57,6 +57,7 @@ Path_t Path$sibling(Path_t path, Text_t name); Path_t Path$with_extension(Path_t path, Text_t extension, bool replace); Path_t Path$current_dir(void); Closure_t Path$by_line(Path_t path); +OptionalList_t Path$lines(Path_t path); List_t Path$glob(Path_t path); uint64_t Path$hash(const void *obj, const TypeInfo_t *); -- cgit v1.2.3 From 76b434a6be6c05d7d3dae5b77ec8fd886fd715d0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 15 Nov 2025 18:16:48 -0500 Subject: Bugfix for CLI arg parsing --- src/stdlib/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 22ed9b94..3c474160 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -397,7 +397,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c List_t texts = Text$split(Text$from_str(arg_value), Text(",")); values = EMPTY_LIST; for (int64_t j = 0; j < (int64_t)texts.length; j++) - List$insert_value(&texts, Text$as_c_string(*(Text_t *)(texts.data + j * texts.stride)), I(0), + List$insert_value(&values, Text$as_c_string(*(Text_t *)(texts.data + j * texts.stride)), I(0), sizeof(const char *)); } else { // Case: -fVALUE -- cgit v1.2.3 From d6de03feb280d179efa07e1727f42955b25d733c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Nov 2025 22:13:21 -0500 Subject: Fix optional path none checks --- CHANGES.md | 1 + src/compile/optionals.c | 4 ++-- test/optionals.tm | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 08fb2396..a714c5fa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -53,6 +53,7 @@ up resources, which can result in file handles being freed up. - `&` references failed to propagate when accessing fields like `foo.baz.method()` when `foo` is a `&Foo` and `baz.method()` takes a `&Baz`. + - Optional paths no longer fail to compile when you check them for `none`. ## v0.3 diff --git a/src/compile/optionals.c b/src/compile/optionals.c index b4930d02..ffe16248 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -92,8 +92,8 @@ Text_t check_none(type_t *t, Text_t value) { // NOTE: these use statement expressions ({...;}) because some compilers // complain about excessive parens around equality comparisons if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) return Texts("(", value, " == NULL)"); - else if (t == PATH_TYPE) return Texts("((", value, ").type.$tag == PATHTYPE_NONE)"); - else if (t == PATH_TYPE_TYPE) return Texts("((", value, ").$tag == PATHTYPE_NONE)"); + else if (t == PATH_TYPE) return Texts("((", value, ").type == PATHTYPE_NONE)"); + else if (t == PATH_TYPE_TYPE) return Texts("((", value, ") == PATHTYPE_NONE)"); else if (t->tag == BigIntType) return Texts("((", value, ").small == 0)"); else if (t->tag == ClosureType) return Texts("((", value, ").fn == NULL)"); else if (t->tag == NumType) diff --git a/test/optionals.tm b/test/optionals.tm index 51a78380..d26a9d46 100644 --- a/test/optionals.tm +++ b/test/optionals.tm @@ -61,6 +61,12 @@ func maybe_c_string(should_i:Bool->CString?) else return none +func maybe_path(should_i:Bool->Path?) + if should_i + return (./foo) + else + return none + func main() optional : Int? = 5 assert optional == 5 @@ -214,6 +220,20 @@ func main() fail("Truthy: $nope") else say("Falsey: $nope") + do + say("...") + say("Paths:") + yep := maybe_path(yes) + assert yep == (./foo) + nope := maybe_path(no) + assert nope == none + >> if yep + assert yep == (./foo) + else fail("Falsey: $yep") + >> if nope + fail("Truthy: $nope") + else say("Falsey: $nope") + if yep := maybe_int(yes) assert yep == 123 else fail("Unreachable") -- cgit v1.2.3 From 3c80527f5d788ef80abe5cd3a28aabe168a6d69c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Nov 2025 22:19:16 -0500 Subject: Bugfix for optional path CLI args --- src/stdlib/cli.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 3c474160..3523296a 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -220,6 +220,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true; else if (nonnull == &Int8$info) ((OptionalInt8_t *)dest)->has_value = true; else if (nonnull == &Byte$info) ((OptionalByte_t *)dest)->has_value = true; + else if (nonnull == &Path$info) return args; else if (nonnull->tag == StructInfo && nonnull != &Path$info) *(bool *)(dest + nonnull->size) = true; else print_err("Unsupported type: ", generic_as_text(NULL, true, nonnull)); return args; -- cgit v1.2.3 From 0c93c3c614aa5b955b40e805226d8609c85d0923 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Nov 2025 22:20:56 -0500 Subject: Bugfix for empty file lines --- src/stdlib/paths.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 704421c2..22effad7 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -718,7 +718,7 @@ OptionalList_t Path$lines(Path_t path) { if (f == NULL) return NONE_LIST; - List_t lines = {}; + List_t lines = EMPTY_LIST; for (OptionalText_t line; (line = _next_line(&f)).tag != TEXT_NONE;) { List$insert(&lines, &line, I(0), sizeof(line)); } -- cgit v1.2.3 From a529e344c07a064de1391c1a4bf354fe1a95707d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 14:15:40 -0500 Subject: Fix hex/octal leading zeroes for zero --- src/stdlib/print.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stdlib/print.c b/src/stdlib/print.c index ef570f94..7da1343f 100644 --- a/src/stdlib/print.c +++ b/src/stdlib/print.c @@ -43,6 +43,7 @@ int _print_hex(FILE *f, hex_format_t hex) { int printed = 0; if (!hex.no_prefix) printed += fputs("0x", f); if (hex.digits > 0) { + hex.digits -= (hex.n == 0); // Don't need a leading zero for a zero for (uint64_t n = hex.n; n > 0 && hex.digits > 0; n /= 16) { hex.digits -= 1; } @@ -68,6 +69,7 @@ int _print_oct(FILE *f, oct_format_t oct) { int printed = 0; if (!oct.no_prefix) printed += fputs("0o", f); if (oct.digits > 0) { + oct.digits -= (oct.n == 0); // Don't need a leading zero for a zero for (uint64_t n = oct.n; n > 0 && oct.digits > 0; n /= 8) oct.digits -= 1; for (; oct.digits > 0; oct.digits -= 1) -- cgit v1.2.3 From e0706bc707ea6a8be86cee9fde21971cde3d7a42 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 16:34:41 -0500 Subject: Bugfix for infinite loop in text.replace("", ...) with empty string --- CHANGES.md | 1 + src/stdlib/text.c | 2 ++ test/text.tm | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a714c5fa..22a1249b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -54,6 +54,7 @@ - `&` references failed to propagate when accessing fields like `foo.baz.method()` when `foo` is a `&Foo` and `baz.method()` takes a `&Baz`. - Optional paths no longer fail to compile when you check them for `none`. + - Text replacement no longer infinitely loops when given an empty text to replace. ## v0.3 diff --git a/src/stdlib/text.c b/src/stdlib/text.c index 8e800c8a..febcafce 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1135,6 +1135,7 @@ Text_t Text$translate(Text_t text, Table_t translations) { struct { Text_t target, replacement; } *entry = replacement_list.data + r * replacement_list.stride; + if (entry->target.length <= 0) continue; TextIter_t target_state = NEW_TEXT_ITER_STATE(entry->target); if (_matches(&text_state, &target_state, i)) { if (i > span_start) result = concat2(result, Text$slice(text, I(span_start + 1), I(i))); @@ -1156,6 +1157,7 @@ Text_t Text$translate(Text_t text, Table_t translations) { public Text_t Text$replace(Text_t text, Text_t target, Text_t replacement) { + if (target.length <= 0) return text; TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target); Text_t result = EMPTY_TEXT; int64_t span_start = 0; diff --git a/test/text.tm b/test/text.tm index 6631b94e..094da8f8 100644 --- a/test/text.tm +++ b/test/text.tm @@ -57,6 +57,8 @@ func main() assert "xxxx".replace("x", "") == "" assert "xxxx".replace("y", "") == "xxxx" assert "One two three four five six".replace("e ", "") == "Ontwo threfour fivsix" + assert "Hello".replace("", "xxx") == "Hello" + assert "".replace("", "xxx") == "" assert amelie.has(amelie2) == yes -- cgit v1.2.3 From 0aeacfbd83b0afe8a8ea654bc1554b8d7d29e9b1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 16:56:57 -0500 Subject: CLI arg parsing fixes for optional types --- src/stdlib/cli.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 3523296a..08260171 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -215,12 +215,14 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty return List$from(args, I(2)); } else { args = parse_arg_list(args, flag, dest, nonnull, allow_dashes); - if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true; + if (nonnull == &Int$info || nonnull == &Path$info || nonnull == &Num$info || nonnull == &Num32$info + || nonnull->tag == TextInfo) + return args; + else if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true; else if (nonnull == &Int32$info) ((OptionalInt32_t *)dest)->has_value = true; else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true; else if (nonnull == &Int8$info) ((OptionalInt8_t *)dest)->has_value = true; else if (nonnull == &Byte$info) ((OptionalByte_t *)dest)->has_value = true; - else if (nonnull == &Path$info) return args; else if (nonnull->tag == StructInfo && nonnull != &Path$info) *(bool *)(dest + nonnull->size) = true; else print_err("Unsupported type: ", generic_as_text(NULL, true, nonnull)); return args; -- cgit v1.2.3 From bb354d6d3626cdc0c2a1b802a954df244cd1facc Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 19:11:55 -0500 Subject: Fixes for conditional expressions for optional types --- src/ast.h | 1 + src/compile/conditionals.c | 15 ++++++++++----- src/compile/expressions.c | 4 +++- src/stdlib/cli.c | 2 +- src/typecheck.c | 3 ++- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ast.h b/src/ast.h index aaa2a993..c01ba68a 100644 --- a/src/ast.h +++ b/src/ast.h @@ -287,6 +287,7 @@ struct ast_s { struct { } Unknown; struct { + struct type_s *type; } None; struct { bool b; diff --git a/src/compile/conditionals.c b/src/compile/conditionals.c index caebdbde..64be29fa 100644 --- a/src/compile/conditionals.c +++ b/src/compile/conditionals.c @@ -126,17 +126,22 @@ Text_t compile_if_expression(env_t *env, ast_t *ast) { } type_t *true_type = get_type(truthy_scope, if_->body); - type_t *false_type = get_type(falsey_scope, if_->else_body); + ast_t *else_body = if_->else_body; + if (else_body && else_body->tag == Block && Match(else_body, Block)->statements + && !Match(else_body, Block)->statements->next) + else_body = Match(else_body, Block)->statements->ast; + if (else_body == NULL || else_body->tag == None) else_body = WrapAST(ast, None, .type = true_type); + type_t *false_type = get_type(falsey_scope, else_body); if (true_type->tag == AbortType || true_type->tag == ReturnType) return Texts("({ ", decl_code, "if (", condition_code, ") ", compile_statement(truthy_scope, if_->body), "\n", - compile(falsey_scope, if_->else_body), "; })"); + compile(falsey_scope, else_body), "; })"); else if (false_type->tag == AbortType || false_type->tag == ReturnType) - return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, if_->else_body), + return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, else_body), "\n", compile(truthy_scope, if_->body), "; })"); else if (decl_code.length > 0) return Texts("({ ", decl_code, "(", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", - compile(falsey_scope, if_->else_body), ";})"); + compile(falsey_scope, else_body), ";})"); else return Texts("((", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", - compile(falsey_scope, if_->else_body), ")"); + compile(falsey_scope, else_body), ")"); } diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 108bda80..130a267c 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -66,7 +66,9 @@ Text_t compile_empty(type_t *t) { Text_t compile(env_t *env, ast_t *ast) { switch (ast->tag) { case None: { - code_err(ast, "I can't figure out what this `none`'s type is!"); + type_t *type = Match(ast, None)->type; + if (type == NULL) code_err(ast, "I can't figure out what this `none`'s type is!"); + return compile_none(non_optional(type)); } case Bool: return Match(ast, Bool)->b ? Text("yes") : Text("no"); case Var: { diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 08260171..359220e9 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -216,7 +216,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty } else { args = parse_arg_list(args, flag, dest, nonnull, allow_dashes); if (nonnull == &Int$info || nonnull == &Path$info || nonnull == &Num$info || nonnull == &Num32$info - || nonnull->tag == TextInfo) + || nonnull->tag == TextInfo || nonnull->tag == EnumInfo) return args; else if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true; else if (nonnull == &Int32$info) ((OptionalInt32_t *)dest)->has_value = true; diff --git a/src/typecheck.c b/src/typecheck.c index 07c3acf9..a0e55a88 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1376,7 +1376,6 @@ type_t *get_type(env_t *env, ast_t *ast) { case If: { DeclareMatch(if_, ast, If); - if (!if_->else_body) return Type(VoidType); env_t *truthy_scope = env; env_t *falsey_scope = env; @@ -1401,6 +1400,8 @@ type_t *get_type(env_t *env, ast_t *ast) { } type_t *true_t = get_type(truthy_scope, if_->body); + ast_t *else_body = if_->else_body; + if (!else_body) else_body = WrapAST(ast, None, .type = true_t); type_t *false_t = get_type(falsey_scope, if_->else_body); type_t *t_either = type_or_type(true_t, false_t); if (!t_either) -- cgit v1.2.3 From 7869c7447a181316abbdd0e4ff9c4331d56a1984 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 21:47:54 -0500 Subject: Fix command line aliases --- CHANGES.md | 1 + src/compile/cli.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 22a1249b..396e4a84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -55,6 +55,7 @@ `foo.baz.method()` when `foo` is a `&Foo` and `baz.method()` takes a `&Baz`. - Optional paths no longer fail to compile when you check them for `none`. - Text replacement no longer infinitely loops when given an empty text to replace. + - Short CLI flag aliases now no longer use the first letter of the argument. ## v0.3 diff --git a/src/compile/cli.c b/src/compile/cli.c index e3d2329f..e5756521 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -144,7 +144,7 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c code = Texts(code, "{", quoted_text(Text$replace(Text$from_str(arg->name), Text("_"), Text("-"))), ", &", Texts("_$", Text$from_str(arg->name)), ", ", compile_type_info(arg->type), arg->default_val ? Text("") : Text(", .required=true"), - arg->alias ? Texts(", .short_flag=", quoted_text(Text$from_str(arg->name)), + arg->alias ? Texts(", .short_flag=", quoted_text(Text$from_str(arg->alias)), "[0]") // TODO: escape char properly : Text(""), -- cgit v1.2.3 From a453ebf215e5e3ec3b27fa5142af77d7e3ca0c92 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 22 Nov 2025 21:49:55 -0500 Subject: Change error message --- src/stdlib/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 359220e9..04538796 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -202,7 +202,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty if ((type->tag == TextInfo || type == &CString$info) && arg[0] == '\\' && arg[1] == '-') { arg = arg + 1; } else if (arg[0] == '-') { - print_err("Not a valid argument for flag ", flag, ": ", arg); + print_err("Not a valid flag: ", arg); } } -- cgit v1.2.3 From cb9d3b1a2c2c59c368f6121a16a9ab928b0ff951 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 00:35:05 -0500 Subject: Added Text.find(text, target, start=1) --- CHANGES.md | 1 + api/api.md | 25 +++++++++++++++++++++++++ api/text.md | 25 +++++++++++++++++++++++++ api/text.yaml | 28 ++++++++++++++++++++++++++++ src/environment.c | 1 + src/stdlib/text.c | 17 +++++++++++++++-- src/stdlib/text.h | 1 + test/text.tm | 6 ++++++ 8 files changed, 102 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 396e4a84..293ff431 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). - Added `Path.lines()` +- Added `Text.find(text, target, start=1)` - Deprecated: - Sets are no longer a separate type with separate methods. - Instead of sets, use tables with a value type of `{KeyType:Empty}`. diff --git a/api/api.md b/api/api.md index 3af79d37..02ad054e 100644 --- a/api/api.md +++ b/api/api.md @@ -3904,6 +3904,31 @@ remainder : Text assert "hello world".ends_with("world", &remainder) == yes assert remainder == "hello " +``` +## Text.find + +```tomo +Text.find : func(text: Text, target: Text, start: Int = 1 -> Int) +``` + +Find a substring within a text and return its index, if found. + +Argument | Type | Description | Default +---------|------|-------------|--------- +text | `Text` | The text to be searched. | - +target | `Text` | The target text to find. | - +start | `Int` | The index at which to begin searching. | `1` + +**Return:** The index where the first occurrence of `target` appears, or `none` if it is not found. + + +**Example:** +```tomo +assert "one two".find("one") == 1 +assert "one two".find("two") == 5 +assert "one two".find("three") == none +assert "one two".find("o", start=2) == 7 + ``` ## Text.from diff --git a/api/text.md b/api/text.md index 9bd99529..928cb6ec 100644 --- a/api/text.md +++ b/api/text.md @@ -204,6 +204,31 @@ remainder : Text assert "hello world".ends_with("world", &remainder) == yes assert remainder == "hello " +``` +## Text.find + +```tomo +Text.find : func(text: Text, target: Text, start: Int = 1 -> Int) +``` + +Find a substring within a text and return its index, if found. + +Argument | Type | Description | Default +---------|------|-------------|--------- +text | `Text` | The text to be searched. | - +target | `Text` | The target text to find. | - +start | `Int` | The index at which to begin searching. | `1` + +**Return:** The index where the first occurrence of `target` appears, or `none` if it is not found. + + +**Example:** +```tomo +assert "one two".find("one") == 1 +assert "one two".find("two") == 5 +assert "one two".find("three") == none +assert "one two".find("o", start=2) == 7 + ``` ## Text.from diff --git a/api/text.yaml b/api/text.yaml index 2c21fa30..6874bfc8 100644 --- a/api/text.yaml +++ b/api/text.yaml @@ -225,6 +225,34 @@ Text.ends_with: assert "hello world".ends_with("world", &remainder) == yes assert remainder == "hello " +Text.find: + short: find a substring + description: > + Find a substring within a text and return its index, if found. + return: + type: 'Int' + description: > + The index where the first occurrence of `target` appears, or `none` if it is not found. + args: + text: + type: 'Text' + description: > + The text to be searched. + target: + type: 'Text' + description: > + The target text to find. + start: + type: 'Int' + default: '1' + description: > + The index at which to begin searching. + example: | + assert "one two".find("one") == 1 + assert "one two".find("two") == 5 + assert "one two".find("three") == none + assert "one two".find("o", start=2) == 7 + Text.from: short: slice from a starting index description: > diff --git a/src/environment.c b/src/environment.c index 8f49f86e..3a2995f7 100644 --- a/src/environment.c +++ b/src/environment.c @@ -353,6 +353,7 @@ env_t *global_env(bool source_mapping) { {"caseless_equals", "Text$equal_ignoring_case", "func(a,b:Text, language='C' -> Bool)"}, // {"codepoint_names", "Text$codepoint_names", "func(text:Text -> [Text])"}, // {"ends_with", "Text$ends_with", "func(text,suffix:Text, remainder:&Text? = none -> Bool)"}, // + {"find", "Text$find", "func(text,target:Text, start=1 -> Int?)"}, // {"from", "Text$from", "func(text:Text, first:Int -> Text)"}, // {"from_c_string", "Text$from_str", "func(str:CString -> Text?)"}, // {"from_codepoint_names", "Text$from_codepoint_names", "func(codepoint_names:[Text] -> Text?)"}, // diff --git a/src/stdlib/text.c b/src/stdlib/text.c index febcafce..e51af49c 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1057,8 +1057,8 @@ PUREFUNC public int32_t Text$compare(const void *va, const void *vb, const TypeI bool _matches(TextIter_t *text_state, TextIter_t *target_state, int64_t pos) { for (int64_t i = 0; i < (int64_t)target_state->stack[0].text.length; i++) { int32_t text_i = Text$get_grapheme_fast(text_state, pos + i); - int32_t prefix_i = Text$get_grapheme_fast(target_state, i); - if (text_i != prefix_i) return false; + int32_t target_i = Text$get_grapheme_fast(target_state, i); + if (text_i != target_i) return false; } return true; } @@ -1106,6 +1106,19 @@ static bool _has_grapheme(TextIter_t *text, int32_t g) { return false; } +public +OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start) { + if (text.length < target.length) return NONE_INT; + if (target.length <= 0) return I(1); + TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target); + for (int64_t i = Int64$from_int(start, false) - 1; i < text.length - target.length + 1; i++) { + if (_matches(&text_state, &target_state, i)) { + return Int$from_int64(i + 1); + } + } + return NONE_INT; +} + public Text_t Text$trim(Text_t text, Text_t to_trim, bool left, bool right) { int64_t first = 0; diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 12b4bc20..fba8b08f 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -84,6 +84,7 @@ PUREFUNC bool Text$starts_with(Text_t text, Text_t prefix, Text_t *remainder); PUREFUNC bool Text$ends_with(Text_t text, Text_t suffix, Text_t *remainder); Text_t Text$without_prefix(Text_t text, Text_t prefix); Text_t Text$without_suffix(Text_t text, Text_t suffix); +OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start); Text_t Text$replace(Text_t text, Text_t target, Text_t replacement); Text_t Text$translate(Text_t text, Table_t translations); PUREFUNC bool Text$has(Text_t text, Text_t target); diff --git a/test/text.tm b/test/text.tm index 094da8f8..6c23042d 100644 --- a/test/text.tm +++ b/test/text.tm @@ -202,3 +202,9 @@ func main() assert Text.from_utf32([150370]) == test assert Text.from_utf16([-10158, -8350]) == test assert Text.from_utf8([0xf0, 0xa4, 0xad, 0xa2]) == test + + + assert "one two".find("one") == 1 + assert "one two".find("two") == 5 + assert "one two".find("three") == none + assert "one two".find("o", start=2) == 7 -- cgit v1.2.3 From eff8d4aa8c59c4f8c531eb19389041c44aa49941 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 00:57:47 -0500 Subject: Bugfix for stack memory not getting properly detected in places --- CHANGES.md | 2 ++ src/types.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 293ff431..b16a4e8c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -57,6 +57,8 @@ - Optional paths no longer fail to compile when you check them for `none`. - Text replacement no longer infinitely loops when given an empty text to replace. - Short CLI flag aliases now no longer use the first letter of the argument. + - Stack memory was not correctly detected in some cases, leading to potential + memory errors. ## v0.3 diff --git a/src/types.c b/src/types.c index 33c5e55b..73c02807 100644 --- a/src/types.c +++ b/src/types.c @@ -249,6 +249,21 @@ PUREFUNC bool has_stack_memory(type_t *t) { switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); + case ListType: return has_stack_memory(Match(t, ListType)->item_type); + case TableType: + return has_stack_memory(Match(t, TableType)->key_type) || has_stack_memory(Match(t, TableType)->value_type); + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (has_stack_memory(field->type)) return true; + } + return false; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && has_stack_memory(tag->type)) return true; + } + return false; + } default: return false; } } -- cgit v1.2.3 From 5e57bafdaf9d26d9445cc89055563e81e13670d3 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 01:29:01 -0500 Subject: Bugfix with ID having a trailing newline --- src/naming.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/naming.c b/src/naming.c index 484e1998..9c0f09e4 100644 --- a/src/naming.c +++ b/src/naming.c @@ -94,11 +94,16 @@ Text_t valid_c_name(const char *name) { return Text$from_str(name); } +#include "stdlib/stdlib.h" public Text_t CONSTFUNC namespace_name(env_t *env, namespace_t *ns, Text_t name) { - for (; ns; ns = ns->parent) + if (Text$has(name, Text("\n"))) fail("WTF??"); + for (; ns; ns = ns->parent) { + if (strchr(ns->name, '\n')) fail("WTF"); name = Texts(ns->name, "$", name); + } if (env->id_suffix.length > 0) name = Texts(name, env->id_suffix); + if (Text$has(env->id_suffix, Text("\n"))) fail("WTF?????"); return name; } @@ -113,5 +118,6 @@ Text_t get_id_suffix(const char *filename) { Path_t id_file = Path$child(build_dir, Texts(Path$base_name(path), Text$from_str(".id"))); OptionalText_t id = Path$read(id_file); if (id.tag == TEXT_NONE) err(1, "Could not read ID file: %s", Path$as_c_string(id_file)); + id = Text$trim(id, Text(" \r\n"), true, true); return Texts("$", id); } -- cgit v1.2.3 From 1c77d596b28ee45e0234cfa7c5f5679492f2178e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 01:32:48 -0500 Subject: Fix up some type comparisons --- src/compile/assertions.c | 4 +++- src/compile/comparisons.c | 8 ++++++-- src/compile/lists.c | 2 +- src/typecheck.c | 13 +++++++++---- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/compile/assertions.c b/src/compile/assertions.c index 5746b21e..34055998 100644 --- a/src/compile/assertions.c +++ b/src/compile/assertions.c @@ -33,7 +33,9 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { type_t *lhs_t = get_type(env, cmp.lhs); type_t *rhs_t = get_type(with_enum_scope(env, lhs_t), cmp.rhs); type_t *operand_t; - if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { + if (type_eq(lhs_t, rhs_t)) { + operand_t = lhs_t; + } else if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { operand_t = rhs_t; } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { operand_t = lhs_t; diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c index ffa04b9d..62196cdf 100644 --- a/src/compile/comparisons.c +++ b/src/compile/comparisons.c @@ -28,7 +28,9 @@ Text_t compile_comparison(env_t *env, ast_t *ast) { type_t *lhs_t = get_type(env, binop.lhs); type_t *rhs_t = get_type(with_enum_scope(env, lhs_t), binop.rhs); type_t *operand_t; - if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) { + if (type_eq(lhs_t, rhs_t)) { + operand_t = lhs_t; + } else if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) { operand_t = rhs_t; } else if (binop.rhs->tag == Int && is_numeric_type(lhs_t)) { operand_t = lhs_t; @@ -68,7 +70,9 @@ Text_t compile_comparison(env_t *env, ast_t *ast) { type_t *lhs_t = get_type(env, cmp.lhs); type_t *rhs_t = get_type(env, cmp.rhs); type_t *operand_t; - if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { + if (type_eq(lhs_t, rhs_t)) { + operand_t = lhs_t; + } else if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { operand_t = rhs_t; } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { operand_t = lhs_t; diff --git a/src/compile/lists.c b/src/compile/lists.c index 54ad6e7f..97b0b85d 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -53,7 +53,7 @@ list_comprehension: { // set_binding(scope, comprehension_name, list_type, comprehension_name); for (ast_list_t *item = list->items; item; item = item->next) { if (item->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, item->ast)); - else code = Texts(code, compile_statement(env, add_to_list_comprehension(item->ast, comprehension_var))); + else code = Texts(code, compile_statement(scope, add_to_list_comprehension(item->ast, comprehension_var))); } code = Texts(code, " ", comprehension_name, "; })"); return code; diff --git a/src/typecheck.c b/src/typecheck.c index a0e55a88..27bb62c6 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1218,6 +1218,7 @@ type_t *get_type(env_t *env, ast_t *ast) { binary_operands_t binop = BINARY_OPERANDS(ast); type_t *lhs_t = get_type(env, binop.lhs); type_t *rhs_t = get_type(env, binop.rhs); + if (type_eq(lhs_t, rhs_t)) return ast->tag == Compare ? Type(IntType, .bits = TYPE_IBITS32) : Type(BoolType); if ((binop.lhs->tag == Int && is_numeric_type(rhs_t)) || (binop.rhs->tag == Int && is_numeric_type(lhs_t)) || can_compile_to_type(env, binop.rhs, lhs_t) || can_compile_to_type(env, binop.lhs, rhs_t)) @@ -1696,9 +1697,7 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (needed->tag == OptionalType && ast->tag == None) return true; - type_t *actual = get_type(env, ast); - if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); - + env = with_enum_scope(env, needed); if (is_numeric_type(needed) && ast->tag == Int) return true; if (needed->tag == NumType && ast->tag == Num) return true; @@ -1720,7 +1719,13 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { return false; } return true; - } else if (needed->tag == PointerType) { + } + + type_t *actual = get_type(env, ast); + if (type_eq(actual, needed)) return true; + if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); + + if (needed->tag == PointerType) { DeclareMatch(ptr, needed, PointerType); if (ast->tag == HeapAllocate) return !ptr->is_stack && can_compile_to_type(env, Match(ast, HeapAllocate)->value, ptr->pointed); -- cgit v1.2.3 From 0fa9a52090eb5d9ce88220c0134a8d2af6eb8d94 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 13:28:48 -0500 Subject: Accessing enum fields now gives an optional value instead of a boolean --- CHANGES.md | 1 + src/compile/enums.c | 13 +++++++++---- src/stdlib/datatypes.h | 5 +++++ src/types.c | 4 +++- test/enums.tm | 37 +++++++++++++++++++++++++++++++++---- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b16a4e8c..fb4ce929 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Library installation has been cleaned up a bit. - List indexing now gives an optional value. - Added support for inline anonymous enums +- Accessing a field on an enum now gives an optional value instead of a boolean. - Syntax for text literals and inline C code has been simplified somewhat. - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). diff --git a/src/compile/enums.c b/src/compile/enums.c index ec7a1755..31af96ad 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -156,15 +156,20 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - if (fielded_t->tag == PointerType) { + if (tag->type != NULL && Match(tag->type, StructType)->fields) { + return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", + promote_to_optional(tag->type, Texts("_e.", tag->name)), " : ", compile_none(tag->type), + "; })"); + } else if (fielded_t->tag == PointerType) { Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); - return Texts("((", fielded, ")->$tag == ", tag_name, ")"); + return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); } else if (enum_has_fields(value_t)) { Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ").$tag == ", tag_name, ")"); + return Texts("((", fielded, ").$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); } else { Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ") == ", tag_name, ")"); + return Texts("((", fielded, ") == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); } } } diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index caabdacd..d51db8ab 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -72,11 +72,16 @@ typedef struct table_s { typedef struct Empty$$struct { } Empty$$type; +#define EMPTY_STRUCT ((Empty$$type){}) + typedef struct { bool has_value; Empty$$type value; } $OptionalEmpty$$type; +#define NONE_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = false}) +#define OPTIONAL_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = true}) + typedef struct { void *fn, *userdata; } Closure_t; diff --git a/src/types.c b/src/types.c index 73c02807..51555560 100644 --- a/src/types.c +++ b/src/types.c @@ -633,7 +633,9 @@ type_t *get_field_type(type_t *t, const char *field_name) { case EnumType: { DeclareMatch(e, t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { - if (streq(field_name, tag->name)) return Type(BoolType); + if (!streq(field_name, tag->name)) continue; + if (tag->type != NULL && Match(tag->type, StructType)->fields) return Type(OptionalType, tag->type); + else return Type(OptionalType, EMPTY_TYPE); } return NULL; } diff --git a/test/enums.tm b/test/enums.tm index fe767ebf..80f66ed8 100644 --- a/test/enums.tm +++ b/test/enums.tm @@ -1,4 +1,5 @@ enum Foo(Zero, One(x:Int), Two(x:Int, y:Int), Three(x:Int, y:Text, z:Bool), Four(x,y,z,w:Int), Last(t:Text)) +enum OnlyTags(A, B, C, D) func choose_text(f:Foo->Text) >> f @@ -33,10 +34,6 @@ func main() assert Foo.One(123) == Foo.One(123) assert Foo.Two(123, 456) == Foo.Two(x=123, y=456) - >> one := Foo.One(123) - assert one.One == yes - assert one.Two == no - assert Foo.One(10) == Foo.One(10) assert Foo.One(10) == Foo.Zero == no @@ -99,3 +96,35 @@ func main() assert EnumFields(A) == EnumFields(x=A) + do + e := OnlyTags.A + assert e.A == EMPTY + assert e.B == none + + do + e := Foo.Zero + assert e.Zero == EMPTY + assert e.One == none + assert e.Two == none + + ep := @Foo.Zero + assert ep.Zero == EMPTY + assert ep.One == none + assert ep.Two == none + + do + e := Foo.Two(123, 456) + assert e.Zero == none + assert e.One == none + assert e.Two != none + + ep := Foo.Two(123, 456) + assert ep.Zero == none + assert ep.One == none + assert ep.Two != none + + two := e.Two! + when e is Two(x,y) + assert two.x == x + assert two.y == y + else fail("Unreachable") -- cgit v1.2.3 From 80505a7eb147422226d1b86da17f516982d0f4c8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 13:52:15 -0500 Subject: Better error messages and bugfix for compile_to_type logic --- src/compile/functions.c | 12 ++++++------ src/typecheck.c | 25 +++++++++++++------------ src/types.c | 21 +++++++++++++-------- src/types.h | 1 + 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/compile/functions.c b/src/compile/functions.c index 4a2812ba..cce93e3d 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -163,12 +163,12 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args); REVERSE_LIST(args); code_err(ast, - "This function's signature doesn't match this call site.\n" - "The signature is: ", - type_to_text(fn_t), - "\n" - "But it's being called with: ", - type_to_text(Type(FunctionType, .args = args))); + "This function's signature doesn't match this call site. \n" + " The function takes these args: (", + arg_types_to_text(Match(fn_t, FunctionType)->args, ", "), + ") \n" + " But it's being called with: (", + arg_types_to_text(args, ", "), ")"); } } return Texts(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")"); diff --git a/src/typecheck.c b/src/typecheck.c index 27bb62c6..98fbf6da 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1588,8 +1588,9 @@ bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, call_opts for (; unused_args; unused_args = unused_args->next) { if (unused_args->name) continue; // Already handled the keyword args if (options.promotion) { - if (!can_compile_to_type(arg_scope, unused_args->value, spec_type)) + if (!can_compile_to_type(arg_scope, unused_args->value, spec_type)) { return false; // Positional arg trying to fill in + } } else { type_t *call_type = get_arg_ast_type(arg_scope, unused_args); type_t *complete_call_type = @@ -1701,16 +1702,16 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (is_numeric_type(needed) && ast->tag == Int) return true; if (needed->tag == NumType && ast->tag == Num) return true; - needed = non_optional(needed); - if (needed->tag == ListType && ast->tag == List) { - type_t *item_type = Match(needed, ListType)->item_type; + type_t *non_optional_needed = non_optional(needed); + if (non_optional_needed->tag == ListType && ast->tag == List) { + type_t *item_type = Match(non_optional_needed, ListType)->item_type; for (ast_list_t *item = Match(ast, List)->items; item; item = item->next) { if (!can_compile_to_type(env, item->ast, item_type)) return false; } return true; - } else if (needed->tag == TableType && ast->tag == Table) { - type_t *key_type = Match(needed, TableType)->key_type; - type_t *value_type = Match(needed, TableType)->value_type; + } else if (non_optional_needed->tag == TableType && ast->tag == Table) { + type_t *key_type = Match(non_optional_needed, TableType)->key_type; + type_t *value_type = Match(non_optional_needed, TableType)->value_type; for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next) { if (entry->ast->tag != TableEntry) continue; // TODO: fix this DeclareMatch(e, entry->ast, TableEntry); @@ -1725,16 +1726,16 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (type_eq(actual, needed)) return true; if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); - if (needed->tag == PointerType) { - DeclareMatch(ptr, needed, PointerType); + if (non_optional_needed->tag == PointerType) { + DeclareMatch(ptr, non_optional_needed, PointerType); if (ast->tag == HeapAllocate) return !ptr->is_stack && can_compile_to_type(env, Match(ast, HeapAllocate)->value, ptr->pointed); else if (ast->tag == StackReference) return ptr->is_stack && can_compile_to_type(env, Match(ast, StackReference)->value, ptr->pointed); - else return can_promote(actual, needed); - } else if (actual->tag == OptionalType && needed->tag != OptionalType) { + else return can_promote(actual, non_optional_needed); + } else if (actual->tag == OptionalType && non_optional_needed->tag != OptionalType) { return false; } else { - return can_promote(actual, needed); + return can_promote(actual, non_optional_needed); } } diff --git a/src/types.c b/src/types.c index 51555560..edfee27d 100644 --- a/src/types.c +++ b/src/types.c @@ -12,6 +12,15 @@ #include "stdlib/util.h" #include "types.h" +Text_t arg_types_to_text(arg_t *args, const char *separator) { + Text_t text = EMPTY_TEXT; + for (arg_t *arg = args; arg; arg = arg->next) { + text = Texts(text, type_to_text(arg->type)); + if (arg->next) text = Texts(text, separator); + } + return text; +} + Text_t type_to_text(type_t *t) { if (!t) return Text("(Unknown type)"); @@ -45,15 +54,11 @@ Text_t type_to_text(type_t *t) { return type_to_text(Match(t, ClosureType)->fn); } case FunctionType: { - Text_t c = Text("func("); DeclareMatch(fn, t, FunctionType); - for (arg_t *arg = fn->args; arg; arg = arg->next) { - c = Texts(c, type_to_text(arg->type)); - if (arg->next) c = Texts(c, ","); - } - if (fn->ret && fn->ret->tag != VoidType) c = Texts(c, fn->args ? " -> " : "-> ", type_to_text(fn->ret)); - c = Texts(c, ")"); - return c; + Text_t text = Texts("func(", arg_types_to_text(fn->args, ",")); + if (fn->ret && fn->ret->tag != VoidType) text = Texts(text, fn->args ? " -> " : "-> ", type_to_text(fn->ret)); + text = Texts(text, ")"); + return text; } case StructType: { DeclareMatch(struct_, t, StructType); diff --git a/src/types.h b/src/types.h index 2a94a512..b8f9a42f 100644 --- a/src/types.h +++ b/src/types.h @@ -136,6 +136,7 @@ struct type_s { _make_function_type(ret, sizeof((arg_t[]){__VA_ARGS__}) / sizeof(arg_t), (arg_t[]){__VA_ARGS__}) Text_t type_to_text(type_t *t); +Text_t arg_types_to_text(arg_t *args, const char *separator); const char *get_type_name(type_t *t); PUREFUNC bool type_eq(type_t *a, type_t *b); PUREFUNC bool type_is_a(type_t *t, type_t *req); -- cgit v1.2.3 From 8700224e98f95807d896d214380796e6f80678d0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 13:58:37 -0500 Subject: use exit() instead of _exit() --- src/stdlib/stdlib.c | 2 +- src/stdlib/stdlib.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 45a4bd00..157dbfd4 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -145,7 +145,7 @@ void say(Text_t text, bool newline) { public _Noreturn void tomo_exit(Text_t text, int32_t status) { if (text.length > 0) print(text); - _exit(status); + exit(status); } public diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index e52b5cd1..aadce27c 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -30,7 +30,7 @@ void tomo_init(void); else fputs("\n", stderr); \ fflush(stderr); \ raise(SIGABRT); \ - _exit(1); \ + exit(1); \ }) #define fail_source(filename, start, end, ...) \ @@ -50,7 +50,7 @@ void tomo_init(void); if (USE_COLOR) fputs("\x1b[m", stderr); \ fflush(stderr); \ raise(SIGABRT); \ - _exit(1); \ + exit(1); \ }) _Noreturn void fail_text(Text_t message); -- cgit v1.2.3 From 2a24b0a3fc3c4986572ae2c4ea0e8e387497a7f6 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 14:19:04 -0500 Subject: Add `at_cleanup()` function --- CHANGES.md | 5 +++-- api/api.md | 26 +++++++++++++++++++++++++- api/builtins.md | 26 +++++++++++++++++++++++++- api/builtins.yaml | 28 +++++++++++++++++++++++++++- man/man3/tomo-exit.3 | 4 ++-- src/environment.c | 13 +++++++------ src/stdlib/stdlib.c | 23 +++++++++++++++++++++++ src/stdlib/stdlib.h | 4 ++++ 8 files changed, 116 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fb4ce929..42db4715 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,8 +12,9 @@ - Syntax for text literals and inline C code has been simplified somewhat. - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). -- Added `Path.lines()` -- Added `Text.find(text, target, start=1)` +- Added `Path.lines()`. +- Added `Text.find(text, target, start=1)`. +- Added `at_cleanup()` to register cleanup functions. - Deprecated: - Sets are no longer a separate type with separate methods. - Instead of sets, use tables with a value type of `{KeyType:Empty}`. diff --git a/api/api.md b/api/api.md index 02ad054e..5c9dc9c4 100644 --- a/api/api.md +++ b/api/api.md @@ -32,11 +32,35 @@ force_tty | `Bool` | Whether or not to force the use of /dev/tty. | `yes` ```tomo assert ask("What's your name? ") == "Arthur Dent" +``` +## at_cleanup + +```tomo +at_cleanup : func(fn: func() -> Void) +``` + +Register a function that runs at cleanup time for Tomo programs. Cleanup time happens when a program exits (see `atexit()` in C), or immediately before printing error messages in a call to `fail()`. This allows for terminal cleanup so error messages can be visible as the program shuts down. + +Use this API very carefully, because errors that occur during cleanup functions may make it extremely hard to figure out what's going on. Cleanup functions should be designed to not error under any circumstances. + +Argument | Type | Description | Default +---------|------|-------------|--------- +fn | `func()` | A function to run at cleanup time. | - + +**Return:** Nothing. + + +**Example:** +```tomo +at_cleanup(func() + (/tmp/file.txt).remove(ignore_missing=yes) +) + ``` ## exit ```tomo -exit : func(message: Text? = none, status: Int32 = Int32(1) -> Void) +exit : func(message: Text? = none, status: Int32 = Int32(1) -> Abort) ``` Exits the program with a given status and optionally prints a message. diff --git a/api/builtins.md b/api/builtins.md index 0b06a41b..6d042741 100644 --- a/api/builtins.md +++ b/api/builtins.md @@ -32,11 +32,35 @@ force_tty | `Bool` | Whether or not to force the use of /dev/tty. | `yes` ```tomo assert ask("What's your name? ") == "Arthur Dent" +``` +## at_cleanup + +```tomo +at_cleanup : func(fn: func() -> Void) +``` + +Register a function that runs at cleanup time for Tomo programs. Cleanup time happens when a program exits (see `atexit()` in C), or immediately before printing error messages in a call to `fail()`. This allows for terminal cleanup so error messages can be visible as the program shuts down. + +Use this API very carefully, because errors that occur during cleanup functions may make it extremely hard to figure out what's going on. Cleanup functions should be designed to not error under any circumstances. + +Argument | Type | Description | Default +---------|------|-------------|--------- +fn | `func()` | A function to run at cleanup time. | - + +**Return:** Nothing. + + +**Example:** +```tomo +at_cleanup(func() + (/tmp/file.txt).remove(ignore_missing=yes) +) + ``` ## exit ```tomo -exit : func(message: Text? = none, status: Int32 = Int32(1) -> Void) +exit : func(message: Text? = none, status: Int32 = Int32(1) -> Abort) ``` Exits the program with a given status and optionally prints a message. diff --git a/api/builtins.yaml b/api/builtins.yaml index 2eae5340..764a1bd4 100644 --- a/api/builtins.yaml +++ b/api/builtins.yaml @@ -38,7 +38,7 @@ exit: description: > Exits the program with a given status and optionally prints a message. return: - type: 'Void' + type: 'Abort' description: > This function never returns. args: @@ -56,6 +56,32 @@ exit: example: | exit(status=1, "Goodbye forever!") +at_cleanup: + short: register a cleanup function + description: > + Register a function that runs at cleanup time for Tomo programs. Cleanup + time happens when a program exits (see `atexit()` in C), or immediately + before printing error messages in a call to `fail()`. This allows for + terminal cleanup so error messages can be visible as the program shuts + down. + note: > + Use this API very carefully, because errors that occur during cleanup + functions may make it extremely hard to figure out what's going on. Cleanup + functions should be designed to not error under any circumstances. + args: + fn: + type: 'func()' + description: > + A function to run at cleanup time. + return: + type: 'Void' + description: > + Nothing. + example: | + at_cleanup(func() + (/tmp/file.txt).remove(ignore_missing=yes) + ) + getenv: short: get an environment variable description: > diff --git a/man/man3/tomo-exit.3 b/man/man3/tomo-exit.3 index 48e0bd79..50c022b8 100644 --- a/man/man3/tomo-exit.3 +++ b/man/man3/tomo-exit.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH exit 3 2025-05-17 "Tomo man-pages" +.TH exit 3 2025-11-23 "Tomo man-pages" .SH NAME exit \- exit the program .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI exit\ :\ func(message:\ Text?\ =\ none,\ status:\ Int32\ =\ Int32(1)\ ->\ Void) +.BI exit\ :\ func(message:\ Text?\ =\ none,\ status:\ Int32\ =\ Int32(1)\ ->\ Abort) .fi .SH DESCRIPTION Exits the program with a given status and optionally prints a message. diff --git a/src/environment.c b/src/environment.c index 3a2995f7..d8c82513 100644 --- a/src/environment.c +++ b/src/environment.c @@ -529,17 +529,18 @@ env_t *global_env(bool source_mapping) { struct { const char *name, *code, *type_str; } global_vars[] = { - {"USE_COLOR", "USE_COLOR", "Bool"}, + {"EMPTY", "EMPTY", "Empty"}, {"TOMO_VERSION", "TOMO_VERSION_TEXT", "Text"}, - {"say", "say", "func(text:Text, newline=yes)"}, - {"print", "say", "func(text:Text, newline=yes)"}, - {"getenv", "getenv_text", "func(name:Text -> Text?)"}, - {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"}, + {"USE_COLOR", "USE_COLOR", "Bool"}, {"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text?)"}, + {"at_cleanup", "tomo_at_cleanup", "func(fn:func())"}, {"exit", "tomo_exit", "func(message:Text?=none, code=Int32(1) -> Abort)"}, {"fail", "fail_text", "func(message:Text -> Abort)"}, + {"getenv", "getenv_text", "func(name:Text -> Text?)"}, + {"print", "say", "func(text:Text, newline=yes)"}, + {"say", "say", "func(text:Text, newline=yes)"}, + {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"}, {"sleep", "sleep_num", "func(seconds:Num)"}, - {"EMPTY", "EMPTY", "Empty"}, }; for (size_t i = 0; i < sizeof(global_vars) / sizeof(global_vars[0]); i++) { diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 157dbfd4..21547efe 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -72,6 +72,7 @@ void tomo_init(void) { sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigaction(SIGILL, &sigact, (struct sigaction *)NULL); + atexit(tomo_cleanup); } public @@ -219,3 +220,25 @@ OptionalText_t getenv_text(Text_t name) { public void setenv_text(Text_t name, Text_t value) { setenv(Text$as_c_string(name), Text$as_c_string(value), 1); } + +typedef struct cleanup_s { + Closure_t cleanup_fn; + struct cleanup_s *next; +} cleanup_t; + +static cleanup_t *cleanups = NULL; + +public +void tomo_at_cleanup(Closure_t fn) { cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); } + +public +void tomo_cleanup(void) { + while (cleanups) { + // NOTE: we *must* remove the cleanup function from the stack before calling it, + // otherwise it will cause an infinite loop if the cleanup function fails or exits. + void (*run_cleanup)(void *) = cleanups->cleanup_fn.fn; + void *userdata = cleanups->cleanup_fn.userdata; + cleanups = cleanups->next; + run_cleanup(userdata); + } +} diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index aadce27c..392b5f23 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -16,9 +16,12 @@ extern bool USE_COLOR; extern Text_t TOMO_VERSION_TEXT; void tomo_init(void); +void tomo_at_cleanup(Closure_t fn); +void tomo_cleanup(void); #define fail(...) \ ({ \ + tomo_cleanup(); \ fflush(stdout); \ if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \ else fputs("==================== ERROR ====================\n\n", stderr); \ @@ -35,6 +38,7 @@ void tomo_init(void); #define fail_source(filename, start, end, ...) \ ({ \ + tomo_cleanup(); \ fflush(stdout); \ if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); \ else fputs("==================== ERROR ====================\n\n", stderr); \ -- cgit v1.2.3 From 83a2bff4bb04b0189d17419baf8ca520992d5033 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 23 Nov 2025 15:57:44 -0500 Subject: Added Metadata section for files instead of _HELP and _USAGE --- CHANGES.md | 7 +++ docs/command-line-parsing.md | 33 ++++++++++ src/ast.c | 17 +++++- src/ast.h | 5 ++ src/compile/cli.c | 142 ++++++++++++++++++++++--------------------- src/compile/cli.h | 3 +- src/compile/files.c | 1 + src/compile/statements.c | 1 + src/parse/expressions.c | 2 +- src/parse/files.c | 41 +++++++++++-- src/parse/text.c | 12 ++-- src/parse/text.h | 4 +- src/tomo.c | 6 +- src/typecheck.c | 7 ++- 14 files changed, 196 insertions(+), 85 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 42db4715..f850d535 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,13 @@ - Syntax for text literals and inline C code has been simplified somewhat. - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). +- Added metadata format instead of `_HELP`/`_USAGE`: + ``` + HELP: "Help text" + USAGE: "Usage text" + MANPAGE_SYNOPSYS: "Synopsys..." + MANPAGE_DESCRIPTION: (./description.txt) + ``` - Added `Path.lines()`. - Added `Text.find(text, target, start=1)`. - Added `at_cleanup()` to register cleanup functions. diff --git a/docs/command-line-parsing.md b/docs/command-line-parsing.md index 714e6e9c..92e9a1c9 100644 --- a/docs/command-line-parsing.md +++ b/docs/command-line-parsing.md @@ -223,3 +223,36 @@ OPTIONS --frob | --no-frob Whether or not to frob your gropnoggles ``` + +## Metadata + +You can specify metadata for a program, which is used for CLI messages like +`--help`, as well as manpage documentation. Metadata can be specified as either +a text literal (no interpolation) or as a file path literal. + +``` +USAGE: "--foo " +HELP: " + This is some custom help text. + You can use these flags: + + --foo The foo parameter + --help Show this message +" +MANPAGE_DESCRIPTION: (./description.roff) +``` + +Supported metadata: + +- `USAGE`: the short form usage shown in CLI parsing errors and help pages. This + should be a single line without the name of the program, so `USAGE: "--foo"` + would translate to the error message `Usage: myprogram --foo`. If this is not + present, it will be generated automatically. + +- `HELP`: The help message displayed when the `--help` flag is used or when there + is an argument parsing error. This should be a description of the program with + a multi-line documentation of commonly used flags. + +- `MANPAGE_SYNOPSYS`: the synopsis section of the manpage (inserted literally). + +- `MANPAGE_DESCRIPTION`: the description section of the manpage (inserted literally). diff --git a/src/ast.c b/src/ast.c index 432ce2d4..b2730d21 100644 --- a/src/ast.c +++ b/src/ast.c @@ -274,6 +274,8 @@ Text_t ast_to_sexp(ast_t *ast) { T(Assert, "(Assert ", ast_to_sexp(data.expr), " ", optional_sexp("message", data.message), ")"); T(Use, "(Use ", optional_sexp("var", data.var), " ", quoted_text(data.path), ")"); T(InlineCCode, "(InlineCCode ", ast_list_to_sexp(data.chunks), optional_type_sexp("type", data.type_ast), ")"); + T(Metadata, "((Metadata ", Text$quoted(data.key, false, Text("\"")), " ", + Text$quoted(data.value, false, Text("\"")), ")"); default: errx(1, "S-expressions are not implemented for this AST"); #undef T } @@ -463,7 +465,8 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) { case Int: case Num: case Path: - case TextLiteral: return; + case TextLiteral: + case Metadata: return; case TextJoin: ast_visit_list(Match(ast, TextJoin)->children, visitor, userdata); return; case Declare: { DeclareMatch(decl, ast, Declare); @@ -780,3 +783,15 @@ void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *use Closure_t fn = {.fn = visitor, .userdata = userdata}; ast_visit(ast, _type_ast_visit, &fn); } + +OptionalText_t ast_metadata(ast_t *ast, const char *key) { + if (ast->tag != Block) return NONE_TEXT; + Text_t key_text = Text$from_str(key); + for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->ast->tag == Metadata) { + DeclareMatch(m, stmt->ast, Metadata); + if (Text$equal_values(m->key, key_text)) return m->value; + } + } + return NONE_TEXT; +} diff --git a/src/ast.h b/src/ast.h index c01ba68a..7fa9092a 100644 --- a/src/ast.h +++ b/src/ast.h @@ -276,6 +276,7 @@ typedef enum { Use, InlineCCode, ExplicitlyTyped, + Metadata, } ast_e; #define NUM_AST_TAGS (ExplicitlyTyped + 1) @@ -458,6 +459,9 @@ struct ast_s { ast_t *ast; struct type_s *type; } ExplicitlyTyped; + struct { + Text_t key, value; + } Metadata; } __data; }; @@ -483,3 +487,4 @@ CONSTFUNC ast_e binop_tag(ast_e tag); CONSTFUNC bool is_binary_operation(ast_t *ast); void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata); void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata); +OptionalText_t ast_metadata(ast_t *ast, const char *key); diff --git a/src/compile/cli.c b/src/compile/cli.c index e5756521..b92a5784 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -58,80 +58,86 @@ static OptionalText_t flagify(const char *name, bool prefix) { return flag; } -public -Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) { +static Text_t generate_usage(env_t *env, type_t *fn_type) { DeclareMatch(fn_info, fn_type, FunctionType); - - env_t *main_env = fresh_scope(env); - - Text_t code = EMPTY_TEXT; - binding_t *usage_binding = get_binding(env, "_USAGE"); - Text_t usage_code = usage_binding ? usage_binding->code : Text("usage"); - binding_t *help_binding = get_binding(env, "_HELP"); - Text_t help_code = help_binding ? help_binding->code : usage_code; - if (!usage_binding) { - bool explicit_help_flag = false; - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - if (streq(arg->name, "help")) { - explicit_help_flag = true; - break; - } + bool explicit_help_flag = false; + for (arg_t *arg = fn_info->args; arg; arg = arg->next) { + if (streq(arg->name, "help")) { + explicit_help_flag = true; + break; } - - Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]"); - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - usage = Texts(usage, " "); - type_t *t = get_arg_type(main_env, arg); - OptionalText_t flag = flagify(arg->name, arg->default_val != NULL); - assert(flag.tag != TEXT_NONE); - OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL); - Text_t flags = Texts("\x1b[1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m"); + } + env_t *main_env = fresh_scope(env); + Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]"); + for (arg_t *arg = fn_info->args; arg; arg = arg->next) { + usage = Texts(usage, " "); + type_t *t = get_arg_type(main_env, arg); + OptionalText_t flag = flagify(arg->name, arg->default_val != NULL); + assert(flag.tag != TEXT_NONE); + OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL); + Text_t flags = Texts("\x1b[1m", flag, "\x1b[m"); + if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m"); + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); + if (arg->default_val || value_type(t)->tag == BoolType) { if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); - if (arg->default_val || value_type(t)->tag == BoolType) { - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - usage = Texts(usage, "[", flags, "]"); - else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); - else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); - } else { - usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); - } + usage = Texts(usage, "[", flags, "]"); + else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + } else { + usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); } - code = Texts(code, - "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), " - "Text$from_str(argv[0])", - usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n"); } - if (!help_binding) { - Text_t help_text = fn_info->args ? Text("\n") : Text("\n\n\x1b[2;3m No arguments...\x1b[m"); - - for (arg_t *arg = fn_info->args; arg; arg = arg->next) { - help_text = Texts(help_text, "\n"); - type_t *t = get_arg_type(main_env, arg); - OptionalText_t flag = flagify(arg->name, true); - assert(flag.tag != TEXT_NONE); - OptionalText_t alias_flag = flagify(arg->alias, true); - Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m"); - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - help_text = Texts(help_text, " ", flags); - else - help_text = Texts(help_text, " ", flags, " \x1b[1;34m", - get_flag_options(t, Text("\x1b[m | \x1b[1;34m")), "\x1b[m"); - - if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m"); - if (arg->default_val) { - Text_t default_text = - Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start)); - help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m"); - } + return usage; +} + +static Text_t generate_help(env_t *env, type_t *fn_type) { + DeclareMatch(fn_info, fn_type, FunctionType); + env_t *main_env = fresh_scope(env); + Text_t help_text = EMPTY_TEXT; + + for (arg_t *arg = fn_info->args; arg; arg = arg->next) { + help_text = Texts(help_text, "\n"); + type_t *t = get_arg_type(main_env, arg); + OptionalText_t flag = flagify(arg->name, true); + assert(flag.tag != TEXT_NONE); + OptionalText_t alias_flag = flagify(arg->alias, true); + Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m"); + if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m"); + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + help_text = Texts(help_text, " ", flags); + else + help_text = Texts(help_text, " ", flags, " \x1b[1;34m", get_flag_options(t, Text("\x1b[m | \x1b[1;34m")), + "\x1b[m"); + + if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m"); + if (arg->default_val) { + Text_t default_text = + Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start)); + help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m"); } - code = Texts(code, "Text_t help = Texts(usage, ", quoted_text(Texts(help_text, "\n")), ");\n"); - help_code = Text("help"); } + return help_text; +} + +public +Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version) { + DeclareMatch(fn_info, fn_type, FunctionType); + + Text_t code = EMPTY_TEXT; + OptionalText_t usage = ast_metadata(ast, "USAGE"); + if (usage.tag == TEXT_NONE) usage = generate_usage(env, fn_type); + + OptionalText_t help = ast_metadata(ast, "HELP"); + if (help.tag == TEXT_NONE) help = generate_help(env, fn_type); + + code = Texts(code, + "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), " + "Text$from_str(argv[0]), Text(\" \")", + usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n", + "Text_t help = Texts(usage, Text(\"\\n\\n\"", quoted_text(help), "));\n"); for (arg_t *arg = fn_info->args; arg; arg = arg->next) { code = Texts(code, compile_declaration(arg->type, Texts("_$", Text$from_str(arg->name))), " = ", @@ -151,7 +157,7 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c "},\n"); } code = Texts(code, "};\n"); - code = Texts(code, "tomo_parse_args(argc, argv, ", usage_code, ", ", help_code, ", ", version_code, + code = Texts(code, "tomo_parse_args(argc, argv, usage, help, ", version_code, ", sizeof(cli_args)/sizeof(cli_args[0]), cli_args);\n"); // Lazily initialize default values to prevent side effects diff --git a/src/compile/cli.h b/src/compile/cli.h index fa60eccf..fbb7347b 100644 --- a/src/compile/cli.h +++ b/src/compile/cli.h @@ -2,9 +2,10 @@ #pragma once +#include "../ast.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../types.h" -Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version); +Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version); Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args); diff --git a/src/compile/files.c b/src/compile/files.c index a4cc07fe..b916f23f 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -142,6 +142,7 @@ Text_t compile_top_level_code(env_t *env, ast_t *ast) { } return code; } + case Metadata: default: return EMPTY_TEXT; } } diff --git a/src/compile/statements.c b/src/compile/statements.c index 0cd85b5d..c6ceccd9 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -216,6 +216,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { return EMPTY_TEXT; } } + case Metadata: return EMPTY_TEXT; default: // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ", // is_discardable(env, ast)); diff --git a/src/parse/expressions.c b/src/parse/expressions.c index b43e4f3a..d031c49f 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -168,7 +168,7 @@ ast_t *parse_term_no_suffix(parse_ctx_t *ctx, const char *pos) { (void)(false || (term = parse_none(ctx, pos)) || (term = parse_num(ctx, pos)) // Must come before int || (term = parse_int(ctx, pos)) || (term = parse_negative(ctx, pos)) // Must come after num/int || (term = parse_heap_alloc(ctx, pos)) || (term = parse_stack_reference(ctx, pos)) - || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos)) || (term = parse_path(ctx, pos)) + || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos, true)) || (term = parse_path(ctx, pos)) || (term = parse_lambda(ctx, pos)) || (term = parse_parens(ctx, pos)) || (term = parse_table(ctx, pos)) || (term = parse_var(ctx, pos)) || (term = parse_list(ctx, pos)) || (term = parse_reduction(ctx, pos)) || (term = parse_pass(ctx, pos)) || (term = parse_defer(ctx, pos)) || (term = parse_skip(ctx, pos)) diff --git a/src/parse/files.c b/src/parse/files.c index 23e940e9..f5d9554a 100644 --- a/src/parse/files.c +++ b/src/parse/files.c @@ -7,8 +7,11 @@ #include #include "../ast.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/paths.h" #include "../stdlib/stdlib.h" #include "../stdlib/tables.h" +#include "../stdlib/text.h" #include "../stdlib/util.h" #include "context.h" #include "errors.h" @@ -31,6 +34,35 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) { return declaration; } +static ast_t *parse_metadata(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + const char *key = get_id(&pos); + if (!key) return NULL; + spaces(&pos); + if (!match(&pos, ":")) return NULL; + spaces(&pos); + ast_t *value = parse_text(ctx, pos, false); + Text_t value_text = EMPTY_TEXT; + if (value) { + for (ast_list_t *child = Match(value, TextJoin)->children; child; child = child->next) { + if (child->ast->tag != TextLiteral) + parser_err(ctx, child->ast->start, child->ast->end, "Text interpolations are not allowed in metadata"); + value_text = Texts(value_text, Match(child->ast, TextLiteral)->text); + } + } else { + value = parse_path(ctx, pos); + if (!value) return NULL; + Path_t path = Path$from_str(Match(value, Path)->path); + path = Path$resolved(path, Path$parent(Path$from_str(ctx->file->filename))); + OptionalText_t contents = Path$read(path); + if (contents.tag == TEXT_NONE) parser_err(ctx, value->start, value->end, "File not found: ", path); + value_text = Text$trim(contents, Text("\r\n\t "), true, true); + } + pos = value->end; + + return NewAST(ctx->file, start, pos, Metadata, .key = Text$from_str(key), .value = value_text); +} + ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { const char *start = pos; whitespace(ctx, &pos); @@ -40,10 +72,11 @@ ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { whitespace(ctx, &next); if (get_indent(ctx, next) != 0) break; ast_t *stmt; - if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def)) - || (stmt = optional(ctx, &pos, parse_enum_def)) || (stmt = optional(ctx, &pos, parse_lang_def)) - || (stmt = optional(ctx, &pos, parse_convert_def)) || (stmt = optional(ctx, &pos, parse_use)) - || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_top_declaration))) { + if ((stmt = optional(ctx, &pos, parse_metadata)) || (stmt = optional(ctx, &pos, parse_struct_def)) + || (stmt = optional(ctx, &pos, parse_func_def)) || (stmt = optional(ctx, &pos, parse_enum_def)) + || (stmt = optional(ctx, &pos, parse_lang_def)) || (stmt = optional(ctx, &pos, parse_convert_def)) + || (stmt = optional(ctx, &pos, parse_use)) || (stmt = optional(ctx, &pos, parse_inline_c)) + || (stmt = optional(ctx, &pos, parse_top_declaration))) { statements = new (ast_list_t, .ast = stmt, .next = statements); pos = stmt->end; whitespace(ctx, &pos); // TODO: check for newline diff --git a/src/parse/text.c b/src/parse/text.c index 7650955c..e23b8417 100644 --- a/src/parse/text.c +++ b/src/parse/text.c @@ -17,7 +17,7 @@ #include "types.h" #include "utils.h" -static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { +static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, bool allow_interps) { const char *pos = *out_pos; int64_t starting_indent = get_indent(ctx, pos); @@ -41,6 +41,8 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { parser_err(ctx, pos, pos, "I expected a valid text here"); } + if (!allow_interps) interp = NULL; + ast_list_t *chunks = NULL; Text_t chunk = EMPTY_TEXT; const char *chunk_start = pos; @@ -123,9 +125,9 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { return chunks; } -ast_t *parse_text(parse_ctx_t *ctx, const char *pos) { +ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps) { // ('"' ... '"' / "'" ... "'" / "`" ... "`") - // "$" [name] [interp-char] quote-char ... close-quote + // "$" [name] quote-char ... close-quote const char *start = pos; const char *lang = NULL; @@ -136,7 +138,7 @@ ast_t *parse_text(parse_ctx_t *ctx, const char *pos) { if (!(*pos == '"' || *pos == '\'' || *pos == '`')) return NULL; - ast_list_t *chunks = _parse_text_helper(ctx, &pos); + ast_list_t *chunks = _parse_text_helper(ctx, &pos, allow_interps); bool colorize = match(&pos, "~") && match_word(&pos, "colorized"); return NewAST(ctx->file, start, pos, TextJoin, .lang = lang, .children = chunks, .colorize = colorize); } @@ -157,7 +159,7 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) { parser_err(ctx, pos, pos + 1, "This is not a valid string quotation character. Valid characters are: \"'`|/;([{<"); - ast_list_t *chunks = _parse_text_helper(ctx, &pos); + ast_list_t *chunks = _parse_text_helper(ctx, &pos, true); return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type); } diff --git a/src/parse/text.h b/src/parse/text.h index 6ab3cab2..865bc5a4 100644 --- a/src/parse/text.h +++ b/src/parse/text.h @@ -1,9 +1,11 @@ // Logic for parsing text literals #pragma once +#include + #include "../ast.h" #include "context.h" -ast_t *parse_text(parse_ctx_t *ctx, const char *pos); +ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps); ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos); ast_t *parse_path(parse_ctx_t *ctx, const char *pos); diff --git a/src/tomo.c b/src/tomo.c index fab725db..a04ccf59 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -841,7 +841,7 @@ void transpile_code(env_t *base_env, Path_t path) { namespace_name(module_env, module_env->namespace, Text("$initialize")), "();\n" "\n", - compile_cli_arg_call(module_env, main_binding->code, main_binding->type, version), + compile_cli_arg_call(module_env, ast, main_binding->code, main_binding->type, version), "return 0;\n" "}\n")); } @@ -877,7 +877,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t Path_t manpage_file = build_file(Path$with_extension(path, Text(".1"), true), ""); if (clean_build || !Path$is_file(manpage_file, true) || is_stale(manpage_file, path, true)) { - Text_t manpage = compile_manpage(Path$base_name(exe_path), NONE_TEXT, NONE_TEXT, + OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS"); + OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION"); + Text_t manpage = compile_manpage(Path$base_name(exe_path), synopsys, description, Match(main_binding->type, FunctionType)->args); if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir())); Path$write(manpage_file, manpage, 0644); diff --git a/src/typecheck.c b/src/typecheck.c index 98fbf6da..4e1f5554 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1511,6 +1511,7 @@ type_t *get_type(env_t *env, ast_t *ast) { } case Unknown: code_err(ast, "I can't figure out the type of: ", ast_to_sexp_str(ast)); case ExplicitlyTyped: return Match(ast, ExplicitlyTyped)->type; + case Metadata: return Type(VoidType); } #ifdef __GNUC__ #pragma GCC diagnostic pop @@ -1529,7 +1530,8 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { case StructDef: case EnumDef: case LangDef: - case Use: return true; + case Use: + case Metadata: return true; default: break; } type_t *t = get_type(env, ast); @@ -1686,7 +1688,8 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) { default: return is_constant(env, binop.lhs) && is_constant(env, binop.rhs); } } - case Use: return true; + case Use: + case Metadata: return true; case FunctionCall: return false; case InlineCCode: return true; default: return false; -- cgit v1.2.3 From 8de3edded640dfb4dc27fd5afae77faa2bf4acd5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 01:12:41 -0500 Subject: Add MANPAGE metadata for overriding whole file --- docs/command-line-parsing.md | 2 ++ src/compile/cli.c | 11 +++++++++-- src/compile/cli.h | 2 +- src/tomo.c | 7 ++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/command-line-parsing.md b/docs/command-line-parsing.md index 92e9a1c9..d0153e37 100644 --- a/docs/command-line-parsing.md +++ b/docs/command-line-parsing.md @@ -253,6 +253,8 @@ Supported metadata: is an argument parsing error. This should be a description of the program with a multi-line documentation of commonly used flags. +- `MANPAGE`: the full manpage (overrules the options below). + - `MANPAGE_SYNOPSYS`: the synopsis section of the manpage (inserted literally). - `MANPAGE_DESCRIPTION`: the description section of the manpage (inserted literally). diff --git a/src/compile/cli.c b/src/compile/cli.c index b92a5784..7cd3cc7c 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -187,14 +187,21 @@ Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_t } public -Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args) { +Text_t compile_manpage(Text_t program, ast_t *ast, arg_t *args) { + OptionalText_t user_manpage = ast_metadata(ast, "MANPAGE"); + if (user_manpage.tag != TEXT_NONE) { + return user_manpage; + } + + OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS"); + OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION"); Text_t date = Text(""); // TODO: use date Text_t man = Texts(".\\\" Automatically generated by Tomo\n" ".TH \"", Text$upper(program, Text("C")), "\" \"1\" \"", date, "\" \"\" \"\"\n" ".SH NAME\n", - program, " \\- ", synopsis.tag == TEXT_NONE ? Text("a Tomo program") : synopsis, "\n"); + program, " \\- ", synopsys.tag == TEXT_NONE ? Text("a Tomo program") : synopsys, "\n"); if (description.tag != TEXT_NONE) { man = Texts(man, ".SH DESCRIPTION\n", description, "\n"); diff --git a/src/compile/cli.h b/src/compile/cli.h index fbb7347b..907554a6 100644 --- a/src/compile/cli.h +++ b/src/compile/cli.h @@ -8,4 +8,4 @@ #include "../types.h" Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version); -Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args); +Text_t compile_manpage(Text_t program, ast_t *ast, arg_t *args); diff --git a/src/tomo.c b/src/tomo.c index a04ccf59..58bfabb3 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -877,12 +877,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t Path_t manpage_file = build_file(Path$with_extension(path, Text(".1"), true), ""); if (clean_build || !Path$is_file(manpage_file, true) || is_stale(manpage_file, path, true)) { - OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS"); - OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION"); - Text_t manpage = compile_manpage(Path$base_name(exe_path), synopsys, description, - Match(main_binding->type, FunctionType)->args); - if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir())); + Text_t manpage = compile_manpage(Path$base_name(exe_path), ast, Match(main_binding->type, FunctionType)->args); Path$write(manpage_file, manpage, 0644); + if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir())); } else { if (verbose) whisper("Unchanged: ", manpage_file); } -- cgit v1.2.3 From 2d56b517d81ea06debdf3cc307a71bfe626544b5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 18:22:43 -0500 Subject: Bugfix for mac --- src/tomo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tomo.c b/src/tomo.c index 58bfabb3..97857286 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -475,7 +475,7 @@ void build_library(Path_t lib_dir) { FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), #ifdef __APPLE__ - " -Wl,-install_name,@rpath/'lib", Path$base_name(lib_dir), SHARED_SUFFIX, + " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX, "'" #else " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX, -- cgit v1.2.3 From 19349dff649ac8039e8d13e07d33cbdc6f6655b9 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 19:39:57 -0500 Subject: Bugfix for downloading the right branch of a module --- src/modules.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules.c b/src/modules.c index 9ebdca09..23b8a0a0 100644 --- a/src/modules.c +++ b/src/modules.c @@ -129,6 +129,7 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) { } print("Installing ", mod.name, " from git..."); if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest); + else if (mod.version) xsystem("git clone --depth=1 --branch ", mod.version, " ", mod.git, " ", dest); else xsystem("git clone --depth=1 ", mod.git, " ", dest); xsystem("tomo -L ", dest); return true; -- cgit v1.2.3 From 9b8c4beb1e64579792fca7c161c9e3b0691e67d4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 19:42:28 -0500 Subject: When running an executable, run it with a relative instead of absolute path so it shows up more nicely in the usage --- src/tomo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tomo.c b/src/tomo.c index 97857286..66ac0ddf 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -423,7 +423,8 @@ int main(int argc, char *argv[]) { pid_t child = i == (int64_t)run_files.length - 1 ? 0 : fork(); if (child == 0) { const char *prog_args[1 + args.length + 1]; - prog_args[0] = (char *)Path$as_c_string(exe_path); + Path_t relative_exe = Path$relative_to(exe_path, Path$current_dir()); + prog_args[0] = (char *)Path$as_c_string(relative_exe); for (int64_t j = 0; j < (int64_t)args.length; j++) prog_args[j + 1] = *(const char **)(args.data + j * args.stride); prog_args[1 + args.length] = NULL; -- cgit v1.2.3 From 04ea51a81720be79170f185879a465e7020ed42b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 20:16:50 -0500 Subject: Fix case where a conditional failure was mistaken for an always failure --- src/typecheck.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/typecheck.c b/src/typecheck.c index 4e1f5554..b89f7eca 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1402,7 +1402,10 @@ type_t *get_type(env_t *env, ast_t *ast) { type_t *true_t = get_type(truthy_scope, if_->body); ast_t *else_body = if_->else_body; - if (!else_body) else_body = WrapAST(ast, None, .type = true_t); + if (!else_body) { + if (true_t->tag == AbortType) return Type(VoidType); + else_body = WrapAST(ast, None, .type = true_t); + } type_t *false_t = get_type(falsey_scope, if_->else_body); type_t *t_either = type_or_type(true_t, false_t); if (!t_either) -- cgit v1.2.3 From bbf7a60dcd524c2d86ac7efe7bc97d4a627b6d15 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 20:22:36 -0500 Subject: Fix `from_num` incomplete renaming --- src/environment.c | 12 ++++++------ src/stdlib/bigint.h | 4 ++-- src/stdlib/intX.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/environment.c b/src/environment.c index d8c82513..6075ce49 100644 --- a/src/environment.c +++ b/src/environment.c @@ -459,7 +459,7 @@ env_t *global_env(bool source_mapping) { {"Int$from_int16", "func(i:Int16 -> Int)"}, // {"Int$from_int32", "func(i:Int32 -> Int)"}, // {"Int$from_int64", "func(i:Int64 -> Int)"}, // - {"Int$from_num", "func(n:Num, truncate=no -> Int)"}, // + {"Int$from_num64", "func(n:Num, truncate=no -> Int)"}, // {"Int$from_num32", "func(n:Num32, truncate=no -> Int)"}); ADD_CONSTRUCTORS("Int64", // {"Int64$from_bool", "func(b:Bool -> Int64)"}, // @@ -468,7 +468,7 @@ env_t *global_env(bool source_mapping) { {"Int64$from_int16", "func(i:Int16 -> Int64)"}, // {"Int64$from_int32", "func(i:Int32 -> Int64)"}, // {"Int64$from_int", "func(i:Int, truncate=no -> Int64)"}, // - {"Int64$from_num", "func(n:Num, truncate=no -> Int64)"}, // + {"Int64$from_num64", "func(n:Num, truncate=no -> Int64)"}, // {"Int64$from_num32", "func(n:Num32, truncate=no -> Int64)"}); ADD_CONSTRUCTORS("Int32", // {"Int32$from_bool", "func(b:Bool -> Int32)"}, // @@ -477,7 +477,7 @@ env_t *global_env(bool source_mapping) { {"Int32$from_int16", "func(i:Int16 -> Int32)"}, // {"Int32$from_int64", "func(i:Int64, truncate=no -> Int32)"}, // {"Int32$from_int", "func(i:Int, truncate=no -> Int32)"}, // - {"Int32$from_num", "func(n:Num, truncate=no -> Int32)"}, // + {"Int32$from_num64", "func(n:Num, truncate=no -> Int32)"}, // {"Int32$from_num32", "func(n:Num32, truncate=no -> Int32)"}); ADD_CONSTRUCTORS("Int16", // {"Int16$from_bool", "func(b:Bool -> Int16)"}, // @@ -486,7 +486,7 @@ env_t *global_env(bool source_mapping) { {"Int16$from_int32", "func(i:Int32, truncate=no -> Int16)"}, // {"Int16$from_int64", "func(i:Int64, truncate=no -> Int16)"}, // {"Int16$from_int", "func(i:Int, truncate=no -> Int16)"}, // - {"Int16$from_num", "func(n:Num, truncate=no -> Int16)"}, // + {"Int16$from_num64", "func(n:Num, truncate=no -> Int16)"}, // {"Int16$from_num32", "func(n:Num32, truncate=no -> Int16)"}); ADD_CONSTRUCTORS("Int8", // {"Int8$from_bool", "func(b:Bool -> Int8)"}, // @@ -495,7 +495,7 @@ env_t *global_env(bool source_mapping) { {"Int8$from_int32", "func(i:Int32, truncate=no -> Int8)"}, // {"Int8$from_int64", "func(i:Int64, truncate=no -> Int8)"}, // {"Int8$from_int", "func(i:Int, truncate=no -> Int8)"}, // - {"Int8$from_num", "func(n:Num, truncate=no -> Int8)"}, // + {"Int8$from_num64", "func(n:Num, truncate=no -> Int8)"}, // {"Int8$from_num32", "func(n:Num32, truncate=no -> Int8)"}); ADD_CONSTRUCTORS("Num", // {"Num$from_bool", "func(b:Bool -> Num)"}, // @@ -514,7 +514,7 @@ env_t *global_env(bool source_mapping) { {"Num32$from_int32", "func(i:Int32, truncate=no -> Num32)"}, // {"Num32$from_int64", "func(i:Int64, truncate=no -> Num32)"}, // {"Num32$from_int", "func(i:Int, truncate=no -> Num32)"}, // - {"Num32$from_num", "func(n:Num -> Num32)"}); + {"Num32$from_num64", "func(n:Num -> Num32)"}); ADD_CONSTRUCTORS("Path", // {"Path$escape_text", "func(text:Text -> Path)"}, // {"Path$escape_path", "func(path:Path -> Path)"}, // diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h index 614e1501..a00bdf2f 100644 --- a/src/stdlib/bigint.h +++ b/src/stdlib/bigint.h @@ -189,13 +189,13 @@ MACROLIKE PUREFUNC bool Int$is_negative(Int_t x) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif -MACROLIKE PUREFUNC Int_t Int$from_num(double n, bool truncate) { +MACROLIKE PUREFUNC Int_t Int$from_num64(double n, bool truncate) { mpz_t result; mpz_init_set_d(result, n); if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n); return Int$from_mpz(result); } -MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) { return Int$from_num((double)n, truncate); } +MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) { return Int$from_num64((double)n, truncate); } MACROLIKE Int_t Int$from_int64(int64_t i) { if likely (i >= SMALLEST_SMALL_INT && i <= BIGGEST_SMALL_INT) return (Int_t){.small = (i << 2L) | 1L}; mpz_t result; diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h index 765543fd..0f4632c2 100644 --- a/src/stdlib/intX.h +++ b/src/stdlib/intX.h @@ -88,7 +88,7 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *, const TypeInfo_t *); void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *, const TypeInfo_t *); -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_num)(Num_t n, bool truncate) { +MACROLIKE PUREFUNC INTX_T NAMESPACED(from_num64)(Num_t n, bool truncate) { INTX_T i = (INTX_T)n; if (!truncate && unlikely((Num_t)i != n)) fail("Could not convert Num to an " NAME_STR " without truncation: ", n); return i; -- cgit v1.2.3 From af585cf127a26148372fe54eb62d1c9fad6f2dba Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 22:14:15 -0500 Subject: Fix for `when` blocks with a single expression inside --- src/compile/promotions.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compile/promotions.c b/src/compile/promotions.c index b3cbb361..93ad5338 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -127,6 +127,10 @@ Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t) { env = with_enum_scope(env, t); } + if (ast->tag == Block && Match(ast, Block)->statements && !Match(ast, Block)->statements->next) { + ast = Match(ast, Block)->statements->ast; + } + if (ast->tag == Int && is_numeric_type(non_optional(t))) { return compile_int_to_type(env, ast, t); } else if (ast->tag == Num && t->tag == NumType) { -- cgit v1.2.3 From 14c8cf34dd75fcf49cc56025efa93dd32e1958fd Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Nov 2025 22:46:59 -0500 Subject: Fix for accidental deserialization of byte array when it should have been promoted to optional --- src/compile/promotions.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/compile/promotions.c b/src/compile/promotions.c index 93ad5338..5b0ccb95 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -26,18 +26,21 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede if (more_complete) return true; // Serialization/deserialization: - if (type_eq(needed, Type(ListType, Type(ByteType)))) { - *code = Texts("generic_serialize((", compile_declaration(actual, Text("[1]")), "){", *code, "}, ", - compile_type_info(actual), ")"); - return true; - } else if (type_eq(actual, Type(ListType, Type(ByteType)))) { - *code = Texts("({ ", compile_declaration(needed, Text("deserialized")), - ";\n" - "generic_deserialize(", - *code, ", &deserialized, ", compile_type_info(needed), - ");\n" - "deserialized; })"); - return true; + if (!type_eq(non_optional(value_type(needed)), Type(ListType, Type(ByteType))) + || !type_eq(non_optional(value_type(actual)), Type(ListType, Type(ByteType)))) { + if (type_eq(needed, Type(ListType, Type(ByteType)))) { + *code = Texts("generic_serialize((", compile_declaration(actual, Text("[1]")), "){", *code, "}, ", + compile_type_info(actual), ")"); + return true; + } else if (type_eq(actual, Type(ListType, Type(ByteType)))) { + *code = Texts("({ ", compile_declaration(needed, Text("deserialized")), + ";\n" + "generic_deserialize(", + *code, ", &deserialized, ", compile_type_info(needed), + ");\n" + "deserialized; })"); + return true; + } } // Optional promotion: -- cgit v1.2.3 From a21f9ddfd05c643049c22bb52ab3a60f41933492 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 26 Nov 2025 21:04:12 -0500 Subject: Bugfix for accidental violation of immutable value guarantees due to inner field members --- src/compile/assignments.c | 10 +++++----- src/compile/enums.c | 9 +++++++-- src/compile/expressions.c | 38 +++++++++++++++++++++++++++++++++----- src/compile/pointers.c | 4 ++-- src/types.c | 21 +++++++++++++++++++++ src/types.h | 1 + test/values.tm | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 test/values.tm diff --git a/src/compile/assignments.c b/src/compile/assignments.c index c0f45f5b..74a00e0b 100644 --- a/src/compile/assignments.c +++ b/src/compile/assignments.c @@ -101,7 +101,7 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) { "variable's scope may outlive the scope of the " "stack memory."); env_t *val_env = with_enum_scope(env, lhs_t); - Text_t val = compile_to_type(val_env, assign->values->ast, lhs_t); + Text_t val = compile_maybe_incref(val_env, assign->values->ast, lhs_t); return Texts(compile_assignment(env, assign->targets->ast, val), ";\n"); } @@ -120,7 +120,7 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) { "variable's scope may outlive the scope of the " "stack memory."); env_t *val_env = with_enum_scope(env, lhs_t); - Text_t val = compile_to_type(val_env, value->ast, lhs_t); + Text_t val = compile_maybe_incref(val_env, value->ast, lhs_t); code = Texts(code, compile_type(lhs_t), " $", i, " = ", val, ";\n"); i += 1; } @@ -178,13 +178,13 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) { type_t *value_type = get_type(env, table_type->default_value); return Texts("*Table$get_or_setdefault(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ", compile_type(table_type->key_type), ", ", compile_type(value_type), ", ", - compile_to_type(env, index->index, table_type->key_type), ", ", - compile_to_type(env, table_type->default_value, table_type->value_type), ", ", + compile_maybe_incref(env, index->index, table_type->key_type), ", ", + compile_maybe_incref(env, table_type->default_value, table_type->value_type), ", ", compile_type_info(container_t), ")"); } return Texts("*(", compile_type(Type(PointerType, table_type->value_type)), ")Table$reserve(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ", "stack(", - compile_to_type(env, index->index, table_type->key_type), ")", ", NULL,", + compile_maybe_incref(env, index->index, table_type->key_type), ")", ", NULL,", compile_type_info(container_t), ")"); } else { code_err(ast, "I don't know how to assign to this target"); diff --git a/src/compile/enums.c b/src/compile/enums.c index 31af96ad..56d6432a 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -157,10 +157,15 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); if (tag->type != NULL && Match(tag->type, StructType)->fields) { + Text_t member = compile_maybe_incref( + env, + WrapAST(ast, InlineCCode, + .chunks = new (ast_list_t, WrapAST(ast, TextLiteral, Texts("_e.", tag->name))), + .type = tag->type), + tag->type); return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", - promote_to_optional(tag->type, Texts("_e.", tag->name)), " : ", compile_none(tag->type), - "; })"); + promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); } else if (fielded_t->tag == PointerType) { Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 130a267c..a69a7d56 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -11,11 +11,39 @@ public Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { - if (is_idempotent(ast) && can_be_mutated(env, ast)) { - type_t *actual = get_type(with_enum_scope(env, t), ast); - if (t->tag == ListType && type_eq(t, actual)) return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); - else if (t->tag == TableType && type_eq(t, actual)) - return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")"); + if (!has_refcounts(t) || !can_be_mutated(env, ast)) { + return compile_to_type(env, ast, t); + } + + // When using a struct as a value, we need to increment the refcounts of the inner fields as well: + if (t->tag == StructType) { + // If the struct is non-idempotent, we have to stash it in a local var first + if (is_idempotent(ast)) { + Text_t code = Texts("((", compile_type(t), "){"); + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = ast, .field = field->name), + get_arg_type(env, field)); + code = Texts(code, val); + if (field->next) code = Texts(code, ", "); + } + return Texts(code, "})"); + } else { + Text_t code = Texts("({ ", compile_declaration(t, Text("_tmp")), " = ", compile_to_type(env, ast, t), "; ", + "((", compile_type(t), "){"); + ast_t *tmp = WrapAST(ast, InlineCCode, + .chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, Text("_tmp"))), .type = t); + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name), + get_arg_type(env, field)); + code = Texts(code, val); + if (field->next) code = Texts(code, ", "); + } + return Texts(code, "}); })"); + } + } else if (t->tag == ListType && ast->tag != List && can_be_mutated(env, ast) && type_eq(get_type(env, ast), t)) { + return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); + } else if (t->tag == TableType && ast->tag != Table && can_be_mutated(env, ast) && type_eq(get_type(env, ast), t)) { + return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")"); } return compile_to_type(env, ast, t); } diff --git a/src/compile/pointers.c b/src/compile/pointers.c index 11348330..98274cc8 100644 --- a/src/compile/pointers.c +++ b/src/compile/pointers.c @@ -55,13 +55,13 @@ Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type) { type_t *pointed = Match(pointer_type, PointerType)->pointed; switch (ast->tag) { case HeapAllocate: { - return Texts("heap(", compile_to_type(env, Match(ast, HeapAllocate)->value, pointed), ")"); + return Texts("heap(", compile_maybe_incref(env, Match(ast, HeapAllocate)->value, pointed), ")"); } case StackReference: { ast_t *subject = Match(ast, StackReference)->value; if (can_be_mutated(env, subject) && type_eq(pointed, get_type(env, subject))) return Texts("(&", compile_lvalue(env, subject), ")"); - else return Texts("stack(", compile_to_type(env, subject, pointed), ")"); + else return Texts("stack(", compile_maybe_incref(env, subject, pointed), ")"); } default: code_err(ast, "Not an allocation!"); } diff --git a/src/types.c b/src/types.c index edfee27d..4d15d493 100644 --- a/src/types.c +++ b/src/types.c @@ -249,6 +249,27 @@ PUREFUNC bool has_heap_memory(type_t *t) { } } +PUREFUNC bool has_refcounts(type_t *t) { + switch (t->tag) { + case ListType: return true; + case TableType: return true; + case OptionalType: return has_refcounts(Match(t, OptionalType)->type); + case StructType: { + for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + if (has_refcounts(field->type)) return true; + } + return false; + } + case EnumType: { + for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { + if (tag->type && has_refcounts(tag->type)) return true; + } + return false; + } + default: return false; + } +} + PUREFUNC bool has_stack_memory(type_t *t) { if (!t) return false; switch (t->tag) { diff --git a/src/types.h b/src/types.h index b8f9a42f..8a2175d9 100644 --- a/src/types.h +++ b/src/types.h @@ -150,6 +150,7 @@ typedef enum { } precision_cmp_e; PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b); PUREFUNC bool has_heap_memory(type_t *t); +PUREFUNC bool has_refcounts(type_t *t); PUREFUNC bool has_stack_memory(type_t *t); PUREFUNC bool can_promote(type_t *actual, type_t *needed); PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t); diff --git a/test/values.tm b/test/values.tm new file mode 100644 index 00000000..86f34a89 --- /dev/null +++ b/test/values.tm @@ -0,0 +1,47 @@ +# Tests for ensuring immutable value nature in various contexts +struct Inner(xs:[Int32]) + +struct Outer(inner:Inner) + +func sneaky(outer:Outer) + (&outer.inner.xs)[1] = 99 + +func sneaky2(outer:&Outer) + (&outer.inner.xs)[1] = 99 + +func main() + do + xs := [10, 20, 30] + copy := xs + (&xs)[1] = 99 + assert xs == [99, 20, 30] + assert copy == [10, 20, 30] + + do + t := {"A":10, "B":20} + copy := t + (&t)["A"] = 99 + assert t == {"A":99, "B":20} + assert copy == {"A":10, "B":20} + + do + foo := Outer(Inner([10, 20, 30])) + copy := foo + (&foo.inner.xs)[1] = 99 + assert foo.inner.xs == [99, 20, 30] + assert copy.inner.xs == [10, 20, 30] + + do + foo := Outer(Inner([10, 20, 30])) + copy := foo + sneaky(foo) + assert foo.inner.xs == [10, 20, 30] + assert copy.inner.xs == [10, 20, 30] + + do + foo := Outer(Inner([10, 20, 30])) + copy := foo + sneaky2(&foo) + assert foo.inner.xs == [99, 20, 30] + assert copy.inner.xs == [10, 20, 30] + -- cgit v1.2.3 From 3ad07260f369dc285e5ae856b78a58c3b13ee622 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 26 Nov 2025 21:12:11 -0500 Subject: Close loophole in `when` statements that allowed mutating inner field members --- src/ast.h | 3 +++ src/compile/expressions.c | 3 +-- src/compile/whens.c | 12 ++++++++---- test/values.tm | 8 ++++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/ast.h b/src/ast.h index 7fa9092a..5307fc2c 100644 --- a/src/ast.h +++ b/src/ast.h @@ -23,6 +23,9 @@ #define LiteralCode(code, ...) \ new (ast_t, .tag = InlineCCode, \ .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, code)), __VA_ARGS__}) +#define WrapLiteralCode(ast, code, ...) \ + new (ast_t, .tag = InlineCCode, .file = (ast)->file, .start = (ast)->start, .end = (ast)->end, \ + .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, code)), __VA_ARGS__}) #define Match(x, _tag) \ ((x)->tag == _tag ? &(x)->__data._tag \ : (errx(1, __FILE__ ":%d This was supposed to be a " #_tag "\n", __LINE__), &(x)->__data._tag)) diff --git a/src/compile/expressions.c b/src/compile/expressions.c index a69a7d56..19e0672e 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -30,8 +30,7 @@ Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { } else { Text_t code = Texts("({ ", compile_declaration(t, Text("_tmp")), " = ", compile_to_type(env, ast, t), "; ", "((", compile_type(t), "){"); - ast_t *tmp = WrapAST(ast, InlineCCode, - .chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, Text("_tmp"))), .type = t); + ast_t *tmp = WrapLiteralCode(ast, Text("_tmp"), .type = t); for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name), get_arg_type(env, field)); diff --git a/src/compile/whens.c b/src/compile/whens.c index 4f6a2a40..122c581c 100644 --- a/src/compile/whens.c +++ b/src/compile/whens.c @@ -83,8 +83,10 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { const char *var_name = Match(args->value, Var)->name; if (!streq(var_name, "_")) { Text_t var = Texts("_$", var_name); - code = Texts(code, compile_declaration(tag_type, var), " = _when_subject.", - valid_c_name(clause_tag_name), ";\n"); + ast_t *member = + WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type); + code = Texts(code, compile_declaration(tag_type, var), " = ", + compile_maybe_incref(env, member, tag_type), ";\n"); scope = fresh_scope(scope); set_binding(scope, Match(args->value, Var)->name, tag_type, EMPTY_TEXT); } @@ -101,8 +103,10 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { const char *var_name = Match(arg->value, Var)->name; if (!streq(var_name, "_")) { Text_t var = Texts("_$", var_name); - code = Texts(code, compile_declaration(field->type, var), " = _when_subject.", - valid_c_name(clause_tag_name), ".", valid_c_name(field->name), ";\n"); + ast_t *member = + WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type); + code = Texts(code, compile_declaration(field->type, var), " = ", + compile_maybe_incref(env, member, tag_type), ".", valid_c_name(field->name), ";\n"); set_binding(scope, Match(arg->value, Var)->name, field->type, var); } field = field->next; diff --git a/test/values.tm b/test/values.tm index 86f34a89..9f86c012 100644 --- a/test/values.tm +++ b/test/values.tm @@ -3,6 +3,8 @@ struct Inner(xs:[Int32]) struct Outer(inner:Inner) +enum HoldsList(HasList(xs:[Int32])) + func sneaky(outer:Outer) (&outer.inner.xs)[1] = 99 @@ -45,3 +47,9 @@ func main() assert foo.inner.xs == [99, 20, 30] assert copy.inner.xs == [10, 20, 30] + do + x := HoldsList.HasList([10, 20, 30]) + when x is HasList(list) + (&list)[1] = 99 + + assert x == HoldsList.HasList([10, 20, 30]) -- cgit v1.2.3 From 35053e65b946264715aca2b348ee25313b55d2f6 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 26 Nov 2025 21:13:04 -0500 Subject: Add missing manpages --- man/man3/tomo-Path.lines.3 | 33 +++++++++++++++++++++++++++++++++ man/man3/tomo-Text.find.3 | 38 ++++++++++++++++++++++++++++++++++++++ man/man3/tomo-at_cleanup.3 | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 man/man3/tomo-Path.lines.3 create mode 100644 man/man3/tomo-Text.find.3 create mode 100644 man/man3/tomo-at_cleanup.3 diff --git a/man/man3/tomo-Path.lines.3 b/man/man3/tomo-Path.lines.3 new file mode 100644 index 00000000..e4e95e7e --- /dev/null +++ b/man/man3/tomo-Path.lines.3 @@ -0,0 +1,33 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Path.lines 3 2025-11-23 "Tomo man-pages" +.SH NAME +Path.lines \- return the lines in a file +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI Path.lines\ :\ func(path:\ Path\ ->\ [Text]?) +.fi +.SH DESCRIPTION +Returns a list with the lines of text in a file or returns none if the file could not be opened. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +path Path The path of the file. - +.TE +.SH RETURN +A list of the lines in a file or none if the file couldn't be read. + +.SH EXAMPLES +.EX +lines := (./file.txt).lines()! +.EE diff --git a/man/man3/tomo-Text.find.3 b/man/man3/tomo-Text.find.3 new file mode 100644 index 00000000..17d78a0a --- /dev/null +++ b/man/man3/tomo-Text.find.3 @@ -0,0 +1,38 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Text.find 3 2025-11-23 "Tomo man-pages" +.SH NAME +Text.find \- find a substring +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI Text.find\ :\ func(text:\ Text,\ target:\ Text,\ start:\ Int\ =\ 1\ ->\ Int) +.fi +.SH DESCRIPTION +Find a substring within a text and return its index, if found. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +text Text The text to be searched. - +target Text The target text to find. - +start Int The index at which to begin searching. 1 +.TE +.SH RETURN +The index where the first occurrence of `target` appears, or `none` if it is not found. + +.SH EXAMPLES +.EX +assert "one two".find("one") == 1 +assert "one two".find("two") == 5 +assert "one two".find("three") == none +assert "one two".find("o", start=2) == 7 +.EE diff --git a/man/man3/tomo-at_cleanup.3 b/man/man3/tomo-at_cleanup.3 new file mode 100644 index 00000000..a8dc04ed --- /dev/null +++ b/man/man3/tomo-at_cleanup.3 @@ -0,0 +1,38 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH at_cleanup 3 2025-11-23 "Tomo man-pages" +.SH NAME +at_cleanup \- register a cleanup function +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI at_cleanup\ :\ func(fn:\ func()\ ->\ Void) +.fi +.SH DESCRIPTION +Register a function that runs at cleanup time for Tomo programs. Cleanup time happens when a program exits (see `atexit()` in C), or immediately before printing error messages in a call to `fail()`. This allows for terminal cleanup so error messages can be visible as the program shuts down. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +fn func() A function to run at cleanup time. - +.TE +.SH RETURN +Nothing. + +.SH NOTES +Use this API very carefully, because errors that occur during cleanup functions may make it extremely hard to figure out what's going on. Cleanup functions should be designed to not error under any circumstances. + +.SH EXAMPLES +.EX +at_cleanup(func() + (/tmp/file.txt).remove(ignore_missing=yes) +) +.EE -- cgit v1.2.3 From 437be558a893ac70c030794df99a866e8ed01879 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:05:49 -0500 Subject: Add `recursive` arg to Path.create_directory() --- CHANGES.md | 2 ++ api/api.md | 4 +++- api/paths.md | 4 +++- api/paths.yaml | 7 +++++++ man/man3/tomo-Path.create_directory.3 | 7 +++++-- src/environment.c | 3 ++- src/modules.c | 4 ++-- src/stdlib/paths.c | 12 ++++++++++-- src/stdlib/paths.h | 2 +- 9 files changed, 35 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f850d535..f54e4d05 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,8 @@ - Added `Path.lines()`. - Added `Text.find(text, target, start=1)`. - Added `at_cleanup()` to register cleanup functions. +- Added `recursive` argument to `Path.create_directory()` to create parent + directories if needed. - Deprecated: - Sets are no longer a separate type with separate methods. - Instead of sets, use tables with a value type of `{KeyType:Empty}`. diff --git a/api/api.md b/api/api.md index 5c9dc9c4..ec6ffb49 100644 --- a/api/api.md +++ b/api/api.md @@ -2777,15 +2777,17 @@ assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"] ## Path.create_directory ```tomo -Path.create_directory : func(path: Path, permissions = Int32(0o755) -> Void) +Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Void) ``` Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. + Argument | Type | Description | Default ---------|------|-------------|--------- path | `Path` | The path of the directory to create. | - permissions | `` | The permissions to set on the new directory. | `Int32(0o755)` +recursive | `` | If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. | `yes` **Return:** Nothing. diff --git a/api/paths.md b/api/paths.md index 07f0560b..4beabdc2 100644 --- a/api/paths.md +++ b/api/paths.md @@ -253,15 +253,17 @@ assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"] ## Path.create_directory ```tomo -Path.create_directory : func(path: Path, permissions = Int32(0o755) -> Void) +Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Void) ``` Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. + Argument | Type | Description | Default ---------|------|-------------|--------- path | `Path` | The path of the directory to create. | - permissions | `` | The permissions to set on the new directory. | `Int32(0o755)` +recursive | `` | If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. | `yes` **Return:** Nothing. diff --git a/api/paths.yaml b/api/paths.yaml index 8fbd18dc..65d63671 100644 --- a/api/paths.yaml +++ b/api/paths.yaml @@ -258,6 +258,7 @@ Path.create_directory: description: > Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. + note: > return: type: 'Void' description: > @@ -271,6 +272,12 @@ Path.create_directory: default: 'Int32(0o755)' description: > The permissions to set on the new directory. + recursive: + default: 'yes' + description: > + If set to `yes`, then recursively create any parent directories if they + don't exist, otherwise fail if the parent directory does not exist. When + set to `yes`, this function behaves like `mkdir -p`. example: | (./new_directory).create_directory() diff --git a/man/man3/tomo-Path.create_directory.3 b/man/man3/tomo-Path.create_directory.3 index adfe7e97..db50d468 100644 --- a/man/man3/tomo-Path.create_directory.3 +++ b/man/man3/tomo-Path.create_directory.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.create_directory 3 2025-05-17 "Tomo man-pages" +.TH Path.create_directory 3 2025-11-27 "Tomo man-pages" .SH NAME Path.create_directory \- make a directory .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755)\ ->\ Void) +.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Void) .fi .SH DESCRIPTION Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. @@ -24,10 +24,13 @@ l l l l. Name Type Description Default path Path The path of the directory to create. - permissions The permissions to set on the new directory. Int32(0o755) +recursive If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. yes .TE .SH RETURN Nothing. +.SH NOTES + .SH EXAMPLES .EX (./new_directory).create_directory() diff --git a/src/environment.c b/src/environment.c index 6075ce49..88a15bb5 100644 --- a/src/environment.c +++ b/src/environment.c @@ -310,7 +310,8 @@ env_t *global_env(bool source_mapping) { {"child", "Path$child", "func(path:Path, child:Text -> Path)"}, // {"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, // - {"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755))"}, // + {"create_directory", "Path$create_directory", + "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, // {"current_dir", "Path$current_dir", "func(->Path)"}, // {"exists", "Path$exists", "func(path:Path -> Bool)"}, // {"expand_home", "Path$expand_home", "func(path:Path -> Path)"}, // diff --git a/src/modules.c b/src/modules.c index 23b8a0a0..df6bade3 100644 --- a/src/modules.c +++ b/src/modules.c @@ -153,10 +153,10 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) { const char *extension = p + 1; Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); tmpdir = Path$child(tmpdir, Text$from_str(mod.name)); - Path$create_directory(tmpdir, 0755); + Path$create_directory(tmpdir, 0755, true); xsystem("curl ", mod.url, " -o ", tmpdir); - Path$create_directory(dest, 0755); + Path$create_directory(dest, 0755, true); if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename, " -d ", dest); else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) xsystem("tar xf ", tmpdir, "/", filename, " -C ", dest); diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 22effad7..6c99cf0b 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -468,11 +468,19 @@ void Path$remove(Path_t path, bool ignore_missing) { } public -void Path$create_directory(Path_t path, int permissions) { +void Path$create_directory(Path_t path, int permissions, bool recursive) { +retry: path = Path$expand_home(path); const char *c_path = Path$as_c_string(path); int status = mkdir(c_path, (mode_t)permissions); - if (status != 0 && errno != EEXIST) fail("Could not create directory: ", c_path, " (", strerror(errno), ")"); + if (status != 0) { + if (recursive && errno == ENOENT) { + Path$create_directory(Path$parent(path), permissions, recursive); + goto retry; + } else if (errno != EEXIST) { + fail("Could not create directory: ", c_path, " (", strerror(errno), ")"); + } + } } static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) { diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 3b1f3ce6..677631b2 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -41,7 +41,7 @@ void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, boo OptionalText_t Path$owner(Path_t path, bool follow_symlinks); OptionalText_t Path$group(Path_t path, bool follow_symlinks); void Path$remove(Path_t path, bool ignore_missing); -void Path$create_directory(Path_t path, int permissions); +void Path$create_directory(Path_t path, int permissions, bool recursive); List_t Path$children(Path_t path, bool include_hidden); List_t Path$files(Path_t path, bool include_hidden); List_t Path$subdirectories(Path_t path, bool include_hidden); -- cgit v1.2.3 From 31c47caa0282cbdf43361a3c0f45fff9699a8814 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:35:30 -0500 Subject: Only output files that changed somewhere besides the date --- scripts/mandoc_gen.py | 52 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/scripts/mandoc_gen.py b/scripts/mandoc_gen.py index 8342e253..1009105e 100755 --- a/scripts/mandoc_gen.py +++ b/scripts/mandoc_gen.py @@ -71,54 +71,64 @@ lb lb lbx lb l l l l. Name Type Description Default''' -def write_method(f, name, info): - def add_line(line): f.write(line + "\n") +def write_method(path, name, info): + lines = [] year = datetime.now().strftime("%Y") date = datetime.now().strftime("%Y-%m-%d") signature = get_signature(name, info) - add_line(template.format(year=year, date=date, name=name, short=info["short"], description=info["description"], signature=signature)) + lines.append(template.format(year=year, date=date, name=name, short=info["short"], description=info["description"], signature=signature)) if "args" in info and info["args"]: - add_line(".SH ARGUMENTS") - add_line(arg_prefix) + lines.append(".SH ARGUMENTS") + lines.append(arg_prefix) for arg,arg_info in info["args"].items(): default = escape(arg_info['default'], spaces=True) if 'default' in arg_info else '-' description = arg_info['description'].replace('\n', ' ') - add_line(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}") - add_line(".TE") + lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}") + lines.append(".TE") if "return" in info: - add_line(".SH RETURN") - add_line(info['return'].get('description', 'Nothing.')) + lines.append(".SH RETURN") + lines.append(info['return'].get('description', 'Nothing.')) if "note" in info: - add_line(".SH NOTES") - add_line(info["note"]) + lines.append(".SH NOTES") + lines.append(info["note"]) if "errors" in info: - add_line(".SH ERRORS") - add_line(info["errors"]) + lines.append(".SH ERRORS") + lines.append(info["errors"]) if "example" in info: - add_line(".SH EXAMPLES") - add_line(".EX") - add_line(escape(info['example'])) - add_line(".EE") + lines.append(".SH EXAMPLES") + lines.append(".EX") + lines.append(escape(info['example'])) + lines.append(".EE") + + to_write = '\n'.join(lines) + '\n' + try: + with open(path, "r") as f: + existing = f.read() + if to_write.splitlines()[5:] == existing.splitlines()[5:]: + return + except FileNotFoundError: + pass + + with open(path, "w") as f: + f.write(to_write) + print(f"Updated {path}") def convert_to_markdown(yaml_doc:str)->str: data = yaml.safe_load(yaml_doc) for name,info in data.items(): - with open(f"man/man3/tomo-{name}.3", "w") as f: - print(f"Wrote to man/man3/tomo-{name}.3") - write_method(f, name, data[name]) + write_method(f"man/man3/tomo-{name}.3", name, data[name]) if __name__ == "__main__": import sys if len(sys.argv) > 1: all_files = "" for filename in sys.argv[1:]: - print(f"Making mandoc for {filename}") with open(filename, "r") as f: all_files += f.read() convert_to_markdown(all_files) -- cgit v1.2.3 From 8b897851facaa177e2346e0d97fcba7411dfc0aa Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:35:52 -0500 Subject: Update `setenv()` to take an optional value, also bugfix for `setenv()` returning a value. --- CHANGES.md | 2 ++ Makefile | 1 - api/api.md | 4 ++-- api/builtins.md | 4 ++-- api/builtins.yaml | 5 +++-- man/man3/tomo-setenv.3 | 6 +++--- src/environment.c | 2 +- src/stdlib/stdlib.c | 5 ++++- 8 files changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f54e4d05..0bbe387f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,8 @@ - Added `at_cleanup()` to register cleanup functions. - Added `recursive` argument to `Path.create_directory()` to create parent directories if needed. +- `setenv()` now takes an optional parameter for value, which allows for + unsetting environment values. - Deprecated: - Sets are no longer a separate type with separate methods. - Instead of sets, use tables with a value type of `{KeyType:Empty}`. diff --git a/Makefile b/Makefile index b8220a95..ab9096cd 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,6 @@ api-docs: $(API_MD) api/api.md .PHONY: manpages manpages: $(API_YAML) man/man1/tomo.1 - rm -f man/man3/* ./scripts/mandoc_gen.py $(API_YAML) man/man1/tomo.1: docs/tomo.1.md diff --git a/api/api.md b/api/api.md index ec6ffb49..f52691d3 100644 --- a/api/api.md +++ b/api/api.md @@ -166,7 +166,7 @@ say("world!") ## setenv ```tomo -setenv : func(name: Text, value: Text -> Void) +setenv : func(name: Text, value: Text? -> Void) ``` Sets an environment variable. @@ -174,7 +174,7 @@ Sets an environment variable. Argument | Type | Description | Default ---------|------|-------------|--------- name | `Text` | The name of the environment variable to set. | - -value | `Text` | The new value of the environment variable. | - +value | `Text?` | The new value of the environment variable. If `none`, then the environment variable will be unset. | - **Return:** Nothing. diff --git a/api/builtins.md b/api/builtins.md index 6d042741..2ef14275 100644 --- a/api/builtins.md +++ b/api/builtins.md @@ -166,7 +166,7 @@ say("world!") ## setenv ```tomo -setenv : func(name: Text, value: Text -> Void) +setenv : func(name: Text, value: Text? -> Void) ``` Sets an environment variable. @@ -174,7 +174,7 @@ Sets an environment variable. Argument | Type | Description | Default ---------|------|-------------|--------- name | `Text` | The name of the environment variable to set. | - -value | `Text` | The new value of the environment variable. | - +value | `Text?` | The new value of the environment variable. If `none`, then the environment variable will be unset. | - **Return:** Nothing. diff --git a/api/builtins.yaml b/api/builtins.yaml index 764a1bd4..af7c9319 100644 --- a/api/builtins.yaml +++ b/api/builtins.yaml @@ -157,9 +157,10 @@ setenv: description: > The name of the environment variable to set. value: - type: 'Text' + type: 'Text?' description: > - The new value of the environment variable. + The new value of the environment variable. If `none`, then the + environment variable will be unset. example: | setenv("FOOBAR", "xyz") diff --git a/man/man3/tomo-setenv.3 b/man/man3/tomo-setenv.3 index a9ed528d..faa6a662 100644 --- a/man/man3/tomo-setenv.3 +++ b/man/man3/tomo-setenv.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH setenv 3 2025-05-17 "Tomo man-pages" +.TH setenv 3 2025-11-27 "Tomo man-pages" .SH NAME setenv \- set an environment variable .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI setenv\ :\ func(name:\ Text,\ value:\ Text\ ->\ Void) +.BI setenv\ :\ func(name:\ Text,\ value:\ Text?\ ->\ Void) .fi .SH DESCRIPTION Sets an environment variable. @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default name Text The name of the environment variable to set. - -value Text The new value of the environment variable. - +value Text? The new value of the environment variable. If `none`, then the environment variable will be unset. - .TE .SH RETURN Nothing. diff --git a/src/environment.c b/src/environment.c index 88a15bb5..43d22c3d 100644 --- a/src/environment.c +++ b/src/environment.c @@ -540,7 +540,7 @@ env_t *global_env(bool source_mapping) { {"getenv", "getenv_text", "func(name:Text -> Text?)"}, {"print", "say", "func(text:Text, newline=yes)"}, {"say", "say", "func(text:Text, newline=yes)"}, - {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"}, + {"setenv", "setenv_text", "func(name:Text, value:Text?)"}, {"sleep", "sleep_num", "func(seconds:Num)"}, }; diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 21547efe..8ec9e90b 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -219,7 +219,10 @@ OptionalText_t getenv_text(Text_t name) { } public -void setenv_text(Text_t name, Text_t value) { setenv(Text$as_c_string(name), Text$as_c_string(value), 1); } +void setenv_text(Text_t name, OptionalText_t value) { + if (value.tag == TEXT_NONE) unsetenv(Text$as_c_string(name)); + else setenv(Text$as_c_string(name), Text$as_c_string(value), 1); +} typedef struct cleanup_s { Closure_t cleanup_fn; -- cgit v1.2.3 From 7e85117099cc77a8c083a3479246d5e130b8afe1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 12:53:24 -0500 Subject: Support EXECUTABLE metadata --- docs/command-line-parsing.md | 3 +++ src/tomo.c | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/command-line-parsing.md b/docs/command-line-parsing.md index d0153e37..e1af5419 100644 --- a/docs/command-line-parsing.md +++ b/docs/command-line-parsing.md @@ -244,6 +244,9 @@ MANPAGE_DESCRIPTION: (./description.roff) Supported metadata: +- `EXECUTABLE`: the name of the executable to compile. If not provided, the name + will be the name of the Tomo source file without the ".tm" extension. + - `USAGE`: the short form usage shown in CLI parsing errors and help pages. This should be a single line without the name of the program, so `USAGE: "--foo"` would translate to the error message `Usage: myprogram --foo`. If this is not diff --git a/src/tomo.c b/src/tomo.c index 66ac0ddf..1aa62a0e 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -116,6 +116,7 @@ static bool is_stale_for_any(Path_t path, List_t relative_to, bool ignore_missin static Path_t build_file(Path_t path, const char *extension); static void wait_for_child_success(pid_t child); static bool is_config_outdated(Path_t path); +static Path_t get_exe_path(Path_t path); typedef struct { bool h : 1, c : 1, o : 1; @@ -368,7 +369,9 @@ int main(int argc, char *argv[]) { for (int64_t i = 0; i < (int64_t)compile_executables.length; i++) { Path_t path = *(Path_t *)(compile_executables.data + i * compile_executables.stride); - Path_t exe_path = Path$with_extension(path, Text(""), true); + Path_t exe_path = get_exe_path(path); + // Put executable as a sibling to the .tm file instead of in the .build directory + exe_path = Path$sibling(path, Path$base_name(exe_path)); pid_t child = fork(); if (child == 0) { env_t *env = global_env(source_mapping); @@ -399,7 +402,7 @@ int main(int argc, char *argv[]) { // Compile runnable files in parallel, then execute in serial: for (int64_t i = 0; i < (int64_t)run_files.length; i++) { Path_t path = *(Path_t *)(run_files.data + i * run_files.stride); - Path_t exe_path = build_file(Path$with_extension(path, Text(""), true), ""); + Path_t exe_path = get_exe_path(path); pid_t child = fork(); if (child == 0) { env_t *env = global_env(source_mapping); @@ -418,7 +421,7 @@ int main(int argc, char *argv[]) { // After parallel compilation, do serial execution: for (int64_t i = 0; i < (int64_t)run_files.length; i++) { Path_t path = *(Path_t *)(run_files.data + i * run_files.stride); - Path_t exe_path = build_file(Path$with_extension(path, Text(""), true), ""); + Path_t exe_path = get_exe_path(path); // Don't fork for the last program pid_t child = i == (int64_t)run_files.length - 1 ? 0 : fork(); if (child == 0) { @@ -449,6 +452,18 @@ void wait_for_child_success(pid_t child) { } } +Path_t get_exe_path(Path_t path) { + ast_t *ast = parse_file(Path$as_c_string(path), NULL); + OptionalText_t exe_name = ast_metadata(ast, "EXECUTABLE"); + if (exe_name.tag == TEXT_NONE) exe_name = Path$base_name(Path$with_extension(path, Text(""), true)); + + Path_t build_dir = Path$sibling(path, Text(".build")); + if (mkdir(Path$as_c_string(build_dir), 0755) != 0) { + if (!Path$is_directory(build_dir, true)) err(1, "Could not make .build directory"); + } + return Path$child(build_dir, exe_name); +} + Path_t build_file(Path_t path, const char *extension) { Path_t build_dir = Path$sibling(path, Text(".build")); if (mkdir(Path$as_c_string(build_dir), 0755) != 0) { -- cgit v1.2.3 From 69067d2512ea495224508a854ab617bed0ea5ab5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 13:37:22 -0500 Subject: Bugfix for infinite recursion in some type methods --- src/types.c | 59 +++++++++++++++++++++++++++++++++++++++++++++-------------- src/types.h | 6 +++--- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/types.c b/src/types.c index 4d15d493..04784735 100644 --- a/src/types.c +++ b/src/types.c @@ -8,6 +8,7 @@ #include "environment.h" #include "stdlib/integers.h" +#include "stdlib/tables.h" #include "stdlib/text.h" #include "stdlib/util.h" #include "types.h" @@ -226,22 +227,27 @@ PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b) { else return NUM_PRECISION_INCOMPARABLE; } -PUREFUNC bool has_heap_memory(type_t *t) { +bool _has_heap_memory(type_t *t, Table_t *visited) { + if (!t) return false; + Text_t type_text = type_to_text(t); + if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false; + Table$set(visited, &type_text, NULL, Set$info(&Text$info)); + switch (t->tag) { case ListType: return true; case TableType: return true; case PointerType: return true; - case OptionalType: return has_heap_memory(Match(t, OptionalType)->type); + case OptionalType: return _has_heap_memory(Match(t, OptionalType)->type, visited); case BigIntType: return true; case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (has_heap_memory(field->type)) return true; + if (_has_heap_memory(field->type, visited)) return true; } return false; } case EnumType: { for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && has_heap_memory(tag->type)) return true; + if (tag->type && _has_heap_memory(tag->type, visited)) return true; } return false; } @@ -249,20 +255,30 @@ PUREFUNC bool has_heap_memory(type_t *t) { } } -PUREFUNC bool has_refcounts(type_t *t) { +bool has_heap_memory(type_t *t) { + Table_t visited = EMPTY_TABLE; + return _has_heap_memory(t, &visited); +} + +bool _has_refcounts(type_t *t, Table_t *visited) { + if (!t) return false; + Text_t type_text = type_to_text(t); + if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false; + Table$set(visited, &type_text, NULL, Set$info(&Text$info)); + switch (t->tag) { case ListType: return true; case TableType: return true; - case OptionalType: return has_refcounts(Match(t, OptionalType)->type); + case OptionalType: return _has_refcounts(Match(t, OptionalType)->type, visited); case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (has_refcounts(field->type)) return true; + if (_has_refcounts(field->type, visited)) return true; } return false; } case EnumType: { for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && has_refcounts(tag->type)) return true; + if (tag->type && _has_refcounts(tag->type, visited)) return true; } return false; } @@ -270,23 +286,33 @@ PUREFUNC bool has_refcounts(type_t *t) { } } -PUREFUNC bool has_stack_memory(type_t *t) { +bool has_refcounts(type_t *t) { + Table_t visited = EMPTY_TABLE; + return _has_refcounts(t, &visited); +} + +bool _has_stack_memory(type_t *t, Table_t *visited) { if (!t) return false; + Text_t type_text = type_to_text(t); + if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false; + Table$set(visited, &type_text, NULL, Set$info(&Text$info)); + switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; - case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); - case ListType: return has_stack_memory(Match(t, ListType)->item_type); + case OptionalType: return _has_stack_memory(Match(t, OptionalType)->type, visited); + case ListType: return _has_stack_memory(Match(t, ListType)->item_type, visited); case TableType: - return has_stack_memory(Match(t, TableType)->key_type) || has_stack_memory(Match(t, TableType)->value_type); + return _has_stack_memory(Match(t, TableType)->key_type, visited) + || _has_stack_memory(Match(t, TableType)->value_type, visited); case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (has_stack_memory(field->type)) return true; + if (_has_stack_memory(field->type, visited)) return true; } return false; } case EnumType: { for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && has_stack_memory(tag->type)) return true; + if (tag->type && _has_stack_memory(tag->type, visited)) return true; } return false; } @@ -294,6 +320,11 @@ PUREFUNC bool has_stack_memory(type_t *t) { } } +bool has_stack_memory(type_t *t) { + Table_t visited = EMPTY_TABLE; + return _has_stack_memory(t, &visited); +} + PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t) { const char *found = NULL; for (tag_t *tag = Match(enum_type, EnumType)->tags; tag; tag = tag->next) { diff --git a/src/types.h b/src/types.h index 8a2175d9..76632c66 100644 --- a/src/types.h +++ b/src/types.h @@ -149,9 +149,9 @@ typedef enum { NUM_PRECISION_INCOMPARABLE } precision_cmp_e; PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b); -PUREFUNC bool has_heap_memory(type_t *t); -PUREFUNC bool has_refcounts(type_t *t); -PUREFUNC bool has_stack_memory(type_t *t); +bool has_heap_memory(type_t *t); +bool has_refcounts(type_t *t); +bool has_stack_memory(type_t *t); PUREFUNC bool can_promote(type_t *actual, type_t *needed); PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t); PUREFUNC bool is_int_type(type_t *t); -- cgit v1.2.3 From d2e82367925c7e10504c1caec1605806bb364629 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 27 Nov 2025 14:25:45 -0500 Subject: Bugfix for Num32$as_text() visibility --- src/stdlib/numX.c.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stdlib/numX.c.h b/src/stdlib/numX.c.h index 7b030ab4..0e84708f 100644 --- a/src/stdlib/numX.c.h +++ b/src/stdlib/numX.c.h @@ -67,6 +67,7 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf #elif NUMX_C_H__BITS == 32 public PUREFUNC Text_t NAMESPACED(value_as_text)(NUM_T x) { return Num$value_as_text((double)x); } +public PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *info) { (void)info; if (!x) return Text(TYPE_STR); -- cgit v1.2.3 From 3867fa93d39d92fc049dcde08cb322d9a78e3744 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 00:46:29 -0500 Subject: Update module versions --- modules/core.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/core.ini b/modules/core.ini index ddad0a09..026e6d06 100644 --- a/modules/core.ini +++ b/modules/core.ini @@ -1,35 +1,35 @@ [patterns] -version=v1.1 +version=v2.0 git=https://github.com/bruce-hill/tomo-patterns [base64] -version=v1.0 +version=v2.0 git=https://github.com/bruce-hill/tomo-base64 [commands] -version=v1.0 +version=v2.0 git=https://github.com/bruce-hill/tomo-commands [json] -version=v1.0 +version=v2.0 git=https://github.com/bruce-hill/tomo-json [pthreads] -version=v1.0 +version=v2.0 git=https://github.com/bruce-hill/tomo-pthreads [random] -version=v1.0 +version=v1.3 git=https://github.com/bruce-hill/tomo-random [shell] -version=v1.0 +version=v2.0 git=https://github.com/bruce-hill/tomo-shell [time] -version=v1.0 +version=v2.0 git=https://github.com/bruce-hill/tomo-time [uuid] -version=v1.0 +version=v1.1 git=https://github.com/bruce-hill/tomo-uuid -- cgit v1.2.3 From 06f5270edfa8231025125909880af27d42e2e52b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 11:14:14 -0500 Subject: Fix accidental naming collision with _tmp var --- src/compile/expressions.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 19e0672e..8b339352 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -28,9 +28,12 @@ Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { } return Texts(code, "})"); } else { - Text_t code = Texts("({ ", compile_declaration(t, Text("_tmp")), " = ", compile_to_type(env, ast, t), "; ", + static int64_t tmp_index = 1; + Text_t tmp_name = Texts("_tmp", tmp_index); + tmp_index += 1; + Text_t code = Texts("({ ", compile_declaration(t, tmp_name), " = ", compile_to_type(env, ast, t), "; ", "((", compile_type(t), "){"); - ast_t *tmp = WrapLiteralCode(ast, Text("_tmp"), .type = t); + ast_t *tmp = WrapLiteralCode(ast, tmp_name, .type = t); for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name), get_arg_type(env, field)); -- cgit v1.2.3 From e22fc67de035d4f998758ffcbb99515f47a8375a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 11:20:15 -0500 Subject: Bugfix for default CLI argument initialization --- src/compile/cli.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compile/cli.c b/src/compile/cli.c index 7cd3cc7c..a66fb47d 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -167,7 +167,6 @@ Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_t Text_t default_val; if (arg->type) { default_val = compile_to_type(env, arg->default_val, arg->type); - if (arg->type->tag != OptionalType) default_val = promote_to_optional(arg->type, default_val); } else { default_val = compile(env, arg->default_val); } -- cgit v1.2.3 From a0ea1840d837650b10ae4f9a3bb05ded08322510 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 11:28:57 -0500 Subject: Version bumps --- modules/core.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core.ini b/modules/core.ini index 026e6d06..debaf7d9 100644 --- a/modules/core.ini +++ b/modules/core.ini @@ -7,7 +7,7 @@ version=v2.0 git=https://github.com/bruce-hill/tomo-base64 [commands] -version=v2.0 +version=v2.1 git=https://github.com/bruce-hill/tomo-commands [json] @@ -23,7 +23,7 @@ version=v1.3 git=https://github.com/bruce-hill/tomo-random [shell] -version=v2.0 +version=v2.1 git=https://github.com/bruce-hill/tomo-shell [time] -- cgit v1.2.3 From 353a1a40173d4722809e2cad215ab734bf68b283 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 13:27:11 -0500 Subject: Improved manpages. --- man/man3/tomo-Bool.3 | 19 ++ man/man3/tomo-Bool.parse.3 | 4 +- man/man3/tomo-Byte.3 | 51 +++ man/man3/tomo-Byte.get_bit.3 | 4 +- man/man3/tomo-Byte.hex.3 | 6 +- man/man3/tomo-Byte.is_between.3 | 4 +- man/man3/tomo-Byte.parse.3 | 4 +- man/man3/tomo-Byte.to.3 | 6 +- man/man3/tomo-CString.3 | 27 ++ man/man3/tomo-CString.as_text.3 | 4 +- man/man3/tomo-CString.join.3 | 4 +- man/man3/tomo-Int.3 | 131 ++++++++ man/man3/tomo-Int.abs.3 | 4 +- man/man3/tomo-Int.choose.3 | 4 +- man/man3/tomo-Int.clamped.3 | 4 +- man/man3/tomo-Int.factorial.3 | 4 +- man/man3/tomo-Int.get_bit.3 | 4 +- man/man3/tomo-Int.hex.3 | 4 +- man/man3/tomo-Int.is_between.3 | 4 +- man/man3/tomo-Int.is_prime.3 | 4 +- man/man3/tomo-Int.next_prime.3 | 4 +- man/man3/tomo-Int.octal.3 | 4 +- man/man3/tomo-Int.onward.3 | 4 +- man/man3/tomo-Int.parse.3 | 4 +- man/man3/tomo-Int.prev_prime.3 | 4 +- man/man3/tomo-Int.sqrt.3 | 4 +- man/man3/tomo-Int.to.3 | 6 +- man/man3/tomo-List.3 | 219 +++++++++++++ man/man3/tomo-List.binary_search.3 | 4 +- man/man3/tomo-List.by.3 | 4 +- man/man3/tomo-List.clear.3 | 4 +- man/man3/tomo-List.counts.3 | 4 +- man/man3/tomo-List.find.3 | 4 +- man/man3/tomo-List.from.3 | 4 +- man/man3/tomo-List.has.3 | 4 +- man/man3/tomo-List.heap_pop.3 | 4 +- man/man3/tomo-List.heap_push.3 | 4 +- man/man3/tomo-List.heapify.3 | 4 +- man/man3/tomo-List.insert.3 | 4 +- man/man3/tomo-List.insert_all.3 | 4 +- man/man3/tomo-List.pop.3 | 4 +- man/man3/tomo-List.random.3 | 6 +- man/man3/tomo-List.remove_at.3 | 4 +- man/man3/tomo-List.remove_item.3 | 4 +- man/man3/tomo-List.reversed.3 | 4 +- man/man3/tomo-List.sample.3 | 6 +- man/man3/tomo-List.shuffle.3 | 6 +- man/man3/tomo-List.shuffled.3 | 6 +- man/man3/tomo-List.slice.3 | 4 +- man/man3/tomo-List.sort.3 | 4 +- man/man3/tomo-List.sorted.3 | 4 +- man/man3/tomo-List.to.3 | 4 +- man/man3/tomo-List.unique.3 | 4 +- man/man3/tomo-List.where.3 | 6 +- man/man3/tomo-Num.1_PI.3 | 4 +- man/man3/tomo-Num.2_PI.3 | 4 +- man/man3/tomo-Num.2_SQRTPI.3 | 4 +- man/man3/tomo-Num.3 | 523 ++++++++++++++++++++++++++++++ man/man3/tomo-Num.E.3 | 4 +- man/man3/tomo-Num.INF.3 | 4 +- man/man3/tomo-Num.LN10.3 | 4 +- man/man3/tomo-Num.LN2.3 | 4 +- man/man3/tomo-Num.LOG2E.3 | 4 +- man/man3/tomo-Num.PI.3 | 4 +- man/man3/tomo-Num.PI_2.3 | 4 +- man/man3/tomo-Num.PI_4.3 | 4 +- man/man3/tomo-Num.SQRT1_2.3 | 4 +- man/man3/tomo-Num.SQRT2.3 | 4 +- man/man3/tomo-Num.TAU.3 | 4 +- man/man3/tomo-Num.abs.3 | 4 +- man/man3/tomo-Num.acos.3 | 4 +- man/man3/tomo-Num.acosh.3 | 4 +- man/man3/tomo-Num.asin.3 | 4 +- man/man3/tomo-Num.asinh.3 | 4 +- man/man3/tomo-Num.atan.3 | 4 +- man/man3/tomo-Num.atan2.3 | 4 +- man/man3/tomo-Num.atanh.3 | 4 +- man/man3/tomo-Num.cbrt.3 | 4 +- man/man3/tomo-Num.ceil.3 | 4 +- man/man3/tomo-Num.clamped.3 | 4 +- man/man3/tomo-Num.copysign.3 | 4 +- man/man3/tomo-Num.cos.3 | 4 +- man/man3/tomo-Num.cosh.3 | 4 +- man/man3/tomo-Num.erf.3 | 4 +- man/man3/tomo-Num.erfc.3 | 4 +- man/man3/tomo-Num.exp.3 | 4 +- man/man3/tomo-Num.exp2.3 | 4 +- man/man3/tomo-Num.expm1.3 | 4 +- man/man3/tomo-Num.fdim.3 | 4 +- man/man3/tomo-Num.floor.3 | 4 +- man/man3/tomo-Num.hypot.3 | 4 +- man/man3/tomo-Num.is_between.3 | 4 +- man/man3/tomo-Num.isfinite.3 | 4 +- man/man3/tomo-Num.isinf.3 | 4 +- man/man3/tomo-Num.j0.3 | 4 +- man/man3/tomo-Num.j1.3 | 4 +- man/man3/tomo-Num.log.3 | 4 +- man/man3/tomo-Num.log10.3 | 4 +- man/man3/tomo-Num.log1p.3 | 4 +- man/man3/tomo-Num.log2.3 | 4 +- man/man3/tomo-Num.logb.3 | 4 +- man/man3/tomo-Num.mix.3 | 6 +- man/man3/tomo-Num.near.3 | 8 +- man/man3/tomo-Num.nextafter.3 | 4 +- man/man3/tomo-Num.parse.3 | 4 +- man/man3/tomo-Num.percent.3 | 4 +- man/man3/tomo-Num.rint.3 | 4 +- man/man3/tomo-Num.round.3 | 4 +- man/man3/tomo-Num.significand.3 | 4 +- man/man3/tomo-Num.sin.3 | 4 +- man/man3/tomo-Num.sinh.3 | 4 +- man/man3/tomo-Num.sqrt.3 | 4 +- man/man3/tomo-Num.tan.3 | 4 +- man/man3/tomo-Num.tanh.3 | 4 +- man/man3/tomo-Num.tgamma.3 | 4 +- man/man3/tomo-Num.trunc.3 | 4 +- man/man3/tomo-Num.with_precision.3 | 4 +- man/man3/tomo-Num.y0.3 | 4 +- man/man3/tomo-Num.y1.3 | 4 +- man/man3/tomo-Path.3 | 347 ++++++++++++++++++++ man/man3/tomo-Path.accessed.3 | 4 +- man/man3/tomo-Path.append.3 | 4 +- man/man3/tomo-Path.append_bytes.3 | 4 +- man/man3/tomo-Path.base_name.3 | 4 +- man/man3/tomo-Path.by_line.3 | 4 +- man/man3/tomo-Path.can_execute.3 | 4 +- man/man3/tomo-Path.can_read.3 | 4 +- man/man3/tomo-Path.can_write.3 | 4 +- man/man3/tomo-Path.changed.3 | 4 +- man/man3/tomo-Path.child.3 | 4 +- man/man3/tomo-Path.children.3 | 6 +- man/man3/tomo-Path.create_directory.3 | 6 +- man/man3/tomo-Path.current_dir.3 | 4 +- man/man3/tomo-Path.exists.3 | 4 +- man/man3/tomo-Path.expand_home.3 | 4 +- man/man3/tomo-Path.extension.3 | 6 +- man/man3/tomo-Path.files.3 | 4 +- man/man3/tomo-Path.from_components.3 | 4 +- man/man3/tomo-Path.glob.3 | 6 +- man/man3/tomo-Path.group.3 | 4 +- man/man3/tomo-Path.has_extension.3 | 6 +- man/man3/tomo-Path.is_directory.3 | 4 +- man/man3/tomo-Path.is_file.3 | 4 +- man/man3/tomo-Path.is_socket.3 | 4 +- man/man3/tomo-Path.is_symlink.3 | 4 +- man/man3/tomo-Path.lines.3 | 4 +- man/man3/tomo-Path.modified.3 | 4 +- man/man3/tomo-Path.owner.3 | 4 +- man/man3/tomo-Path.parent.3 | 4 +- man/man3/tomo-Path.read.3 | 4 +- man/man3/tomo-Path.read_bytes.3 | 4 +- man/man3/tomo-Path.relative_to.3 | 4 +- man/man3/tomo-Path.remove.3 | 4 +- man/man3/tomo-Path.resolved.3 | 4 +- man/man3/tomo-Path.set_owner.3 | 4 +- man/man3/tomo-Path.sibling.3 | 4 +- man/man3/tomo-Path.subdirectories.3 | 4 +- man/man3/tomo-Path.unique_directory.3 | 6 +- man/man3/tomo-Path.write.3 | 4 +- man/man3/tomo-Path.write_bytes.3 | 4 +- man/man3/tomo-Path.write_unique.3 | 6 +- man/man3/tomo-Path.write_unique_bytes.3 | 6 +- man/man3/tomo-Table.3 | 99 ++++++ man/man3/tomo-Table.clear.3 | 4 +- man/man3/tomo-Table.difference.3 | 4 +- man/man3/tomo-Table.get.3 | 4 +- man/man3/tomo-Table.get_or_set.3 | 4 +- man/man3/tomo-Table.has.3 | 4 +- man/man3/tomo-Table.intersection.3 | 4 +- man/man3/tomo-Table.remove.3 | 4 +- man/man3/tomo-Table.set.3 | 4 +- man/man3/tomo-Table.with.3 | 4 +- man/man3/tomo-Table.with_fallback.3 | 4 +- man/man3/tomo-Table.without.3 | 4 +- man/man3/tomo-Text.3 | 339 +++++++++++++++++++ man/man3/tomo-Text.as_c_string.3 | 4 +- man/man3/tomo-Text.at.3 | 4 +- man/man3/tomo-Text.by_line.3 | 4 +- man/man3/tomo-Text.by_split.3 | 4 +- man/man3/tomo-Text.by_split_any.3 | 4 +- man/man3/tomo-Text.caseless_equals.3 | 4 +- man/man3/tomo-Text.codepoint_names.3 | 4 +- man/man3/tomo-Text.ends_with.3 | 4 +- man/man3/tomo-Text.find.3 | 4 +- man/man3/tomo-Text.from.3 | 4 +- man/man3/tomo-Text.from_c_string.3 | 4 +- man/man3/tomo-Text.from_codepoint_names.3 | 4 +- man/man3/tomo-Text.from_utf16.3 | 4 +- man/man3/tomo-Text.from_utf32.3 | 4 +- man/man3/tomo-Text.from_utf8.3 | 4 +- man/man3/tomo-Text.has.3 | 4 +- man/man3/tomo-Text.join.3 | 4 +- man/man3/tomo-Text.left_pad.3 | 4 +- man/man3/tomo-Text.lines.3 | 4 +- man/man3/tomo-Text.lower.3 | 4 +- man/man3/tomo-Text.middle_pad.3 | 4 +- man/man3/tomo-Text.quoted.3 | 4 +- man/man3/tomo-Text.repeat.3 | 4 +- man/man3/tomo-Text.replace.3 | 4 +- man/man3/tomo-Text.reversed.3 | 4 +- man/man3/tomo-Text.right_pad.3 | 4 +- man/man3/tomo-Text.slice.3 | 4 +- man/man3/tomo-Text.split.3 | 4 +- man/man3/tomo-Text.split_any.3 | 4 +- man/man3/tomo-Text.starts_with.3 | 4 +- man/man3/tomo-Text.title.3 | 4 +- man/man3/tomo-Text.to.3 | 4 +- man/man3/tomo-Text.translate.3 | 4 +- man/man3/tomo-Text.trim.3 | 4 +- man/man3/tomo-Text.upper.3 | 4 +- man/man3/tomo-Text.utf16.3 | 4 +- man/man3/tomo-Text.utf32.3 | 4 +- man/man3/tomo-Text.utf8.3 | 4 +- man/man3/tomo-Text.width.3 | 4 +- man/man3/tomo-Text.without_prefix.3 | 4 +- man/man3/tomo-Text.without_suffix.3 | 4 +- man/man3/tomo-ask.3 | 2 +- man/man3/tomo-at_cleanup.3 | 2 +- man/man3/tomo-exit.3 | 2 +- man/man3/tomo-fail.3 | 2 +- man/man3/tomo-getenv.3 | 2 +- man/man3/tomo-print.3 | 2 +- man/man3/tomo-say.3 | 2 +- man/man3/tomo-setenv.3 | 4 +- man/man3/tomo-sleep.3 | 2 +- scripts/mandoc_gen.py | 77 ++++- 226 files changed, 2481 insertions(+), 237 deletions(-) create mode 100644 man/man3/tomo-Bool.3 create mode 100644 man/man3/tomo-Byte.3 create mode 100644 man/man3/tomo-CString.3 create mode 100644 man/man3/tomo-Int.3 create mode 100644 man/man3/tomo-List.3 create mode 100644 man/man3/tomo-Num.3 create mode 100644 man/man3/tomo-Path.3 create mode 100644 man/man3/tomo-Table.3 create mode 100644 man/man3/tomo-Text.3 diff --git a/man/man3/tomo-Bool.3 b/man/man3/tomo-Bool.3 new file mode 100644 index 00000000..dd090c86 --- /dev/null +++ b/man/man3/tomo-Bool.3 @@ -0,0 +1,19 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Bool 3 2025-11-29 "Tomo man-pages" +.SH NAME +Bool \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Bool.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Bool?) +Converts a text representation of a boolean value into a boolean. Acceptable boolean values are case-insensitive variations of \fByes\fR/\fBno\fR, \fBy\fR/\fBn\fR, \fBtrue\fR/\fBfalse\fR, \fBon\fR/\fBoff\fR. + +For more, see: +.BR Tomo-Bool.parse (3) + diff --git a/man/man3/tomo-Bool.parse.3 b/man/man3/tomo-Bool.parse.3 index 8fe9f2dd..af2e3f67 100644 --- a/man/man3/tomo-Bool.parse.3 +++ b/man/man3/tomo-Bool.parse.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Bool.parse 3 2025-09-21 "Tomo man-pages" +.TH Bool.parse 3 2025-11-29 "Tomo man-pages" .SH NAME Bool.parse \- parse into boolean .SH LIBRARY @@ -39,3 +39,5 @@ remainder : Text assert Bool.parse("yesJUNK", &remainder) == yes assert remainder == "JUNK" .EE +.SH SEE ALSO +.BR Tomo-Bool (3) diff --git a/man/man3/tomo-Byte.3 b/man/man3/tomo-Byte.3 new file mode 100644 index 00000000..94ec4213 --- /dev/null +++ b/man/man3/tomo-Byte.3 @@ -0,0 +1,51 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Byte 3 2025-11-29 "Tomo man-pages" +.SH NAME +Byte \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Byte.get_bit\ :\ func(i:\ Byte,\ bit_index:\ Int\ ->\ Bool) +In the binary representation of a byte, check whether a given bit index is set to 1 or not. + +For more, see: +.BR Tomo-Byte.get_bit (3) + + +.TP +.BI Byte.hex\ :\ func(byte:\ Byte,\ uppercase:\ Bool\ =\ yes,\ prefix:\ Bool\ =\ no\ ->\ Text) +Convert a byte to a hexidecimal text representation. + +For more, see: +.BR Tomo-Byte.hex (3) + + +.TP +.BI Byte.is_between\ :\ func(x:\ Byte,\ low:\ Byte,\ high:\ Byte\ ->\ Bool) +Determines if an integer is between two numbers (inclusive). + +For more, see: +.BR Tomo-Byte.is_between (3) + + +.TP +.BI Byte.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Byte?) +Parse a byte literal from text. + +For more, see: +.BR Tomo-Byte.parse (3) + + +.TP +.BI Byte.to\ :\ func(first:\ Byte,\ last:\ Byte,\ step:\ Byte?\ =\ none\ ->\ func(->Byte?)) +Returns an iterator function that iterates over the range of bytes specified. + +For more, see: +.BR Tomo-Byte.to (3) + diff --git a/man/man3/tomo-Byte.get_bit.3 b/man/man3/tomo-Byte.get_bit.3 index b815e9d4..2e383fff 100644 --- a/man/man3/tomo-Byte.get_bit.3 +++ b/man/man3/tomo-Byte.get_bit.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Byte.get_bit 3 2025-09-21 "Tomo man-pages" +.TH Byte.get_bit 3 2025-11-29 "Tomo man-pages" .SH NAME Byte.get_bit \- check whether a bit is set .SH LIBRARY @@ -38,3 +38,5 @@ assert Byte(6).get_bit(2) == yes assert Byte(6).get_bit(3) == yes assert Byte(6).get_bit(4) == no .EE +.SH SEE ALSO +.BR Tomo-Byte (3) diff --git a/man/man3/tomo-Byte.hex.3 b/man/man3/tomo-Byte.hex.3 index c09ba5c1..35f6c001 100644 --- a/man/man3/tomo-Byte.hex.3 +++ b/man/man3/tomo-Byte.hex.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Byte.hex 3 2025-09-21 "Tomo man-pages" +.TH Byte.hex 3 2025-11-29 "Tomo man-pages" .SH NAME Byte.hex \- convert to hexidecimal .SH LIBRARY @@ -24,7 +24,7 @@ l l l l. Name Type Description Default byte Byte The byte to convert to hex. - uppercase Bool Whether or not to use uppercase hexidecimal letters. yes -prefix Bool Whether or not to prepend a `0x` prefix. no +prefix Bool Whether or not to prepend a \fB0x\fR prefix. no .TE .SH RETURN The byte as a hexidecimal text. @@ -33,3 +33,5 @@ The byte as a hexidecimal text. .EX assert Byte(18).hex() == "0x12" .EE +.SH SEE ALSO +.BR Tomo-Byte (3) diff --git a/man/man3/tomo-Byte.is_between.3 b/man/man3/tomo-Byte.is_between.3 index 042747ac..8c7e871c 100644 --- a/man/man3/tomo-Byte.is_between.3 +++ b/man/man3/tomo-Byte.is_between.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Byte.is_between 3 2025-09-21 "Tomo man-pages" +.TH Byte.is_between 3 2025-11-29 "Tomo man-pages" .SH NAME Byte.is_between \- test if inside a range .SH LIBRARY @@ -35,3 +35,5 @@ assert Byte(7).is_between(1, 10) == yes assert Byte(7).is_between(100, 200) == no assert Byte(7).is_between(1, 7) == yes .EE +.SH SEE ALSO +.BR Tomo-Byte (3) diff --git a/man/man3/tomo-Byte.parse.3 b/man/man3/tomo-Byte.parse.3 index 57dbaee2..fcc7e75e 100644 --- a/man/man3/tomo-Byte.parse.3 +++ b/man/man3/tomo-Byte.parse.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Byte.parse 3 2025-09-21 "Tomo man-pages" +.TH Byte.parse 3 2025-11-29 "Tomo man-pages" .SH NAME Byte.parse \- convert text to a byte .SH LIBRARY @@ -38,3 +38,5 @@ remainder : Text assert Byte.parse("123xyz", &remainder) == Byte(123) assert remainder == "xyz" .EE +.SH SEE ALSO +.BR Tomo-Byte (3) diff --git a/man/man3/tomo-Byte.to.3 b/man/man3/tomo-Byte.to.3 index 15774ba7..39946e8d 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-10-11 "Tomo man-pages" +.TH Byte.to 3 2025-11-29 "Tomo man-pages" .SH NAME Byte.to \- iterate over a range of bytes .SH LIBRARY @@ -24,7 +24,7 @@ l l l l. Name Type Description Default first Byte The starting value of the range. - last Byte The ending value of the range. - -step Byte? An optional step size to use. If unspecified or `none`, the step will be inferred to be `+1` if `last >= first`, otherwise `-1`. none +step Byte? An optional step size to use. If unspecified or \fBnone\fR, the step will be inferred to be \fB+1\fR if \fBlast >= first\fR, otherwise \fB-1\fR. none .TE .SH RETURN An iterator function that returns each byte in the given range (inclusive). @@ -41,3 +41,5 @@ 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 +.SH SEE ALSO +.BR Tomo-Byte (3) diff --git a/man/man3/tomo-CString.3 b/man/man3/tomo-CString.3 new file mode 100644 index 00000000..dac93133 --- /dev/null +++ b/man/man3/tomo-CString.3 @@ -0,0 +1,27 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH CString 3 2025-11-29 "Tomo man-pages" +.SH NAME +CString \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI CString.as_text\ :\ func(str:\ CString\ ->\ Text) +Convert a C string to Text. + +For more, see: +.BR Tomo-CString.as_text (3) + + +.TP +.BI CString.join\ :\ func(glue:\ CString,\ pieces:\ [CString]\ ->\ CString) +Join a list of C strings together with a separator. + +For more, see: +.BR Tomo-CString.join (3) + diff --git a/man/man3/tomo-CString.as_text.3 b/man/man3/tomo-CString.as_text.3 index 463a6f98..c896861a 100644 --- a/man/man3/tomo-CString.as_text.3 +++ b/man/man3/tomo-CString.as_text.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH CString.as_text 3 2025-10-18 "Tomo man-pages" +.TH CString.as_text 3 2025-11-29 "Tomo man-pages" .SH NAME CString.as_text \- convert a C string to Text .SH LIBRARY @@ -31,3 +31,5 @@ The C string as a Text. .EX assert CString("Hello").as_text() == "Hello" .EE +.SH SEE ALSO +.BR Tomo-CString (3) diff --git a/man/man3/tomo-CString.join.3 b/man/man3/tomo-CString.join.3 index 27e74495..a4aa16f3 100644 --- a/man/man3/tomo-CString.join.3 +++ b/man/man3/tomo-CString.join.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH CString.join 3 2025-10-18 "Tomo man-pages" +.TH CString.join 3 2025-11-29 "Tomo man-pages" .SH NAME CString.join \- join a list of C strings .SH LIBRARY @@ -32,3 +32,5 @@ A C string of the joined together bits. .EX assert CString(",").join([CString("a"), CString("b")]) == CString("a,b") .EE +.SH SEE ALSO +.BR Tomo-CString (3) diff --git a/man/man3/tomo-Int.3 b/man/man3/tomo-Int.3 new file mode 100644 index 00000000..2476a27b --- /dev/null +++ b/man/man3/tomo-Int.3 @@ -0,0 +1,131 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Int 3 2025-11-29 "Tomo man-pages" +.SH NAME +Int \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Int.abs\ :\ func(x:\ Int\ ->\ Int) +Calculates the absolute value of an integer. + +For more, see: +.BR Tomo-Int.abs (3) + + +.TP +.BI Int.choose\ :\ func(n:\ Int,\ k:\ Int\ ->\ Int) +Computes the binomial coefficient of the given numbers (the equivalent of \fBn\fR choose \fBk\fR in combinatorics). This is equal to \fBn.factorial()/(k.factorial() * (n-k).factorial())\fR. + +For more, see: +.BR Tomo-Int.choose (3) + + +.TP +.BI Int.clamped\ :\ func(x:\ Int,\ low:\ Int,\ high:\ Int\ ->\ Int) +Returns the given number clamped between two values so that it is within that range. + +For more, see: +.BR Tomo-Int.clamped (3) + + +.TP +.BI Int.factorial\ :\ func(n:\ Int\ ->\ Text) +Computes the factorial of an integer. + +For more, see: +.BR Tomo-Int.factorial (3) + + +.TP +.BI Int.get_bit\ :\ func(i:\ Int,\ bit_index:\ Int\ ->\ Bool) +In the binary representation of an integer, check whether a given bit index is set to 1 or not. + +For more, see: +.BR Tomo-Int.get_bit (3) + + +.TP +.BI Int.hex\ :\ func(i:\ Int,\ digits:\ Int\ =\ 0,\ uppercase:\ Bool\ =\ yes,\ prefix:\ Bool\ =\ yes\ ->\ Text) +Converts an integer to its hexadecimal representation. + +For more, see: +.BR Tomo-Int.hex (3) + + +.TP +.BI Int.is_between\ :\ func(x:\ Int,\ low:\ Int,\ high:\ Int\ ->\ Bool) +Determines if an integer is between two numbers (inclusive). + +For more, see: +.BR Tomo-Int.is_between (3) + + +.TP +.BI Int.is_prime\ :\ func(x:\ Int,\ reps:\ Int\ =\ 50\ ->\ Bool) +Determines if an integer is a prime number. + +For more, see: +.BR Tomo-Int.is_prime (3) + + +.TP +.BI Int.next_prime\ :\ func(x:\ Int\ ->\ Int) +Finds the next prime number greater than the given integer. + +For more, see: +.BR Tomo-Int.next_prime (3) + + +.TP +.BI Int.octal\ :\ func(i:\ Int,\ digits:\ Int\ =\ 0,\ prefix:\ Bool\ =\ yes\ ->\ Text) +Converts an integer to its octal representation. + +For more, see: +.BR Tomo-Int.octal (3) + + +.TP +.BI Int.onward\ :\ func(first:\ Int,\ step:\ Int\ =\ 1\ ->\ Text) +Return an iterator that counts infinitely from the starting integer (with an optional step size). + +For more, see: +.BR Tomo-Int.onward (3) + + +.TP +.BI Int.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Int?) +Converts a text representation of an integer into an integer. + +For more, see: +.BR Tomo-Int.parse (3) + + +.TP +.BI Int.prev_prime\ :\ func(x:\ Int\ ->\ Int?) +Finds the previous prime number less than the given integer. If there is no previous prime number (i.e. if a number less than \fB2\fR is provided), then the function will create a runtime error. + +For more, see: +.BR Tomo-Int.prev_prime (3) + + +.TP +.BI Int.sqrt\ :\ func(x:\ Int\ ->\ Int) +Calculates the nearest square root of an integer. + +For more, see: +.BR Tomo-Int.sqrt (3) + + +.TP +.BI Int.to\ :\ func(first:\ Int,\ last:\ Int,\ step:\ Int?\ =\ none\ ->\ func(->Int?)) +Returns an iterator function that iterates over the range of numbers specified. + +For more, see: +.BR Tomo-Int.to (3) + diff --git a/man/man3/tomo-Int.abs.3 b/man/man3/tomo-Int.abs.3 index d5f212cd..ad5d9411 100644 --- a/man/man3/tomo-Int.abs.3 +++ b/man/man3/tomo-Int.abs.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.abs 3 2025-09-21 "Tomo man-pages" +.TH Int.abs 3 2025-11-29 "Tomo man-pages" .SH NAME Int.abs \- absolute value .SH LIBRARY @@ -31,3 +31,5 @@ The absolute value of `x`. .EX assert (-10).abs() == 10 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.choose.3 b/man/man3/tomo-Int.choose.3 index 5d178732..231d75f0 100644 --- a/man/man3/tomo-Int.choose.3 +++ b/man/man3/tomo-Int.choose.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.choose 3 2025-09-21 "Tomo man-pages" +.TH Int.choose 3 2025-11-29 "Tomo man-pages" .SH NAME Int.choose \- binomial coefficient .SH LIBRARY @@ -32,3 +32,5 @@ The binomial coefficient, equivalent to the number of ways to uniquely choose `k .EX assert (4).choose(2) == 6 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.clamped.3 b/man/man3/tomo-Int.clamped.3 index fbfe9fa8..deb1a98b 100644 --- a/man/man3/tomo-Int.clamped.3 +++ b/man/man3/tomo-Int.clamped.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.clamped 3 2025-09-21 "Tomo man-pages" +.TH Int.clamped 3 2025-11-29 "Tomo man-pages" .SH NAME Int.clamped \- clamp an integer .SH LIBRARY @@ -33,3 +33,5 @@ The first argument clamped between the other two arguments. .EX assert (2).clamped(5, 10) == 5 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.factorial.3 b/man/man3/tomo-Int.factorial.3 index 34efbfa2..accebcdb 100644 --- a/man/man3/tomo-Int.factorial.3 +++ b/man/man3/tomo-Int.factorial.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.factorial 3 2025-09-21 "Tomo man-pages" +.TH Int.factorial 3 2025-11-29 "Tomo man-pages" .SH NAME Int.factorial \- factorial .SH LIBRARY @@ -31,3 +31,5 @@ The factorial of the given integer. .EX assert (10).factorial() == 3628800 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.get_bit.3 b/man/man3/tomo-Int.get_bit.3 index d2009d58..d5614a6a 100644 --- a/man/man3/tomo-Int.get_bit.3 +++ b/man/man3/tomo-Int.get_bit.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.get_bit 3 2025-09-21 "Tomo man-pages" +.TH Int.get_bit 3 2025-11-29 "Tomo man-pages" .SH NAME Int.get_bit \- check whether a bit is set .SH LIBRARY @@ -38,3 +38,5 @@ assert (6).get_bit(2) == yes assert (6).get_bit(3) == yes assert (6).get_bit(4) == no .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.hex.3 b/man/man3/tomo-Int.hex.3 index fcfee9de..37fe45e4 100644 --- a/man/man3/tomo-Int.hex.3 +++ b/man/man3/tomo-Int.hex.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.hex 3 2025-09-21 "Tomo man-pages" +.TH Int.hex 3 2025-11-29 "Tomo man-pages" .SH NAME Int.hex \- convert to hexidecimal .SH LIBRARY @@ -34,3 +34,5 @@ The hexadecimal string representation of the integer. .EX assert (255).hex(digits=4, uppercase=yes, prefix=yes) == "0x00FF" .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.is_between.3 b/man/man3/tomo-Int.is_between.3 index 41c84c15..261bb5a4 100644 --- a/man/man3/tomo-Int.is_between.3 +++ b/man/man3/tomo-Int.is_between.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.is_between 3 2025-09-21 "Tomo man-pages" +.TH Int.is_between 3 2025-11-29 "Tomo man-pages" .SH NAME Int.is_between \- test if an int is in a range .SH LIBRARY @@ -35,3 +35,5 @@ assert (7).is_between(1, 10) == yes assert (7).is_between(100, 200) == no assert (7).is_between(1, 7) == yes .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.is_prime.3 b/man/man3/tomo-Int.is_prime.3 index d5adbac8..5dc2c826 100644 --- a/man/man3/tomo-Int.is_prime.3 +++ b/man/man3/tomo-Int.is_prime.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.is_prime 3 2025-09-21 "Tomo man-pages" +.TH Int.is_prime 3 2025-11-29 "Tomo man-pages" .SH NAME Int.is_prime \- check if an integer is prime .SH LIBRARY @@ -36,3 +36,5 @@ This function is _probabilistic_. With the default arguments, the chances of get assert (7).is_prime() == yes assert (6).is_prime() == no .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.next_prime.3 b/man/man3/tomo-Int.next_prime.3 index bac4e2ed..0afcf7e9 100644 --- a/man/man3/tomo-Int.next_prime.3 +++ b/man/man3/tomo-Int.next_prime.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.next_prime 3 2025-09-21 "Tomo man-pages" +.TH Int.next_prime 3 2025-11-29 "Tomo man-pages" .SH NAME Int.next_prime \- get the next prime .SH LIBRARY @@ -34,3 +34,5 @@ This function is _probabilistic_, but the chances of getting an incorrect answer .EX assert (11).next_prime() == 13 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.octal.3 b/man/man3/tomo-Int.octal.3 index 3cd4f7f6..627a2e9f 100644 --- a/man/man3/tomo-Int.octal.3 +++ b/man/man3/tomo-Int.octal.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.octal 3 2025-09-21 "Tomo man-pages" +.TH Int.octal 3 2025-11-29 "Tomo man-pages" .SH NAME Int.octal \- convert to octal .SH LIBRARY @@ -33,3 +33,5 @@ The octal string representation of the integer. .EX assert (64).octal(digits=4, prefix=yes) == "0o0100" .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.onward.3 b/man/man3/tomo-Int.onward.3 index 2b38014b..596cf842 100644 --- a/man/man3/tomo-Int.onward.3 +++ b/man/man3/tomo-Int.onward.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.onward 3 2025-09-21 "Tomo man-pages" +.TH Int.onward 3 2025-11-29 "Tomo man-pages" .SH NAME Int.onward \- iterate from a number onward .SH LIBRARY @@ -36,3 +36,5 @@ nums.insert(i) stop if i == 10 assert nums[] == [5, 6, 7, 8, 9, 10] .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.parse.3 b/man/man3/tomo-Int.parse.3 index 16793672..df3888db 100644 --- a/man/man3/tomo-Int.parse.3 +++ b/man/man3/tomo-Int.parse.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.parse 3 2025-09-21 "Tomo man-pages" +.TH Int.parse 3 2025-11-29 "Tomo man-pages" .SH NAME Int.parse \- convert text to integer .SH LIBRARY @@ -43,3 +43,5 @@ assert Int.parse("asdf") == none # Outside valid range: assert Int8.parse("9999999") == none .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.prev_prime.3 b/man/man3/tomo-Int.prev_prime.3 index 3391de4e..6908f408 100644 --- a/man/man3/tomo-Int.prev_prime.3 +++ b/man/man3/tomo-Int.prev_prime.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.prev_prime 3 2025-09-21 "Tomo man-pages" +.TH Int.prev_prime 3 2025-11-29 "Tomo man-pages" .SH NAME Int.prev_prime \- get the previous prime .SH LIBRARY @@ -34,3 +34,5 @@ This function is _probabilistic_, but the chances of getting an incorrect answer .EX assert (11).prev_prime() == 7 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.sqrt.3 b/man/man3/tomo-Int.sqrt.3 index a2a66a09..f929a5fd 100644 --- a/man/man3/tomo-Int.sqrt.3 +++ b/man/man3/tomo-Int.sqrt.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.sqrt 3 2025-09-21 "Tomo man-pages" +.TH Int.sqrt 3 2025-11-29 "Tomo man-pages" .SH NAME Int.sqrt \- square root .SH LIBRARY @@ -32,3 +32,5 @@ The integer part of the square root of `x`. assert (16).sqrt() == 4 assert (17).sqrt() == 4 .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-Int.to.3 b/man/man3/tomo-Int.to.3 index ea8112bc..9c0fe4dc 100644 --- a/man/man3/tomo-Int.to.3 +++ b/man/man3/tomo-Int.to.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.to 3 2025-09-21 "Tomo man-pages" +.TH Int.to 3 2025-11-29 "Tomo man-pages" .SH NAME Int.to \- iterate a range of integers .SH LIBRARY @@ -24,7 +24,7 @@ l l l l. Name Type Description Default first Int The starting value of the range. - last Int The ending value of the range. - -step Int? An optional step size to use. If unspecified or `none`, the step will be inferred to be `+1` if `last >= first`, otherwise `-1`. none +step Int? An optional step size to use. If unspecified or \fBnone\fR, the step will be inferred to be \fB+1\fR if \fBlast >= first\fR, otherwise \fB-1\fR. none .TE .SH RETURN An iterator function that returns each integer in the given range (inclusive). @@ -42,3 +42,5 @@ assert [x for x in (2).to(5)] == [2, 3, 4, 5] assert [x for x in (5).to(2)] == [5, 4, 3, 2] assert [x for x in (2).to(5, step=2)] == [2, 4] .EE +.SH SEE ALSO +.BR Tomo-Int (3) diff --git a/man/man3/tomo-List.3 b/man/man3/tomo-List.3 new file mode 100644 index 00000000..5ff85015 --- /dev/null +++ b/man/man3/tomo-List.3 @@ -0,0 +1,219 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH List 3 2025-11-29 "Tomo man-pages" +.SH NAME +List \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI List.binary_search\ :\ func(list:\ [T],\ by:\ func(x,y:&T->Int32)\ =\ T.compare\ ->\ Int) +Performs a binary search on a sorted list. + +For more, see: +.BR Tomo-List.binary_search (3) + + +.TP +.BI List.by\ :\ func(list:\ [T],\ step:\ Int\ ->\ [T]) +Creates a new list with elements spaced by the specified step value. + +For more, see: +.BR Tomo-List.by (3) + + +.TP +.BI List.clear\ :\ func(list:\ @[T]\ ->\ Void) +Clears all elements from the list. + +For more, see: +.BR Tomo-List.clear (3) + + +.TP +.BI List.counts\ :\ func(list:\ [T]\ ->\ {T=Int}) +Counts the occurrences of each element in the list. + +For more, see: +.BR Tomo-List.counts (3) + + +.TP +.BI List.find\ :\ func(list:\ [T],\ target:\ T\ ->\ Int?) +Finds the index of the first occurrence of an element (if any). + +For more, see: +.BR Tomo-List.find (3) + + +.TP +.BI List.from\ :\ func(list:\ [T],\ first:\ Int\ ->\ [T]) +Returns a slice of the list starting from a specified index. + +For more, see: +.BR Tomo-List.from (3) + + +.TP +.BI List.has\ :\ func(list:\ [T],\ target:\ T\ ->\ Bool) +Checks if the list has an element. + +For more, see: +.BR Tomo-List.has (3) + + +.TP +.BI List.heap_pop\ :\ func(list:\ @[T],\ by:\ func(x,y:&T->Int32)\ =\ T.compare\ ->\ T?) +Removes and returns the top element of a heap or \fBnone\fR if the list is empty. By default, this is the *minimum* value in the heap. + +For more, see: +.BR Tomo-List.heap_pop (3) + + +.TP +.BI List.heap_push\ :\ func(list:\ @[T],\ item:\ T,\ by\ =\ T.compare\ ->\ Void) +Adds an element to the heap and maintains the heap property. By default, this is a *minimum* heap. + +For more, see: +.BR Tomo-List.heap_push (3) + + +.TP +.BI List.heapify\ :\ func(list:\ @[T],\ by:\ func(x,y:&T->Int32)\ =\ T.compare\ ->\ Void) +Converts a list into a heap. + +For more, see: +.BR Tomo-List.heapify (3) + + +.TP +.BI List.insert\ :\ func(list:\ @[T],\ item:\ T,\ at:\ Int\ =\ 0\ ->\ Void) +Inserts an element at a specified position in the list. + +For more, see: +.BR Tomo-List.insert (3) + + +.TP +.BI List.insert_all\ :\ func(list:\ @[T],\ items:\ [T],\ at:\ Int\ =\ 0\ ->\ Void) +Inserts a list of items at a specified position in the list. + +For more, see: +.BR Tomo-List.insert_all (3) + + +.TP +.BI List.pop\ :\ func(list:\ &[T],\ index:\ Int\ =\ -1\ ->\ T?) +Removes and returns an item from the list. If the given index is present in the list, the item at that index will be removed and the list will become one element shorter. + +For more, see: +.BR Tomo-List.pop (3) + + +.TP +.BI List.random\ :\ func(list:\ [T],\ random:\ func(min,max:Int64->Int64)?\ =\ none\ ->\ T) +Selects a random element from the list. + +For more, see: +.BR Tomo-List.random (3) + + +.TP +.BI List.remove_at\ :\ func(list:\ @[T],\ at:\ Int\ =\ -1,\ count:\ Int\ =\ 1\ ->\ Void) +Removes elements from the list starting at a specified index. + +For more, see: +.BR Tomo-List.remove_at (3) + + +.TP +.BI List.remove_item\ :\ func(list:\ @[T],\ item:\ T,\ max_count:\ Int\ =\ -1\ ->\ Void) +Removes all occurrences of a specified item from the list. + +For more, see: +.BR Tomo-List.remove_item (3) + + +.TP +.BI List.reversed\ :\ func(list:\ [T]\ ->\ [T]) +Returns a reversed slice of the list. + +For more, see: +.BR Tomo-List.reversed (3) + + +.TP +.BI List.sample\ :\ func(list:\ [T],\ count:\ Int,\ weights:\ [Num]?\ =\ none,\ random:\ func(->Num)?\ =\ none\ ->\ [T]) +Selects a sample of elements from the list, optionally with weighted probabilities. + +For more, see: +.BR Tomo-List.sample (3) + + +.TP +.BI List.shuffle\ :\ func(list:\ @[T],\ random:\ func(min,max:Int64->Int64)?\ =\ none\ ->\ Void) +Shuffles the elements of the list in place. + +For more, see: +.BR Tomo-List.shuffle (3) + + +.TP +.BI List.shuffled\ :\ func(list:\ [T],\ random:\ func(min,max:Int64->Int64)?\ =\ none\ ->\ [T]) +Creates a new list with elements shuffled. + +For more, see: +.BR Tomo-List.shuffled (3) + + +.TP +.BI List.slice\ :\ func(list:\ [T],\ from:\ Int,\ to:\ Int\ ->\ [T]) +Returns a slice of the list spanning the given indices (inclusive). + +For more, see: +.BR Tomo-List.slice (3) + + +.TP +.BI List.sort\ :\ func(list:\ @[T],\ by\ =\ T.compare\ ->\ Void) +Sorts the elements of the list in place in ascending order (small to large). + +For more, see: +.BR Tomo-List.sort (3) + + +.TP +.BI List.sorted\ :\ func(list:\ [T],\ by\ =\ T.compare\ ->\ [T]) +Creates a new list with elements sorted. + +For more, see: +.BR Tomo-List.sorted (3) + + +.TP +.BI List.to\ :\ func(list:\ [T],\ last:\ Int\ ->\ [T]) +Returns a slice of the list from the start of the original list up to a specified index (inclusive). + +For more, see: +.BR Tomo-List.to (3) + + +.TP +.BI List.unique\ :\ func(list:\ [T]\ ->\ {T}) +Returns a set of the unique elements of the list. + +For more, see: +.BR Tomo-List.unique (3) + + +.TP +.BI List.where\ :\ func(list:\ [T],\ predicate:\ func(item:&T\ ->\ Bool)\ ->\ Int) +Find the index of the first item that matches a predicate function (if any). + +For more, see: +.BR Tomo-List.where (3) + diff --git a/man/man3/tomo-List.binary_search.3 b/man/man3/tomo-List.binary_search.3 index b2cb1759..ca92602d 100644 --- a/man/man3/tomo-List.binary_search.3 +++ b/man/man3/tomo-List.binary_search.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.binary_search 3 2025-09-21 "Tomo man-pages" +.TH List.binary_search 3 2025-11-29 "Tomo man-pages" .SH NAME List.binary_search \- binary search .SH LIBRARY @@ -34,3 +34,5 @@ assert [1, 3, 5, 7, 9].binary_search(5) == 3 assert [1, 3, 5, 7, 9].binary_search(-999) == 1 assert [1, 3, 5, 7, 9].binary_search(999) == 6 .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.by.3 b/man/man3/tomo-List.by.3 index 4d201c43..5f114df8 100644 --- a/man/man3/tomo-List.by.3 +++ b/man/man3/tomo-List.by.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.by 3 2025-09-21 "Tomo man-pages" +.TH List.by 3 2025-11-29 "Tomo man-pages" .SH NAME List.by \- slice by a step value .SH LIBRARY @@ -32,3 +32,5 @@ A new list with every `step`-th element from the original list. .EX assert [1, 2, 3, 4, 5, 6].by(2) == [1, 3, 5] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.clear.3 b/man/man3/tomo-List.clear.3 index bece5524..0f767272 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-10-11 "Tomo man-pages" +.TH List.clear 3 2025-11-29 "Tomo man-pages" .SH NAME List.clear \- clear a list .SH LIBRARY @@ -31,3 +31,5 @@ Nothing. .EX my_list.clear() .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.counts.3 b/man/man3/tomo-List.counts.3 index 5b6d9b1b..c4e22303 100644 --- a/man/man3/tomo-List.counts.3 +++ b/man/man3/tomo-List.counts.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.counts 3 2025-09-21 "Tomo man-pages" +.TH List.counts 3 2025-11-29 "Tomo man-pages" .SH NAME List.counts \- count occurrences .SH LIBRARY @@ -31,3 +31,5 @@ A table mapping each element to its count. .EX assert [10, 20, 30, 30, 30].counts() == {10=1, 20=1, 30=3} .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.find.3 b/man/man3/tomo-List.find.3 index 65ae5926..1eef0bb2 100644 --- a/man/man3/tomo-List.find.3 +++ b/man/man3/tomo-List.find.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.find 3 2025-09-21 "Tomo man-pages" +.TH List.find 3 2025-11-29 "Tomo man-pages" .SH NAME List.find \- find an element's index .SH LIBRARY @@ -33,3 +33,5 @@ The index of the first occurrence or `none` if not found. assert [10, 20, 30, 40, 50].find(20) == 2 assert [10, 20, 30, 40, 50].find(9999) == none .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.from.3 b/man/man3/tomo-List.from.3 index a7a24cd6..f58e669e 100644 --- a/man/man3/tomo-List.from.3 +++ b/man/man3/tomo-List.from.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.from 3 2025-09-21 "Tomo man-pages" +.TH List.from 3 2025-11-29 "Tomo man-pages" .SH NAME List.from \- slice an array from a start index .SH LIBRARY @@ -32,3 +32,5 @@ A new list starting from the specified index. .EX assert [10, 20, 30, 40, 50].from(3) == [30, 40, 50] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.has.3 b/man/man3/tomo-List.has.3 index 96507253..75eb5bc8 100644 --- a/man/man3/tomo-List.has.3 +++ b/man/man3/tomo-List.has.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.has 3 2025-09-21 "Tomo man-pages" +.TH List.has 3 2025-11-29 "Tomo man-pages" .SH NAME List.has \- check for member .SH LIBRARY @@ -32,3 +32,5 @@ target T The element to check for. - .EX assert [10, 20, 30].has(20) == yes .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.heap_pop.3 b/man/man3/tomo-List.heap_pop.3 index cd4e292a..2d6621ee 100644 --- a/man/man3/tomo-List.heap_pop.3 +++ b/man/man3/tomo-List.heap_pop.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.heap_pop 3 2025-09-21 "Tomo man-pages" +.TH List.heap_pop 3 2025-11-29 "Tomo man-pages" .SH NAME List.heap_pop \- heap pop .SH LIBRARY @@ -34,3 +34,5 @@ my_heap := [30, 10, 20] my_heap.heapify() assert my_heap.heap_pop() == 10 .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.heap_push.3 b/man/man3/tomo-List.heap_push.3 index 3c1804bd..108e9007 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-10-11 "Tomo man-pages" +.TH List.heap_push 3 2025-11-29 "Tomo man-pages" .SH NAME List.heap_push \- heap push .SH LIBRARY @@ -33,3 +33,5 @@ Nothing. .EX my_heap.heap_push(10) .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.heapify.3 b/man/man3/tomo-List.heapify.3 index 345a47b9..5f7f47c9 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-10-11 "Tomo man-pages" +.TH List.heapify 3 2025-11-29 "Tomo man-pages" .SH NAME List.heapify \- convert a list into a heap .SH LIBRARY @@ -33,3 +33,5 @@ Nothing. my_heap := [30, 10, 20] my_heap.heapify() .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.insert.3 b/man/man3/tomo-List.insert.3 index 0ab71a3f..f885c1eb 100644 --- a/man/man3/tomo-List.insert.3 +++ b/man/man3/tomo-List.insert.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.insert 3 2025-09-21 "Tomo man-pages" +.TH List.insert 3 2025-11-29 "Tomo man-pages" .SH NAME List.insert \- add an item to a list .SH LIBRARY @@ -41,3 +41,5 @@ assert list == [10, 20, 30] list.insert(999, at=2) assert list == [10, 999, 20, 30] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.insert_all.3 b/man/man3/tomo-List.insert_all.3 index 795f4a2d..145ccd5b 100644 --- a/man/man3/tomo-List.insert_all.3 +++ b/man/man3/tomo-List.insert_all.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.insert_all 3 2025-09-21 "Tomo man-pages" +.TH List.insert_all 3 2025-11-29 "Tomo man-pages" .SH NAME List.insert_all \- add multiple items to a list .SH LIBRARY @@ -41,3 +41,5 @@ assert list == [10, 20, 30, 40] list.insert_all([99, 100], at=2) assert list == [10, 99, 100, 20, 30, 40] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.pop.3 b/man/man3/tomo-List.pop.3 index b157e0ac..aabe249d 100644 --- a/man/man3/tomo-List.pop.3 +++ b/man/man3/tomo-List.pop.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.pop 3 2025-09-21 "Tomo man-pages" +.TH List.pop 3 2025-11-29 "Tomo man-pages" .SH NAME List.pop \- pop an item from a list .SH LIBRARY @@ -41,3 +41,5 @@ assert list[] == [10, 20, 30] assert list.pop(index=2) == 20 assert list[] == [10, 30] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.random.3 b/man/man3/tomo-List.random.3 index 0c82bfd0..55702485 100644 --- a/man/man3/tomo-List.random.3 +++ b/man/man3/tomo-List.random.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.random 3 2025-09-21 "Tomo man-pages" +.TH List.random 3 2025-11-29 "Tomo man-pages" .SH NAME List.random \- pick a random element .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default list [T] The list from which to select a random element. - -random func(min,max:Int64->Int64)? If provided, this function will be used to get a random index in the list. Returned values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number generation) none +random func(min,max:Int64->Int64)? If provided, this function will be used to get a random index in the list. Returned values must be between \fBmin\fR and \fBmax\fR (inclusive). (Used for deterministic pseudorandom number generation) none .TE .SH RETURN A random element from the list. @@ -32,3 +32,5 @@ A random element from the list. .EX assert [10, 20, 30].random() == 20 .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.remove_at.3 b/man/man3/tomo-List.remove_at.3 index 8e993931..f0695836 100644 --- a/man/man3/tomo-List.remove_at.3 +++ b/man/man3/tomo-List.remove_at.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.remove_at 3 2025-09-21 "Tomo man-pages" +.TH List.remove_at 3 2025-11-29 "Tomo man-pages" .SH NAME List.remove_at \- remove an item by index .SH LIBRARY @@ -41,3 +41,5 @@ assert list == [10, 30, 40, 50] list.remove_at(2, count=2) assert list == [10, 50] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.remove_item.3 b/man/man3/tomo-List.remove_item.3 index 02631ba6..12662797 100644 --- a/man/man3/tomo-List.remove_item.3 +++ b/man/man3/tomo-List.remove_item.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.remove_item 3 2025-09-21 "Tomo man-pages" +.TH List.remove_item 3 2025-11-29 "Tomo man-pages" .SH NAME List.remove_item \- remove an item by value .SH LIBRARY @@ -41,3 +41,5 @@ assert list == [20, 20, 30] list.remove_item(20, max_count=1) assert list == [20, 30] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.reversed.3 b/man/man3/tomo-List.reversed.3 index 2b84e333..b49d82a9 100644 --- a/man/man3/tomo-List.reversed.3 +++ b/man/man3/tomo-List.reversed.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.reversed 3 2025-09-21 "Tomo man-pages" +.TH List.reversed 3 2025-11-29 "Tomo man-pages" .SH NAME List.reversed \- get a reversed list .SH LIBRARY @@ -31,3 +31,5 @@ A slice of the list with elements in reverse order. .EX assert [10, 20, 30].reversed() == [30, 20, 10] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.sample.3 b/man/man3/tomo-List.sample.3 index 0e4d779c..29ad8563 100644 --- a/man/man3/tomo-List.sample.3 +++ b/man/man3/tomo-List.sample.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.sample 3 2025-09-21 "Tomo man-pages" +.TH List.sample 3 2025-11-29 "Tomo man-pages" .SH NAME List.sample \- weighted random choices .SH LIBRARY @@ -25,7 +25,7 @@ Name Type Description Default list [T] The list to sample from. - count Int The number of elements to sample. - weights [Num]? The probability weights for each element in the list. These values do not need to add up to any particular number, they are relative weights. If no weights are given, elements will be sampled with uniform probability. none -random func(->Num)? If provided, this function will be used to get random values for sampling the list. The provided function should return random numbers between `0.0` (inclusive) and `1.0` (exclusive). (Used for deterministic pseudorandom number generation) none +random func(->Num)? If provided, this function will be used to get random values for sampling the list. The provided function should return random numbers between \fB0.0\fR (inclusive) and \fB1.0\fR (exclusive). (Used for deterministic pseudorandom number generation) none .TE .SH RETURN A list of sampled elements from the list. @@ -37,3 +37,5 @@ Errors will be raised if any of the following conditions occurs: - The given lis .EX assert [10, 20, 30].sample(2, weights=[90%, 5%, 5%]) == [10, 10] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.shuffle.3 b/man/man3/tomo-List.shuffle.3 index 875a73c6..112bf88d 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-10-11 "Tomo man-pages" +.TH List.shuffle 3 2025-11-29 "Tomo man-pages" .SH NAME List.shuffle \- shuffle a list in place .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default list @[T] The mutable reference to the list to be shuffled. - -random func(min,max:Int64->Int64)? If provided, this function will be used to get a random index in the list. Returned values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number generation) none +random func(min,max:Int64->Int64)? If provided, this function will be used to get a random index in the list. Returned values must be between \fBmin\fR and \fBmax\fR (inclusive). (Used for deterministic pseudorandom number generation) none .TE .SH RETURN Nothing. @@ -32,3 +32,5 @@ Nothing. .EX list.shuffle() .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.shuffled.3 b/man/man3/tomo-List.shuffled.3 index 25e63154..98fef55f 100644 --- a/man/man3/tomo-List.shuffled.3 +++ b/man/man3/tomo-List.shuffled.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.shuffled 3 2025-09-21 "Tomo man-pages" +.TH List.shuffled 3 2025-11-29 "Tomo man-pages" .SH NAME List.shuffled \- return a shuffled list .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default list [T] The list to be shuffled. - -random func(min,max:Int64->Int64)? If provided, this function will be used to get a random index in the list. Returned values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number generation) none +random func(min,max:Int64->Int64)? If provided, this function will be used to get a random index in the list. Returned values must be between \fBmin\fR and \fBmax\fR (inclusive). (Used for deterministic pseudorandom number generation) none .TE .SH RETURN A new list with shuffled elements. @@ -32,3 +32,5 @@ A new list with shuffled elements. .EX assert [10, 20, 30, 40].shuffled() == [40, 10, 30, 20] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.slice.3 b/man/man3/tomo-List.slice.3 index 676e2a08..d160c863 100644 --- a/man/man3/tomo-List.slice.3 +++ b/man/man3/tomo-List.slice.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.slice 3 2025-09-21 "Tomo man-pages" +.TH List.slice 3 2025-11-29 "Tomo man-pages" .SH NAME List.slice \- get a slice of a list .SH LIBRARY @@ -34,3 +34,5 @@ A new list spanning the given indices. Note: negative indices are counted from t assert [10, 20, 30, 40, 50].slice(2, 4) == [20, 30, 40] assert [10, 20, 30, 40, 50].slice(-3, -2) == [30, 40] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.sort.3 b/man/man3/tomo-List.sort.3 index 3957e9fe..3e93c86e 100644 --- a/man/man3/tomo-List.sort.3 +++ b/man/man3/tomo-List.sort.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.sort 3 2025-09-21 "Tomo man-pages" +.TH List.sort 3 2025-11-29 "Tomo man-pages" .SH NAME List.sort \- sort a list .SH LIBRARY @@ -37,3 +37,5 @@ assert list == [-30, 10, 20, 40] list.sort(func(a,b:&Int): a.abs() <> b.abs()) assert list == [10, 20, -30, 40] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.sorted.3 b/man/man3/tomo-List.sorted.3 index 49d75ecd..f6fe93a4 100644 --- a/man/man3/tomo-List.sorted.3 +++ b/man/man3/tomo-List.sorted.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.sorted 3 2025-09-21 "Tomo man-pages" +.TH List.sorted 3 2025-11-29 "Tomo man-pages" .SH NAME List.sorted \- sorted copy of a list .SH LIBRARY @@ -35,3 +35,5 @@ assert [40, 10, -30, 20].sorted( func(a,b:&Int): a.abs() <> b.abs() ) == [10, 20, -30, 40] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.to.3 b/man/man3/tomo-List.to.3 index 5af2a6db..dd1a23a9 100644 --- a/man/man3/tomo-List.to.3 +++ b/man/man3/tomo-List.to.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.to 3 2025-09-21 "Tomo man-pages" +.TH List.to 3 2025-11-29 "Tomo man-pages" .SH NAME List.to \- slice a list to an end index .SH LIBRARY @@ -33,3 +33,5 @@ A new list containing elements from the start up to the specified index. assert [10, 20, 30, 40, 50].to(3) == [10, 20, 30] assert [10, 20, 30, 40, 50].to(-2) == [10, 20, 30, 40] .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.unique.3 b/man/man3/tomo-List.unique.3 index 9ed81687..1c203a62 100644 --- a/man/man3/tomo-List.unique.3 +++ b/man/man3/tomo-List.unique.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.unique 3 2025-10-11 "Tomo man-pages" +.TH List.unique 3 2025-11-29 "Tomo man-pages" .SH NAME List.unique \- get the unique items in a list .SH LIBRARY @@ -31,3 +31,5 @@ A set of the unique elements from the list. .EX assert [10, 20, 10, 10, 30].unique() == {10, 20, 30} .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-List.where.3 b/man/man3/tomo-List.where.3 index 8d5c71ef..a436d2ea 100644 --- a/man/man3/tomo-List.where.3 +++ b/man/man3/tomo-List.where.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH List.where 3 2025-09-21 "Tomo man-pages" +.TH List.where 3 2025-11-29 "Tomo man-pages" .SH NAME List.where \- find an index where a predicate matches .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default list [T] The list to search through. - -predicate func(item:&T -> Bool) A function that returns `yes` if the item's index should be returned or `no` if it should not. - +predicate func(item:&T -> Bool) A function that returns \fByes\fR if the item's index should be returned or \fBno\fR if it should not. - .TE .SH RETURN Returns the index of the first item where the predicate is true or `none` if no item matches. @@ -33,3 +33,5 @@ Returns the index of the first item where the predicate is true or `none` if no assert [4, 5, 6].where(func(i:&Int): i.is_prime()) == 5 assert [4, 6, 8].find(func(i:&Int): i.is_prime()) == none .EE +.SH SEE ALSO +.BR Tomo-List (3) diff --git a/man/man3/tomo-Num.1_PI.3 b/man/man3/tomo-Num.1_PI.3 index ad91d33b..75245ad4 100644 --- a/man/man3/tomo-Num.1_PI.3 +++ b/man/man3/tomo-Num.1_PI.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.1_PI 3 2025-04-30 "Tomo man-pages" +.TH Num.1_PI 3 2025-11-29 "Tomo man-pages" .SH NAME Num.1_PI \- 1/pi .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The constant $\frac{1}{\pi}$. +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.2_PI.3 b/man/man3/tomo-Num.2_PI.3 index 3e7db63b..9ea37335 100644 --- a/man/man3/tomo-Num.2_PI.3 +++ b/man/man3/tomo-Num.2_PI.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.2_PI 3 2025-04-30 "Tomo man-pages" +.TH Num.2_PI 3 2025-11-29 "Tomo man-pages" .SH NAME Num.2_PI \- 2*pi .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The constant $2 \times \pi$. +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.2_SQRTPI.3 b/man/man3/tomo-Num.2_SQRTPI.3 index 15a19bc2..aab8577a 100644 --- a/man/man3/tomo-Num.2_SQRTPI.3 +++ b/man/man3/tomo-Num.2_SQRTPI.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.2_SQRTPI 3 2025-04-30 "Tomo man-pages" +.TH Num.2_SQRTPI 3 2025-11-29 "Tomo man-pages" .SH NAME Num.2_SQRTPI \- 2*sqrt(pi) .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The constant $2 \times \sqrt{\pi}$. +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.3 b/man/man3/tomo-Num.3 new file mode 100644 index 00000000..99c9e8d5 --- /dev/null +++ b/man/man3/tomo-Num.3 @@ -0,0 +1,523 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Num 3 2025-11-29 "Tomo man-pages" +.SH NAME +Num \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Num.1_PI\ :\ Num +The constant $\frac{1}{\pi}$. + +For more, see: +.BR Tomo-Num.1_PI (3) + + +.TP +.BI Num.2_PI\ :\ Num +The constant $2 \times \pi$. + +For more, see: +.BR Tomo-Num.2_PI (3) + + +.TP +.BI Num.2_SQRTPI\ :\ Num +The constant $2 \times \sqrt{\pi}$. + +For more, see: +.BR Tomo-Num.2_SQRTPI (3) + + +.TP +.BI Num.E\ :\ Num +The base of the natural logarithm ($e$). + +For more, see: +.BR Tomo-Num.E (3) + + +.TP +.BI Num.INF\ :\ Num +Positive infinity. + +For more, see: +.BR Tomo-Num.INF (3) + + +.TP +.BI Num.LN10\ :\ Num +The natural logarithm of 10. + +For more, see: +.BR Tomo-Num.LN10 (3) + + +.TP +.BI Num.LN2\ :\ Num +The natural logarithm of 2. + +For more, see: +.BR Tomo-Num.LN2 (3) + + +.TP +.BI Num.LOG2E\ :\ Num +The base 2 logarithm of $e$ + +For more, see: +.BR Tomo-Num.LOG2E (3) + + +.TP +.BI Num.PI\ :\ Num +Pi ($\pi$). + +For more, see: +.BR Tomo-Num.PI (3) + + +.TP +.BI Num.PI_2\ :\ Num +$\frac{\pi}{2}$ + +For more, see: +.BR Tomo-Num.PI_2 (3) + + +.TP +.BI Num.PI_4\ :\ Num +$\frac{\pi}{4}$ + +For more, see: +.BR Tomo-Num.PI_4 (3) + + +.TP +.BI Num.SQRT1_2\ :\ Num +$\sqrt{\frac{1}{2}}$ + +For more, see: +.BR Tomo-Num.SQRT1_2 (3) + + +.TP +.BI Num.SQRT2\ :\ Num +$\sqrt{2}$ + +For more, see: +.BR Tomo-Num.SQRT2 (3) + + +.TP +.BI Num.TAU\ :\ Num +Tau ($2 \times \pi$) + +For more, see: +.BR Tomo-Num.TAU (3) + + +.TP +.BI Num.abs\ :\ func(n:\ Num\ ->\ Num) +Calculates the absolute value of a number. + +For more, see: +.BR Tomo-Num.abs (3) + + +.TP +.BI Num.acos\ :\ func(x:\ Num\ ->\ Num) +Computes the arc cosine of a number. + +For more, see: +.BR Tomo-Num.acos (3) + + +.TP +.BI Num.acosh\ :\ func(x:\ Num\ ->\ Num) +Computes the inverse hyperbolic cosine of a number. + +For more, see: +.BR Tomo-Num.acosh (3) + + +.TP +.BI Num.asin\ :\ func(x:\ Num\ ->\ Num) +Computes the arc sine of a number. + +For more, see: +.BR Tomo-Num.asin (3) + + +.TP +.BI Num.asinh\ :\ func(x:\ Num\ ->\ Num) +Computes the inverse hyperbolic sine of a number. + +For more, see: +.BR Tomo-Num.asinh (3) + + +.TP +.BI Num.atan\ :\ func(x:\ Num\ ->\ Num) +Computes the arc tangent of a number. + +For more, see: +.BR Tomo-Num.atan (3) + + +.TP +.BI Num.atan2\ :\ func(x:\ Num,\ y:\ Num\ ->\ Num) +Computes the arc tangent of the quotient of two numbers. + +For more, see: +.BR Tomo-Num.atan2 (3) + + +.TP +.BI Num.atanh\ :\ func(x:\ Num\ ->\ Num) +Computes the inverse hyperbolic tangent of a number. + +For more, see: +.BR Tomo-Num.atanh (3) + + +.TP +.BI Num.cbrt\ :\ func(x:\ Num\ ->\ Num) +Computes the cube root of a number. + +For more, see: +.BR Tomo-Num.cbrt (3) + + +.TP +.BI Num.ceil\ :\ func(x:\ Num\ ->\ Num) +Rounds a number up to the nearest integer. + +For more, see: +.BR Tomo-Num.ceil (3) + + +.TP +.BI Num.clamped\ :\ func(x:\ Num,\ low:\ Num,\ high:\ Num\ ->\ Num) +Returns the given number clamped between two values so that it is within that range. + +For more, see: +.BR Tomo-Num.clamped (3) + + +.TP +.BI Num.copysign\ :\ func(x:\ Num,\ y:\ Num\ ->\ Num) +Copies the sign of one number to another. + +For more, see: +.BR Tomo-Num.copysign (3) + + +.TP +.BI Num.cos\ :\ func(x:\ Num\ ->\ Num) +Computes the cosine of a number (angle in radians). + +For more, see: +.BR Tomo-Num.cos (3) + + +.TP +.BI Num.cosh\ :\ func(x:\ Num\ ->\ Num) +Computes the hyperbolic cosine of a number. + +For more, see: +.BR Tomo-Num.cosh (3) + + +.TP +.BI Num.erf\ :\ func(x:\ Num\ ->\ Num) +Computes the error function of a number. + +For more, see: +.BR Tomo-Num.erf (3) + + +.TP +.BI Num.erfc\ :\ func(x:\ Num\ ->\ Num) +Computes the complementary error function of a number. + +For more, see: +.BR Tomo-Num.erfc (3) + + +.TP +.BI Num.exp\ :\ func(x:\ Num\ ->\ Num) +Computes the exponential function $e^x$ for a number. + +For more, see: +.BR Tomo-Num.exp (3) + + +.TP +.BI Num.exp2\ :\ func(x:\ Num\ ->\ Num) +Computes $2^x$ for a number. + +For more, see: +.BR Tomo-Num.exp2 (3) + + +.TP +.BI Num.expm1\ :\ func(x:\ Num\ ->\ Num) +Computes $e^x - 1$ for a number. + +For more, see: +.BR Tomo-Num.expm1 (3) + + +.TP +.BI Num.fdim\ :\ func(x:\ Num,\ y:\ Num\ ->\ Num) +Computes the positive difference between two numbers. + +For more, see: +.BR Tomo-Num.fdim (3) + + +.TP +.BI Num.floor\ :\ func(x:\ Num\ ->\ Num) +Rounds a number down to the nearest integer. + +For more, see: +.BR Tomo-Num.floor (3) + + +.TP +.BI Num.hypot\ :\ func(x:\ Num,\ y:\ Num\ ->\ Num) +Computes the Euclidean norm, $\sqrt{x^2 + y^2}$, of two numbers. + +For more, see: +.BR Tomo-Num.hypot (3) + + +.TP +.BI Num.is_between\ :\ func(x:\ Num,\ low:\ Num,\ high:\ Num\ ->\ Bool) +Determines if a number is between two numbers (inclusive). + +For more, see: +.BR Tomo-Num.is_between (3) + + +.TP +.BI Num.isfinite\ :\ func(n:\ Num\ ->\ Bool) +Checks if a number is finite. + +For more, see: +.BR Tomo-Num.isfinite (3) + + +.TP +.BI Num.isinf\ :\ func(n:\ Num\ ->\ Bool) +Checks if a number is infinite. + +For more, see: +.BR Tomo-Num.isinf (3) + + +.TP +.BI Num.j0\ :\ func(x:\ Num\ ->\ Num) +Computes the Bessel function of the first kind of order 0. + +For more, see: +.BR Tomo-Num.j0 (3) + + +.TP +.BI Num.j1\ :\ func(x:\ Num\ ->\ Num) +Computes the Bessel function of the first kind of order 1. + +For more, see: +.BR Tomo-Num.j1 (3) + + +.TP +.BI Num.log\ :\ func(x:\ Num\ ->\ Num) +Computes the natural logarithm (base $e$) of a number. + +For more, see: +.BR Tomo-Num.log (3) + + +.TP +.BI Num.log10\ :\ func(x:\ Num\ ->\ Num) +Computes the base-10 logarithm of a number. + +For more, see: +.BR Tomo-Num.log10 (3) + + +.TP +.BI Num.log1p\ :\ func(x:\ Num\ ->\ Num) +Computes $\log(1 + x)$ for a number. + +For more, see: +.BR Tomo-Num.log1p (3) + + +.TP +.BI Num.log2\ :\ func(x:\ Num\ ->\ Num) +Computes the base-2 logarithm of a number. + +For more, see: +.BR Tomo-Num.log2 (3) + + +.TP +.BI Num.logb\ :\ func(x:\ Num\ ->\ Num) +Computes the binary exponent (base-2 logarithm) of a number. + +For more, see: +.BR Tomo-Num.logb (3) + + +.TP +.BI Num.mix\ :\ func(amount:\ Num,\ x:\ Num,\ y:\ Num\ ->\ Num) +Interpolates between two numbers based on a given amount. + +For more, see: +.BR Tomo-Num.mix (3) + + +.TP +.BI Num.near\ :\ func(x:\ Num,\ y:\ Num,\ ratio:\ Num\ =\ 1e-9,\ min_epsilon:\ Num\ =\ 1e-9\ ->\ Bool) +Checks if two numbers are approximately equal within specified tolerances. If two numbers are within an absolute difference or the ratio between the two is small enough, they are considered near each other. + +For more, see: +.BR Tomo-Num.near (3) + + +.TP +.BI Num.nextafter\ :\ func(x:\ Num,\ y:\ Num\ ->\ Num) +Computes the next representable value after a given number towards a specified direction. + +For more, see: +.BR Tomo-Num.nextafter (3) + + +.TP +.BI Num.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Num?) +Converts a text representation of a number into a floating-point number. + +For more, see: +.BR Tomo-Num.parse (3) + + +.TP +.BI Num.percent\ :\ func(n:\ Num,\ precision:\ Num\ =\ 0.01\ ->\ Text) +Convert a number into a percentage text with a percent sign. + +For more, see: +.BR Tomo-Num.percent (3) + + +.TP +.BI Num.rint\ :\ func(x:\ Num\ ->\ Num) +Rounds a number to the nearest integer, with ties rounded to the nearest even integer. + +For more, see: +.BR Tomo-Num.rint (3) + + +.TP +.BI Num.round\ :\ func(x:\ Num\ ->\ Num) +Rounds a number to the nearest whole number integer. + +For more, see: +.BR Tomo-Num.round (3) + + +.TP +.BI Num.significand\ :\ func(x:\ Num\ ->\ Num) +Extracts the significand (or mantissa) of a number. + +For more, see: +.BR Tomo-Num.significand (3) + + +.TP +.BI Num.sin\ :\ func(x:\ Num\ ->\ Num) +Computes the sine of a number (angle in radians). + +For more, see: +.BR Tomo-Num.sin (3) + + +.TP +.BI Num.sinh\ :\ func(x:\ Num\ ->\ Num) +Computes the hyperbolic sine of a number. + +For more, see: +.BR Tomo-Num.sinh (3) + + +.TP +.BI Num.sqrt\ :\ func(x:\ Num\ ->\ Num) +Computes the square root of a number. + +For more, see: +.BR Tomo-Num.sqrt (3) + + +.TP +.BI Num.tan\ :\ func(x:\ Num\ ->\ Num) +Computes the tangent of a number (angle in radians). + +For more, see: +.BR Tomo-Num.tan (3) + + +.TP +.BI Num.tanh\ :\ func(x:\ Num\ ->\ Num) +Computes the hyperbolic tangent of a number. + +For more, see: +.BR Tomo-Num.tanh (3) + + +.TP +.BI Num.tgamma\ :\ func(x:\ Num\ ->\ Num) +Computes the gamma function of a number. + +For more, see: +.BR Tomo-Num.tgamma (3) + + +.TP +.BI Num.trunc\ :\ func(x:\ Num\ ->\ Num) +Truncates a number to the nearest integer towards zero. + +For more, see: +.BR Tomo-Num.trunc (3) + + +.TP +.BI Num.with_precision\ :\ func(n:\ Num,\ precision:\ Num\ ->\ Num) +Round a number to the given precision level (specified as \fB10\fR, \fB.1\fR, \fB.001\fR etc). + +For more, see: +.BR Tomo-Num.with_precision (3) + + +.TP +.BI Num.y0\ :\ func(x:\ Num\ ->\ Num) +Computes the Bessel function of the second kind of order 0. + +For more, see: +.BR Tomo-Num.y0 (3) + + +.TP +.BI Num.y1\ :\ func(x:\ Num\ ->\ Num) +Computes the Bessel function of the second kind of order 1. + +For more, see: +.BR Tomo-Num.y1 (3) + diff --git a/man/man3/tomo-Num.E.3 b/man/man3/tomo-Num.E.3 index 227c3a78..771c015a 100644 --- a/man/man3/tomo-Num.E.3 +++ b/man/man3/tomo-Num.E.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.E 3 2025-04-30 "Tomo man-pages" +.TH Num.E 3 2025-11-29 "Tomo man-pages" .SH NAME Num.E \- Euler's constant .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The base of the natural logarithm ($e$). +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.INF.3 b/man/man3/tomo-Num.INF.3 index 6b7560e8..364274e2 100644 --- a/man/man3/tomo-Num.INF.3 +++ b/man/man3/tomo-Num.INF.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.INF 3 2025-04-30 "Tomo man-pages" +.TH Num.INF 3 2025-11-29 "Tomo man-pages" .SH NAME Num.INF \- infinity .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library Positive infinity. +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.LN10.3 b/man/man3/tomo-Num.LN10.3 index 00383a60..25c9ea99 100644 --- a/man/man3/tomo-Num.LN10.3 +++ b/man/man3/tomo-Num.LN10.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.LN10 3 2025-04-30 "Tomo man-pages" +.TH Num.LN10 3 2025-11-29 "Tomo man-pages" .SH NAME Num.LN10 \- log(10) .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The natural logarithm of 10. +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.LN2.3 b/man/man3/tomo-Num.LN2.3 index 6e3d3df6..e91741df 100644 --- a/man/man3/tomo-Num.LN2.3 +++ b/man/man3/tomo-Num.LN2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.LN2 3 2025-04-30 "Tomo man-pages" +.TH Num.LN2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.LN2 \- log(2) .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The natural logarithm of 2. +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.LOG2E.3 b/man/man3/tomo-Num.LOG2E.3 index b729be0c..35f8abc5 100644 --- a/man/man3/tomo-Num.LOG2E.3 +++ b/man/man3/tomo-Num.LOG2E.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.LOG2E 3 2025-04-30 "Tomo man-pages" +.TH Num.LOG2E 3 2025-11-29 "Tomo man-pages" .SH NAME Num.LOG2E \- log_2(e) .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library The base 2 logarithm of $e$ +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.PI.3 b/man/man3/tomo-Num.PI.3 index 61d4a0f8..a3d3518a 100644 --- a/man/man3/tomo-Num.PI.3 +++ b/man/man3/tomo-Num.PI.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.PI 3 2025-04-30 "Tomo man-pages" +.TH Num.PI 3 2025-11-29 "Tomo man-pages" .SH NAME Num.PI \- pi .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library Pi ($\pi$). +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.PI_2.3 b/man/man3/tomo-Num.PI_2.3 index 93e7f4ca..6801a71c 100644 --- a/man/man3/tomo-Num.PI_2.3 +++ b/man/man3/tomo-Num.PI_2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.PI_2 3 2025-04-30 "Tomo man-pages" +.TH Num.PI_2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.PI_2 \- pi/2 .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library $\frac{\pi}{2}$ +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.PI_4.3 b/man/man3/tomo-Num.PI_4.3 index c6796704..e1a18648 100644 --- a/man/man3/tomo-Num.PI_4.3 +++ b/man/man3/tomo-Num.PI_4.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.PI_4 3 2025-04-30 "Tomo man-pages" +.TH Num.PI_4 3 2025-11-29 "Tomo man-pages" .SH NAME Num.PI_4 \- pi/4 .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library $\frac{\pi}{4}$ +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.SQRT1_2.3 b/man/man3/tomo-Num.SQRT1_2.3 index 0feac353..fc347716 100644 --- a/man/man3/tomo-Num.SQRT1_2.3 +++ b/man/man3/tomo-Num.SQRT1_2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.SQRT1_2 3 2025-04-30 "Tomo man-pages" +.TH Num.SQRT1_2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.SQRT1_2 \- sqrt(1/2) .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library $\sqrt{\frac{1}{2}}$ +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.SQRT2.3 b/man/man3/tomo-Num.SQRT2.3 index 75bd86bf..5b3bf304 100644 --- a/man/man3/tomo-Num.SQRT2.3 +++ b/man/man3/tomo-Num.SQRT2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.SQRT2 3 2025-04-30 "Tomo man-pages" +.TH Num.SQRT2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.SQRT2 \- sqrt(2) .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library $\sqrt{2}$ +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.TAU.3 b/man/man3/tomo-Num.TAU.3 index 2abc33d2..38d22030 100644 --- a/man/man3/tomo-Num.TAU.3 +++ b/man/man3/tomo-Num.TAU.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.TAU 3 2025-04-30 "Tomo man-pages" +.TH Num.TAU 3 2025-11-29 "Tomo man-pages" .SH NAME Num.TAU \- 2*pi .SH LIBRARY @@ -15,3 +15,5 @@ Tomo Standard Library Tau ($2 \times \pi$) +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.abs.3 b/man/man3/tomo-Num.abs.3 index 425d43cb..e34723f8 100644 --- a/man/man3/tomo-Num.abs.3 +++ b/man/man3/tomo-Num.abs.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.abs 3 2025-09-21 "Tomo man-pages" +.TH Num.abs 3 2025-11-29 "Tomo man-pages" .SH NAME Num.abs \- absolute value .SH LIBRARY @@ -31,3 +31,5 @@ The absolute value of `n`. .EX assert (-3.5).abs() == 3.5 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.acos.3 b/man/man3/tomo-Num.acos.3 index 819c43d3..8c007109 100644 --- a/man/man3/tomo-Num.acos.3 +++ b/man/man3/tomo-Num.acos.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.acos 3 2025-09-21 "Tomo man-pages" +.TH Num.acos 3 2025-11-29 "Tomo man-pages" .SH NAME Num.acos \- arc cosine .SH LIBRARY @@ -31,3 +31,5 @@ The arc cosine of `x` in radians. .EX assert (0.0).acos() == 1.5708 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.acosh.3 b/man/man3/tomo-Num.acosh.3 index 0899fff8..f2980b39 100644 --- a/man/man3/tomo-Num.acosh.3 +++ b/man/man3/tomo-Num.acosh.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.acosh 3 2025-09-21 "Tomo man-pages" +.TH Num.acosh 3 2025-11-29 "Tomo man-pages" .SH NAME Num.acosh \- arc hyperbolic cosine .SH LIBRARY @@ -31,3 +31,5 @@ The inverse hyperbolic cosine of `x`. .EX assert (1.0).acosh() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.asin.3 b/man/man3/tomo-Num.asin.3 index 529a974c..4501c81e 100644 --- a/man/man3/tomo-Num.asin.3 +++ b/man/man3/tomo-Num.asin.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.asin 3 2025-09-21 "Tomo man-pages" +.TH Num.asin 3 2025-11-29 "Tomo man-pages" .SH NAME Num.asin \- arc sine .SH LIBRARY @@ -31,3 +31,5 @@ The arc sine of `x` in radians. .EX assert (0.5).asin() == 0.5236 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.asinh.3 b/man/man3/tomo-Num.asinh.3 index da88b719..4c2216a8 100644 --- a/man/man3/tomo-Num.asinh.3 +++ b/man/man3/tomo-Num.asinh.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.asinh 3 2025-09-21 "Tomo man-pages" +.TH Num.asinh 3 2025-11-29 "Tomo man-pages" .SH NAME Num.asinh \- arc hyperbolic sine .SH LIBRARY @@ -31,3 +31,5 @@ The inverse hyperbolic sine of `x`. .EX assert (0.0).asinh() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.atan.3 b/man/man3/tomo-Num.atan.3 index 808338d4..28efbba5 100644 --- a/man/man3/tomo-Num.atan.3 +++ b/man/man3/tomo-Num.atan.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.atan 3 2025-09-21 "Tomo man-pages" +.TH Num.atan 3 2025-11-29 "Tomo man-pages" .SH NAME Num.atan \- arc tangent .SH LIBRARY @@ -31,3 +31,5 @@ The arc tangent of `x` in radians. .EX assert (1.0).atan() == 0.7854 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.atan2.3 b/man/man3/tomo-Num.atan2.3 index ee2400aa..e960bf99 100644 --- a/man/man3/tomo-Num.atan2.3 +++ b/man/man3/tomo-Num.atan2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.atan2 3 2025-09-21 "Tomo man-pages" +.TH Num.atan2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.atan2 \- arc tangent from 2 variables .SH LIBRARY @@ -32,3 +32,5 @@ The arc tangent of `x/y` in radians. .EX assert Num.atan2(1, 1) == 0.7854 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.atanh.3 b/man/man3/tomo-Num.atanh.3 index c7c30692..677e13ab 100644 --- a/man/man3/tomo-Num.atanh.3 +++ b/man/man3/tomo-Num.atanh.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.atanh 3 2025-09-21 "Tomo man-pages" +.TH Num.atanh 3 2025-11-29 "Tomo man-pages" .SH NAME Num.atanh \- arc hyperbolic tangent. .SH LIBRARY @@ -31,3 +31,5 @@ The inverse hyperbolic tangent of `x`. .EX assert (0.5).atanh() == 0.5493 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.cbrt.3 b/man/man3/tomo-Num.cbrt.3 index 15e0ee9e..05bfbb84 100644 --- a/man/man3/tomo-Num.cbrt.3 +++ b/man/man3/tomo-Num.cbrt.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.cbrt 3 2025-09-21 "Tomo man-pages" +.TH Num.cbrt 3 2025-11-29 "Tomo man-pages" .SH NAME Num.cbrt \- cube root .SH LIBRARY @@ -31,3 +31,5 @@ The cube root of `x`. .EX assert (27.0).cbrt() == 3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.ceil.3 b/man/man3/tomo-Num.ceil.3 index c0aac8a9..d096bce4 100644 --- a/man/man3/tomo-Num.ceil.3 +++ b/man/man3/tomo-Num.ceil.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.ceil 3 2025-09-21 "Tomo man-pages" +.TH Num.ceil 3 2025-11-29 "Tomo man-pages" .SH NAME Num.ceil \- ceiling function .SH LIBRARY @@ -31,3 +31,5 @@ The smallest integer greater than or equal to `x`. .EX assert (3.2).ceil() == 4 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.clamped.3 b/man/man3/tomo-Num.clamped.3 index 3ed48344..8561babc 100644 --- a/man/man3/tomo-Num.clamped.3 +++ b/man/man3/tomo-Num.clamped.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.clamped 3 2025-09-21 "Tomo man-pages" +.TH Num.clamped 3 2025-11-29 "Tomo man-pages" .SH NAME Num.clamped \- clamp a number .SH LIBRARY @@ -33,3 +33,5 @@ The first argument clamped between the other two arguments. .EX assert (2.5).clamped(5.5, 10.5) == 5.5 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.copysign.3 b/man/man3/tomo-Num.copysign.3 index 9230754c..e7ed54b9 100644 --- a/man/man3/tomo-Num.copysign.3 +++ b/man/man3/tomo-Num.copysign.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.copysign 3 2025-09-21 "Tomo man-pages" +.TH Num.copysign 3 2025-11-29 "Tomo man-pages" .SH NAME Num.copysign \- copy a number's sign .SH LIBRARY @@ -32,3 +32,5 @@ A number with the magnitude of `x` and the sign of `y`. .EX assert (3.0).copysign(-1) == -3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.cos.3 b/man/man3/tomo-Num.cos.3 index 6071c022..884a4f3e 100644 --- a/man/man3/tomo-Num.cos.3 +++ b/man/man3/tomo-Num.cos.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.cos 3 2025-09-21 "Tomo man-pages" +.TH Num.cos 3 2025-11-29 "Tomo man-pages" .SH NAME Num.cos \- cosine .SH LIBRARY @@ -31,3 +31,5 @@ The cosine of `x`. .EX assert (0.0).cos() == 1 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.cosh.3 b/man/man3/tomo-Num.cosh.3 index 843ec369..8a85b7c3 100644 --- a/man/man3/tomo-Num.cosh.3 +++ b/man/man3/tomo-Num.cosh.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.cosh 3 2025-09-21 "Tomo man-pages" +.TH Num.cosh 3 2025-11-29 "Tomo man-pages" .SH NAME Num.cosh \- hyperbolic cosine .SH LIBRARY @@ -31,3 +31,5 @@ The hyperbolic cosine of `x`. .EX assert (0.0).cosh() == 1 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.erf.3 b/man/man3/tomo-Num.erf.3 index aec6194f..a858ecda 100644 --- a/man/man3/tomo-Num.erf.3 +++ b/man/man3/tomo-Num.erf.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.erf 3 2025-09-21 "Tomo man-pages" +.TH Num.erf 3 2025-11-29 "Tomo man-pages" .SH NAME Num.erf \- error function .SH LIBRARY @@ -31,3 +31,5 @@ The error function of `x`. .EX assert (0.0).erf() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.erfc.3 b/man/man3/tomo-Num.erfc.3 index 27dcb44a..8b51d6cd 100644 --- a/man/man3/tomo-Num.erfc.3 +++ b/man/man3/tomo-Num.erfc.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.erfc 3 2025-09-21 "Tomo man-pages" +.TH Num.erfc 3 2025-11-29 "Tomo man-pages" .SH NAME Num.erfc \- complimentary error function .SH LIBRARY @@ -31,3 +31,5 @@ The complementary error function of `x`. .EX assert (0.0).erfc() == 1 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.exp.3 b/man/man3/tomo-Num.exp.3 index 4778a76d..651f43ea 100644 --- a/man/man3/tomo-Num.exp.3 +++ b/man/man3/tomo-Num.exp.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.exp 3 2025-09-21 "Tomo man-pages" +.TH Num.exp 3 2025-11-29 "Tomo man-pages" .SH NAME Num.exp \- base-e exponentiation .SH LIBRARY @@ -31,3 +31,5 @@ The value of $e^x$. .EX assert (1.0).exp() == 2.7183 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.exp2.3 b/man/man3/tomo-Num.exp2.3 index 1d997802..6e4f6626 100644 --- a/man/man3/tomo-Num.exp2.3 +++ b/man/man3/tomo-Num.exp2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.exp2 3 2025-09-21 "Tomo man-pages" +.TH Num.exp2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.exp2 \- base-2 exponentiation .SH LIBRARY @@ -31,3 +31,5 @@ The value of $2^x$. .EX assert (3.0).exp2() == 8 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.expm1.3 b/man/man3/tomo-Num.expm1.3 index 956bb3ac..c82f38df 100644 --- a/man/man3/tomo-Num.expm1.3 +++ b/man/man3/tomo-Num.expm1.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.expm1 3 2025-09-21 "Tomo man-pages" +.TH Num.expm1 3 2025-11-29 "Tomo man-pages" .SH NAME Num.expm1 \- base-e exponential minus 1 .SH LIBRARY @@ -31,3 +31,5 @@ The value of $e^x - 1$. .EX assert (1.0).expm1() == 1.7183 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.fdim.3 b/man/man3/tomo-Num.fdim.3 index 2498fc7b..f4b0a46a 100644 --- a/man/man3/tomo-Num.fdim.3 +++ b/man/man3/tomo-Num.fdim.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.fdim 3 2025-09-21 "Tomo man-pages" +.TH Num.fdim 3 2025-11-29 "Tomo man-pages" .SH NAME Num.fdim \- positive difference .SH LIBRARY @@ -34,3 +34,5 @@ fd assert (5.0).fdim(3) == 2 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.floor.3 b/man/man3/tomo-Num.floor.3 index 2107a1f0..ff78d081 100644 --- a/man/man3/tomo-Num.floor.3 +++ b/man/man3/tomo-Num.floor.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.floor 3 2025-09-21 "Tomo man-pages" +.TH Num.floor 3 2025-11-29 "Tomo man-pages" .SH NAME Num.floor \- floor function .SH LIBRARY @@ -31,3 +31,5 @@ The largest integer less than or equal to `x`. .EX assert (3.7).floor() == 3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.hypot.3 b/man/man3/tomo-Num.hypot.3 index 3c2e0913..de0d64a1 100644 --- a/man/man3/tomo-Num.hypot.3 +++ b/man/man3/tomo-Num.hypot.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.hypot 3 2025-09-21 "Tomo man-pages" +.TH Num.hypot 3 2025-11-29 "Tomo man-pages" .SH NAME Num.hypot \- Euclidean distance function .SH LIBRARY @@ -32,3 +32,5 @@ The Euclidean norm of `x` and `y`. .EX assert Num.hypot(3, 4) == 5 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.is_between.3 b/man/man3/tomo-Num.is_between.3 index 7077b397..bba3ad19 100644 --- a/man/man3/tomo-Num.is_between.3 +++ b/man/man3/tomo-Num.is_between.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.is_between 3 2025-09-21 "Tomo man-pages" +.TH Num.is_between 3 2025-11-29 "Tomo man-pages" .SH NAME Num.is_between \- check if a number is in a range .SH LIBRARY @@ -35,3 +35,5 @@ assert (7.5).is_between(1, 10) == yes assert (7.5).is_between(100, 200) == no assert (7.5).is_between(1, 7.5) == yes .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.isfinite.3 b/man/man3/tomo-Num.isfinite.3 index 99fdd913..fac2f19d 100644 --- a/man/man3/tomo-Num.isfinite.3 +++ b/man/man3/tomo-Num.isfinite.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.isfinite 3 2025-09-21 "Tomo man-pages" +.TH Num.isfinite 3 2025-11-29 "Tomo man-pages" .SH NAME Num.isfinite \- check for finite number .SH LIBRARY @@ -32,3 +32,5 @@ n Num The number to be checked. - assert (1.0).isfinite() == yes assert Num.INF.isfinite() == no .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.isinf.3 b/man/man3/tomo-Num.isinf.3 index f8587d7a..8de9736e 100644 --- a/man/man3/tomo-Num.isinf.3 +++ b/man/man3/tomo-Num.isinf.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.isinf 3 2025-09-21 "Tomo man-pages" +.TH Num.isinf 3 2025-11-29 "Tomo man-pages" .SH NAME Num.isinf \- check for infinite number .SH LIBRARY @@ -32,3 +32,5 @@ n Num The number to be checked. - assert Num.INF.isinf() == yes assert (1.0).isinf() == no .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.j0.3 b/man/man3/tomo-Num.j0.3 index 1ad0ed38..c93a5f24 100644 --- a/man/man3/tomo-Num.j0.3 +++ b/man/man3/tomo-Num.j0.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.j0 3 2025-09-21 "Tomo man-pages" +.TH Num.j0 3 2025-11-29 "Tomo man-pages" .SH NAME Num.j0 \- Bessel function .SH LIBRARY @@ -31,3 +31,5 @@ The Bessel function of the first kind of order 0 of `x`. .EX assert (0.0).j0() == 1 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.j1.3 b/man/man3/tomo-Num.j1.3 index 7f3fda5a..3d924958 100644 --- a/man/man3/tomo-Num.j1.3 +++ b/man/man3/tomo-Num.j1.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.j1 3 2025-09-21 "Tomo man-pages" +.TH Num.j1 3 2025-11-29 "Tomo man-pages" .SH NAME Num.j1 \- Bessel function .SH LIBRARY @@ -31,3 +31,5 @@ The Bessel function of the first kind of order 1 of `x`. .EX assert (0.0).j1() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.log.3 b/man/man3/tomo-Num.log.3 index 5cfd660c..aae022c0 100644 --- a/man/man3/tomo-Num.log.3 +++ b/man/man3/tomo-Num.log.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.log 3 2025-09-21 "Tomo man-pages" +.TH Num.log 3 2025-11-29 "Tomo man-pages" .SH NAME Num.log \- natural logarithm .SH LIBRARY @@ -31,3 +31,5 @@ The natural logarithm of `x`. .EX assert Num.E.log() == 1 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.log10.3 b/man/man3/tomo-Num.log10.3 index 87000cb9..1e5a13fc 100644 --- a/man/man3/tomo-Num.log10.3 +++ b/man/man3/tomo-Num.log10.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.log10 3 2025-09-21 "Tomo man-pages" +.TH Num.log10 3 2025-11-29 "Tomo man-pages" .SH NAME Num.log10 \- logarithm base-10 .SH LIBRARY @@ -31,3 +31,5 @@ The base-10 logarithm of `x`. .EX assert (100.0).log10() == 2 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.log1p.3 b/man/man3/tomo-Num.log1p.3 index dcf4a253..467e535d 100644 --- a/man/man3/tomo-Num.log1p.3 +++ b/man/man3/tomo-Num.log1p.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.log1p 3 2025-09-21 "Tomo man-pages" +.TH Num.log1p 3 2025-11-29 "Tomo man-pages" .SH NAME Num.log1p \- logarithm of 1 plus x .SH LIBRARY @@ -31,3 +31,5 @@ The value of $\log(1 + x)$. .EX assert (1.0).log1p() == 0.6931 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.log2.3 b/man/man3/tomo-Num.log2.3 index 3e162d15..ee0ea14b 100644 --- a/man/man3/tomo-Num.log2.3 +++ b/man/man3/tomo-Num.log2.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.log2 3 2025-09-21 "Tomo man-pages" +.TH Num.log2 3 2025-11-29 "Tomo man-pages" .SH NAME Num.log2 \- logarithm base-2 .SH LIBRARY @@ -31,3 +31,5 @@ The base-2 logarithm of `x`. .EX assert (8.0).log2() == 3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.logb.3 b/man/man3/tomo-Num.logb.3 index 519e6042..e2e5b738 100644 --- a/man/man3/tomo-Num.logb.3 +++ b/man/man3/tomo-Num.logb.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.logb 3 2025-09-21 "Tomo man-pages" +.TH Num.logb 3 2025-11-29 "Tomo man-pages" .SH NAME Num.logb \- exponent of a floating point value .SH LIBRARY @@ -31,3 +31,5 @@ The binary exponent of `x`. .EX assert (8.0).logb() == 3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.mix.3 b/man/man3/tomo-Num.mix.3 index f06462eb..01e5d7dd 100644 --- a/man/man3/tomo-Num.mix.3 +++ b/man/man3/tomo-Num.mix.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.mix 3 2025-09-21 "Tomo man-pages" +.TH Num.mix 3 2025-11-29 "Tomo man-pages" .SH NAME Num.mix \- mix two numbers by an amount .SH LIBRARY @@ -22,7 +22,7 @@ allbox; lb lb lbx lb l l l l. Name Type Description Default -amount Num The interpolation factor (between `0` and `1`). - +amount Num The interpolation factor (between \fB0\fR and \fB1\fR). - x Num The starting number. - y Num The ending number. - .TE @@ -34,3 +34,5 @@ The interpolated number between `x` and `y` based on `amount`. assert (0.5).mix(10, 20) == 15 assert (0.25).mix(10, 20) == 12.5 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.near.3 b/man/man3/tomo-Num.near.3 index ef1b80ce..824ba241 100644 --- a/man/man3/tomo-Num.near.3 +++ b/man/man3/tomo-Num.near.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.near 3 2025-09-21 "Tomo man-pages" +.TH Num.near 3 2025-11-29 "Tomo man-pages" .SH NAME Num.near \- check if two numbers are near each other .SH LIBRARY @@ -24,8 +24,8 @@ l l l l. Name Type Description Default x Num The first number. - y Num The second number. - -ratio Num The relative tolerance. Default is `1e-9`. 1e-9 -min_epsilon Num The absolute tolerance. Default is `1e-9`. 1e-9 +ratio Num The relative tolerance. Default is \fB1e-9\fR. 1e-9 +min_epsilon Num The absolute tolerance. Default is \fB1e-9\fR. 1e-9 .TE .SH RETURN `yes` if `x` and `y` are approximately equal within the specified tolerances, `no` otherwise. @@ -36,3 +36,5 @@ assert (1.0).near(1.000000001) == yes assert (100.0).near(110, ratio=0.1) == yes assert (5.0).near(5.1, min_epsilon=0.1) == yes .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.nextafter.3 b/man/man3/tomo-Num.nextafter.3 index 1cc51818..06256bdf 100644 --- a/man/man3/tomo-Num.nextafter.3 +++ b/man/man3/tomo-Num.nextafter.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.nextafter 3 2025-09-21 "Tomo man-pages" +.TH Num.nextafter 3 2025-11-29 "Tomo man-pages" .SH NAME Num.nextafter \- next floating point number .SH LIBRARY @@ -32,3 +32,5 @@ The next representable value after `x` in the direction of `y`. .EX assert (1.0).nextafter(1.1) == 1.0000000000000002 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.parse.3 b/man/man3/tomo-Num.parse.3 index d224e4e8..0df34a8f 100644 --- a/man/man3/tomo-Num.parse.3 +++ b/man/man3/tomo-Num.parse.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.parse 3 2025-09-21 "Tomo man-pages" +.TH Num.parse 3 2025-11-29 "Tomo man-pages" .SH NAME Num.parse \- convert text to number .SH LIBRARY @@ -37,3 +37,5 @@ remainder : Text assert Num.parse("1.5junk", &remainder) == 1.5 assert remainder == "junk" .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.percent.3 b/man/man3/tomo-Num.percent.3 index 5c41845e..4102bc86 100644 --- a/man/man3/tomo-Num.percent.3 +++ b/man/man3/tomo-Num.percent.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.percent 3 2025-09-21 "Tomo man-pages" +.TH Num.percent 3 2025-11-29 "Tomo man-pages" .SH NAME Num.percent \- format as a percentage .SH LIBRARY @@ -35,3 +35,5 @@ assert (1./3.).percent(2) == "33.33%" assert (1./3.).percent(2, precision=0.0001) == "33.3333%" assert (1./3.).percent(2, precision=10.) == "30%" .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.rint.3 b/man/man3/tomo-Num.rint.3 index ca1f9bfa..ae134089 100644 --- a/man/man3/tomo-Num.rint.3 +++ b/man/man3/tomo-Num.rint.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.rint 3 2025-09-21 "Tomo man-pages" +.TH Num.rint 3 2025-11-29 "Tomo man-pages" .SH NAME Num.rint \- round to nearest integer .SH LIBRARY @@ -32,3 +32,5 @@ The nearest integer value of `x`. assert (3.5).rint() == 4 assert (2.5).rint() == 2 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.round.3 b/man/man3/tomo-Num.round.3 index 18075d34..e35cf39f 100644 --- a/man/man3/tomo-Num.round.3 +++ b/man/man3/tomo-Num.round.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.round 3 2025-09-21 "Tomo man-pages" +.TH Num.round 3 2025-11-29 "Tomo man-pages" .SH NAME Num.round \- round to nearest integer .SH LIBRARY @@ -32,3 +32,5 @@ The nearest integer value of `x`. assert (2.3).round() == 2 assert (2.7).round() == 3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.significand.3 b/man/man3/tomo-Num.significand.3 index 8cf9102c..1cd6421d 100644 --- a/man/man3/tomo-Num.significand.3 +++ b/man/man3/tomo-Num.significand.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.significand 3 2025-09-21 "Tomo man-pages" +.TH Num.significand 3 2025-11-29 "Tomo man-pages" .SH NAME Num.significand \- get mantissa .SH LIBRARY @@ -31,3 +31,5 @@ The significand of `x`. .EX assert (1234.567).significand() == 0.1234567 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.sin.3 b/man/man3/tomo-Num.sin.3 index 1d67353f..5f3ad779 100644 --- a/man/man3/tomo-Num.sin.3 +++ b/man/man3/tomo-Num.sin.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.sin 3 2025-09-21 "Tomo man-pages" +.TH Num.sin 3 2025-11-29 "Tomo man-pages" .SH NAME Num.sin \- sine .SH LIBRARY @@ -31,3 +31,5 @@ The sine of `x`. .EX assert (0.0).sin() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.sinh.3 b/man/man3/tomo-Num.sinh.3 index c154356f..b3157201 100644 --- a/man/man3/tomo-Num.sinh.3 +++ b/man/man3/tomo-Num.sinh.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.sinh 3 2025-09-21 "Tomo man-pages" +.TH Num.sinh 3 2025-11-29 "Tomo man-pages" .SH NAME Num.sinh \- hyperbolic sine .SH LIBRARY @@ -31,3 +31,5 @@ The hyperbolic sine of `x`. .EX assert (0.0).sinh() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.sqrt.3 b/man/man3/tomo-Num.sqrt.3 index 82cba25e..c8db5200 100644 --- a/man/man3/tomo-Num.sqrt.3 +++ b/man/man3/tomo-Num.sqrt.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.sqrt 3 2025-09-21 "Tomo man-pages" +.TH Num.sqrt 3 2025-11-29 "Tomo man-pages" .SH NAME Num.sqrt \- square root .SH LIBRARY @@ -31,3 +31,5 @@ The square root of `x`. .EX assert (16.0).sqrt() == 4 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.tan.3 b/man/man3/tomo-Num.tan.3 index 3e66ebb7..a159a4fa 100644 --- a/man/man3/tomo-Num.tan.3 +++ b/man/man3/tomo-Num.tan.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.tan 3 2025-09-21 "Tomo man-pages" +.TH Num.tan 3 2025-11-29 "Tomo man-pages" .SH NAME Num.tan \- tangent .SH LIBRARY @@ -31,3 +31,5 @@ The tangent of `x`. .EX assert (0.0).tan() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.tanh.3 b/man/man3/tomo-Num.tanh.3 index 86c97449..0bd99114 100644 --- a/man/man3/tomo-Num.tanh.3 +++ b/man/man3/tomo-Num.tanh.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.tanh 3 2025-09-21 "Tomo man-pages" +.TH Num.tanh 3 2025-11-29 "Tomo man-pages" .SH NAME Num.tanh \- hyperbolic tangent .SH LIBRARY @@ -31,3 +31,5 @@ The hyperbolic tangent of `x`. .EX assert (0.0).tanh() == 0 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.tgamma.3 b/man/man3/tomo-Num.tgamma.3 index c2cfb0f1..3aa2b61a 100644 --- a/man/man3/tomo-Num.tgamma.3 +++ b/man/man3/tomo-Num.tgamma.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.tgamma 3 2025-09-21 "Tomo man-pages" +.TH Num.tgamma 3 2025-11-29 "Tomo man-pages" .SH NAME Num.tgamma \- true gamma function .SH LIBRARY @@ -31,3 +31,5 @@ The gamma function of `x`. .EX assert (1.0).tgamma() == 1 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.trunc.3 b/man/man3/tomo-Num.trunc.3 index e32b0949..7de1bbea 100644 --- a/man/man3/tomo-Num.trunc.3 +++ b/man/man3/tomo-Num.trunc.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.trunc 3 2025-09-21 "Tomo man-pages" +.TH Num.trunc 3 2025-11-29 "Tomo man-pages" .SH NAME Num.trunc \- truncate a number .SH LIBRARY @@ -32,3 +32,5 @@ The integer part of `x` towards zero. assert (3.7).trunc() == 3 assert (-3.7).trunc() == -3 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.with_precision.3 b/man/man3/tomo-Num.with_precision.3 index 66b5c0e9..5b1dc30c 100644 --- a/man/man3/tomo-Num.with_precision.3 +++ b/man/man3/tomo-Num.with_precision.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.with_precision 3 2025-09-21 "Tomo man-pages" +.TH Num.with_precision 3 2025-11-29 "Tomo man-pages" .SH NAME Num.with_precision \- round to a given precision .SH LIBRARY @@ -34,3 +34,5 @@ assert (0.1234567).with_precision(0.01) == 0.12 assert (123456.).with_precision(100) == 123500 assert (1234567.).with_precision(5) == 1234565 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.y0.3 b/man/man3/tomo-Num.y0.3 index 0645c252..7c20e545 100644 --- a/man/man3/tomo-Num.y0.3 +++ b/man/man3/tomo-Num.y0.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.y0 3 2025-09-21 "Tomo man-pages" +.TH Num.y0 3 2025-11-29 "Tomo man-pages" .SH NAME Num.y0 \- Bessel function .SH LIBRARY @@ -31,3 +31,5 @@ The Bessel function of the second kind of order 0 of `x`. .EX assert (1.0).y0() == -0.7652 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Num.y1.3 b/man/man3/tomo-Num.y1.3 index ef572874..1fee30e7 100644 --- a/man/man3/tomo-Num.y1.3 +++ b/man/man3/tomo-Num.y1.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Num.y1 3 2025-09-21 "Tomo man-pages" +.TH Num.y1 3 2025-11-29 "Tomo man-pages" .SH NAME Num.y1 \- Bessel function .SH LIBRARY @@ -31,3 +31,5 @@ The Bessel function of the second kind of order 1 of `x`. .EX assert (1.0).y1() == 0.4401 .EE +.SH SEE ALSO +.BR Tomo-Num (3) diff --git a/man/man3/tomo-Path.3 b/man/man3/tomo-Path.3 new file mode 100644 index 00000000..f383196e --- /dev/null +++ b/man/man3/tomo-Path.3 @@ -0,0 +1,347 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Path 3 2025-11-29 "Tomo man-pages" +.SH NAME +Path \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Path.accessed\ :\ func(path:\ Path,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Int64?) +Gets the file access time of a file. + +For more, see: +.BR Tomo-Path.accessed (3) + + +.TP +.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void) +Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. + +For more, see: +.BR Tomo-Path.append (3) + + +.TP +.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void) +Appends the given bytes to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. + +For more, see: +.BR Tomo-Path.append_bytes (3) + + +.TP +.BI Path.base_name\ :\ func(path:\ Path\ ->\ Text) +Returns the base name of the file or directory at the specified path. + +For more, see: +.BR Tomo-Path.base_name (3) + + +.TP +.BI Path.by_line\ :\ func(path:\ Path\ ->\ func(->Text?)?) +Returns an iterator that can be used to iterate over a file one line at a time, or returns none if the file could not be opened. + +For more, see: +.BR Tomo-Path.by_line (3) + + +.TP +.BI Path.can_execute\ :\ func(path:\ Path\ ->\ Bool) +Returns whether or not a file can be executed by the current user/group. + +For more, see: +.BR Tomo-Path.can_execute (3) + + +.TP +.BI Path.can_read\ :\ func(path:\ Path\ ->\ Bool) +Returns whether or not a file can be read by the current user/group. + +For more, see: +.BR Tomo-Path.can_read (3) + + +.TP +.BI Path.can_write\ :\ func(path:\ Path\ ->\ Bool) +Returns whether or not a file can be written by the current user/group. + +For more, see: +.BR Tomo-Path.can_write (3) + + +.TP +.BI Path.changed\ :\ func(path:\ Path,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Int64?) +Gets the file change time of a file. + +For more, see: +.BR Tomo-Path.changed (3) + + +.TP +.BI Path.child\ :\ func(path:\ Path,\ child:\ Text\ ->\ Path) +Return a path that is a child of another path. + +For more, see: +.BR Tomo-Path.child (3) + + +.TP +.BI Path.children\ :\ func(path:\ Path,\ include_hidden\ =\ no\ ->\ [Path]) +Returns a list of children (files and directories) within the directory at the specified path. Optionally includes hidden files. + +For more, see: +.BR Tomo-Path.children (3) + + +.TP +.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Void) +Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. + +For more, see: +.BR Tomo-Path.create_directory (3) + + +.TP +.BI Path.current_dir\ :\ func(->\ Path) +Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. + +For more, see: +.BR Tomo-Path.current_dir (3) + + +.TP +.BI Path.exists\ :\ func(path:\ Path\ ->\ Bool) +Checks if a file or directory exists at the specified path. + +For more, see: +.BR Tomo-Path.exists (3) + + +.TP +.BI Path.expand_home\ :\ func(path:\ Path\ ->\ Path) +For home-based paths (those starting with \fB~\fR), expand the path to replace the tilde with and absolute path to the user's \fB$HOME\fR directory. + +For more, see: +.BR Tomo-Path.expand_home (3) + + +.TP +.BI Path.extension\ :\ func(path:\ Path,\ full:\ Bool\ =\ yes\ ->\ Text) +Returns the file extension of the file at the specified path. Optionally returns the full extension. + +For more, see: +.BR Tomo-Path.extension (3) + + +.TP +.BI Path.files\ :\ func(path:\ Path,\ include_hidden:\ Bool\ =\ no\ ->\ [Path]) +Returns a list of files within the directory at the specified path. Optionally includes hidden files. + +For more, see: +.BR Tomo-Path.files (3) + + +.TP +.BI Path.from_components\ :\ func(components:\ [Text]\ ->\ Path) +Returns a path built from a list of path components. + +For more, see: +.BR Tomo-Path.from_components (3) + + +.TP +.BI Path.glob\ :\ func(path:\ Path\ ->\ [Path]) +Perform a globbing operation and return a list of matching paths. Some glob specific details: - The paths "." and ".." are *not* included in any globbing results. - Files or directories that begin with "." will not match \fB*\fR, but will match \fB.*\fR. - Globs do support \fB{a,b}\fR syntax for matching files that match any of several choices of patterns. - The shell-style syntax \fB**\fR for matching subdirectories is not supported. + +For more, see: +.BR Tomo-Path.glob (3) + + +.TP +.BI Path.group\ :\ func(path:\ Path,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Text?) +Get the owning group of a file or directory. + +For more, see: +.BR Tomo-Path.group (3) + + +.TP +.BI Path.has_extension\ :\ func(path:\ Path,\ extension:\ Text\ ->\ Bool) +Return whether or not a path has a given file extension. + +For more, see: +.BR Tomo-Path.has_extension (3) + + +.TP +.BI Path.is_directory\ :\ func(path:\ Path,\ follow_symlinks\ =\ yes\ ->\ Bool) +Checks if the path represents a directory. Optionally follows symbolic links. + +For more, see: +.BR Tomo-Path.is_directory (3) + + +.TP +.BI Path.is_file\ :\ func(path:\ Path,\ follow_symlinks\ =\ yes\ ->\ Bool) +Checks if the path represents a file. Optionally follows symbolic links. + +For more, see: +.BR Tomo-Path.is_file (3) + + +.TP +.BI Path.is_socket\ :\ func(path:\ Path,\ follow_symlinks\ =\ yes\ ->\ Bool) +Checks if the path represents a socket. Optionally follows symbolic links. + +For more, see: +.BR Tomo-Path.is_socket (3) + + +.TP +.BI Path.is_symlink\ :\ func(path:\ Path\ ->\ Bool) +Checks if the path represents a symbolic link. + +For more, see: +.BR Tomo-Path.is_symlink (3) + + +.TP +.BI Path.lines\ :\ func(path:\ Path\ ->\ [Text]?) +Returns a list with the lines of text in a file or returns none if the file could not be opened. + +For more, see: +.BR Tomo-Path.lines (3) + + +.TP +.BI Path.modified\ :\ func(path:\ Path,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Int64?) +Gets the file modification time of a file. + +For more, see: +.BR Tomo-Path.modified (3) + + +.TP +.BI Path.owner\ :\ func(path:\ Path,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Text?) +Get the owning user of a file or directory. + +For more, see: +.BR Tomo-Path.owner (3) + + +.TP +.BI Path.parent\ :\ func(path:\ Path\ ->\ Path) +Returns the parent directory of the file or directory at the specified path. + +For more, see: +.BR Tomo-Path.parent (3) + + +.TP +.BI Path.read\ :\ func(path:\ Path\ ->\ Text?) +Reads the contents of the file at the specified path or none if the file could not be read. + +For more, see: +.BR Tomo-Path.read (3) + + +.TP +.BI Path.read_bytes\ :\ func(path:\ Path,\ limit:\ Int?\ =\ none\ ->\ [Byte]?) +Reads the contents of the file at the specified path or none if the file could not be read. + +For more, see: +.BR Tomo-Path.read_bytes (3) + + +.TP +.BI Path.relative_to\ :\ func(path:\ Path,\ relative_to\ =\ (./)\ ->\ Path) +Returns the path relative to a given base path. By default, the base path is the current directory. + +For more, see: +.BR Tomo-Path.relative_to (3) + + +.TP +.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Void) +Removes the file or directory at the specified path. A runtime error is raised if something goes wrong. + +For more, see: +.BR Tomo-Path.remove (3) + + +.TP +.BI Path.resolved\ :\ func(path:\ Path,\ relative_to\ =\ (./)\ ->\ Path) +Resolves the absolute path of the given path relative to a base path. By default, the base path is the current directory. + +For more, see: +.BR Tomo-Path.resolved (3) + + +.TP +.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Void) +Set the owning user and/or group for a path. + +For more, see: +.BR Tomo-Path.set_owner (3) + + +.TP +.BI Path.sibling\ :\ func(path:\ Path,\ name:\ Text\ ->\ Path) +Return a path that is a sibling of another path (i.e. has the same parent, but a different name). This is equivalent to \fB.parent().child(name)\fR + +For more, see: +.BR Tomo-Path.sibling (3) + + +.TP +.BI Path.subdirectories\ :\ func(path:\ Path,\ include_hidden\ =\ no\ ->\ [Path]) +Returns a list of subdirectories within the directory at the specified path. Optionally includes hidden subdirectories. + +For more, see: +.BR Tomo-Path.subdirectories (3) + + +.TP +.BI Path.unique_directory\ :\ func(path:\ Path\ ->\ Path) +Generates a unique directory path based on the given path. Useful for creating temporary directories. + +For more, see: +.BR Tomo-Path.unique_directory (3) + + +.TP +.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Void) +Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. + +For more, see: +.BR Tomo-Path.write (3) + + +.TP +.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Void) +Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. + +For more, see: +.BR Tomo-Path.write_bytes (3) + + +.TP +.BI Path.write_unique\ :\ func(path:\ Path,\ text:\ Text\ ->\ Path) +Writes the given text to a unique file path based on the specified path. The file is created if it doesn't exist. This is useful for creating temporary files. + +For more, see: +.BR Tomo-Path.write_unique (3) + + +.TP +.BI Path.write_unique_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte]\ ->\ Path) +Writes the given bytes to a unique file path based on the specified path. The file is created if it doesn't exist. This is useful for creating temporary files. + +For more, see: +.BR Tomo-Path.write_unique_bytes (3) + diff --git a/man/man3/tomo-Path.accessed.3 b/man/man3/tomo-Path.accessed.3 index 51e4bbed..0c09b7cf 100644 --- a/man/man3/tomo-Path.accessed.3 +++ b/man/man3/tomo-Path.accessed.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.accessed 3 2025-09-21 "Tomo man-pages" +.TH Path.accessed 3 2025-11-29 "Tomo man-pages" .SH NAME Path.accessed \- access time .SH LIBRARY @@ -33,3 +33,5 @@ A 64-bit unix epoch timestamp representing when the file or directory was last a assert (./file.txt).accessed() == 1704221100 assert (./not-a-file).accessed() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.append.3 b/man/man3/tomo-Path.append.3 index af1f0a5f..b065d55b 100644 --- a/man/man3/tomo-Path.append.3 +++ b/man/man3/tomo-Path.append.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.append 3 2025-05-17 "Tomo man-pages" +.TH Path.append 3 2025-11-29 "Tomo man-pages" .SH NAME Path.append \- append to a file .SH LIBRARY @@ -33,3 +33,5 @@ Nothing. .EX (./log.txt).append("extra line$(\[rs]n)") .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.append_bytes.3 b/man/man3/tomo-Path.append_bytes.3 index 3a6dc1c8..48f795c3 100644 --- a/man/man3/tomo-Path.append_bytes.3 +++ b/man/man3/tomo-Path.append_bytes.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.append_bytes 3 2025-05-17 "Tomo man-pages" +.TH Path.append_bytes 3 2025-11-29 "Tomo man-pages" .SH NAME Path.append_bytes \- append bytes to a file .SH LIBRARY @@ -33,3 +33,5 @@ Nothing. .EX (./log.txt).append_bytes([104, 105]) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.base_name.3 b/man/man3/tomo-Path.base_name.3 index fe43fc04..3d6e0f07 100644 --- a/man/man3/tomo-Path.base_name.3 +++ b/man/man3/tomo-Path.base_name.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.base_name 3 2025-09-21 "Tomo man-pages" +.TH Path.base_name 3 2025-11-29 "Tomo man-pages" .SH NAME Path.base_name \- base name of a file .SH LIBRARY @@ -31,3 +31,5 @@ The base name of the file or directory. .EX assert (./path/to/file.txt).base_name() == "file.txt" .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.by_line.3 b/man/man3/tomo-Path.by_line.3 index 02a75c5a..ad08ca33 100644 --- a/man/man3/tomo-Path.by_line.3 +++ b/man/man3/tomo-Path.by_line.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.by_line 3 2025-11-15 "Tomo man-pages" +.TH Path.by_line 3 2025-11-29 "Tomo man-pages" .SH NAME Path.by_line \- iterate by line .SH LIBRARY @@ -40,3 +40,5 @@ else for line in (/dev/stdin).by_line()! say(line.upper()) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.can_execute.3 b/man/man3/tomo-Path.can_execute.3 index 7f9ed358..90978a32 100644 --- a/man/man3/tomo-Path.can_execute.3 +++ b/man/man3/tomo-Path.can_execute.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.can_execute 3 2025-09-21 "Tomo man-pages" +.TH Path.can_execute 3 2025-11-29 "Tomo man-pages" .SH NAME Path.can_execute \- check execute permissions .SH LIBRARY @@ -33,3 +33,5 @@ assert (/bin/sh).can_execute() == yes assert (/usr/include/stdlib.h).can_execute() == no assert (/non/existant/file).can_execute() == no .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.can_read.3 b/man/man3/tomo-Path.can_read.3 index 30cb39a0..919b5312 100644 --- a/man/man3/tomo-Path.can_read.3 +++ b/man/man3/tomo-Path.can_read.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.can_read 3 2025-09-21 "Tomo man-pages" +.TH Path.can_read 3 2025-11-29 "Tomo man-pages" .SH NAME Path.can_read \- check read permissions .SH LIBRARY @@ -33,3 +33,5 @@ assert (/usr/include/stdlib.h).can_read() == yes assert (/etc/shadow).can_read() == no assert (/non/existant/file).can_read() == no .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.can_write.3 b/man/man3/tomo-Path.can_write.3 index d1be5006..98e8c0bb 100644 --- a/man/man3/tomo-Path.can_write.3 +++ b/man/man3/tomo-Path.can_write.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.can_write 3 2025-09-21 "Tomo man-pages" +.TH Path.can_write 3 2025-11-29 "Tomo man-pages" .SH NAME Path.can_write \- check write permissions .SH LIBRARY @@ -33,3 +33,5 @@ assert (/tmp).can_write() == yes assert (/etc/passwd).can_write() == no assert (/non/existant/file).can_write() == no .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.changed.3 b/man/man3/tomo-Path.changed.3 index b6bd8033..ab32a94f 100644 --- a/man/man3/tomo-Path.changed.3 +++ b/man/man3/tomo-Path.changed.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.changed 3 2025-09-21 "Tomo man-pages" +.TH Path.changed 3 2025-11-29 "Tomo man-pages" .SH NAME Path.changed \- get the last changed time .SH LIBRARY @@ -36,3 +36,5 @@ This is the ["ctime"](https://en.wikipedia.org/wiki/Stat_(system_call)#ctime) of assert (./file.txt).changed() == 1704221100 assert (./not-a-file).changed() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.child.3 b/man/man3/tomo-Path.child.3 index f61bc9d0..ee0ca448 100644 --- a/man/man3/tomo-Path.child.3 +++ b/man/man3/tomo-Path.child.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.child 3 2025-09-21 "Tomo man-pages" +.TH Path.child 3 2025-11-29 "Tomo man-pages" .SH NAME Path.child \- append a child to a path .SH LIBRARY @@ -32,3 +32,5 @@ A new path representing the child. .EX assert (./directory).child("file.txt") == (./directory/file.txt) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.children.3 b/man/man3/tomo-Path.children.3 index 10e80713..feee88d9 100644 --- a/man/man3/tomo-Path.children.3 +++ b/man/man3/tomo-Path.children.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.children 3 2025-09-21 "Tomo man-pages" +.TH Path.children 3 2025-11-29 "Tomo man-pages" .SH NAME Path.children \- get children of a directory .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default path Path The path of the directory. - -include_hidden Whether to include hidden files, which start with a `.`. no +include_hidden Whether to include hidden files, which start with a \fB.\fR. no .TE .SH RETURN A list of paths for the children. @@ -32,3 +32,5 @@ A list of paths for the children. .EX assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"] .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.create_directory.3 b/man/man3/tomo-Path.create_directory.3 index db50d468..539aae36 100644 --- a/man/man3/tomo-Path.create_directory.3 +++ b/man/man3/tomo-Path.create_directory.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.create_directory 3 2025-11-27 "Tomo man-pages" +.TH Path.create_directory 3 2025-11-29 "Tomo man-pages" .SH NAME Path.create_directory \- make a directory .SH LIBRARY @@ -24,7 +24,7 @@ l l l l. Name Type Description Default path Path The path of the directory to create. - permissions The permissions to set on the new directory. Int32(0o755) -recursive If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. yes +recursive If set to \fByes\fR, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to \fByes\fR, this function behaves like \fBmkdir -p\fR. yes .TE .SH RETURN Nothing. @@ -35,3 +35,5 @@ Nothing. .EX (./new_directory).create_directory() .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.current_dir.3 b/man/man3/tomo-Path.current_dir.3 index 2d887983..8f9f58c3 100644 --- a/man/man3/tomo-Path.current_dir.3 +++ b/man/man3/tomo-Path.current_dir.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.current_dir 3 2025-09-21 "Tomo man-pages" +.TH Path.current_dir 3 2025-11-29 "Tomo man-pages" .SH NAME Path.current_dir \- get current directory .SH LIBRARY @@ -22,3 +22,5 @@ The absolute path of the current directory. .EX assert Path.current_dir() == (/home/user/tomo) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.exists.3 b/man/man3/tomo-Path.exists.3 index 923fa27d..5143a4d3 100644 --- a/man/man3/tomo-Path.exists.3 +++ b/man/man3/tomo-Path.exists.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.exists 3 2025-09-21 "Tomo man-pages" +.TH Path.exists 3 2025-11-29 "Tomo man-pages" .SH NAME Path.exists \- check if a path exists .SH LIBRARY @@ -31,3 +31,5 @@ path Path The path to check. - .EX assert (/).exists() == yes .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.expand_home.3 b/man/man3/tomo-Path.expand_home.3 index 7114b65b..b14aff25 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-10-11 "Tomo man-pages" +.TH Path.expand_home 3 2025-11-29 "Tomo man-pages" .SH NAME Path.expand_home \- expand ~ to $HOME .SH LIBRARY @@ -34,3 +34,5 @@ assert (~/foo).expand_home() == (/home/user/foo) # No change assert (/foo).expand_home() == (/foo) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.extension.3 b/man/man3/tomo-Path.extension.3 index f77f692a..71b65f77 100644 --- a/man/man3/tomo-Path.extension.3 +++ b/man/man3/tomo-Path.extension.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.extension 3 2025-09-21 "Tomo man-pages" +.TH Path.extension 3 2025-11-29 "Tomo man-pages" .SH NAME Path.extension \- get file extension .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default path Path The path of the file. - -full Bool Whether to return everything after the first `.` in the base name, or only the last part of the extension. yes +full Bool Whether to return everything after the first \fB.\fR in the base name, or only the last part of the extension. yes .TE .SH RETURN The file extension (not including the leading `.`) or an empty text if there is no file extension. @@ -35,3 +35,5 @@ assert (./file.tar.gz).extension(full=no) == "gz" assert (/foo).extension() == "" assert (./.git).extension() == "" .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.files.3 b/man/man3/tomo-Path.files.3 index fcde934f..cf72a388 100644 --- a/man/man3/tomo-Path.files.3 +++ b/man/man3/tomo-Path.files.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.files 3 2025-09-21 "Tomo man-pages" +.TH Path.files 3 2025-11-29 "Tomo man-pages" .SH NAME Path.files \- list files in a directory .SH LIBRARY @@ -32,3 +32,5 @@ A list of file paths. .EX assert (./directory).files(include_hidden=yes) == [(./directory/file1.txt), (./directory/file2.txt)] .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.from_components.3 b/man/man3/tomo-Path.from_components.3 index f7dbd7f1..6ef19089 100644 --- a/man/man3/tomo-Path.from_components.3 +++ b/man/man3/tomo-Path.from_components.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.from_components 3 2025-09-21 "Tomo man-pages" +.TH Path.from_components 3 2025-11-29 "Tomo man-pages" .SH NAME Path.from_components \- build a path from components .SH LIBRARY @@ -33,3 +33,5 @@ assert Path.from_components(["/", "usr", "include"]) == (/usr/include) assert Path.from_components(["foo.txt"]) == (./foo.txt) assert Path.from_components(["~", ".local"]) == (~/.local) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.glob.3 b/man/man3/tomo-Path.glob.3 index 07de7a33..fc8d04b8 100644 --- a/man/man3/tomo-Path.glob.3 +++ b/man/man3/tomo-Path.glob.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.glob 3 2025-09-21 "Tomo man-pages" +.TH Path.glob 3 2025-11-29 "Tomo man-pages" .SH NAME Path.glob \- perform file globbing .SH LIBRARY @@ -28,7 +28,7 @@ allbox; lb lb lbx lb l l l l. Name Type Description Default -path Path The path of the directory which may contain special globbing characters like `*`, `?`, or `{...}` - +path Path The path of the directory which may contain special globbing characters like \fB*\fR, \fB?\fR, or \fB{...}\fR - .TE .SH RETURN A list of file paths that match the glob. @@ -44,3 +44,5 @@ assert (./.*).glob() == [(./.hidden)] # Globs with no matches return an empty list: assert (./*.xxx).glob() == [] .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.group.3 b/man/man3/tomo-Path.group.3 index 5c9f80c8..c7ebdb27 100644 --- a/man/man3/tomo-Path.group.3 +++ b/man/man3/tomo-Path.group.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.group 3 2025-09-21 "Tomo man-pages" +.TH Path.group 3 2025-11-29 "Tomo man-pages" .SH NAME Path.group \- get the owning group .SH LIBRARY @@ -33,3 +33,5 @@ The name of the group which owns the file or directory, or `none` if the path do assert (/bin).group() == "root" assert (/non/existent/file).group() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.has_extension.3 b/man/man3/tomo-Path.has_extension.3 index ffd1f967..85b181e1 100644 --- a/man/man3/tomo-Path.has_extension.3 +++ b/man/man3/tomo-Path.has_extension.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.has_extension 3 2025-09-21 "Tomo man-pages" +.TH Path.has_extension 3 2025-11-29 "Tomo man-pages" .SH NAME Path.has_extension \- check if a path has a given extension .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default path Path A path. - -extension Text A file extension (leading `.` is optional). If empty, the check will test if the file does not have any file extension. - +extension Text A file extension (leading \fB.\fR is optional). If empty, the check will test if the file does not have any file extension. - .TE .SH RETURN Whether or not the path has the given extension. @@ -35,3 +35,5 @@ assert (/foo.txt).has_extension(".txt") == yes assert (/foo.tar.gz).has_extension("gz") == yes assert (/foo.tar.gz).has_extension("zip") == no .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.is_directory.3 b/man/man3/tomo-Path.is_directory.3 index 8ed41249..81391108 100644 --- a/man/man3/tomo-Path.is_directory.3 +++ b/man/man3/tomo-Path.is_directory.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.is_directory 3 2025-09-21 "Tomo man-pages" +.TH Path.is_directory 3 2025-11-29 "Tomo man-pages" .SH NAME Path.is_directory \- check if a path is a directory .SH LIBRARY @@ -33,3 +33,5 @@ follow_symlinks Whether to follow symbolic links. yes assert (./directory/).is_directory() == yes assert (./file.txt).is_directory() == no .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.is_file.3 b/man/man3/tomo-Path.is_file.3 index 8d6218f3..7d7be65b 100644 --- a/man/man3/tomo-Path.is_file.3 +++ b/man/man3/tomo-Path.is_file.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.is_file 3 2025-09-21 "Tomo man-pages" +.TH Path.is_file 3 2025-11-29 "Tomo man-pages" .SH NAME Path.is_file \- check if a path is a file .SH LIBRARY @@ -33,3 +33,5 @@ follow_symlinks Whether to follow symbolic links. yes assert (./file.txt).is_file() == yes assert (./directory/).is_file() == no .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.is_socket.3 b/man/man3/tomo-Path.is_socket.3 index 1733364e..1da3bdce 100644 --- a/man/man3/tomo-Path.is_socket.3 +++ b/man/man3/tomo-Path.is_socket.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.is_socket 3 2025-09-21 "Tomo man-pages" +.TH Path.is_socket 3 2025-11-29 "Tomo man-pages" .SH NAME Path.is_socket \- check if a path is a socket .SH LIBRARY @@ -32,3 +32,5 @@ follow_symlinks Whether to follow symbolic links. yes .EX assert (./socket).is_socket() == yes .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.is_symlink.3 b/man/man3/tomo-Path.is_symlink.3 index 6a8bdb7e..85db4e43 100644 --- a/man/man3/tomo-Path.is_symlink.3 +++ b/man/man3/tomo-Path.is_symlink.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.is_symlink 3 2025-09-21 "Tomo man-pages" +.TH Path.is_symlink 3 2025-11-29 "Tomo man-pages" .SH NAME Path.is_symlink \- check if a path is a symbolic link .SH LIBRARY @@ -31,3 +31,5 @@ path Path The path to check. - .EX assert (./link).is_symlink() == yes .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.lines.3 b/man/man3/tomo-Path.lines.3 index e4e95e7e..cf9a2312 100644 --- a/man/man3/tomo-Path.lines.3 +++ b/man/man3/tomo-Path.lines.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.lines 3 2025-11-23 "Tomo man-pages" +.TH Path.lines 3 2025-11-29 "Tomo man-pages" .SH NAME Path.lines \- return the lines in a file .SH LIBRARY @@ -31,3 +31,5 @@ A list of the lines in a file or none if the file couldn't be read. .EX lines := (./file.txt).lines()! .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.modified.3 b/man/man3/tomo-Path.modified.3 index c91aae03..a5780e9f 100644 --- a/man/man3/tomo-Path.modified.3 +++ b/man/man3/tomo-Path.modified.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.modified 3 2025-09-21 "Tomo man-pages" +.TH Path.modified 3 2025-11-29 "Tomo man-pages" .SH NAME Path.modified \- get file modification time .SH LIBRARY @@ -33,3 +33,5 @@ A 64-bit unix epoch timestamp representing when the file or directory was last m assert (./file.txt).modified() == 1704221100 assert (./not-a-file).modified() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.owner.3 b/man/man3/tomo-Path.owner.3 index 2f4913cd..95bcd089 100644 --- a/man/man3/tomo-Path.owner.3 +++ b/man/man3/tomo-Path.owner.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.owner 3 2025-09-21 "Tomo man-pages" +.TH Path.owner 3 2025-11-29 "Tomo man-pages" .SH NAME Path.owner \- get file owner .SH LIBRARY @@ -33,3 +33,5 @@ The name of the user who owns the file or directory, or `none` if the path does assert (/bin).owner() == "root" assert (/non/existent/file).owner() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.parent.3 b/man/man3/tomo-Path.parent.3 index 459d97c3..7113f4e0 100644 --- a/man/man3/tomo-Path.parent.3 +++ b/man/man3/tomo-Path.parent.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.parent 3 2025-09-21 "Tomo man-pages" +.TH Path.parent 3 2025-11-29 "Tomo man-pages" .SH NAME Path.parent \- get parent directory .SH LIBRARY @@ -31,3 +31,5 @@ The path of the parent directory. .EX assert (./path/to/file.txt).parent() == (./path/to/) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.read.3 b/man/man3/tomo-Path.read.3 index 779bb397..7787b66a 100644 --- a/man/man3/tomo-Path.read.3 +++ b/man/man3/tomo-Path.read.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.read 3 2025-09-21 "Tomo man-pages" +.TH Path.read 3 2025-11-29 "Tomo man-pages" .SH NAME Path.read \- read file contents .SH LIBRARY @@ -32,3 +32,5 @@ The contents of the file. If the file could not be read, none will be returned. assert (./hello.txt).read() == "Hello" assert (./nosuchfile.xxx).read() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.read_bytes.3 b/man/man3/tomo-Path.read_bytes.3 index a23cbf6d..11c0310e 100644 --- a/man/man3/tomo-Path.read_bytes.3 +++ b/man/man3/tomo-Path.read_bytes.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.read_bytes 3 2025-09-21 "Tomo man-pages" +.TH Path.read_bytes 3 2025-11-29 "Tomo man-pages" .SH NAME Path.read_bytes \- read file contents as bytes .SH LIBRARY @@ -33,3 +33,5 @@ The byte contents of the file. If the file cannot be read, none will be returned assert (./hello.txt).read() == [72, 101, 108, 108, 111] assert (./nosuchfile.xxx).read() == none .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.relative_to.3 b/man/man3/tomo-Path.relative_to.3 index 963b9a5b..a3bf2136 100644 --- a/man/man3/tomo-Path.relative_to.3 +++ b/man/man3/tomo-Path.relative_to.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.relative_to 3 2025-09-21 "Tomo man-pages" +.TH Path.relative_to 3 2025-11-29 "Tomo man-pages" .SH NAME Path.relative_to \- apply a relative path to another .SH LIBRARY @@ -32,3 +32,5 @@ The relative path. .EX assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.remove.3 b/man/man3/tomo-Path.remove.3 index 7dcb4361..a98b4483 100644 --- a/man/man3/tomo-Path.remove.3 +++ b/man/man3/tomo-Path.remove.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.remove 3 2025-05-17 "Tomo man-pages" +.TH Path.remove 3 2025-11-29 "Tomo man-pages" .SH NAME Path.remove \- remove a file or directory .SH LIBRARY @@ -32,3 +32,5 @@ Nothing. .EX (./file.txt).remove() .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.resolved.3 b/man/man3/tomo-Path.resolved.3 index a40fd80c..1b22ec08 100644 --- a/man/man3/tomo-Path.resolved.3 +++ b/man/man3/tomo-Path.resolved.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.resolved 3 2025-09-21 "Tomo man-pages" +.TH Path.resolved 3 2025-11-29 "Tomo man-pages" .SH NAME Path.resolved \- resolve a path .SH LIBRARY @@ -33,3 +33,5 @@ The resolved absolute path. assert (~/foo).resolved() == (/home/user/foo) assert (./path/to/file.txt).resolved(relative_to=(/foo)) == (/foo/path/to/file.txt) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.set_owner.3 b/man/man3/tomo-Path.set_owner.3 index f83d9470..b0b24e71 100644 --- a/man/man3/tomo-Path.set_owner.3 +++ b/man/man3/tomo-Path.set_owner.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.set_owner 3 2025-05-17 "Tomo man-pages" +.TH Path.set_owner 3 2025-11-29 "Tomo man-pages" .SH NAME Path.set_owner \- set the owner .SH LIBRARY @@ -34,3 +34,5 @@ Nothing. If a path does not exist, a failure will be raised. .EX (./file.txt).set_owner(owner="root", group="wheel") .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.sibling.3 b/man/man3/tomo-Path.sibling.3 index 5c39d159..7d4bbd04 100644 --- a/man/man3/tomo-Path.sibling.3 +++ b/man/man3/tomo-Path.sibling.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.sibling 3 2025-09-21 "Tomo man-pages" +.TH Path.sibling 3 2025-11-29 "Tomo man-pages" .SH NAME Path.sibling \- get another path in the same directory .SH LIBRARY @@ -32,3 +32,5 @@ A new path representing the sibling. .EX assert (/foo/baz).sibling("doop") == (/foo/doop) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.subdirectories.3 b/man/man3/tomo-Path.subdirectories.3 index a691f94a..f2bb371b 100644 --- a/man/man3/tomo-Path.subdirectories.3 +++ b/man/man3/tomo-Path.subdirectories.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.subdirectories 3 2025-09-21 "Tomo man-pages" +.TH Path.subdirectories 3 2025-11-29 "Tomo man-pages" .SH NAME Path.subdirectories \- get subdirectories .SH LIBRARY @@ -33,3 +33,5 @@ A list of subdirectory paths. assert (./directory).subdirectories() == [(./directory/subdir1), (./directory/subdir2)] assert (./directory).subdirectories(include_hidden=yes) == [(./directory/.git), (./directory/subdir1), (./directory/subdir2)] .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.unique_directory.3 b/man/man3/tomo-Path.unique_directory.3 index 866ca565..16df8d8b 100644 --- a/man/man3/tomo-Path.unique_directory.3 +++ b/man/man3/tomo-Path.unique_directory.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.unique_directory 3 2025-09-21 "Tomo man-pages" +.TH Path.unique_directory 3 2025-11-29 "Tomo man-pages" .SH NAME Path.unique_directory \- create a directory with a unique name .SH LIBRARY @@ -22,7 +22,7 @@ allbox; lb lb lbx lb l l l l. Name Type Description Default -path Path The base path for generating the unique directory. The last six letters of this path must be `XXXXXX`. - +path Path The base path for generating the unique directory. The last six letters of this path must be \fBXXXXXX\fR. - .TE .SH RETURN A unique directory path after creating the directory. @@ -33,3 +33,5 @@ assert created := (/tmp/my-dir.XXXXXX).unique_directory() == (/tmp/my-dir-AwoxbM assert created.is_directory() == yes created.remove() .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.write.3 b/man/man3/tomo-Path.write.3 index 447e407e..6d9e1043 100644 --- a/man/man3/tomo-Path.write.3 +++ b/man/man3/tomo-Path.write.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.write 3 2025-05-17 "Tomo man-pages" +.TH Path.write 3 2025-11-29 "Tomo man-pages" .SH NAME Path.write \- write to a file .SH LIBRARY @@ -33,3 +33,5 @@ Nothing. .EX (./file.txt).write("Hello, world!") .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.write_bytes.3 b/man/man3/tomo-Path.write_bytes.3 index d914378f..6534d18e 100644 --- a/man/man3/tomo-Path.write_bytes.3 +++ b/man/man3/tomo-Path.write_bytes.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.write_bytes 3 2025-05-17 "Tomo man-pages" +.TH Path.write_bytes 3 2025-11-29 "Tomo man-pages" .SH NAME Path.write_bytes \- write bytes to a file .SH LIBRARY @@ -33,3 +33,5 @@ Nothing. .EX (./file.txt).write_bytes([104, 105]) .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.write_unique.3 b/man/man3/tomo-Path.write_unique.3 index 94f3cd6f..49d9aa78 100644 --- a/man/man3/tomo-Path.write_unique.3 +++ b/man/man3/tomo-Path.write_unique.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.write_unique 3 2025-09-21 "Tomo man-pages" +.TH Path.write_unique 3 2025-11-29 "Tomo man-pages" .SH NAME Path.write_unique \- write to a uniquely named file .SH LIBRARY @@ -22,7 +22,7 @@ allbox; lb lb lbx lb l l l l. Name Type Description Default -path Path The base path for generating the unique file. This path must include the string `XXXXXX` in the file base name. - +path Path The base path for generating the unique file. This path must include the string \fBXXXXXX\fR in the file base name. - text Text The text to write to the file. - .TE .SH RETURN @@ -35,3 +35,5 @@ assert created == (./file-27QHtq.txt) assert created.read() == "Hello, world!" created.remove() .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.write_unique_bytes.3 b/man/man3/tomo-Path.write_unique_bytes.3 index f4a82e63..5d08812e 100644 --- a/man/man3/tomo-Path.write_unique_bytes.3 +++ b/man/man3/tomo-Path.write_unique_bytes.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.write_unique_bytes 3 2025-09-21 "Tomo man-pages" +.TH Path.write_unique_bytes 3 2025-11-29 "Tomo man-pages" .SH NAME Path.write_unique_bytes \- write bytes to a uniquely named file .SH LIBRARY @@ -22,7 +22,7 @@ allbox; lb lb lbx lb l l l l. Name Type Description Default -path Path The base path for generating the unique file. This path must include the string `XXXXXX` in the file base name. - +path Path The base path for generating the unique file. This path must include the string \fBXXXXXX\fR in the file base name. - bytes [Byte] The bytes to write to the file. - .TE .SH RETURN @@ -35,3 +35,5 @@ assert created == (./file-27QHtq.txt) assert created.read() == [1, 2, 3] created.remove() .EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Table.3 b/man/man3/tomo-Table.3 new file mode 100644 index 00000000..f12f5b18 --- /dev/null +++ b/man/man3/tomo-Table.3 @@ -0,0 +1,99 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Table 3 2025-11-29 "Tomo man-pages" +.SH NAME +Table \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Table.clear\ :\ func(t:\ &{K:V}\ ->\ Void) +Removes all key-value pairs from the table. + +For more, see: +.BR Tomo-Table.clear (3) + + +.TP +.BI Table.difference\ :\ func(t:\ {K:V},\ other:\ {K:V}\ ->\ {K:V}) +Return a table whose key/value pairs correspond to keys only present in one table, but not the other. + +For more, see: +.BR Tomo-Table.difference (3) + + +.TP +.BI Table.get\ :\ func(t:\ {K:V},\ key:\ K\ ->\ V?) +Retrieves the value associated with a key, or returns \fBnone\fR if the key is not present. + +For more, see: +.BR Tomo-Table.get (3) + + +.TP +.BI Table.get_or_set\ :\ func(t:\ &{K:V},\ key:\ K,\ default:\ V\ ->\ V?) +If the given key is in the table, return the associated value. Otherwise, insert the given default value into the table and return it. + +For more, see: +.BR Tomo-Table.get_or_set (3) + + +.TP +.BI Table.has\ :\ func(t:\ {K:V},\ key:\ K\ ->\ Bool) +Checks if the table contains a specified key. + +For more, see: +.BR Tomo-Table.has (3) + + +.TP +.BI Table.intersection\ :\ func(t:\ {K:V},\ other:\ {K:V}\ ->\ {K:V}) +Return a table with only the matching key/value pairs that are common to both tables. + +For more, see: +.BR Tomo-Table.intersection (3) + + +.TP +.BI Table.remove\ :\ func(t:\ {K:V},\ key:\ K\ ->\ Void) +Removes the key-value pair associated with a specified key. + +For more, see: +.BR Tomo-Table.remove (3) + + +.TP +.BI Table.set\ :\ func(t:\ {K:V},\ key:\ K,\ value:\ V\ ->\ Void) +Sets or updates the value associated with a specified key. + +For more, see: +.BR Tomo-Table.set (3) + + +.TP +.BI Table.with\ :\ func(t:\ {K:V},\ other:\ {K:V}\ ->\ {K:V}) +Return a copy of a table with values added from another table + +For more, see: +.BR Tomo-Table.with (3) + + +.TP +.BI Table.with_fallback\ :\ func(t:\ {K:V},\ fallback:\ {K:V}?\ ->\ {K:V}) +Return a copy of a table with a different fallback table. + +For more, see: +.BR Tomo-Table.with_fallback (3) + + +.TP +.BI Table.without\ :\ func(t:\ {K:V},\ other:\ {K:V}\ ->\ {K:V}) +Return a copy of a table, but without any of the exact key/value pairs found in the other table. + +For more, see: +.BR Tomo-Table.without (3) + diff --git a/man/man3/tomo-Table.clear.3 b/man/man3/tomo-Table.clear.3 index 5b1be29e..18938cb1 100644 --- a/man/man3/tomo-Table.clear.3 +++ b/man/man3/tomo-Table.clear.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.clear 3 2025-09-21 "Tomo man-pages" +.TH Table.clear 3 2025-11-29 "Tomo man-pages" .SH NAME Table.clear \- clear a table .SH LIBRARY @@ -33,3 +33,5 @@ t := &{"A":1} t.clear() assert t == {} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.difference.3 b/man/man3/tomo-Table.difference.3 index 8f123834..999f0e55 100644 --- a/man/man3/tomo-Table.difference.3 +++ b/man/man3/tomo-Table.difference.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.difference 3 2025-09-21 "Tomo man-pages" +.TH Table.difference 3 2025-11-29 "Tomo man-pages" .SH NAME Table.difference \- return a table using keys not present in both tables .SH LIBRARY @@ -34,3 +34,5 @@ t1 := {"A": 1; "B": 2, "C": 3} t2 := {"B": 2, "C":30, "D": 40} assert t1.difference(t2) == {"A": 1, "D": 40} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.get.3 b/man/man3/tomo-Table.get.3 index ede699cf..e12143f1 100644 --- a/man/man3/tomo-Table.get.3 +++ b/man/man3/tomo-Table.get.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.get 3 2025-09-21 "Tomo man-pages" +.TH Table.get 3 2025-11-29 "Tomo man-pages" .SH NAME Table.get \- get an item from a table .SH LIBRARY @@ -39,3 +39,5 @@ assert t.get("????") == none assert t.get("A")! == 1 assert t.get("????") or 0 == 0 .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.get_or_set.3 b/man/man3/tomo-Table.get_or_set.3 index 38e73a4b..a5d100c4 100644 --- a/man/man3/tomo-Table.get_or_set.3 +++ b/man/man3/tomo-Table.get_or_set.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.get_or_set 3 2025-09-21 "Tomo man-pages" +.TH Table.get_or_set 3 2025-11-29 "Tomo man-pages" .SH NAME Table.get_or_set \- get an item or set a default if absent .SH LIBRARY @@ -43,3 +43,5 @@ assert t == &{"A": @[1, 2, 3, 4], "B": @[99]} assert t.get_or_set("C", @[0, 0, 0]) == @[0, 0, 0] assert t == &{"A": @[1, 2, 3, 4], "B": @[99], "C": @[0, 0, 0]} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.has.3 b/man/man3/tomo-Table.has.3 index b2efffd6..5bfb9554 100644 --- a/man/man3/tomo-Table.has.3 +++ b/man/man3/tomo-Table.has.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.has 3 2025-09-21 "Tomo man-pages" +.TH Table.has 3 2025-11-29 "Tomo man-pages" .SH NAME Table.has \- check for a key .SH LIBRARY @@ -33,3 +33,5 @@ key K The key to check for presence. - assert {"A": 1, "B": 2}.has("A") == yes assert {"A": 1, "B": 2}.has("xxx") == no .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.intersection.3 b/man/man3/tomo-Table.intersection.3 index 64a2e32c..67b4c0d3 100644 --- a/man/man3/tomo-Table.intersection.3 +++ b/man/man3/tomo-Table.intersection.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.intersection 3 2025-09-21 "Tomo man-pages" +.TH Table.intersection 3 2025-11-29 "Tomo man-pages" .SH NAME Table.intersection \- return a table with common key/value pairs from two tables .SH LIBRARY @@ -34,3 +34,5 @@ t1 := {"A": 1; "B": 2, "C": 3} t2 := {"B": 2, "C":30, "D": 40} assert t1.intersection(t2) == {"B": 2} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.remove.3 b/man/man3/tomo-Table.remove.3 index ce6010b3..d41c2ec0 100644 --- a/man/man3/tomo-Table.remove.3 +++ b/man/man3/tomo-Table.remove.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.remove 3 2025-09-21 "Tomo man-pages" +.TH Table.remove 3 2025-11-29 "Tomo man-pages" .SH NAME Table.remove \- remove a table entry .SH LIBRARY @@ -34,3 +34,5 @@ t := {"A": 1, "B": 2} t.remove("A") assert t == {"B": 2} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.set.3 b/man/man3/tomo-Table.set.3 index f1303189..f321f8eb 100644 --- a/man/man3/tomo-Table.set.3 +++ b/man/man3/tomo-Table.set.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.set 3 2025-09-21 "Tomo man-pages" +.TH Table.set 3 2025-11-29 "Tomo man-pages" .SH NAME Table.set \- set a table entry .SH LIBRARY @@ -35,3 +35,5 @@ t := {"A": 1, "B": 2} t.set("C", 3) assert t == {"A": 1, "B": 2, "C": 3} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.with.3 b/man/man3/tomo-Table.with.3 index 988fb888..740a1058 100644 --- a/man/man3/tomo-Table.with.3 +++ b/man/man3/tomo-Table.with.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.with 3 2025-09-21 "Tomo man-pages" +.TH Table.with 3 2025-11-29 "Tomo man-pages" .SH NAME Table.with \- return a table with values added from another table .SH LIBRARY @@ -33,3 +33,5 @@ The original table, but with values from the other table added. t := {"A": 1; "B": 2} assert t.with({"B": 20, "C": 30}) == {"A": 1, "B": 20, "C": 30} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.with_fallback.3 b/man/man3/tomo-Table.with_fallback.3 index c0719bd3..7ef520f0 100644 --- a/man/man3/tomo-Table.with_fallback.3 +++ b/man/man3/tomo-Table.with_fallback.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.with_fallback 3 2025-09-21 "Tomo man-pages" +.TH Table.with_fallback 3 2025-11-29 "Tomo man-pages" .SH NAME Table.with_fallback \- return a table with a new fallback .SH LIBRARY @@ -36,3 +36,5 @@ assert t2["B"] == 3 t3 = t.with_fallback(none) assert t2["B"] == none .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Table.without.3 b/man/man3/tomo-Table.without.3 index 7244dac6..c71b96a3 100644 --- a/man/man3/tomo-Table.without.3 +++ b/man/man3/tomo-Table.without.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Table.without 3 2025-09-21 "Tomo man-pages" +.TH Table.without 3 2025-11-29 "Tomo man-pages" .SH NAME Table.without \- return a table without key/value pairs in another table .SH LIBRARY @@ -36,3 +36,5 @@ Only exact key/value pairs will be discarded. Keys with a non-matching value wil t := {"A": 1; "B": 2, "C": 3} assert t.without({"B": 2, "C": 30, "D": 40}) == {"A": 1, "C": 3} .EE +.SH SEE ALSO +.BR Tomo-Table (3) diff --git a/man/man3/tomo-Text.3 b/man/man3/tomo-Text.3 new file mode 100644 index 00000000..634e3e0a --- /dev/null +++ b/man/man3/tomo-Text.3 @@ -0,0 +1,339 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Text 3 2025-11-29 "Tomo man-pages" +.SH NAME +Text \- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS + +.TP +.BI Text.as_c_string\ :\ func(text:\ Text\ ->\ CString) +Converts a \fBText\fR value to a C-style string. + +For more, see: +.BR Tomo-Text.as_c_string (3) + + +.TP +.BI Text.at\ :\ func(text:\ Text,\ index:\ Int\ ->\ Text) +Get the graphical cluster at a given index. This is similar to \fBstr[i]\fR with ASCII text, but has more correct behavior for unicode text. + +For more, see: +.BR Tomo-Text.at (3) + + +.TP +.BI Text.by_line\ :\ func(text:\ Text\ ->\ func(->Text?)) +Returns an iterator function that can be used to iterate over the lines in a text. + +For more, see: +.BR Tomo-Text.by_line (3) + + +.TP +.BI Text.by_split\ :\ func(text:\ Text,\ delimiter:\ Text\ =\ ""\ ->\ func(->Text?)) +Returns an iterator function that can be used to iterate over text separated by a delimiter. + +For more, see: +.BR Tomo-Text.by_split (3) + + +.TP +.BI Text.by_split_any\ :\ func(text:\ Text,\ delimiters:\ Text\ =\ "\ $\[rs]t\[rs]r\[rs]n"\ ->\ func(->Text?)) +Returns an iterator function that can be used to iterate over text separated by one or more characters (grapheme clusters) from a given text of delimiters. + +For more, see: +.BR Tomo-Text.by_split_any (3) + + +.TP +.BI Text.caseless_equals\ :\ func(a:\ Text,\ b:\ Text,\ language:\ Text\ =\ "C"\ ->\ Bool) +Checks whether two texts are equal, ignoring the casing of the letters (i.e. case-insensitive comparison). + +For more, see: +.BR Tomo-Text.caseless_equals (3) + + +.TP +.BI Text.codepoint_names\ :\ func(text:\ Text\ ->\ [Text]) +Returns a list of the names of each codepoint in the text. + +For more, see: +.BR Tomo-Text.codepoint_names (3) + + +.TP +.BI Text.ends_with\ :\ func(text:\ Text,\ suffix:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Bool) +Checks if the \fBText\fR ends with a literal suffix text. + +For more, see: +.BR Tomo-Text.ends_with (3) + + +.TP +.BI Text.find\ :\ func(text:\ Text,\ target:\ Text,\ start:\ Int\ =\ 1\ ->\ Int) +Find a substring within a text and return its index, if found. + +For more, see: +.BR Tomo-Text.find (3) + + +.TP +.BI Text.from\ :\ func(text:\ Text,\ first:\ Int\ ->\ Text) +Get a slice of the text, starting at the given position. + +For more, see: +.BR Tomo-Text.from (3) + + +.TP +.BI Text.from_c_string\ :\ func(str:\ CString\ ->\ Text) +Converts a C-style string to a \fBText\fR value. + +For more, see: +.BR Tomo-Text.from_c_string (3) + + +.TP +.BI Text.from_codepoint_names\ :\ func(codepoint_names:\ [Text]\ ->\ [Text]) +Returns text that has the given codepoint names (according to the Unicode specification) as its codepoints. + +For more, see: +.BR Tomo-Text.from_codepoint_names (3) + + +.TP +.BI Text.from_utf16\ :\ func(bytes:\ [Int16]\ ->\ [Text]) +Returns text that has been constructed from the given UTF16 sequence. + +For more, see: +.BR Tomo-Text.from_utf16 (3) + + +.TP +.BI Text.from_utf32\ :\ func(codepoints:\ [Int32]\ ->\ [Text]) +Returns text that has been constructed from the given UTF32 codepoints. + +For more, see: +.BR Tomo-Text.from_utf32 (3) + + +.TP +.BI Text.from_utf8\ :\ func(bytes:\ [Byte]\ ->\ [Text]) +Returns text that has been constructed from the given UTF8 bytes. + +For more, see: +.BR Tomo-Text.from_utf8 (3) + + +.TP +.BI Text.has\ :\ func(text:\ Text,\ target:\ Text\ ->\ Bool) +Checks if the \fBText\fR contains some target text. + +For more, see: +.BR Tomo-Text.has (3) + + +.TP +.BI Text.join\ :\ func(glue:\ Text,\ pieces:\ [Text]\ ->\ Text) +Joins a list of text pieces with a specified glue. + +For more, see: +.BR Tomo-Text.join (3) + + +.TP +.BI Text.left_pad\ :\ func(text:\ Text,\ width:\ Int,\ pad:\ Text\ =\ "\ ",\ language:\ Text\ =\ "C"\ ->\ Text) +Pad some text on the left side so it reaches a target width. + +For more, see: +.BR Tomo-Text.left_pad (3) + + +.TP +.BI Text.lines\ :\ func(text:\ Text\ ->\ [Text]) +Splits the text into a list of lines of text, preserving blank lines, ignoring trailing newlines, and handling \fB\r\n\fR the same as \fB\n\fR. + +For more, see: +.BR Tomo-Text.lines (3) + + +.TP +.BI Text.lower\ :\ func(text:\ Text,\ language:\ Text\ =\ "C"\ ->\ Text) +Converts all characters in the text to lowercase. + +For more, see: +.BR Tomo-Text.lower (3) + + +.TP +.BI Text.middle_pad\ :\ func(text:\ Text,\ width:\ Int,\ pad:\ Text\ =\ "\ ",\ language:\ Text\ =\ "C"\ ->\ Text) +Pad some text on the left and right side so it reaches a target width. + +For more, see: +.BR Tomo-Text.middle_pad (3) + + +.TP +.BI Text.quoted\ :\ func(text:\ Text,\ color:\ Bool\ =\ no,\ quotation_mark:\ Text\ =\ `"`\ ->\ Text) +Formats the text with quotation marks and escapes. + +For more, see: +.BR Tomo-Text.quoted (3) + + +.TP +.BI Text.repeat\ :\ func(text:\ Text,\ count:\ Int\ ->\ Text) +Repeat some text multiple times. + +For more, see: +.BR Tomo-Text.repeat (3) + + +.TP +.BI Text.replace\ :\ func(text:\ Text,\ target:\ Text,\ replacement:\ Text\ ->\ Text) +Replaces occurrences of a target text with a replacement text. + +For more, see: +.BR Tomo-Text.replace (3) + + +.TP +.BI Text.reversed\ :\ func(text:\ Text\ ->\ Text) +Return a text that has the grapheme clusters in reverse order. + +For more, see: +.BR Tomo-Text.reversed (3) + + +.TP +.BI Text.right_pad\ :\ func(text:\ Text,\ width:\ Int,\ pad:\ Text\ =\ "\ ",\ language:\ Text\ =\ "C"\ ->\ Text) +Pad some text on the right side so it reaches a target width. + +For more, see: +.BR Tomo-Text.right_pad (3) + + +.TP +.BI Text.slice\ :\ func(text:\ Text,\ from:\ Int\ =\ 1,\ to:\ Int\ =\ -1\ ->\ Text) +Get a slice of the text. + +For more, see: +.BR Tomo-Text.slice (3) + + +.TP +.BI Text.split\ :\ func(text:\ Text,\ delimiter:\ Text\ =\ ""\ ->\ [Text]) +Splits the text into a list of substrings based on exact matches of a delimiter. + +For more, see: +.BR Tomo-Text.split (3) + + +.TP +.BI Text.split_any\ :\ func(text:\ Text,\ delimiters:\ Text\ =\ "\ $\[rs]t\[rs]r\[rs]n"\ ->\ [Text]) +Splits the text into a list of substrings at one or more occurrences of a set of delimiter characters (grapheme clusters). + +For more, see: +.BR Tomo-Text.split_any (3) + + +.TP +.BI Text.starts_with\ :\ func(text:\ Text,\ prefix:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Bool) +Checks if the \fBText\fR starts with a literal prefix text. + +For more, see: +.BR Tomo-Text.starts_with (3) + + +.TP +.BI Text.title\ :\ func(text:\ Text,\ language:\ Text\ =\ "C"\ ->\ Text) +Converts the text to title case (capitalizing the first letter of each word). + +For more, see: +.BR Tomo-Text.title (3) + + +.TP +.BI Text.to\ :\ func(text:\ Text,\ last:\ Int\ ->\ Text) +Get a slice of the text, ending at the given position. + +For more, see: +.BR Tomo-Text.to (3) + + +.TP +.BI Text.translate\ :\ func(text:\ Text,\ translations:\ {Text:Text}\ ->\ Text) +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. + +For more, see: +.BR Tomo-Text.translate (3) + + +.TP +.BI Text.trim\ :\ func(text:\ Text,\ to_trim:\ Text\ =\ "\ $\[rs]t\[rs]r\[rs]n",\ left:\ Bool\ =\ yes,\ right:\ Bool\ =\ yes\ ->\ Text) +Trims the given characters (grapheme clusters) from the left and/or right side of the text. + +For more, see: +.BR Tomo-Text.trim (3) + + +.TP +.BI Text.upper\ :\ func(text:\ Text,\ language:\ Text\ =\ "C"\ ->\ Text) +Converts all characters in the text to uppercase. + +For more, see: +.BR Tomo-Text.upper (3) + + +.TP +.BI Text.utf16\ :\ func(text:\ Text\ ->\ [Int16]) +Returns a list of Unicode code points for UTF16 encoding of the text. + +For more, see: +.BR Tomo-Text.utf16 (3) + + +.TP +.BI Text.utf32\ :\ func(text:\ Text\ ->\ [Int32]) +Returns a list of Unicode code points for UTF32 encoding of the text. + +For more, see: +.BR Tomo-Text.utf32 (3) + + +.TP +.BI Text.utf8\ :\ func(text:\ Text\ ->\ [Byte]) +Converts a \fBText\fR value to a list of bytes representing a UTF8 encoding of the text. + +For more, see: +.BR Tomo-Text.utf8 (3) + + +.TP +.BI Text.width\ :\ func(text:\ Text\ ->\ Int) +Returns the display width of the text as seen in a terminal with appropriate font rendering. This is usually the same as the text's \fB.length\fR, but there are some characters like emojis that render wider than 1 cell. + +For more, see: +.BR Tomo-Text.width (3) + + +.TP +.BI Text.without_prefix\ :\ func(text:\ Text,\ prefix:\ Text\ ->\ Text) +Returns the text with a given prefix removed (if present). + +For more, see: +.BR Tomo-Text.without_prefix (3) + + +.TP +.BI Text.without_suffix\ :\ func(text:\ Text,\ suffix:\ Text\ ->\ Text) +Returns the text with a given suffix removed (if present). + +For more, see: +.BR Tomo-Text.without_suffix (3) + diff --git a/man/man3/tomo-Text.as_c_string.3 b/man/man3/tomo-Text.as_c_string.3 index ef49eeb7..2bdf0b11 100644 --- a/man/man3/tomo-Text.as_c_string.3 +++ b/man/man3/tomo-Text.as_c_string.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.as_c_string 3 2025-09-21 "Tomo man-pages" +.TH Text.as_c_string 3 2025-11-29 "Tomo man-pages" .SH NAME Text.as_c_string \- convert to C-style string .SH LIBRARY @@ -31,3 +31,5 @@ A C-style string (`CString`) representing the text. .EX assert "Hello".as_c_string() == CString("Hello") .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.at.3 b/man/man3/tomo-Text.at.3 index 2d46c256..3396ae19 100644 --- a/man/man3/tomo-Text.at.3 +++ b/man/man3/tomo-Text.at.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.at 3 2025-09-21 "Tomo man-pages" +.TH Text.at 3 2025-11-29 "Tomo man-pages" .SH NAME Text.at \- get a letter .SH LIBRARY @@ -35,3 +35,5 @@ Negative indices are counted from the back of the text, so `-1` means the last c .EX assert "Amélie".at(3) == "é" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.by_line.3 b/man/man3/tomo-Text.by_line.3 index 6f28263b..f1fa17bc 100644 --- a/man/man3/tomo-Text.by_line.3 +++ b/man/man3/tomo-Text.by_line.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.by_line 3 2025-04-30 "Tomo man-pages" +.TH Text.by_line 3 2025-11-29 "Tomo man-pages" .SH NAME Text.by_line \- iterate by line .SH LIBRARY @@ -40,3 +40,5 @@ for line in text.by_line() # Prints: "line one" then "line two": say(line) .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.by_split.3 b/man/man3/tomo-Text.by_split.3 index 3acef72e..12f15674 100644 --- a/man/man3/tomo-Text.by_split.3 +++ b/man/man3/tomo-Text.by_split.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.by_split 3 2025-04-30 "Tomo man-pages" +.TH Text.by_split 3 2025-11-29 "Tomo man-pages" .SH NAME Text.by_split \- iterate by a spliting text .SH LIBRARY @@ -39,3 +39,5 @@ for chunk in text.by_split(",") # Prints: "one" then "two" then "three": say(chunk) .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.by_split_any.3 b/man/man3/tomo-Text.by_split_any.3 index c8616c82..158fed1c 100644 --- a/man/man3/tomo-Text.by_split_any.3 +++ b/man/man3/tomo-Text.by_split_any.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.by_split_any 3 2025-04-30 "Tomo man-pages" +.TH Text.by_split_any 3 2025-11-29 "Tomo man-pages" .SH NAME Text.by_split_any \- iterate by one of many splitting characters .SH LIBRARY @@ -39,3 +39,5 @@ for chunk in text.by_split_any(",;") # Prints: "one" then "two" then "three": say(chunk) .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.caseless_equals.3 b/man/man3/tomo-Text.caseless_equals.3 index 1470c4db..d4fdf8de 100644 --- a/man/man3/tomo-Text.caseless_equals.3 +++ b/man/man3/tomo-Text.caseless_equals.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.caseless_equals 3 2025-09-21 "Tomo man-pages" +.TH Text.caseless_equals 3 2025-11-29 "Tomo man-pages" .SH NAME Text.caseless_equals \- case-insensitive comparison .SH LIBRARY @@ -36,3 +36,5 @@ assert "A".caseless_equals("a") == yes # Turkish lowercase "I" is "ı" (dotless I), not "i" assert "I".caseless_equals("i", language="tr_TR") == no .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.codepoint_names.3 b/man/man3/tomo-Text.codepoint_names.3 index 61a4c9a5..b91baba4 100644 --- a/man/man3/tomo-Text.codepoint_names.3 +++ b/man/man3/tomo-Text.codepoint_names.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.codepoint_names 3 2025-09-21 "Tomo man-pages" +.TH Text.codepoint_names 3 2025-11-29 "Tomo man-pages" .SH NAME Text.codepoint_names \- get unicode codepoint names .SH LIBRARY @@ -38,3 +38,5 @@ assert "Amélie".codepoint_names() == [ "LATIN SMALL LETTER E", ] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.ends_with.3 b/man/man3/tomo-Text.ends_with.3 index 7b5ff696..a9391874 100644 --- a/man/man3/tomo-Text.ends_with.3 +++ b/man/man3/tomo-Text.ends_with.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.ends_with 3 2025-09-21 "Tomo man-pages" +.TH Text.ends_with 3 2025-11-29 "Tomo man-pages" .SH NAME Text.ends_with \- check suffix .SH LIBRARY @@ -36,3 +36,5 @@ remainder : Text assert "hello world".ends_with("world", &remainder) == yes assert remainder == "hello " .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.find.3 b/man/man3/tomo-Text.find.3 index 17d78a0a..b145ae3c 100644 --- a/man/man3/tomo-Text.find.3 +++ b/man/man3/tomo-Text.find.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.find 3 2025-11-23 "Tomo man-pages" +.TH Text.find 3 2025-11-29 "Tomo man-pages" .SH NAME Text.find \- find a substring .SH LIBRARY @@ -36,3 +36,5 @@ assert "one two".find("two") == 5 assert "one two".find("three") == none assert "one two".find("o", start=2) == 7 .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.from.3 b/man/man3/tomo-Text.from.3 index 6132c572..30e62958 100644 --- a/man/man3/tomo-Text.from.3 +++ b/man/man3/tomo-Text.from.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.from 3 2025-09-21 "Tomo man-pages" +.TH Text.from 3 2025-11-29 "Tomo man-pages" .SH NAME Text.from \- slice from a starting index .SH LIBRARY @@ -36,3 +36,5 @@ A negative index counts backwards from the end of the text, so `-1` refers to th assert "hello".from(2) == "ello" assert "hello".from(-2) == "lo" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.from_c_string.3 b/man/man3/tomo-Text.from_c_string.3 index 98b153d8..1144001b 100644 --- a/man/man3/tomo-Text.from_c_string.3 +++ b/man/man3/tomo-Text.from_c_string.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.from_c_string 3 2025-09-21 "Tomo man-pages" +.TH Text.from_c_string 3 2025-11-29 "Tomo man-pages" .SH NAME Text.from_c_string \- convert C-style string to text .SH LIBRARY @@ -31,3 +31,5 @@ A `Text` value representing the C-style string. .EX assert Text.from_c_string(CString("Hello")) == "Hello" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.from_codepoint_names.3 b/man/man3/tomo-Text.from_codepoint_names.3 index 4a1f9f56..2951b0f2 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-10-11 "Tomo man-pages" +.TH Text.from_codepoint_names 3 2025-11-29 "Tomo man-pages" .SH NAME Text.from_codepoint_names \- convert list of unicode codepoint names to text .SH LIBRARY @@ -39,3 +39,5 @@ text := Text.from_codepoint_names([ ] assert text == "Åke" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.from_utf16.3 b/man/man3/tomo-Text.from_utf16.3 index 6668baf0..ba92c691 100644 --- a/man/man3/tomo-Text.from_utf16.3 +++ b/man/man3/tomo-Text.from_utf16.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.from_utf16 3 2025-09-21 "Tomo man-pages" +.TH Text.from_utf16 3 2025-11-29 "Tomo man-pages" .SH NAME Text.from_utf16 \- convert UTF16 list to text .SH LIBRARY @@ -35,3 +35,5 @@ The text will be normalized, so the resulting text's UTF16 sequence may not exac assert Text.from_utf16([197, 107, 101]) == "Åke" assert Text.from_utf16([12371, 12435, 12395, 12385, 12399, 19990, 30028]) == "こんにちは世界".utf16() .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.from_utf32.3 b/man/man3/tomo-Text.from_utf32.3 index d93ad2c9..526545fb 100644 --- a/man/man3/tomo-Text.from_utf32.3 +++ b/man/man3/tomo-Text.from_utf32.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.from_utf32 3 2025-09-21 "Tomo man-pages" +.TH Text.from_utf32 3 2025-11-29 "Tomo man-pages" .SH NAME Text.from_utf32 \- convert UTF32 codepoints to text .SH LIBRARY @@ -34,3 +34,5 @@ The text will be normalized, so the resulting text's codepoints may not exactly .EX assert Text.from_utf32([197, 107, 101]) == "Åke" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.from_utf8.3 b/man/man3/tomo-Text.from_utf8.3 index ec5261ec..25ab2897 100644 --- a/man/man3/tomo-Text.from_utf8.3 +++ b/man/man3/tomo-Text.from_utf8.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.from_utf8 3 2025-09-21 "Tomo man-pages" +.TH Text.from_utf8 3 2025-11-29 "Tomo man-pages" .SH NAME Text.from_utf8 \- convert UTF8 byte list to text .SH LIBRARY @@ -34,3 +34,5 @@ The text will be normalized, so the resulting text's UTF8 bytes may not exactly .EX assert Text.from_utf8([195, 133, 107, 101]) == "Åke" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.has.3 b/man/man3/tomo-Text.has.3 index 75b6b0c9..a562b908 100644 --- a/man/man3/tomo-Text.has.3 +++ b/man/man3/tomo-Text.has.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.has 3 2025-09-21 "Tomo man-pages" +.TH Text.has 3 2025-11-29 "Tomo man-pages" .SH NAME Text.has \- check for substring .SH LIBRARY @@ -33,3 +33,5 @@ target Text The text to search for. - assert "hello world".has("wo") == yes assert "hello world".has("xxx") == no .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.join.3 b/man/man3/tomo-Text.join.3 index b736f721..d6b7d15a 100644 --- a/man/man3/tomo-Text.join.3 +++ b/man/man3/tomo-Text.join.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.join 3 2025-09-21 "Tomo man-pages" +.TH Text.join 3 2025-11-29 "Tomo man-pages" .SH NAME Text.join \- concatenate with separator .SH LIBRARY @@ -32,3 +32,5 @@ A single `Text` value with the pieces joined by the glue. .EX assert ", ".join(["one", "two", "three"]) == "one, two, three" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.left_pad.3 b/man/man3/tomo-Text.left_pad.3 index e79e4ad6..4a12fe82 100644 --- a/man/man3/tomo-Text.left_pad.3 +++ b/man/man3/tomo-Text.left_pad.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.left_pad 3 2025-09-21 "Tomo man-pages" +.TH Text.left_pad 3 2025-11-29 "Tomo man-pages" .SH NAME Text.left_pad \- left-pad text .SH LIBRARY @@ -35,3 +35,5 @@ Text with length at least `width`, with extra padding on the left as needed. If assert "x".left_pad(5) == " x" assert "x".left_pad(5, "ABC") == "ABCAx" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.lines.3 b/man/man3/tomo-Text.lines.3 index 60aa4430..2085d7d9 100644 --- a/man/man3/tomo-Text.lines.3 +++ b/man/man3/tomo-Text.lines.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.lines 3 2025-09-21 "Tomo man-pages" +.TH Text.lines 3 2025-11-29 "Tomo man-pages" .SH NAME Text.lines \- get list of lines .SH LIBRARY @@ -35,3 +35,5 @@ assert "one\[rs]ntwo\[rs]nthree\[rs]n\[rs]n".lines() == ["one", "two", "three", assert "one\[rs]r\[rs]ntwo\[rs]r\[rs]nthree\[rs]r\[rs]n".lines() == ["one", "two", "three"] assert "".lines() == [] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.lower.3 b/man/man3/tomo-Text.lower.3 index 877227e7..53563774 100644 --- a/man/man3/tomo-Text.lower.3 +++ b/man/man3/tomo-Text.lower.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.lower 3 2025-09-21 "Tomo man-pages" +.TH Text.lower 3 2025-11-29 "Tomo man-pages" .SH NAME Text.lower \- convert to lowercase .SH LIBRARY @@ -33,3 +33,5 @@ The lowercase version of the text. assert "AMÉLIE".lower() == "amélie" assert "I".lower(language="tr_TR") == "ı" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.middle_pad.3 b/man/man3/tomo-Text.middle_pad.3 index 8b57bf6e..601129f6 100644 --- a/man/man3/tomo-Text.middle_pad.3 +++ b/man/man3/tomo-Text.middle_pad.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.middle_pad 3 2025-09-21 "Tomo man-pages" +.TH Text.middle_pad 3 2025-11-29 "Tomo man-pages" .SH NAME Text.middle_pad \- pad text, centered .SH LIBRARY @@ -35,3 +35,5 @@ Text with length at least `width`, with extra padding on the left and right as n assert "x".middle_pad(6) == " x " assert "x".middle_pad(10, "ABC") == "ABCAxABCAB" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.quoted.3 b/man/man3/tomo-Text.quoted.3 index 8f6824fd..921b5c59 100644 --- a/man/man3/tomo-Text.quoted.3 +++ b/man/man3/tomo-Text.quoted.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.quoted 3 2025-09-21 "Tomo man-pages" +.TH Text.quoted 3 2025-11-29 "Tomo man-pages" .SH NAME Text.quoted \- add quotation marks and escapes .SH LIBRARY @@ -33,3 +33,5 @@ The text formatted as a quoted text. .EX assert "one\[rs]ntwo".quoted() == "\[rs]"one\[rs]\[rs]ntwo\[rs]"" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.repeat.3 b/man/man3/tomo-Text.repeat.3 index fdc9561f..70b49c03 100644 --- a/man/man3/tomo-Text.repeat.3 +++ b/man/man3/tomo-Text.repeat.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.repeat 3 2025-09-21 "Tomo man-pages" +.TH Text.repeat 3 2025-11-29 "Tomo man-pages" .SH NAME Text.repeat \- repeat text .SH LIBRARY @@ -32,3 +32,5 @@ The text repeated the given number of times. .EX assert "Abc".repeat(3) == "AbcAbcAbc" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.replace.3 b/man/man3/tomo-Text.replace.3 index 7a272106..7636fede 100644 --- a/man/man3/tomo-Text.replace.3 +++ b/man/man3/tomo-Text.replace.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.replace 3 2025-09-21 "Tomo man-pages" +.TH Text.replace 3 2025-11-29 "Tomo man-pages" .SH NAME Text.replace \- replace a substring .SH LIBRARY @@ -33,3 +33,5 @@ The text with occurrences of the target replaced. .EX assert "Hello world".replace("world", "there") == "Hello there" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.reversed.3 b/man/man3/tomo-Text.reversed.3 index 51b37acb..21b2be4e 100644 --- a/man/man3/tomo-Text.reversed.3 +++ b/man/man3/tomo-Text.reversed.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.reversed 3 2025-09-21 "Tomo man-pages" +.TH Text.reversed 3 2025-11-29 "Tomo man-pages" .SH NAME Text.reversed \- get a reversed copy .SH LIBRARY @@ -31,3 +31,5 @@ A reversed version of the text. .EX assert "Abc".reversed() == "cbA" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.right_pad.3 b/man/man3/tomo-Text.right_pad.3 index 0fb128cb..b91490f3 100644 --- a/man/man3/tomo-Text.right_pad.3 +++ b/man/man3/tomo-Text.right_pad.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.right_pad 3 2025-09-21 "Tomo man-pages" +.TH Text.right_pad 3 2025-11-29 "Tomo man-pages" .SH NAME Text.right_pad \- right-pad text .SH LIBRARY @@ -35,3 +35,5 @@ Text with length at least `width`, with extra padding on the right as needed. If assert "x".right_pad(5) == "x " assert "x".right_pad(5, "ABC") == "xABCA" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.slice.3 b/man/man3/tomo-Text.slice.3 index 48e0516a..ec7a160e 100644 --- a/man/man3/tomo-Text.slice.3 +++ b/man/man3/tomo-Text.slice.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.slice 3 2025-09-21 "Tomo man-pages" +.TH Text.slice 3 2025-11-29 "Tomo man-pages" .SH NAME Text.slice \- get a slice of a text .SH LIBRARY @@ -38,3 +38,5 @@ assert "hello".slice(2, 3) == "el" assert "hello".slice(to=-2) == "hell" assert "hello".slice(from=2) == "ello" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.split.3 b/man/man3/tomo-Text.split.3 index 8634c509..27ae13b1 100644 --- a/man/man3/tomo-Text.split.3 +++ b/man/man3/tomo-Text.split.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.split 3 2025-09-21 "Tomo man-pages" +.TH Text.split 3 2025-11-29 "Tomo man-pages" .SH NAME Text.split \- split a text by a delimiter .SH LIBRARY @@ -37,3 +37,5 @@ If an empty text is given as the delimiter, then each split will be the graphica assert "one,two,,three".split(",") == ["one", "two", "", "three"] assert "abc".split() == ["a", "b", "c"] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.split_any.3 b/man/man3/tomo-Text.split_any.3 index 6c77d8d6..4e30e5d5 100644 --- a/man/man3/tomo-Text.split_any.3 +++ b/man/man3/tomo-Text.split_any.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.split_any 3 2025-09-21 "Tomo man-pages" +.TH Text.split_any 3 2025-11-29 "Tomo man-pages" .SH NAME Text.split_any \- split a text by multiple delimiters .SH LIBRARY @@ -36,3 +36,5 @@ To split based on an exact delimiter, use Text.split(). .EX assert "one, two,,three".split_any(", ") == ["one", "two", "three"] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.starts_with.3 b/man/man3/tomo-Text.starts_with.3 index ac35fa8d..b8b870eb 100644 --- a/man/man3/tomo-Text.starts_with.3 +++ b/man/man3/tomo-Text.starts_with.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.starts_with 3 2025-09-21 "Tomo man-pages" +.TH Text.starts_with 3 2025-11-29 "Tomo man-pages" .SH NAME Text.starts_with \- check prefix .SH LIBRARY @@ -36,3 +36,5 @@ remainder : Text assert "hello world".starts_with("hello", &remainder) == yes assert remainder == " world" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.title.3 b/man/man3/tomo-Text.title.3 index d1b3e962..9b5b5245 100644 --- a/man/man3/tomo-Text.title.3 +++ b/man/man3/tomo-Text.title.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.title 3 2025-09-21 "Tomo man-pages" +.TH Text.title 3 2025-11-29 "Tomo man-pages" .SH NAME Text.title \- titlecase .SH LIBRARY @@ -35,3 +35,5 @@ assert "amélie".title() == "Amélie" # In Turkish, uppercase "i" is "İ" assert "i".title(language="tr_TR") == "İ" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.to.3 b/man/man3/tomo-Text.to.3 index 4de082d3..66fde6e1 100644 --- a/man/man3/tomo-Text.to.3 +++ b/man/man3/tomo-Text.to.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.to 3 2025-09-21 "Tomo man-pages" +.TH Text.to 3 2025-11-29 "Tomo man-pages" .SH NAME Text.to \- slice to an end index .SH LIBRARY @@ -36,3 +36,5 @@ A negative index counts backwards from the end of the text, so `-1` refers to th assert "goodbye".to(3) == "goo" assert "goodbye".to(-2) == "goodby" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.translate.3 b/man/man3/tomo-Text.translate.3 index 8cfeb3f7..fe944018 100644 --- a/man/man3/tomo-Text.translate.3 +++ b/man/man3/tomo-Text.translate.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.translate 3 2025-10-11 "Tomo man-pages" +.TH Text.translate 3 2025-11-29 "Tomo man-pages" .SH NAME Text.translate \- perform multiple replacements .SH LIBRARY @@ -39,3 +39,5 @@ text := "A & an amperand".translate({ }) assert text == "A <tag> & an ampersand" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.trim.3 b/man/man3/tomo-Text.trim.3 index b7daa310..1a2a8503 100644 --- a/man/man3/tomo-Text.trim.3 +++ b/man/man3/tomo-Text.trim.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.trim 3 2025-09-21 "Tomo man-pages" +.TH Text.trim 3 2025-11-29 "Tomo man-pages" .SH NAME Text.trim \- trim characters .SH LIBRARY @@ -36,3 +36,5 @@ assert " x y z \[rs]n".trim() == "x y z" assert "one,".trim(",") == "one" assert " xyz ".trim(right=no) == "xyz " .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.upper.3 b/man/man3/tomo-Text.upper.3 index dc10ff21..63d61bac 100644 --- a/man/man3/tomo-Text.upper.3 +++ b/man/man3/tomo-Text.upper.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.upper 3 2025-09-21 "Tomo man-pages" +.TH Text.upper 3 2025-11-29 "Tomo man-pages" .SH NAME Text.upper \- uppercase .SH LIBRARY @@ -35,3 +35,5 @@ assert "amélie".upper() == "AMÉLIE" # In Turkish, uppercase "i" is "İ" assert "i".upper(language="tr_TR") == "İ" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.utf16.3 b/man/man3/tomo-Text.utf16.3 index 2d5d0817..d8f00d22 100644 --- a/man/man3/tomo-Text.utf16.3 +++ b/man/man3/tomo-Text.utf16.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.utf16 3 2025-09-21 "Tomo man-pages" +.TH Text.utf16 3 2025-11-29 "Tomo man-pages" .SH NAME Text.utf16 \- get UTF16 codepoints .SH LIBRARY @@ -32,3 +32,5 @@ A list of 16-bit integer Unicode code points (`[Int16]`). assert "Åke".utf16() == [197, 107, 101] assert "こんにちは世界".utf16() == [12371, 12435, 12395, 12385, 12399, 19990, 30028] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.utf32.3 b/man/man3/tomo-Text.utf32.3 index a74b04d2..cf0d4a05 100644 --- a/man/man3/tomo-Text.utf32.3 +++ b/man/man3/tomo-Text.utf32.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.utf32 3 2025-09-21 "Tomo man-pages" +.TH Text.utf32 3 2025-11-29 "Tomo man-pages" .SH NAME Text.utf32 \- get UTF32 codepoints .SH LIBRARY @@ -31,3 +31,5 @@ A list of 32-bit integer Unicode code points (`[Int32]`). .EX assert "Amélie".utf32() == [65, 109, 233, 108, 105, 101] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.utf8.3 b/man/man3/tomo-Text.utf8.3 index e64bbaf1..05544cc1 100644 --- a/man/man3/tomo-Text.utf8.3 +++ b/man/man3/tomo-Text.utf8.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.utf8 3 2025-09-21 "Tomo man-pages" +.TH Text.utf8 3 2025-11-29 "Tomo man-pages" .SH NAME Text.utf8 \- get UTF8 bytes .SH LIBRARY @@ -31,3 +31,5 @@ A list of bytes (`[Byte]`) representing the text in UTF8 encoding. .EX assert "Amélie".utf8() == [65, 109, 195, 169, 108, 105, 101] .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.width.3 b/man/man3/tomo-Text.width.3 index 7118c9bb..00e32fcf 100644 --- a/man/man3/tomo-Text.width.3 +++ b/man/man3/tomo-Text.width.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.width 3 2025-09-21 "Tomo man-pages" +.TH Text.width 3 2025-11-29 "Tomo man-pages" .SH NAME Text.width \- get display width .SH LIBRARY @@ -35,3 +35,5 @@ This will not always be exactly accurate when your terminal's font rendering can assert "Amélie".width() == 6 assert "🤠".width() == 2 .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.without_prefix.3 b/man/man3/tomo-Text.without_prefix.3 index 12479791..43aa6e5e 100644 --- a/man/man3/tomo-Text.without_prefix.3 +++ b/man/man3/tomo-Text.without_prefix.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.without_prefix 3 2025-09-21 "Tomo man-pages" +.TH Text.without_prefix 3 2025-11-29 "Tomo man-pages" .SH NAME Text.without_prefix \- remove prefix .SH LIBRARY @@ -33,3 +33,5 @@ A text without the given prefix (if present) or the unmodified text if the prefi assert "foo:baz".without_prefix("foo:") == "baz" assert "qux".without_prefix("foo:") == "qux" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-Text.without_suffix.3 b/man/man3/tomo-Text.without_suffix.3 index 45d818a7..9b45d624 100644 --- a/man/man3/tomo-Text.without_suffix.3 +++ b/man/man3/tomo-Text.without_suffix.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Text.without_suffix 3 2025-09-21 "Tomo man-pages" +.TH Text.without_suffix 3 2025-11-29 "Tomo man-pages" .SH NAME Text.without_suffix \- remove suffix .SH LIBRARY @@ -33,3 +33,5 @@ A text without the given suffix (if present) or the unmodified text if the suffi assert "baz.foo".without_suffix(".foo") == "baz" assert "qux".without_suffix(".foo") == "qux" .EE +.SH SEE ALSO +.BR Tomo-Text (3) diff --git a/man/man3/tomo-ask.3 b/man/man3/tomo-ask.3 index 684efd63..76e06e8f 100644 --- a/man/man3/tomo-ask.3 +++ b/man/man3/tomo-ask.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH ask 3 2025-09-21 "Tomo man-pages" +.TH ask 3 2025-11-29 "Tomo man-pages" .SH NAME ask \- get user input .SH LIBRARY diff --git a/man/man3/tomo-at_cleanup.3 b/man/man3/tomo-at_cleanup.3 index a8dc04ed..d8d07faa 100644 --- a/man/man3/tomo-at_cleanup.3 +++ b/man/man3/tomo-at_cleanup.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH at_cleanup 3 2025-11-23 "Tomo man-pages" +.TH at_cleanup 3 2025-11-29 "Tomo man-pages" .SH NAME at_cleanup \- register a cleanup function .SH LIBRARY diff --git a/man/man3/tomo-exit.3 b/man/man3/tomo-exit.3 index 50c022b8..865d8b72 100644 --- a/man/man3/tomo-exit.3 +++ b/man/man3/tomo-exit.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH exit 3 2025-11-23 "Tomo man-pages" +.TH exit 3 2025-11-29 "Tomo man-pages" .SH NAME exit \- exit the program .SH LIBRARY diff --git a/man/man3/tomo-fail.3 b/man/man3/tomo-fail.3 index ed969cc4..b5f0b502 100644 --- a/man/man3/tomo-fail.3 +++ b/man/man3/tomo-fail.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH fail 3 2025-05-17 "Tomo man-pages" +.TH fail 3 2025-11-29 "Tomo man-pages" .SH NAME fail \- abort the program .SH LIBRARY diff --git a/man/man3/tomo-getenv.3 b/man/man3/tomo-getenv.3 index 84bef6be..6b4a7bbf 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-10-11 "Tomo man-pages" +.TH getenv 3 2025-11-29 "Tomo man-pages" .SH NAME getenv \- get an environment variable .SH LIBRARY diff --git a/man/man3/tomo-print.3 b/man/man3/tomo-print.3 index ccb9863e..d6d2993f 100644 --- a/man/man3/tomo-print.3 +++ b/man/man3/tomo-print.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH print 3 2025-05-17 "Tomo man-pages" +.TH print 3 2025-11-29 "Tomo man-pages" .SH NAME print \- print some text .SH LIBRARY diff --git a/man/man3/tomo-say.3 b/man/man3/tomo-say.3 index 4e9bfeb4..7d40a88e 100644 --- a/man/man3/tomo-say.3 +++ b/man/man3/tomo-say.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH say 3 2025-05-17 "Tomo man-pages" +.TH say 3 2025-11-29 "Tomo man-pages" .SH NAME say \- print some text .SH LIBRARY diff --git a/man/man3/tomo-setenv.3 b/man/man3/tomo-setenv.3 index faa6a662..a2bc01dd 100644 --- a/man/man3/tomo-setenv.3 +++ b/man/man3/tomo-setenv.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH setenv 3 2025-11-27 "Tomo man-pages" +.TH setenv 3 2025-11-29 "Tomo man-pages" .SH NAME setenv \- set an environment variable .SH LIBRARY @@ -23,7 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default name Text The name of the environment variable to set. - -value Text? The new value of the environment variable. If `none`, then the environment variable will be unset. - +value Text? The new value of the environment variable. If \fBnone\fR, then the environment variable will be unset. - .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-sleep.3 b/man/man3/tomo-sleep.3 index efe73c14..a652c6e8 100644 --- a/man/man3/tomo-sleep.3 +++ b/man/man3/tomo-sleep.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH sleep 3 2025-05-17 "Tomo man-pages" +.TH sleep 3 2025-11-29 "Tomo man-pages" .SH NAME sleep \- wait for an interval .SH LIBRARY diff --git a/scripts/mandoc_gen.py b/scripts/mandoc_gen.py index 1009105e..260bc5d0 100755 --- a/scripts/mandoc_gen.py +++ b/scripts/mandoc_gen.py @@ -71,6 +71,25 @@ lb lb lbx lb l l l l. Name Type Description Default''' +type_template = ''''\\" t +.\\" Copyright (c) {year} Bruce Hill +.\\" All rights reserved. +.\\" +.TH {type} 3 {date} "Tomo man-pages" +.SH NAME +{type} \\- a Tomo type +.SH LIBRARY +Tomo Standard Library +.fi +.SH METHODS +{methods} +''' + +def markdown_to_roff(text): + text = text.replace('\n', ' ') + text = re.sub(r'`([^`]*)`', '\\\\fB\\1\\\\fR', text) + return text + def write_method(path, name, info): lines = [] year = datetime.now().strftime("%Y") @@ -83,7 +102,7 @@ def write_method(path, name, info): lines.append(arg_prefix) for arg,arg_info in info["args"].items(): default = escape(arg_info['default'], spaces=True) if 'default' in arg_info else '-' - description = arg_info['description'].replace('\n', ' ') + description = markdown_to_roff(arg_info['description']) lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}") lines.append(".TE") @@ -105,6 +124,11 @@ def write_method(path, name, info): lines.append(escape(info['example'])) lines.append(".EE") + if "." in name: + type,_ = name.split(".") + lines.append(".SH SEE ALSO") + lines.append(f".BR Tomo-{type} (3)") + to_write = '\n'.join(lines) + '\n' try: with open(path, "r") as f: @@ -118,11 +142,62 @@ def write_method(path, name, info): f.write(to_write) print(f"Updated {path}") +fn_summary_template = ''' +.TP +{signature} +{description} + +For more, see: +.BR Tomo-{type}.{name} (3) +''' + +def fn_summary(type, name, info) -> str: + signature = get_signature(type+"."+name, info) + return fn_summary_template.format( + type=type, + name=name, + signature=signature, + description=markdown_to_roff(info["description"]), + ) + +def write_type_manpage(path, type, methods): + year = datetime.now().strftime("%Y") + date = datetime.now().strftime("%Y-%m-%d") + method_summaries = [fn_summary(type, name, methods[name]) for name in sorted(methods.keys())] + type_manpage = type_template.format( + year=year, + date=date, + type=type, + methods='\n'.join(method_summaries), + ) + + try: + with open(path, "r") as f: + existing = f.read() + if type_manpage.splitlines()[5:] == existing.splitlines()[5:]: + return + except FileNotFoundError: + pass + + with open(path, "w") as f: + f.write(type_manpage) + print(f"Updated {path}") + + def convert_to_markdown(yaml_doc:str)->str: data = yaml.safe_load(yaml_doc) + types = {} for name,info in data.items(): write_method(f"man/man3/tomo-{name}.3", name, data[name]) + if "." in name: + type,fn = name.split(".") + if type not in types: + types[type] = {} + types[type][fn] = info + + for type,methods in types.items(): + write_type_manpage(f"man/man3/tomo-{type}.3", type, methods) if __name__ == "__main__": import sys -- cgit v1.2.3 From a50e2df51a63ab809e700c7c8a28c124c6fef95e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 13:31:37 -0500 Subject: No 'default' column for functions with no defaults --- man/man3/tomo-Byte.get_bit.3 | 10 +++++----- man/man3/tomo-Byte.is_between.3 | 12 ++++++------ man/man3/tomo-CString.as_text.3 | 8 ++++---- man/man3/tomo-CString.join.3 | 10 +++++----- man/man3/tomo-Int.abs.3 | 8 ++++---- man/man3/tomo-Int.choose.3 | 10 +++++----- man/man3/tomo-Int.clamped.3 | 12 ++++++------ man/man3/tomo-Int.factorial.3 | 8 ++++---- man/man3/tomo-Int.get_bit.3 | 10 +++++----- man/man3/tomo-Int.is_between.3 | 12 ++++++------ man/man3/tomo-Int.next_prime.3 | 8 ++++---- man/man3/tomo-Int.prev_prime.3 | 8 ++++---- man/man3/tomo-Int.sqrt.3 | 8 ++++---- man/man3/tomo-List.by.3 | 10 +++++----- man/man3/tomo-List.clear.3 | 8 ++++---- man/man3/tomo-List.counts.3 | 8 ++++---- man/man3/tomo-List.find.3 | 10 +++++----- man/man3/tomo-List.from.3 | 10 +++++----- man/man3/tomo-List.has.3 | 10 +++++----- man/man3/tomo-List.reversed.3 | 8 ++++---- man/man3/tomo-List.slice.3 | 12 ++++++------ man/man3/tomo-List.to.3 | 10 +++++----- man/man3/tomo-List.unique.3 | 8 ++++---- man/man3/tomo-List.where.3 | 10 +++++----- man/man3/tomo-Num.abs.3 | 8 ++++---- man/man3/tomo-Num.acos.3 | 8 ++++---- man/man3/tomo-Num.acosh.3 | 8 ++++---- man/man3/tomo-Num.asin.3 | 8 ++++---- man/man3/tomo-Num.asinh.3 | 8 ++++---- man/man3/tomo-Num.atan.3 | 8 ++++---- man/man3/tomo-Num.atan2.3 | 10 +++++----- man/man3/tomo-Num.atanh.3 | 8 ++++---- man/man3/tomo-Num.cbrt.3 | 8 ++++---- man/man3/tomo-Num.ceil.3 | 8 ++++---- man/man3/tomo-Num.clamped.3 | 12 ++++++------ man/man3/tomo-Num.copysign.3 | 10 +++++----- man/man3/tomo-Num.cos.3 | 8 ++++---- man/man3/tomo-Num.cosh.3 | 8 ++++---- man/man3/tomo-Num.erf.3 | 8 ++++---- man/man3/tomo-Num.erfc.3 | 8 ++++---- man/man3/tomo-Num.exp.3 | 8 ++++---- man/man3/tomo-Num.exp2.3 | 8 ++++---- man/man3/tomo-Num.expm1.3 | 8 ++++---- man/man3/tomo-Num.fdim.3 | 10 +++++----- man/man3/tomo-Num.floor.3 | 8 ++++---- man/man3/tomo-Num.hypot.3 | 10 +++++----- man/man3/tomo-Num.is_between.3 | 12 ++++++------ man/man3/tomo-Num.isfinite.3 | 8 ++++---- man/man3/tomo-Num.isinf.3 | 8 ++++---- man/man3/tomo-Num.j0.3 | 8 ++++---- man/man3/tomo-Num.j1.3 | 8 ++++---- man/man3/tomo-Num.log.3 | 8 ++++---- man/man3/tomo-Num.log10.3 | 8 ++++---- man/man3/tomo-Num.log1p.3 | 8 ++++---- man/man3/tomo-Num.log2.3 | 8 ++++---- man/man3/tomo-Num.logb.3 | 8 ++++---- man/man3/tomo-Num.mix.3 | 12 ++++++------ man/man3/tomo-Num.nextafter.3 | 10 +++++----- man/man3/tomo-Num.rint.3 | 8 ++++---- man/man3/tomo-Num.round.3 | 8 ++++---- man/man3/tomo-Num.significand.3 | 8 ++++---- man/man3/tomo-Num.sin.3 | 8 ++++---- man/man3/tomo-Num.sinh.3 | 8 ++++---- man/man3/tomo-Num.sqrt.3 | 8 ++++---- man/man3/tomo-Num.tan.3 | 8 ++++---- man/man3/tomo-Num.tanh.3 | 8 ++++---- man/man3/tomo-Num.tgamma.3 | 8 ++++---- man/man3/tomo-Num.trunc.3 | 8 ++++---- man/man3/tomo-Num.with_precision.3 | 10 +++++----- man/man3/tomo-Num.y0.3 | 8 ++++---- man/man3/tomo-Num.y1.3 | 8 ++++---- man/man3/tomo-Path.base_name.3 | 8 ++++---- man/man3/tomo-Path.by_line.3 | 8 ++++---- man/man3/tomo-Path.can_execute.3 | 8 ++++---- man/man3/tomo-Path.can_read.3 | 8 ++++---- man/man3/tomo-Path.can_write.3 | 8 ++++---- man/man3/tomo-Path.child.3 | 10 +++++----- man/man3/tomo-Path.exists.3 | 8 ++++---- man/man3/tomo-Path.expand_home.3 | 8 ++++---- man/man3/tomo-Path.from_components.3 | 8 ++++---- man/man3/tomo-Path.glob.3 | 8 ++++---- man/man3/tomo-Path.has_extension.3 | 10 +++++----- man/man3/tomo-Path.is_symlink.3 | 8 ++++---- man/man3/tomo-Path.lines.3 | 8 ++++---- man/man3/tomo-Path.parent.3 | 8 ++++---- man/man3/tomo-Path.read.3 | 8 ++++---- man/man3/tomo-Path.sibling.3 | 10 +++++----- man/man3/tomo-Path.unique_directory.3 | 8 ++++---- man/man3/tomo-Path.write_unique.3 | 10 +++++----- man/man3/tomo-Path.write_unique_bytes.3 | 10 +++++----- man/man3/tomo-Table.clear.3 | 8 ++++---- man/man3/tomo-Table.difference.3 | 10 +++++----- man/man3/tomo-Table.get.3 | 10 +++++----- man/man3/tomo-Table.get_or_set.3 | 12 ++++++------ man/man3/tomo-Table.has.3 | 10 +++++----- man/man3/tomo-Table.intersection.3 | 10 +++++----- man/man3/tomo-Table.remove.3 | 10 +++++----- man/man3/tomo-Table.set.3 | 12 ++++++------ man/man3/tomo-Table.with.3 | 10 +++++----- man/man3/tomo-Table.with_fallback.3 | 10 +++++----- man/man3/tomo-Table.without.3 | 10 +++++----- man/man3/tomo-Text.as_c_string.3 | 8 ++++---- man/man3/tomo-Text.at.3 | 10 +++++----- man/man3/tomo-Text.by_line.3 | 8 ++++---- man/man3/tomo-Text.codepoint_names.3 | 8 ++++---- man/man3/tomo-Text.from.3 | 10 +++++----- man/man3/tomo-Text.from_c_string.3 | 8 ++++---- man/man3/tomo-Text.from_codepoint_names.3 | 8 ++++---- man/man3/tomo-Text.from_utf16.3 | 8 ++++---- man/man3/tomo-Text.from_utf32.3 | 8 ++++---- man/man3/tomo-Text.from_utf8.3 | 8 ++++---- man/man3/tomo-Text.has.3 | 10 +++++----- man/man3/tomo-Text.join.3 | 10 +++++----- man/man3/tomo-Text.lines.3 | 8 ++++---- man/man3/tomo-Text.repeat.3 | 10 +++++----- man/man3/tomo-Text.replace.3 | 12 ++++++------ man/man3/tomo-Text.reversed.3 | 8 ++++---- man/man3/tomo-Text.to.3 | 10 +++++----- man/man3/tomo-Text.translate.3 | 10 +++++----- man/man3/tomo-Text.utf16.3 | 8 ++++---- man/man3/tomo-Text.utf32.3 | 8 ++++---- man/man3/tomo-Text.utf8.3 | 8 ++++---- man/man3/tomo-Text.width.3 | 8 ++++---- man/man3/tomo-Text.without_prefix.3 | 10 +++++----- man/man3/tomo-Text.without_suffix.3 | 10 +++++----- man/man3/tomo-at_cleanup.3 | 8 ++++---- man/man3/tomo-fail.3 | 8 ++++---- man/man3/tomo-getenv.3 | 8 ++++---- man/man3/tomo-setenv.3 | 10 +++++----- man/man3/tomo-sleep.3 | 8 ++++---- scripts/mandoc_gen.py | 20 ++++++++++++++++---- 131 files changed, 595 insertions(+), 583 deletions(-) diff --git a/man/man3/tomo-Byte.get_bit.3 b/man/man3/tomo-Byte.get_bit.3 index 2e383fff..ad92560e 100644 --- a/man/man3/tomo-Byte.get_bit.3 +++ b/man/man3/tomo-Byte.get_bit.3 @@ -19,11 +19,11 @@ In the binary representation of a byte, check whether a given bit index is set t .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -i Byte The byte whose bits are being inspected. - -bit_index Int The index of the bit to check (1-indexed, range 1-8). - +lb lb lbx +l l l. +Name Type Description +i Byte The byte whose bits are being inspected. +bit_index Int The index of the bit to check (1-indexed, range 1-8). .TE .SH RETURN Whether or not the given bit index is set to 1 in the byte. diff --git a/man/man3/tomo-Byte.is_between.3 b/man/man3/tomo-Byte.is_between.3 index 8c7e871c..06e53fb0 100644 --- a/man/man3/tomo-Byte.is_between.3 +++ b/man/man3/tomo-Byte.is_between.3 @@ -19,12 +19,12 @@ Determines if an integer is between two numbers (inclusive). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Byte The integer to be checked. - -low Byte The lower bound to check (inclusive). - -high Byte The upper bound to check (inclusive). - +lb lb lbx +l l l. +Name Type Description +x Byte The integer to be checked. +low Byte The lower bound to check (inclusive). +high Byte The upper bound to check (inclusive). .TE .SH RETURN `yes` if `low <= x and x <= high`, otherwise `no` diff --git a/man/man3/tomo-CString.as_text.3 b/man/man3/tomo-CString.as_text.3 index c896861a..b9a8db95 100644 --- a/man/man3/tomo-CString.as_text.3 +++ b/man/man3/tomo-CString.as_text.3 @@ -19,10 +19,10 @@ Convert a C string to Text. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -str CString The C string. - +lb lb lbx +l l l. +Name Type Description +str CString The C string. .TE .SH RETURN The C string as a Text. diff --git a/man/man3/tomo-CString.join.3 b/man/man3/tomo-CString.join.3 index a4aa16f3..c44df80d 100644 --- a/man/man3/tomo-CString.join.3 +++ b/man/man3/tomo-CString.join.3 @@ -19,11 +19,11 @@ Join a list of C strings together with a separator. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -glue CString The C joiner used to between elements. - -pieces [CString] A list of C strings to join. - +lb lb lbx +l l l. +Name Type Description +glue CString The C joiner used to between elements. +pieces [CString] A list of C strings to join. .TE .SH RETURN A C string of the joined together bits. diff --git a/man/man3/tomo-Int.abs.3 b/man/man3/tomo-Int.abs.3 index ad5d9411..71c60960 100644 --- a/man/man3/tomo-Int.abs.3 +++ b/man/man3/tomo-Int.abs.3 @@ -19,10 +19,10 @@ Calculates the absolute value of an integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Int The integer whose absolute value is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Int The integer whose absolute value is to be calculated. .TE .SH RETURN The absolute value of `x`. diff --git a/man/man3/tomo-Int.choose.3 b/man/man3/tomo-Int.choose.3 index 231d75f0..1e5c705b 100644 --- a/man/man3/tomo-Int.choose.3 +++ b/man/man3/tomo-Int.choose.3 @@ -19,11 +19,11 @@ Computes the binomial coefficient of the given numbers (the equivalent of `n` ch .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -n Int The number of things to choose from. - -k Int The number of things to be chosen. - +lb lb lbx +l l l. +Name Type Description +n Int The number of things to choose from. +k Int The number of things to be chosen. .TE .SH RETURN The binomial coefficient, equivalent to the number of ways to uniquely choose `k` objects from among `n` objects, ignoring order. diff --git a/man/man3/tomo-Int.clamped.3 b/man/man3/tomo-Int.clamped.3 index deb1a98b..5d846a21 100644 --- a/man/man3/tomo-Int.clamped.3 +++ b/man/man3/tomo-Int.clamped.3 @@ -19,12 +19,12 @@ Returns the given number clamped between two values so that it is within that ra .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Int The integer to clamp. - -low Int The lowest value the result can take. - -high Int The highest value the result can take. - +lb lb lbx +l l l. +Name Type Description +x Int The integer to clamp. +low Int The lowest value the result can take. +high Int The highest value the result can take. .TE .SH RETURN The first argument clamped between the other two arguments. diff --git a/man/man3/tomo-Int.factorial.3 b/man/man3/tomo-Int.factorial.3 index accebcdb..e7105287 100644 --- a/man/man3/tomo-Int.factorial.3 +++ b/man/man3/tomo-Int.factorial.3 @@ -19,10 +19,10 @@ Computes the factorial of an integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -n Int The integer to compute the factorial of. - +lb lb lbx +l l l. +Name Type Description +n Int The integer to compute the factorial of. .TE .SH RETURN The factorial of the given integer. diff --git a/man/man3/tomo-Int.get_bit.3 b/man/man3/tomo-Int.get_bit.3 index d5614a6a..bf9f11b2 100644 --- a/man/man3/tomo-Int.get_bit.3 +++ b/man/man3/tomo-Int.get_bit.3 @@ -19,11 +19,11 @@ In the binary representation of an integer, check whether a given bit index is s .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -i Int The integer whose bits are being inspected. - -bit_index Int The index of the bit to check (1-indexed). - +lb lb lbx +l l l. +Name Type Description +i Int The integer whose bits are being inspected. +bit_index Int The index of the bit to check (1-indexed). .TE .SH RETURN Whether or not the given bit index is set to 1 in the binary representation of the integer. diff --git a/man/man3/tomo-Int.is_between.3 b/man/man3/tomo-Int.is_between.3 index 261bb5a4..8087e0d0 100644 --- a/man/man3/tomo-Int.is_between.3 +++ b/man/man3/tomo-Int.is_between.3 @@ -19,12 +19,12 @@ Determines if an integer is between two numbers (inclusive). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Int The integer to be checked. - -low Int The lower bound to check (inclusive). - -high Int The upper bound to check (inclusive). - +lb lb lbx +l l l. +Name Type Description +x Int The integer to be checked. +low Int The lower bound to check (inclusive). +high Int The upper bound to check (inclusive). .TE .SH RETURN `yes` if `low <= x and x <= high`, otherwise `no` diff --git a/man/man3/tomo-Int.next_prime.3 b/man/man3/tomo-Int.next_prime.3 index 0afcf7e9..ab2c4307 100644 --- a/man/man3/tomo-Int.next_prime.3 +++ b/man/man3/tomo-Int.next_prime.3 @@ -19,10 +19,10 @@ Finds the next prime number greater than the given integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Int The integer after which to find the next prime. - +lb lb lbx +l l l. +Name Type Description +x Int The integer after which to find the next prime. .TE .SH RETURN The next prime number greater than `x`. diff --git a/man/man3/tomo-Int.prev_prime.3 b/man/man3/tomo-Int.prev_prime.3 index 6908f408..87f7be36 100644 --- a/man/man3/tomo-Int.prev_prime.3 +++ b/man/man3/tomo-Int.prev_prime.3 @@ -19,10 +19,10 @@ Finds the previous prime number less than the given integer. If there is no prev .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Int The integer before which to find the previous prime. - +lb lb lbx +l l l. +Name Type Description +x Int The integer before which to find the previous prime. .TE .SH RETURN The previous prime number less than `x`, or `none` if `x` is less than 2. diff --git a/man/man3/tomo-Int.sqrt.3 b/man/man3/tomo-Int.sqrt.3 index f929a5fd..6a3e7eaf 100644 --- a/man/man3/tomo-Int.sqrt.3 +++ b/man/man3/tomo-Int.sqrt.3 @@ -19,10 +19,10 @@ Calculates the nearest square root of an integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Int The integer whose square root is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Int The integer whose square root is to be calculated. .TE .SH RETURN The integer part of the square root of `x`. diff --git a/man/man3/tomo-List.by.3 b/man/man3/tomo-List.by.3 index 5f114df8..463ea9ed 100644 --- a/man/man3/tomo-List.by.3 +++ b/man/man3/tomo-List.by.3 @@ -19,11 +19,11 @@ Creates a new list with elements spaced by the specified step value. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The original list. - -step Int The step value for selecting elements. - +lb lb lbx +l l l. +Name Type Description +list [T] The original list. +step Int The step value for selecting elements. .TE .SH RETURN A new list with every `step`-th element from the original list. diff --git a/man/man3/tomo-List.clear.3 b/man/man3/tomo-List.clear.3 index 0f767272..b4237e45 100644 --- a/man/man3/tomo-List.clear.3 +++ b/man/man3/tomo-List.clear.3 @@ -19,10 +19,10 @@ Clears all elements from the list. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list @[T] The mutable reference to the list to be cleared. - +lb lb lbx +l l l. +Name Type Description +list @[T] The mutable reference to the list to be cleared. .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-List.counts.3 b/man/man3/tomo-List.counts.3 index c4e22303..54698085 100644 --- a/man/man3/tomo-List.counts.3 +++ b/man/man3/tomo-List.counts.3 @@ -19,10 +19,10 @@ Counts the occurrences of each element in the list. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The list to count elements in. - +lb lb lbx +l l l. +Name Type Description +list [T] The list to count elements in. .TE .SH RETURN A table mapping each element to its count. diff --git a/man/man3/tomo-List.find.3 b/man/man3/tomo-List.find.3 index 1eef0bb2..eece41a1 100644 --- a/man/man3/tomo-List.find.3 +++ b/man/man3/tomo-List.find.3 @@ -19,11 +19,11 @@ Finds the index of the first occurrence of an element (if any). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The list to search through. - -target T The item to search for. - +lb lb lbx +l l l. +Name Type Description +list [T] The list to search through. +target T The item to search for. .TE .SH RETURN The index of the first occurrence or `none` if not found. diff --git a/man/man3/tomo-List.from.3 b/man/man3/tomo-List.from.3 index f58e669e..e63a089c 100644 --- a/man/man3/tomo-List.from.3 +++ b/man/man3/tomo-List.from.3 @@ -19,11 +19,11 @@ Returns a slice of the list starting from a specified index. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The original list. - -first Int The index to start from. - +lb lb lbx +l l l. +Name Type Description +list [T] The original list. +first Int The index to start from. .TE .SH RETURN A new list starting from the specified index. diff --git a/man/man3/tomo-List.has.3 b/man/man3/tomo-List.has.3 index 75eb5bc8..6406e294 100644 --- a/man/man3/tomo-List.has.3 +++ b/man/man3/tomo-List.has.3 @@ -19,11 +19,11 @@ Checks if the list has an element. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The list to check. - -target T The element to check for. - +lb lb lbx +l l l. +Name Type Description +list [T] The list to check. +target T The element to check for. .TE .SH RETURN `yes` if the list has the element, `no` otherwise. diff --git a/man/man3/tomo-List.reversed.3 b/man/man3/tomo-List.reversed.3 index b49d82a9..f0450007 100644 --- a/man/man3/tomo-List.reversed.3 +++ b/man/man3/tomo-List.reversed.3 @@ -19,10 +19,10 @@ Returns a reversed slice of the list. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The list to be reversed. - +lb lb lbx +l l l. +Name Type Description +list [T] The list to be reversed. .TE .SH RETURN A slice of the list with elements in reverse order. diff --git a/man/man3/tomo-List.slice.3 b/man/man3/tomo-List.slice.3 index d160c863..5025733a 100644 --- a/man/man3/tomo-List.slice.3 +++ b/man/man3/tomo-List.slice.3 @@ -19,12 +19,12 @@ Returns a slice of the list spanning the given indices (inclusive). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The original list. - -from Int The first index to include. - -to Int The last index to include. - +lb lb lbx +l l l. +Name Type Description +list [T] The original list. +from Int The first index to include. +to Int The last index to include. .TE .SH RETURN A new list spanning the given indices. Note: negative indices are counted from the back of the list, so `-1` refers to the last element, `-2` the second-to-last, and so on. diff --git a/man/man3/tomo-List.to.3 b/man/man3/tomo-List.to.3 index dd1a23a9..5e3e3d77 100644 --- a/man/man3/tomo-List.to.3 +++ b/man/man3/tomo-List.to.3 @@ -19,11 +19,11 @@ Returns a slice of the list from the start of the original list up to a specifie .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The original list. - -last Int The index up to which elements should be included. - +lb lb lbx +l l l. +Name Type Description +list [T] The original list. +last Int The index up to which elements should be included. .TE .SH RETURN A new list containing elements from the start up to the specified index. diff --git a/man/man3/tomo-List.unique.3 b/man/man3/tomo-List.unique.3 index 1c203a62..80b9b9f5 100644 --- a/man/man3/tomo-List.unique.3 +++ b/man/man3/tomo-List.unique.3 @@ -19,10 +19,10 @@ Returns a set of the unique elements of the list. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The list to process. - +lb lb lbx +l l l. +Name Type Description +list [T] The list to process. .TE .SH RETURN A set of the unique elements from the list. diff --git a/man/man3/tomo-List.where.3 b/man/man3/tomo-List.where.3 index a436d2ea..9a02aaaf 100644 --- a/man/man3/tomo-List.where.3 +++ b/man/man3/tomo-List.where.3 @@ -19,11 +19,11 @@ Find the index of the first item that matches a predicate function (if any). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -list [T] The list to search through. - -predicate func(item:&T -> Bool) A function that returns \fByes\fR if the item's index should be returned or \fBno\fR if it should not. - +lb lb lbx +l l l. +Name Type Description +list [T] The list to search through. +predicate func(item:&T -> Bool) A function that returns \fByes\fR if the item's index should be returned or \fBno\fR if it should not. .TE .SH RETURN Returns the index of the first item where the predicate is true or `none` if no item matches. diff --git a/man/man3/tomo-Num.abs.3 b/man/man3/tomo-Num.abs.3 index e34723f8..7bff8981 100644 --- a/man/man3/tomo-Num.abs.3 +++ b/man/man3/tomo-Num.abs.3 @@ -19,10 +19,10 @@ Calculates the absolute value of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -n Num The number whose absolute value is to be computed. - +lb lb lbx +l l l. +Name Type Description +n Num The number whose absolute value is to be computed. .TE .SH RETURN The absolute value of `n`. diff --git a/man/man3/tomo-Num.acos.3 b/man/man3/tomo-Num.acos.3 index 8c007109..83070c32 100644 --- a/man/man3/tomo-Num.acos.3 +++ b/man/man3/tomo-Num.acos.3 @@ -19,10 +19,10 @@ Computes the arc cosine of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the arc cosine is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the arc cosine is to be calculated. .TE .SH RETURN The arc cosine of `x` in radians. diff --git a/man/man3/tomo-Num.acosh.3 b/man/man3/tomo-Num.acosh.3 index f2980b39..680d6e24 100644 --- a/man/man3/tomo-Num.acosh.3 +++ b/man/man3/tomo-Num.acosh.3 @@ -19,10 +19,10 @@ Computes the inverse hyperbolic cosine of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the inverse hyperbolic cosine is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the inverse hyperbolic cosine is to be calculated. .TE .SH RETURN The inverse hyperbolic cosine of `x`. diff --git a/man/man3/tomo-Num.asin.3 b/man/man3/tomo-Num.asin.3 index 4501c81e..c44e5fe4 100644 --- a/man/man3/tomo-Num.asin.3 +++ b/man/man3/tomo-Num.asin.3 @@ -19,10 +19,10 @@ Computes the arc sine of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the arc sine is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the arc sine is to be calculated. .TE .SH RETURN The arc sine of `x` in radians. diff --git a/man/man3/tomo-Num.asinh.3 b/man/man3/tomo-Num.asinh.3 index 4c2216a8..ac1fe25a 100644 --- a/man/man3/tomo-Num.asinh.3 +++ b/man/man3/tomo-Num.asinh.3 @@ -19,10 +19,10 @@ Computes the inverse hyperbolic sine of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the inverse hyperbolic sine is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the inverse hyperbolic sine is to be calculated. .TE .SH RETURN The inverse hyperbolic sine of `x`. diff --git a/man/man3/tomo-Num.atan.3 b/man/man3/tomo-Num.atan.3 index 28efbba5..e2303e64 100644 --- a/man/man3/tomo-Num.atan.3 +++ b/man/man3/tomo-Num.atan.3 @@ -19,10 +19,10 @@ Computes the arc tangent of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the arc tangent is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the arc tangent is to be calculated. .TE .SH RETURN The arc tangent of `x` in radians. diff --git a/man/man3/tomo-Num.atan2.3 b/man/man3/tomo-Num.atan2.3 index e960bf99..aa9b2585 100644 --- a/man/man3/tomo-Num.atan2.3 +++ b/man/man3/tomo-Num.atan2.3 @@ -19,11 +19,11 @@ Computes the arc tangent of the quotient of two numbers. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The numerator. - -y Num The denominator. - +lb lb lbx +l l l. +Name Type Description +x Num The numerator. +y Num The denominator. .TE .SH RETURN The arc tangent of `x/y` in radians. diff --git a/man/man3/tomo-Num.atanh.3 b/man/man3/tomo-Num.atanh.3 index 677e13ab..13e2ccb2 100644 --- a/man/man3/tomo-Num.atanh.3 +++ b/man/man3/tomo-Num.atanh.3 @@ -19,10 +19,10 @@ Computes the inverse hyperbolic tangent of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the inverse hyperbolic tangent is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the inverse hyperbolic tangent is to be calculated. .TE .SH RETURN The inverse hyperbolic tangent of `x`. diff --git a/man/man3/tomo-Num.cbrt.3 b/man/man3/tomo-Num.cbrt.3 index 05bfbb84..790abf86 100644 --- a/man/man3/tomo-Num.cbrt.3 +++ b/man/man3/tomo-Num.cbrt.3 @@ -19,10 +19,10 @@ Computes the cube root of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the cube root is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the cube root is to be calculated. .TE .SH RETURN The cube root of `x`. diff --git a/man/man3/tomo-Num.ceil.3 b/man/man3/tomo-Num.ceil.3 index d096bce4..a43b0414 100644 --- a/man/man3/tomo-Num.ceil.3 +++ b/man/man3/tomo-Num.ceil.3 @@ -19,10 +19,10 @@ Rounds a number up to the nearest integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number to be rounded up. - +lb lb lbx +l l l. +Name Type Description +x Num The number to be rounded up. .TE .SH RETURN The smallest integer greater than or equal to `x`. diff --git a/man/man3/tomo-Num.clamped.3 b/man/man3/tomo-Num.clamped.3 index 8561babc..21136b26 100644 --- a/man/man3/tomo-Num.clamped.3 +++ b/man/man3/tomo-Num.clamped.3 @@ -19,12 +19,12 @@ Returns the given number clamped between two values so that it is within that ra .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number to clamp. - -low Num The lowest value the result can take. - -high Num The highest value the result can take. - +lb lb lbx +l l l. +Name Type Description +x Num The number to clamp. +low Num The lowest value the result can take. +high Num The highest value the result can take. .TE .SH RETURN The first argument clamped between the other two arguments. diff --git a/man/man3/tomo-Num.copysign.3 b/man/man3/tomo-Num.copysign.3 index e7ed54b9..919772cd 100644 --- a/man/man3/tomo-Num.copysign.3 +++ b/man/man3/tomo-Num.copysign.3 @@ -19,11 +19,11 @@ Copies the sign of one number to another. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number whose magnitude will be copied. - -y Num The number whose sign will be copied. - +lb lb lbx +l l l. +Name Type Description +x Num The number whose magnitude will be copied. +y Num The number whose sign will be copied. .TE .SH RETURN A number with the magnitude of `x` and the sign of `y`. diff --git a/man/man3/tomo-Num.cos.3 b/man/man3/tomo-Num.cos.3 index 884a4f3e..0d4932d2 100644 --- a/man/man3/tomo-Num.cos.3 +++ b/man/man3/tomo-Num.cos.3 @@ -19,10 +19,10 @@ Computes the cosine of a number (angle in radians). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The angle in radians. - +lb lb lbx +l l l. +Name Type Description +x Num The angle in radians. .TE .SH RETURN The cosine of `x`. diff --git a/man/man3/tomo-Num.cosh.3 b/man/man3/tomo-Num.cosh.3 index 8a85b7c3..adc0bba5 100644 --- a/man/man3/tomo-Num.cosh.3 +++ b/man/man3/tomo-Num.cosh.3 @@ -19,10 +19,10 @@ Computes the hyperbolic cosine of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the hyperbolic cosine is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the hyperbolic cosine is to be calculated. .TE .SH RETURN The hyperbolic cosine of `x`. diff --git a/man/man3/tomo-Num.erf.3 b/man/man3/tomo-Num.erf.3 index a858ecda..8a2afed3 100644 --- a/man/man3/tomo-Num.erf.3 +++ b/man/man3/tomo-Num.erf.3 @@ -19,10 +19,10 @@ Computes the error function of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the error function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the error function is to be calculated. .TE .SH RETURN The error function of `x`. diff --git a/man/man3/tomo-Num.erfc.3 b/man/man3/tomo-Num.erfc.3 index 8b51d6cd..82de219c 100644 --- a/man/man3/tomo-Num.erfc.3 +++ b/man/man3/tomo-Num.erfc.3 @@ -19,10 +19,10 @@ Computes the complementary error function of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the complementary error function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the complementary error function is to be calculated. .TE .SH RETURN The complementary error function of `x`. diff --git a/man/man3/tomo-Num.exp.3 b/man/man3/tomo-Num.exp.3 index 651f43ea..ac784957 100644 --- a/man/man3/tomo-Num.exp.3 +++ b/man/man3/tomo-Num.exp.3 @@ -19,10 +19,10 @@ Computes the exponential function $e^x$ for a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The exponent. - +lb lb lbx +l l l. +Name Type Description +x Num The exponent. .TE .SH RETURN The value of $e^x$. diff --git a/man/man3/tomo-Num.exp2.3 b/man/man3/tomo-Num.exp2.3 index 6e4f6626..194de5ce 100644 --- a/man/man3/tomo-Num.exp2.3 +++ b/man/man3/tomo-Num.exp2.3 @@ -19,10 +19,10 @@ Computes $2^x$ for a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The exponent. - +lb lb lbx +l l l. +Name Type Description +x Num The exponent. .TE .SH RETURN The value of $2^x$. diff --git a/man/man3/tomo-Num.expm1.3 b/man/man3/tomo-Num.expm1.3 index c82f38df..099186cc 100644 --- a/man/man3/tomo-Num.expm1.3 +++ b/man/man3/tomo-Num.expm1.3 @@ -19,10 +19,10 @@ Computes $e^x - 1$ for a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The exponent. - +lb lb lbx +l l l. +Name Type Description +x Num The exponent. .TE .SH RETURN The value of $e^x - 1$. diff --git a/man/man3/tomo-Num.fdim.3 b/man/man3/tomo-Num.fdim.3 index f4b0a46a..2b1071d1 100644 --- a/man/man3/tomo-Num.fdim.3 +++ b/man/man3/tomo-Num.fdim.3 @@ -19,11 +19,11 @@ Computes the positive difference between two numbers. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The first number. - -y Num The second number. - +lb lb lbx +l l l. +Name Type Description +x Num The first number. +y Num The second number. .TE .SH RETURN The positive difference $\max(0, x - y)$. diff --git a/man/man3/tomo-Num.floor.3 b/man/man3/tomo-Num.floor.3 index ff78d081..5dfc2ae0 100644 --- a/man/man3/tomo-Num.floor.3 +++ b/man/man3/tomo-Num.floor.3 @@ -19,10 +19,10 @@ Rounds a number down to the nearest integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number to be rounded down. - +lb lb lbx +l l l. +Name Type Description +x Num The number to be rounded down. .TE .SH RETURN The largest integer less than or equal to `x`. diff --git a/man/man3/tomo-Num.hypot.3 b/man/man3/tomo-Num.hypot.3 index de0d64a1..67bbd9af 100644 --- a/man/man3/tomo-Num.hypot.3 +++ b/man/man3/tomo-Num.hypot.3 @@ -19,11 +19,11 @@ Computes the Euclidean norm, $\sqrt{x^2 + y^2}$, of two numbers. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The first number. - -y Num The second number. - +lb lb lbx +l l l. +Name Type Description +x Num The first number. +y Num The second number. .TE .SH RETURN The Euclidean norm of `x` and `y`. diff --git a/man/man3/tomo-Num.is_between.3 b/man/man3/tomo-Num.is_between.3 index bba3ad19..4276c8e3 100644 --- a/man/man3/tomo-Num.is_between.3 +++ b/man/man3/tomo-Num.is_between.3 @@ -19,12 +19,12 @@ Determines if a number is between two numbers (inclusive). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The integer to be checked. - -low Num The lower bound to check (inclusive). - -high Num The upper bound to check (inclusive). - +lb lb lbx +l l l. +Name Type Description +x Num The integer to be checked. +low Num The lower bound to check (inclusive). +high Num The upper bound to check (inclusive). .TE .SH RETURN `yes` if `low <= x and x <= high`, otherwise `no` diff --git a/man/man3/tomo-Num.isfinite.3 b/man/man3/tomo-Num.isfinite.3 index fac2f19d..81002b32 100644 --- a/man/man3/tomo-Num.isfinite.3 +++ b/man/man3/tomo-Num.isfinite.3 @@ -19,10 +19,10 @@ Checks if a number is finite. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -n Num The number to be checked. - +lb lb lbx +l l l. +Name Type Description +n Num The number to be checked. .TE .SH RETURN `yes` if `n` is finite, `no` otherwise. diff --git a/man/man3/tomo-Num.isinf.3 b/man/man3/tomo-Num.isinf.3 index 8de9736e..33016ae8 100644 --- a/man/man3/tomo-Num.isinf.3 +++ b/man/man3/tomo-Num.isinf.3 @@ -19,10 +19,10 @@ Checks if a number is infinite. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -n Num The number to be checked. - +lb lb lbx +l l l. +Name Type Description +n Num The number to be checked. .TE .SH RETURN `yes` if `n` is infinite, `no` otherwise. diff --git a/man/man3/tomo-Num.j0.3 b/man/man3/tomo-Num.j0.3 index c93a5f24..a4847bec 100644 --- a/man/man3/tomo-Num.j0.3 +++ b/man/man3/tomo-Num.j0.3 @@ -19,10 +19,10 @@ Computes the Bessel function of the first kind of order 0. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the Bessel function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the Bessel function is to be calculated. .TE .SH RETURN The Bessel function of the first kind of order 0 of `x`. diff --git a/man/man3/tomo-Num.j1.3 b/man/man3/tomo-Num.j1.3 index 3d924958..eae8b9b7 100644 --- a/man/man3/tomo-Num.j1.3 +++ b/man/man3/tomo-Num.j1.3 @@ -19,10 +19,10 @@ Computes the Bessel function of the first kind of order 1. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the Bessel function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the Bessel function is to be calculated. .TE .SH RETURN The Bessel function of the first kind of order 1 of `x`. diff --git a/man/man3/tomo-Num.log.3 b/man/man3/tomo-Num.log.3 index aae022c0..594e9edc 100644 --- a/man/man3/tomo-Num.log.3 +++ b/man/man3/tomo-Num.log.3 @@ -19,10 +19,10 @@ Computes the natural logarithm (base $e$) of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the natural logarithm is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the natural logarithm is to be calculated. .TE .SH RETURN The natural logarithm of `x`. diff --git a/man/man3/tomo-Num.log10.3 b/man/man3/tomo-Num.log10.3 index 1e5a13fc..181b26f8 100644 --- a/man/man3/tomo-Num.log10.3 +++ b/man/man3/tomo-Num.log10.3 @@ -19,10 +19,10 @@ Computes the base-10 logarithm of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the base-10 logarithm is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the base-10 logarithm is to be calculated. .TE .SH RETURN The base-10 logarithm of `x`. diff --git a/man/man3/tomo-Num.log1p.3 b/man/man3/tomo-Num.log1p.3 index 467e535d..677b101d 100644 --- a/man/man3/tomo-Num.log1p.3 +++ b/man/man3/tomo-Num.log1p.3 @@ -19,10 +19,10 @@ Computes $\log(1 + x)$ for a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which $\log(1 + x)$ is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which $\log(1 + x)$ is to be calculated. .TE .SH RETURN The value of $\log(1 + x)$. diff --git a/man/man3/tomo-Num.log2.3 b/man/man3/tomo-Num.log2.3 index ee0ea14b..4599df08 100644 --- a/man/man3/tomo-Num.log2.3 +++ b/man/man3/tomo-Num.log2.3 @@ -19,10 +19,10 @@ Computes the base-2 logarithm of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the base-2 logarithm is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the base-2 logarithm is to be calculated. .TE .SH RETURN The base-2 logarithm of `x`. diff --git a/man/man3/tomo-Num.logb.3 b/man/man3/tomo-Num.logb.3 index e2e5b738..a6930551 100644 --- a/man/man3/tomo-Num.logb.3 +++ b/man/man3/tomo-Num.logb.3 @@ -19,10 +19,10 @@ Computes the binary exponent (base-2 logarithm) of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the binary exponent is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the binary exponent is to be calculated. .TE .SH RETURN The binary exponent of `x`. diff --git a/man/man3/tomo-Num.mix.3 b/man/man3/tomo-Num.mix.3 index 01e5d7dd..0514de66 100644 --- a/man/man3/tomo-Num.mix.3 +++ b/man/man3/tomo-Num.mix.3 @@ -19,12 +19,12 @@ Interpolates between two numbers based on a given amount. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -amount Num The interpolation factor (between \fB0\fR and \fB1\fR). - -x Num The starting number. - -y Num The ending number. - +lb lb lbx +l l l. +Name Type Description +amount Num The interpolation factor (between \fB0\fR and \fB1\fR). +x Num The starting number. +y Num The ending number. .TE .SH RETURN The interpolated number between `x` and `y` based on `amount`. diff --git a/man/man3/tomo-Num.nextafter.3 b/man/man3/tomo-Num.nextafter.3 index 06256bdf..1379239b 100644 --- a/man/man3/tomo-Num.nextafter.3 +++ b/man/man3/tomo-Num.nextafter.3 @@ -19,11 +19,11 @@ Computes the next representable value after a given number towards a specified d .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The starting number. - -y Num The direction towards which to find the next representable value. - +lb lb lbx +l l l. +Name Type Description +x Num The starting number. +y Num The direction towards which to find the next representable value. .TE .SH RETURN The next representable value after `x` in the direction of `y`. diff --git a/man/man3/tomo-Num.rint.3 b/man/man3/tomo-Num.rint.3 index ae134089..948838be 100644 --- a/man/man3/tomo-Num.rint.3 +++ b/man/man3/tomo-Num.rint.3 @@ -19,10 +19,10 @@ Rounds a number to the nearest integer, with ties rounded to the nearest even in .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number to be rounded. - +lb lb lbx +l l l. +Name Type Description +x Num The number to be rounded. .TE .SH RETURN The nearest integer value of `x`. diff --git a/man/man3/tomo-Num.round.3 b/man/man3/tomo-Num.round.3 index e35cf39f..712d3038 100644 --- a/man/man3/tomo-Num.round.3 +++ b/man/man3/tomo-Num.round.3 @@ -19,10 +19,10 @@ Rounds a number to the nearest whole number integer. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number to be rounded. - +lb lb lbx +l l l. +Name Type Description +x Num The number to be rounded. .TE .SH RETURN The nearest integer value of `x`. diff --git a/man/man3/tomo-Num.significand.3 b/man/man3/tomo-Num.significand.3 index 1cd6421d..4f78407d 100644 --- a/man/man3/tomo-Num.significand.3 +++ b/man/man3/tomo-Num.significand.3 @@ -19,10 +19,10 @@ Extracts the significand (or mantissa) of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number from which to extract the significand. - +lb lb lbx +l l l. +Name Type Description +x Num The number from which to extract the significand. .TE .SH RETURN The significand of `x`. diff --git a/man/man3/tomo-Num.sin.3 b/man/man3/tomo-Num.sin.3 index 5f3ad779..826035bf 100644 --- a/man/man3/tomo-Num.sin.3 +++ b/man/man3/tomo-Num.sin.3 @@ -19,10 +19,10 @@ Computes the sine of a number (angle in radians). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The angle in radians. - +lb lb lbx +l l l. +Name Type Description +x Num The angle in radians. .TE .SH RETURN The sine of `x`. diff --git a/man/man3/tomo-Num.sinh.3 b/man/man3/tomo-Num.sinh.3 index b3157201..a65c19a9 100644 --- a/man/man3/tomo-Num.sinh.3 +++ b/man/man3/tomo-Num.sinh.3 @@ -19,10 +19,10 @@ Computes the hyperbolic sine of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the hyperbolic sine is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the hyperbolic sine is to be calculated. .TE .SH RETURN The hyperbolic sine of `x`. diff --git a/man/man3/tomo-Num.sqrt.3 b/man/man3/tomo-Num.sqrt.3 index c8db5200..5ade1af5 100644 --- a/man/man3/tomo-Num.sqrt.3 +++ b/man/man3/tomo-Num.sqrt.3 @@ -19,10 +19,10 @@ Computes the square root of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the square root is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the square root is to be calculated. .TE .SH RETURN The square root of `x`. diff --git a/man/man3/tomo-Num.tan.3 b/man/man3/tomo-Num.tan.3 index a159a4fa..7370d4b6 100644 --- a/man/man3/tomo-Num.tan.3 +++ b/man/man3/tomo-Num.tan.3 @@ -19,10 +19,10 @@ Computes the tangent of a number (angle in radians). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The angle in radians. - +lb lb lbx +l l l. +Name Type Description +x Num The angle in radians. .TE .SH RETURN The tangent of `x`. diff --git a/man/man3/tomo-Num.tanh.3 b/man/man3/tomo-Num.tanh.3 index 0bd99114..51dca0c6 100644 --- a/man/man3/tomo-Num.tanh.3 +++ b/man/man3/tomo-Num.tanh.3 @@ -19,10 +19,10 @@ Computes the hyperbolic tangent of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the hyperbolic tangent is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the hyperbolic tangent is to be calculated. .TE .SH RETURN The hyperbolic tangent of `x`. diff --git a/man/man3/tomo-Num.tgamma.3 b/man/man3/tomo-Num.tgamma.3 index 3aa2b61a..13d7ff5d 100644 --- a/man/man3/tomo-Num.tgamma.3 +++ b/man/man3/tomo-Num.tgamma.3 @@ -19,10 +19,10 @@ Computes the gamma function of a number. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the gamma function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the gamma function is to be calculated. .TE .SH RETURN The gamma function of `x`. diff --git a/man/man3/tomo-Num.trunc.3 b/man/man3/tomo-Num.trunc.3 index 7de1bbea..4ba83aa2 100644 --- a/man/man3/tomo-Num.trunc.3 +++ b/man/man3/tomo-Num.trunc.3 @@ -19,10 +19,10 @@ Truncates a number to the nearest integer towards zero. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number to be truncated. - +lb lb lbx +l l l. +Name Type Description +x Num The number to be truncated. .TE .SH RETURN The integer part of `x` towards zero. diff --git a/man/man3/tomo-Num.with_precision.3 b/man/man3/tomo-Num.with_precision.3 index 5b1dc30c..aee46a28 100644 --- a/man/man3/tomo-Num.with_precision.3 +++ b/man/man3/tomo-Num.with_precision.3 @@ -19,11 +19,11 @@ Round a number to the given precision level (specified as `10`, `.1`, `.001` etc .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -n Num The number to be rounded to a given precision. - -precision Num The precision to which the number should be rounded. - +lb lb lbx +l l l. +Name Type Description +n Num The number to be rounded to a given precision. +precision Num The precision to which the number should be rounded. .TE .SH RETURN The number, rounded to the given precision level. diff --git a/man/man3/tomo-Num.y0.3 b/man/man3/tomo-Num.y0.3 index 7c20e545..6b692de2 100644 --- a/man/man3/tomo-Num.y0.3 +++ b/man/man3/tomo-Num.y0.3 @@ -19,10 +19,10 @@ Computes the Bessel function of the second kind of order 0. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the Bessel function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the Bessel function is to be calculated. .TE .SH RETURN The Bessel function of the second kind of order 0 of `x`. diff --git a/man/man3/tomo-Num.y1.3 b/man/man3/tomo-Num.y1.3 index 1fee30e7..d1e98bad 100644 --- a/man/man3/tomo-Num.y1.3 +++ b/man/man3/tomo-Num.y1.3 @@ -19,10 +19,10 @@ Computes the Bessel function of the second kind of order 1. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -x Num The number for which the Bessel function is to be calculated. - +lb lb lbx +l l l. +Name Type Description +x Num The number for which the Bessel function is to be calculated. .TE .SH RETURN The Bessel function of the second kind of order 1 of `x`. diff --git a/man/man3/tomo-Path.base_name.3 b/man/man3/tomo-Path.base_name.3 index 3d6e0f07..f557a971 100644 --- a/man/man3/tomo-Path.base_name.3 +++ b/man/man3/tomo-Path.base_name.3 @@ -19,10 +19,10 @@ Returns the base name of the file or directory at the specified path. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file or directory. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file or directory. .TE .SH RETURN The base name of the file or directory. diff --git a/man/man3/tomo-Path.by_line.3 b/man/man3/tomo-Path.by_line.3 index ad08ca33..46a47d8c 100644 --- a/man/man3/tomo-Path.by_line.3 +++ b/man/man3/tomo-Path.by_line.3 @@ -19,10 +19,10 @@ Returns an iterator that can be used to iterate over a file one line at a time, .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file. .TE .SH RETURN An iterator that can be used to get lines from a file one at a time or none if the file couldn't be read. diff --git a/man/man3/tomo-Path.can_execute.3 b/man/man3/tomo-Path.can_execute.3 index 90978a32..53dee69c 100644 --- a/man/man3/tomo-Path.can_execute.3 +++ b/man/man3/tomo-Path.can_execute.3 @@ -19,10 +19,10 @@ Returns whether or not a file can be executed by the current user/group. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file to check. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file to check. .TE .SH RETURN `yes` if the file or directory exists and the current user has execute permissions, otherwise `no`. diff --git a/man/man3/tomo-Path.can_read.3 b/man/man3/tomo-Path.can_read.3 index 919b5312..b3c19a89 100644 --- a/man/man3/tomo-Path.can_read.3 +++ b/man/man3/tomo-Path.can_read.3 @@ -19,10 +19,10 @@ Returns whether or not a file can be read by the current user/group. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file to check. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file to check. .TE .SH RETURN `yes` if the file or directory exists and the current user has read permissions, otherwise `no`. diff --git a/man/man3/tomo-Path.can_write.3 b/man/man3/tomo-Path.can_write.3 index 98e8c0bb..78880c4b 100644 --- a/man/man3/tomo-Path.can_write.3 +++ b/man/man3/tomo-Path.can_write.3 @@ -19,10 +19,10 @@ Returns whether or not a file can be written by the current user/group. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file to check. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file to check. .TE .SH RETURN `yes` if the file or directory exists and the current user has write permissions, otherwise `no`. diff --git a/man/man3/tomo-Path.child.3 b/man/man3/tomo-Path.child.3 index ee0ca448..8e6c4859 100644 --- a/man/man3/tomo-Path.child.3 +++ b/man/man3/tomo-Path.child.3 @@ -19,11 +19,11 @@ Return a path that is a child of another path. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of a directory. - -child Text The name of a child file or directory. - +lb lb lbx +l l l. +Name Type Description +path Path The path of a directory. +child Text The name of a child file or directory. .TE .SH RETURN A new path representing the child. diff --git a/man/man3/tomo-Path.exists.3 b/man/man3/tomo-Path.exists.3 index 5143a4d3..7d03e468 100644 --- a/man/man3/tomo-Path.exists.3 +++ b/man/man3/tomo-Path.exists.3 @@ -19,10 +19,10 @@ Checks if a file or directory exists at the specified path. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path to check. - +lb lb lbx +l l l. +Name Type Description +path Path The path to check. .TE .SH RETURN `True` if the file or directory exists, `False` otherwise. diff --git a/man/man3/tomo-Path.expand_home.3 b/man/man3/tomo-Path.expand_home.3 index b14aff25..0f3ee0d0 100644 --- a/man/man3/tomo-Path.expand_home.3 +++ b/man/man3/tomo-Path.expand_home.3 @@ -19,10 +19,10 @@ For home-based paths (those starting with `~`), expand the path to replace the t .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path to expand. - +lb lb lbx +l l l. +Name Type Description +path Path The path to expand. .TE .SH RETURN If the path does not start with a `~`, then return it unmodified. Otherwise, replace the `~` with an absolute path to the user's home directory. diff --git a/man/man3/tomo-Path.from_components.3 b/man/man3/tomo-Path.from_components.3 index 6ef19089..d20efdd2 100644 --- a/man/man3/tomo-Path.from_components.3 +++ b/man/man3/tomo-Path.from_components.3 @@ -19,10 +19,10 @@ Returns a path built from a list of path components. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -components [Text] A list of path components. - +lb lb lbx +l l l. +Name Type Description +components [Text] A list of path components. .TE .SH RETURN A path representing the given components. diff --git a/man/man3/tomo-Path.glob.3 b/man/man3/tomo-Path.glob.3 index fc8d04b8..1e593035 100644 --- a/man/man3/tomo-Path.glob.3 +++ b/man/man3/tomo-Path.glob.3 @@ -25,10 +25,10 @@ Perform a globbing operation and return a list of matching paths. Some glob spec .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the directory which may contain special globbing characters like \fB*\fR, \fB?\fR, or \fB{...}\fR - +lb lb lbx +l l l. +Name Type Description +path Path The path of the directory which may contain special globbing characters like \fB*\fR, \fB?\fR, or \fB{...}\fR .TE .SH RETURN A list of file paths that match the glob. diff --git a/man/man3/tomo-Path.has_extension.3 b/man/man3/tomo-Path.has_extension.3 index 85b181e1..390015da 100644 --- a/man/man3/tomo-Path.has_extension.3 +++ b/man/man3/tomo-Path.has_extension.3 @@ -19,11 +19,11 @@ Return whether or not a path has a given file extension. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path A path. - -extension Text A file extension (leading \fB.\fR is optional). If empty, the check will test if the file does not have any file extension. - +lb lb lbx +l l l. +Name Type Description +path Path A path. +extension Text A file extension (leading \fB.\fR is optional). If empty, the check will test if the file does not have any file extension. .TE .SH RETURN Whether or not the path has the given extension. diff --git a/man/man3/tomo-Path.is_symlink.3 b/man/man3/tomo-Path.is_symlink.3 index 85db4e43..d38591d0 100644 --- a/man/man3/tomo-Path.is_symlink.3 +++ b/man/man3/tomo-Path.is_symlink.3 @@ -19,10 +19,10 @@ Checks if the path represents a symbolic link. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path to check. - +lb lb lbx +l l l. +Name Type Description +path Path The path to check. .TE .SH RETURN `True` if the path is a symbolic link, `False` otherwise. diff --git a/man/man3/tomo-Path.lines.3 b/man/man3/tomo-Path.lines.3 index cf9a2312..19e48a99 100644 --- a/man/man3/tomo-Path.lines.3 +++ b/man/man3/tomo-Path.lines.3 @@ -19,10 +19,10 @@ Returns a list with the lines of text in a file or returns none if the file coul .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file. .TE .SH RETURN A list of the lines in a file or none if the file couldn't be read. diff --git a/man/man3/tomo-Path.parent.3 b/man/man3/tomo-Path.parent.3 index 7113f4e0..7af4f98f 100644 --- a/man/man3/tomo-Path.parent.3 +++ b/man/man3/tomo-Path.parent.3 @@ -19,10 +19,10 @@ Returns the parent directory of the file or directory at the specified path. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file or directory. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file or directory. .TE .SH RETURN The path of the parent directory. diff --git a/man/man3/tomo-Path.read.3 b/man/man3/tomo-Path.read.3 index 7787b66a..31b32fd2 100644 --- a/man/man3/tomo-Path.read.3 +++ b/man/man3/tomo-Path.read.3 @@ -19,10 +19,10 @@ Reads the contents of the file at the specified path or none if the file could n .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The path of the file to read. - +lb lb lbx +l l l. +Name Type Description +path Path The path of the file to read. .TE .SH RETURN The contents of the file. If the file could not be read, none will be returned. If the file can be read, but is not valid UTF8 data, an error will be raised. diff --git a/man/man3/tomo-Path.sibling.3 b/man/man3/tomo-Path.sibling.3 index 7d4bbd04..10bc821c 100644 --- a/man/man3/tomo-Path.sibling.3 +++ b/man/man3/tomo-Path.sibling.3 @@ -19,11 +19,11 @@ Return a path that is a sibling of another path (i.e. has the same parent, but a .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path A path. - -name Text The name of a sibling file or directory. - +lb lb lbx +l l l. +Name Type Description +path Path A path. +name Text The name of a sibling file or directory. .TE .SH RETURN A new path representing the sibling. diff --git a/man/man3/tomo-Path.unique_directory.3 b/man/man3/tomo-Path.unique_directory.3 index 16df8d8b..7f66fad5 100644 --- a/man/man3/tomo-Path.unique_directory.3 +++ b/man/man3/tomo-Path.unique_directory.3 @@ -19,10 +19,10 @@ Generates a unique directory path based on the given path. Useful for creating t .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The base path for generating the unique directory. The last six letters of this path must be \fBXXXXXX\fR. - +lb lb lbx +l l l. +Name Type Description +path Path The base path for generating the unique directory. The last six letters of this path must be \fBXXXXXX\fR. .TE .SH RETURN A unique directory path after creating the directory. diff --git a/man/man3/tomo-Path.write_unique.3 b/man/man3/tomo-Path.write_unique.3 index 49d9aa78..d7763bdb 100644 --- a/man/man3/tomo-Path.write_unique.3 +++ b/man/man3/tomo-Path.write_unique.3 @@ -19,11 +19,11 @@ Writes the given text to a unique file path based on the specified path. The fil .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The base path for generating the unique file. This path must include the string \fBXXXXXX\fR in the file base name. - -text Text The text to write to the file. - +lb lb lbx +l l l. +Name Type Description +path Path The base path for generating the unique file. This path must include the string \fBXXXXXX\fR in the file base name. +text Text The text to write to the file. .TE .SH RETURN The path of the newly created unique file. diff --git a/man/man3/tomo-Path.write_unique_bytes.3 b/man/man3/tomo-Path.write_unique_bytes.3 index 5d08812e..6bb4abd0 100644 --- a/man/man3/tomo-Path.write_unique_bytes.3 +++ b/man/man3/tomo-Path.write_unique_bytes.3 @@ -19,11 +19,11 @@ Writes the given bytes to a unique file path based on the specified path. The fi .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -path Path The base path for generating the unique file. This path must include the string \fBXXXXXX\fR in the file base name. - -bytes [Byte] The bytes to write to the file. - +lb lb lbx +l l l. +Name Type Description +path Path The base path for generating the unique file. This path must include the string \fBXXXXXX\fR in the file base name. +bytes [Byte] The bytes to write to the file. .TE .SH RETURN The path of the newly created unique file. diff --git a/man/man3/tomo-Table.clear.3 b/man/man3/tomo-Table.clear.3 index 18938cb1..1e2816dd 100644 --- a/man/man3/tomo-Table.clear.3 +++ b/man/man3/tomo-Table.clear.3 @@ -19,10 +19,10 @@ Removes all key-value pairs from the table. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t &{K:V} The reference to the table. - +lb lb lbx +l l l. +Name Type Description +t &{K:V} The reference to the table. .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-Table.difference.3 b/man/man3/tomo-Table.difference.3 index 999f0e55..2db0def3 100644 --- a/man/man3/tomo-Table.difference.3 +++ b/man/man3/tomo-Table.difference.3 @@ -19,11 +19,11 @@ Return a table whose key/value pairs correspond to keys only present in one tabl .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The base table. - -other {K:V} The other table. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The base table. +other {K:V} The other table. .TE .SH RETURN A table containing the common key/value pairs whose keys only appear in one table. diff --git a/man/man3/tomo-Table.get.3 b/man/man3/tomo-Table.get.3 index e12143f1..87dc40ec 100644 --- a/man/man3/tomo-Table.get.3 +++ b/man/man3/tomo-Table.get.3 @@ -19,11 +19,11 @@ Retrieves the value associated with a key, or returns `none` if the key is not p .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The table. - -key K The key whose associated value is to be retrieved. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The table. +key K The key whose associated value is to be retrieved. .TE .SH RETURN The value associated with the key or `none` if the key is not found. diff --git a/man/man3/tomo-Table.get_or_set.3 b/man/man3/tomo-Table.get_or_set.3 index a5d100c4..61c84305 100644 --- a/man/man3/tomo-Table.get_or_set.3 +++ b/man/man3/tomo-Table.get_or_set.3 @@ -19,12 +19,12 @@ If the given key is in the table, return the associated value. Otherwise, insert .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t &{K:V} The table. - -key K The key whose associated value is to be retrieved. - -default V The default value to insert and return if the key is not present in the table. - +lb lb lbx +l l l. +Name Type Description +t &{K:V} The table. +key K The key whose associated value is to be retrieved. +default V The default value to insert and return if the key is not present in the table. .TE .SH RETURN Either the value associated with the key (if present) or the default value. The table will be mutated if the key is not already present. diff --git a/man/man3/tomo-Table.has.3 b/man/man3/tomo-Table.has.3 index 5bfb9554..45936f89 100644 --- a/man/man3/tomo-Table.has.3 +++ b/man/man3/tomo-Table.has.3 @@ -19,11 +19,11 @@ Checks if the table contains a specified key. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The table. - -key K The key to check for presence. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The table. +key K The key to check for presence. .TE .SH RETURN `yes` if the key is present, `no` otherwise. diff --git a/man/man3/tomo-Table.intersection.3 b/man/man3/tomo-Table.intersection.3 index 67b4c0d3..bbd8497d 100644 --- a/man/man3/tomo-Table.intersection.3 +++ b/man/man3/tomo-Table.intersection.3 @@ -19,11 +19,11 @@ Return a table with only the matching key/value pairs that are common to both ta .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The base table. - -other {K:V} The other table. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The base table. +other {K:V} The other table. .TE .SH RETURN A table containing the common key/value pairs shared between two tables. diff --git a/man/man3/tomo-Table.remove.3 b/man/man3/tomo-Table.remove.3 index d41c2ec0..5f5b06b7 100644 --- a/man/man3/tomo-Table.remove.3 +++ b/man/man3/tomo-Table.remove.3 @@ -19,11 +19,11 @@ Removes the key-value pair associated with a specified key. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The reference to the table. - -key K The key of the key-value pair to remove. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The reference to the table. +key K The key of the key-value pair to remove. .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-Table.set.3 b/man/man3/tomo-Table.set.3 index f321f8eb..ba904c29 100644 --- a/man/man3/tomo-Table.set.3 +++ b/man/man3/tomo-Table.set.3 @@ -19,12 +19,12 @@ Sets or updates the value associated with a specified key. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The reference to the table. - -key K The key to set or update. - -value V The value to associate with the key. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The reference to the table. +key K The key to set or update. +value V The value to associate with the key. .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-Table.with.3 b/man/man3/tomo-Table.with.3 index 740a1058..c2285343 100644 --- a/man/man3/tomo-Table.with.3 +++ b/man/man3/tomo-Table.with.3 @@ -19,11 +19,11 @@ Return a copy of a table with values added from another table .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The base table. - -other {K:V} The other table from which new key/value pairs will be added. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The base table. +other {K:V} The other table from which new key/value pairs will be added. .TE .SH RETURN The original table, but with values from the other table added. diff --git a/man/man3/tomo-Table.with_fallback.3 b/man/man3/tomo-Table.with_fallback.3 index 7ef520f0..72dd0589 100644 --- a/man/man3/tomo-Table.with_fallback.3 +++ b/man/man3/tomo-Table.with_fallback.3 @@ -19,11 +19,11 @@ Return a copy of a table with a different fallback table. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The table whose fallback will be replaced. - -fallback {K:V}? The new fallback table value. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The table whose fallback will be replaced. +fallback {K:V}? The new fallback table value. .TE .SH RETURN The original table with a different fallback. diff --git a/man/man3/tomo-Table.without.3 b/man/man3/tomo-Table.without.3 index c71b96a3..80154a3a 100644 --- a/man/man3/tomo-Table.without.3 +++ b/man/man3/tomo-Table.without.3 @@ -19,11 +19,11 @@ Return a copy of a table, but without any of the exact key/value pairs found in .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -t {K:V} The base table. - -other {K:V} The other table whose key/value pairs will be omitted. - +lb lb lbx +l l l. +Name Type Description +t {K:V} The base table. +other {K:V} The other table whose key/value pairs will be omitted. .TE .SH RETURN The original table, but without the key/value pairs from the other table. diff --git a/man/man3/tomo-Text.as_c_string.3 b/man/man3/tomo-Text.as_c_string.3 index 2bdf0b11..5369905d 100644 --- a/man/man3/tomo-Text.as_c_string.3 +++ b/man/man3/tomo-Text.as_c_string.3 @@ -19,10 +19,10 @@ Converts a `Text` value to a C-style string. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be converted to a C-style string. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be converted to a C-style string. .TE .SH RETURN A C-style string (`CString`) representing the text. diff --git a/man/man3/tomo-Text.at.3 b/man/man3/tomo-Text.at.3 index 3396ae19..a1d30371 100644 --- a/man/man3/tomo-Text.at.3 +++ b/man/man3/tomo-Text.at.3 @@ -19,11 +19,11 @@ Get the graphical cluster at a given index. This is similar to `str[i]` with ASC .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text from which to get a cluster. - -index Int The index of the graphical cluster (1-indexed). - +lb lb lbx +l l l. +Name Type Description +text Text The text from which to get a cluster. +index Int The index of the graphical cluster (1-indexed). .TE .SH RETURN A `Text` with the single graphical cluster at the given index. diff --git a/man/man3/tomo-Text.by_line.3 b/man/man3/tomo-Text.by_line.3 index f1fa17bc..2abc4c23 100644 --- a/man/man3/tomo-Text.by_line.3 +++ b/man/man3/tomo-Text.by_line.3 @@ -19,10 +19,10 @@ Returns an iterator function that can be used to iterate over the lines in a tex .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be iterated over, line by line. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be iterated over, line by line. .TE .SH RETURN An iterator function that returns one line at a time, until it runs out and returns `none`. diff --git a/man/man3/tomo-Text.codepoint_names.3 b/man/man3/tomo-Text.codepoint_names.3 index b91baba4..320af3f6 100644 --- a/man/man3/tomo-Text.codepoint_names.3 +++ b/man/man3/tomo-Text.codepoint_names.3 @@ -19,10 +19,10 @@ Returns a list of the names of each codepoint in the text. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text from which to extract codepoint names. - +lb lb lbx +l l l. +Name Type Description +text Text The text from which to extract codepoint names. .TE .SH RETURN A list of codepoint names (`[Text]`). diff --git a/man/man3/tomo-Text.from.3 b/man/man3/tomo-Text.from.3 index 30e62958..53df8278 100644 --- a/man/man3/tomo-Text.from.3 +++ b/man/man3/tomo-Text.from.3 @@ -19,11 +19,11 @@ Get a slice of the text, starting at the given position. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be sliced. - -first Int The index to begin the slice. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be sliced. +first Int The index to begin the slice. .TE .SH RETURN The text from the given grapheme cluster to the end of the text. diff --git a/man/man3/tomo-Text.from_c_string.3 b/man/man3/tomo-Text.from_c_string.3 index 1144001b..32e6e4f5 100644 --- a/man/man3/tomo-Text.from_c_string.3 +++ b/man/man3/tomo-Text.from_c_string.3 @@ -19,10 +19,10 @@ Converts a C-style string to a `Text` value. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -str CString The C-style string to be converted. - +lb lb lbx +l l l. +Name Type Description +str CString The C-style string to be converted. .TE .SH RETURN A `Text` value representing the C-style string. diff --git a/man/man3/tomo-Text.from_codepoint_names.3 b/man/man3/tomo-Text.from_codepoint_names.3 index 2951b0f2..ac0bf01b 100644 --- a/man/man3/tomo-Text.from_codepoint_names.3 +++ b/man/man3/tomo-Text.from_codepoint_names.3 @@ -19,10 +19,10 @@ Returns text that has the given codepoint names (according to the Unicode specif .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -codepoint_names [Text] The names of each codepoint in the desired text (case-insentive). - +lb lb lbx +l l l. +Name Type Description +codepoint_names [Text] The names of each codepoint in the desired text (case-insentive). .TE .SH RETURN A new text with the specified codepoints after normalization has been applied. Any invalid names are ignored. diff --git a/man/man3/tomo-Text.from_utf16.3 b/man/man3/tomo-Text.from_utf16.3 index ba92c691..be046ddb 100644 --- a/man/man3/tomo-Text.from_utf16.3 +++ b/man/man3/tomo-Text.from_utf16.3 @@ -19,10 +19,10 @@ Returns text that has been constructed from the given UTF16 sequence. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -bytes [Int16] The UTF-16 integers of the desired text. - +lb lb lbx +l l l. +Name Type Description +bytes [Int16] The UTF-16 integers of the desired text. .TE .SH RETURN A new text based on the input UTF16 sequence after normalization has been applied. diff --git a/man/man3/tomo-Text.from_utf32.3 b/man/man3/tomo-Text.from_utf32.3 index 526545fb..7f78c191 100644 --- a/man/man3/tomo-Text.from_utf32.3 +++ b/man/man3/tomo-Text.from_utf32.3 @@ -19,10 +19,10 @@ Returns text that has been constructed from the given UTF32 codepoints. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -codepoints [Int32] The UTF32 codepoints in the desired text. - +lb lb lbx +l l l. +Name Type Description +codepoints [Int32] The UTF32 codepoints in the desired text. .TE .SH RETURN A new text with the specified codepoints after normalization has been applied. diff --git a/man/man3/tomo-Text.from_utf8.3 b/man/man3/tomo-Text.from_utf8.3 index 25ab2897..497a6107 100644 --- a/man/man3/tomo-Text.from_utf8.3 +++ b/man/man3/tomo-Text.from_utf8.3 @@ -19,10 +19,10 @@ Returns text that has been constructed from the given UTF8 bytes. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -bytes [Byte] The UTF-8 bytes of the desired text. - +lb lb lbx +l l l. +Name Type Description +bytes [Byte] The UTF-8 bytes of the desired text. .TE .SH RETURN A new text based on the input UTF8 bytes after normalization has been applied. diff --git a/man/man3/tomo-Text.has.3 b/man/man3/tomo-Text.has.3 index a562b908..3397cc2b 100644 --- a/man/man3/tomo-Text.has.3 +++ b/man/man3/tomo-Text.has.3 @@ -19,11 +19,11 @@ Checks if the `Text` contains some target text. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be searched. - -target Text The text to search for. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be searched. +target Text The text to search for. .TE .SH RETURN `yes` if the target text is found, `no` otherwise. diff --git a/man/man3/tomo-Text.join.3 b/man/man3/tomo-Text.join.3 index d6b7d15a..0eccb241 100644 --- a/man/man3/tomo-Text.join.3 +++ b/man/man3/tomo-Text.join.3 @@ -19,11 +19,11 @@ Joins a list of text pieces with a specified glue. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -glue Text The text used to join the pieces. - -pieces [Text] The list of text pieces to be joined. - +lb lb lbx +l l l. +Name Type Description +glue Text The text used to join the pieces. +pieces [Text] The list of text pieces to be joined. .TE .SH RETURN A single `Text` value with the pieces joined by the glue. diff --git a/man/man3/tomo-Text.lines.3 b/man/man3/tomo-Text.lines.3 index 2085d7d9..7c53ed54 100644 --- a/man/man3/tomo-Text.lines.3 +++ b/man/man3/tomo-Text.lines.3 @@ -19,10 +19,10 @@ Splits the text into a list of lines of text, preserving blank lines, ignoring t .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be split into lines. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be split into lines. .TE .SH RETURN A list of substrings resulting from the split. diff --git a/man/man3/tomo-Text.repeat.3 b/man/man3/tomo-Text.repeat.3 index 70b49c03..d917f96c 100644 --- a/man/man3/tomo-Text.repeat.3 +++ b/man/man3/tomo-Text.repeat.3 @@ -19,11 +19,11 @@ Repeat some text multiple times. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to repeat. - -count Int The number of times to repeat it. (Negative numbers are equivalent to zero). - +lb lb lbx +l l l. +Name Type Description +text Text The text to repeat. +count Int The number of times to repeat it. (Negative numbers are equivalent to zero). .TE .SH RETURN The text repeated the given number of times. diff --git a/man/man3/tomo-Text.replace.3 b/man/man3/tomo-Text.replace.3 index 7636fede..8631f32a 100644 --- a/man/man3/tomo-Text.replace.3 +++ b/man/man3/tomo-Text.replace.3 @@ -19,12 +19,12 @@ Replaces occurrences of a target text with a replacement text. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text in which to perform replacements. - -target Text The target text to be replaced. - -replacement Text The text to replace the target with. - +lb lb lbx +l l l. +Name Type Description +text Text The text in which to perform replacements. +target Text The target text to be replaced. +replacement Text The text to replace the target with. .TE .SH RETURN The text with occurrences of the target replaced. diff --git a/man/man3/tomo-Text.reversed.3 b/man/man3/tomo-Text.reversed.3 index 21b2be4e..821e1ab3 100644 --- a/man/man3/tomo-Text.reversed.3 +++ b/man/man3/tomo-Text.reversed.3 @@ -19,10 +19,10 @@ Return a text that has the grapheme clusters in reverse order. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to reverse. - +lb lb lbx +l l l. +Name Type Description +text Text The text to reverse. .TE .SH RETURN A reversed version of the text. diff --git a/man/man3/tomo-Text.to.3 b/man/man3/tomo-Text.to.3 index 66fde6e1..2507311a 100644 --- a/man/man3/tomo-Text.to.3 +++ b/man/man3/tomo-Text.to.3 @@ -19,11 +19,11 @@ Get a slice of the text, ending at the given position. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be sliced. - -last Int The index of the last grapheme cluster to include (1-indexed). - +lb lb lbx +l l l. +Name Type Description +text Text The text to be sliced. +last Int The index of the last grapheme cluster to include (1-indexed). .TE .SH RETURN The text up to and including the given grapheme cluster. diff --git a/man/man3/tomo-Text.translate.3 b/man/man3/tomo-Text.translate.3 index fe944018..2cac6a47 100644 --- a/man/man3/tomo-Text.translate.3 +++ b/man/man3/tomo-Text.translate.3 @@ -19,11 +19,11 @@ Takes a table mapping target texts to their replacements and performs all the re .TS allbox; -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. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be translated. +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. diff --git a/man/man3/tomo-Text.utf16.3 b/man/man3/tomo-Text.utf16.3 index d8f00d22..f65d05d2 100644 --- a/man/man3/tomo-Text.utf16.3 +++ b/man/man3/tomo-Text.utf16.3 @@ -19,10 +19,10 @@ Returns a list of Unicode code points for UTF16 encoding of the text. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text from which to extract Unicode code points. - +lb lb lbx +l l l. +Name Type Description +text Text The text from which to extract Unicode code points. .TE .SH RETURN A list of 16-bit integer Unicode code points (`[Int16]`). diff --git a/man/man3/tomo-Text.utf32.3 b/man/man3/tomo-Text.utf32.3 index cf0d4a05..d43d31e7 100644 --- a/man/man3/tomo-Text.utf32.3 +++ b/man/man3/tomo-Text.utf32.3 @@ -19,10 +19,10 @@ Returns a list of Unicode code points for UTF32 encoding of the text. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text from which to extract Unicode code points. - +lb lb lbx +l l l. +Name Type Description +text Text The text from which to extract Unicode code points. .TE .SH RETURN A list of 32-bit integer Unicode code points (`[Int32]`). diff --git a/man/man3/tomo-Text.utf8.3 b/man/man3/tomo-Text.utf8.3 index 05544cc1..5490c882 100644 --- a/man/man3/tomo-Text.utf8.3 +++ b/man/man3/tomo-Text.utf8.3 @@ -19,10 +19,10 @@ Converts a `Text` value to a list of bytes representing a UTF8 encoding of the t .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to be converted to UTF8 bytes. - +lb lb lbx +l l l. +Name Type Description +text Text The text to be converted to UTF8 bytes. .TE .SH RETURN A list of bytes (`[Byte]`) representing the text in UTF8 encoding. diff --git a/man/man3/tomo-Text.width.3 b/man/man3/tomo-Text.width.3 index 00e32fcf..ec6307e2 100644 --- a/man/man3/tomo-Text.width.3 +++ b/man/man3/tomo-Text.width.3 @@ -19,10 +19,10 @@ Returns the display width of the text as seen in a terminal with appropriate fon .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text whose length you want. - +lb lb lbx +l l l. +Name Type Description +text Text The text whose length you want. .TE .SH RETURN An integer representing the display width of the text. diff --git a/man/man3/tomo-Text.without_prefix.3 b/man/man3/tomo-Text.without_prefix.3 index 43aa6e5e..d1b902da 100644 --- a/man/man3/tomo-Text.without_prefix.3 +++ b/man/man3/tomo-Text.without_prefix.3 @@ -19,11 +19,11 @@ Returns the text with a given prefix removed (if present). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to remove the prefix from. - -prefix Text The prefix to remove. - +lb lb lbx +l l l. +Name Type Description +text Text The text to remove the prefix from. +prefix Text The prefix to remove. .TE .SH RETURN A text without the given prefix (if present) or the unmodified text if the prefix is not present. diff --git a/man/man3/tomo-Text.without_suffix.3 b/man/man3/tomo-Text.without_suffix.3 index 9b45d624..b905bee8 100644 --- a/man/man3/tomo-Text.without_suffix.3 +++ b/man/man3/tomo-Text.without_suffix.3 @@ -19,11 +19,11 @@ Returns the text with a given suffix removed (if present). .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -text Text The text to remove the suffix from. - -suffix Text The suffix to remove. - +lb lb lbx +l l l. +Name Type Description +text Text The text to remove the suffix from. +suffix Text The suffix to remove. .TE .SH RETURN A text without the given suffix (if present) or the unmodified text if the suffix is not present. diff --git a/man/man3/tomo-at_cleanup.3 b/man/man3/tomo-at_cleanup.3 index d8d07faa..556178c9 100644 --- a/man/man3/tomo-at_cleanup.3 +++ b/man/man3/tomo-at_cleanup.3 @@ -19,10 +19,10 @@ Register a function that runs at cleanup time for Tomo programs. Cleanup time ha .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -fn func() A function to run at cleanup time. - +lb lb lbx +l l l. +Name Type Description +fn func() A function to run at cleanup time. .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-fail.3 b/man/man3/tomo-fail.3 index b5f0b502..27eb0660 100644 --- a/man/man3/tomo-fail.3 +++ b/man/man3/tomo-fail.3 @@ -19,10 +19,10 @@ Prints a message to the console, aborts the program, and prints a stack trace. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -message Text The error message to print. - +lb lb lbx +l l l. +Name Type Description +message Text The error message to print. .TE .SH RETURN Nothing, aborts the program. diff --git a/man/man3/tomo-getenv.3 b/man/man3/tomo-getenv.3 index 6b4a7bbf..71f828eb 100644 --- a/man/man3/tomo-getenv.3 +++ b/man/man3/tomo-getenv.3 @@ -19,10 +19,10 @@ Gets an environment variable. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -name Text The name of the environment variable to get. - +lb lb lbx +l l l. +Name Type Description +name Text The name of the environment variable to get. .TE .SH RETURN If set, the environment variable's value, otherwise, `none`. diff --git a/man/man3/tomo-setenv.3 b/man/man3/tomo-setenv.3 index a2bc01dd..56ab32bf 100644 --- a/man/man3/tomo-setenv.3 +++ b/man/man3/tomo-setenv.3 @@ -19,11 +19,11 @@ Sets an environment variable. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -name Text The name of the environment variable to set. - -value Text? The new value of the environment variable. If \fBnone\fR, then the environment variable will be unset. - +lb lb lbx +l l l. +Name Type Description +name Text The name of the environment variable to set. +value Text? The new value of the environment variable. If \fBnone\fR, then the environment variable will be unset. .TE .SH RETURN Nothing. diff --git a/man/man3/tomo-sleep.3 b/man/man3/tomo-sleep.3 index a652c6e8..baea8966 100644 --- a/man/man3/tomo-sleep.3 +++ b/man/man3/tomo-sleep.3 @@ -19,10 +19,10 @@ Pause execution for a given number of seconds. .TS allbox; -lb lb lbx lb -l l l l. -Name Type Description Default -seconds Num How many seconds to sleep for. - +lb lb lbx +l l l. +Name Type Description +seconds Num How many seconds to sleep for. .TE .SH RETURN Nothing. diff --git a/scripts/mandoc_gen.py b/scripts/mandoc_gen.py index 260bc5d0..b9cf1cc2 100755 --- a/scripts/mandoc_gen.py +++ b/scripts/mandoc_gen.py @@ -71,6 +71,13 @@ lb lb lbx lb l l l l. Name Type Description Default''' +arg_prefix_no_default = ''' +.TS +allbox; +lb lb lbx +l l l. +Name Type Description''' + type_template = ''''\\" t .\\" Copyright (c) {year} Bruce Hill .\\" All rights reserved. @@ -99,11 +106,16 @@ def write_method(path, name, info): if "args" in info and info["args"]: lines.append(".SH ARGUMENTS") - lines.append(arg_prefix) + has_defaults = any('default' in a for a in info['args'].values()) + lines.append(arg_prefix if has_defaults else arg_prefix_no_default) for arg,arg_info in info["args"].items(): - default = escape(arg_info['default'], spaces=True) if 'default' in arg_info else '-' - description = markdown_to_roff(arg_info['description']) - lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}") + if has_defaults: + default = escape(arg_info['default'], spaces=True) if 'default' in arg_info else '-' + description = markdown_to_roff(arg_info['description']) + lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}") + else: + description = markdown_to_roff(arg_info['description']) + lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}") lines.append(".TE") if "return" in info: -- cgit v1.2.3 From 6db7b88c930657c38512709f808b422d39484a12 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 13:47:23 -0500 Subject: Fix alignment --- src/tomo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tomo.c b/src/tomo.c index 1aa62a0e..e1743d47 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -195,8 +195,8 @@ int main(int argc, char *argv[]) { Text_t usage = Texts("\x1b[33;4;1mUsage:\x1b[m\n" "\x1b[1mRun a program:\x1b[m tomo file.tm [-- args...]\n" "\x1b[1mTranspile files:\x1b[m tomo -t file.tm\n" - "\x1b[1mCompile object file:\x1b[m tomo -c file.tm\n" - "\x1b[1mCompile executable:\x1b[m tomo -e file.tm\n" + "\x1b[1mCompile object file:\x1b[m tomo -c file.tm\n" + "\x1b[1mCompile executable:\x1b[m tomo -e file.tm\n" "\x1b[1mBuild libraries:\x1b[m tomo -L lib...\n" "\x1b[1mUninstall libraries:\x1b[m tomo -u lib...\n" "\x1b[1mOther flags:\x1b[m\n" -- cgit v1.2.3 From bb2f890fd470fff3e42698710b56c68164491d85 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 14:18:07 -0500 Subject: Overhaul to versioning system (paths go to `/tomo@TOMOVERSION/lib@LIBVERSION` instead of using underscores. Tomo versioning now uses date-based versions. --- CHANGES.md | 106 +++++++++++++++++++++++++++-------------------- Makefile | 31 +++++++------- docs/libraries.md | 13 +++--- link_versions.sh | 16 +------ local-tomo | 4 +- src/compile/files.c | 2 +- src/compile/headers.c | 6 +-- src/compile/statements.c | 4 +- src/modules.c | 10 ++--- src/stdlib/stacktrace.c | 2 +- src/tomo.c | 22 +++++----- src/typecheck.c | 4 +- 12 files changed, 113 insertions(+), 107 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0bbe387f..212fe54c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,17 +1,12 @@ # Version History -## v0.4 -- Tomo libraries are now installed to `$TOMO_PATH/lib/tomo_vX.Y/module_vZ.W` - instead of `$TOMO_PATH/share/tomo_vX.Y/installed/module_vZ.W` -- Core libraries are no longer shipped with the compiler, they have moved to - separate repositories. -- Library installation has been cleaned up a bit. -- List indexing now gives an optional value. -- Added support for inline anonymous enums -- Accessing a field on an enum now gives an optional value instead of a boolean. -- Syntax for text literals and inline C code has been simplified somewhat. +## v2025-11-29 + +### Syntax changes + - Syntax for tables has changed to use colons (`{k: v}`) instead of equals (`{k=v}`). +- Syntax for text literals and inline C code has been simplified. - Added metadata format instead of `_HELP`/`_USAGE`: ``` HELP: "Help text" @@ -19,6 +14,44 @@ MANPAGE_SYNOPSYS: "Synopsys..." MANPAGE_DESCRIPTION: (./description.txt) ``` +- **Deprecated:** `extern` keyword for declaring external symbols from C. + - Use `C_code` instead. +- **Deprecated:** postfix `?` to make values optional. + - Explicitly optional values can be declared as `my_var : T? = value`. +- **Deprecated:** `>> ... = ...` form of doctests. They are now called "debug logs" + and you can specify multiple values: `>> a, b, c` +- **Deprecated:** `extend` blocks +- **Deprecated:** `deserialize` operation and `.serialized()` method call + - Instead, convert to and from `[Byte]` + +### Versioning and library changes + +- Tomo versioning now uses dates instead of semantic versioning. +- Tomo libraries are now installed to + `$TOMO_PATH/lib/tomo@TOMO_VERSION/library@LIBRARY_VERSION` instead of + `$TOMO_PATH/share/tomo_TOMO_VERSION/installed/module_LIBRARY_VERSION` +- Core libraries are no longer shipped with the compiler, they have moved to + separate repositories. +- Library installation has been cleaned up a bit. + +### Type Changes + +- List indexing now gives an optional value. +- Added support for inline anonymous enums +- Accessing a field on an enum now gives an optional value instead of a boolean. +- **Deprecated**: 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}`. +- Added `Empty` for a built-in empty struct type and `EMPTY` for an instance of + the empty struct. +- Struct fields that start with underscores can be accessed again and function + arguments that start with underscore can be passed (but only as keyword + arguments). + +### API changes + - Added `Path.lines()`. - Added `Text.find(text, target, start=1)`. - Added `at_cleanup()` to register cleanup functions. @@ -26,29 +59,9 @@ directories if needed. - `setenv()` now takes an optional parameter for value, which allows for unsetting environment values. -- Deprecated: - - 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. - - Postfix `?` to make values optional. - - Explicitly optional values can be declared as `my_var : T? = value`. - - `>> ... = ...` form of doctests. They are now called "debug logs" and you - can specify multiple values: `>> a, b, c` - - `extend` blocks - - `deserialize` operation and `.serialized()` method call - - Instead, convert to and from `[Byte]` -- Struct fields that start with underscores can be accessed again and function - arguments that start with underscore can be passed (but only as keyword - arguments). +- Tables now have `and`, `or`, `xor`, and `-` (minus) metamethods. - Added `table.with(other)`, `table.without(other)`, `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 @@ -57,21 +70,26 @@ - `Text.from_utf8()`/`Text.utf8()` - `Text.from_utf16()`/`Text.utf16()` - `Text.from_utf32()`/`Text.utf32()` + +### Bug fixes + +- `Int.parse()` had a memory bug. +- Breaking out of a `for line in file.by_line()!` loop would leak file handle + resources, which could lead to exhausting the number of open file handles. + When that happens, the standard library now forces a GC collection to clean + up resources, which can result in file handles being freed up. +- `&` references failed to propagate when accessing fields like + `foo.baz.method()` when `foo` is a `&Foo` and `baz.method()` takes a `&Baz`. +- Optional paths no longer fail to compile when you check them for `none`. +- Text replacement no longer infinitely loops when given an empty text to replace. +- Short CLI flag aliases now no longer use the first letter of the argument. +- Stack memory was not correctly detected in some cases, leading to potential + memory errors. + +### Other changes + - Added automatic manpage generation. - Major improvements to robustness of CLI argument parsing. -- Fixed bugs: - - `Int.parse()` had a memory bug. - - Breaking out of a `for line in file.by_line()!` loop would leak file handle - resources, which could lead to exhausting the number of open file handles. - When that happens, the standard library now forces a GC collection to clean - up resources, which can result in file handles being freed up. - - `&` references failed to propagate when accessing fields like - `foo.baz.method()` when `foo` is a `&Foo` and `baz.method()` takes a `&Baz`. - - Optional paths no longer fail to compile when you check them for `none`. - - Text replacement no longer infinitely loops when given an empty text to replace. - - Short CLI flag aliases now no longer use the first letter of the argument. - - Stack memory was not correctly detected in some cases, leading to potential - memory errors. ## v0.3 diff --git a/Makefile b/Makefile index ab9096cd..2bfde7a1 100644 --- a/Makefile +++ b/Makefile @@ -96,29 +96,30 @@ else LDLIBS += -ldl endif -AR_FILE=libtomo_$(TOMO_VERSION).a +AR_FILE=libtomo@$(TOMO_VERSION).a ifeq ($(OS),Darwin) INCLUDE_DIRS += -I/opt/homebrew/include LDFLAGS += -L/opt/homebrew/lib - LIB_FILE=libtomo_$(TOMO_VERSION).dylib - LIBTOMO_FLAGS += -Wl,-install_name,@rpath/libtomo_$(TOMO_VERSION).dylib + LIB_FILE=libtomo@$(TOMO_VERSION).dylib + LIBTOMO_FLAGS += -Wl,-install_name,@rpath/libtomo@$(TOMO_VERSION).dylib else - LIB_FILE=libtomo_$(TOMO_VERSION).so - LIBTOMO_FLAGS += -Wl,-soname,libtomo_$(TOMO_VERSION).so + LIB_FILE=libtomo@$(TOMO_VERSION).so + LIBTOMO_FLAGS += -Wl,-soname,libtomo@$(TOMO_VERSION).so endif -EXE_FILE=tomo_$(TOMO_VERSION) -MODULES_FILE=build/lib/tomo_$(TOMO_VERSION)/modules.ini +EXE_FILE=tomo@$(TOMO_VERSION) +MODULES_FILE=build/lib/tomo@$(TOMO_VERSION)/modules.ini COMPILER_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/compile/*.c src/parse/*.c src/formatter/*.c)) STDLIB_OBJS=$(patsubst %.c,%.o,$(wildcard src/stdlib/*.c)) TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/[!_]*.tm)) API_YAML=$(wildcard api/*.yaml) API_MD=$(patsubst %.yaml,%.md,$(API_YAML)) +INCLUDE_SYMLINK=build/include/tomo@$(TOMO_VERSION) -all: config.mk check-c-compiler check-libs build/include/tomo_$(TOMO_VERSION) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) build/bin/$(EXE_FILE) +all: config.mk check-c-compiler check-libs $(INCLUDE_SYMLINK) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) build/bin/$(EXE_FILE) @$(ECHO) "All done!" -build/include/tomo_$(TOMO_VERSION): +$(INCLUDE_SYMLINK): ln -s ../../src/stdlib $@ version: @@ -143,7 +144,7 @@ build/lib/$(LIB_FILE): $(STDLIB_OBJS) @$(CC) $^ $(CFLAGS) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ $(MODULES_FILE): modules/core.ini modules/examples.ini - @mkdir -p build/lib/tomo_$(TOMO_VERSION) + @mkdir -p build/lib/tomo@$(TOMO_VERSION) @cat $^ > $@ build/lib/$(AR_FILE): $(STDLIB_OBJS) @@ -239,11 +240,11 @@ install-files: build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) exit 0; \ fi; \ mkdir -p -m 755 "$(PREFIX)/man/man1" "$(PREFIX)/man/man3" "$(PREFIX)/bin" \ - "$(PREFIX)/include/tomo_$(TOMO_VERSION)" "$(PREFIX)/lib" "$(PREFIX)/lib/tomo_$(TOMO_VERSION)" "$(PREFIX)/share/licenses/tomo_$(TOMO_VERSION)"; \ - cp src/stdlib/*.h "$(PREFIX)/include/tomo_$(TOMO_VERSION)/"; \ + "$(PREFIX)/include/tomo@$(TOMO_VERSION)" "$(PREFIX)/lib" "$(PREFIX)/lib/tomo@$(TOMO_VERSION)" "$(PREFIX)/share/licenses/tomo@$(TOMO_VERSION)"; \ + cp src/stdlib/*.h "$(PREFIX)/include/tomo@$(TOMO_VERSION)/"; \ cp build/lib/$(LIB_FILE) build/lib/$(AR_FILE) "$(PREFIX)/lib/"; \ - cp $(MODULES_FILE) "$(PREFIX)/lib/tomo_$(TOMO_VERSION)"; \ - cp LICENSE.md "$(PREFIX)/share/licenses/tomo_$(TOMO_VERSION)"; \ + cp $(MODULES_FILE) "$(PREFIX)/lib/tomo@$(TOMO_VERSION)"; \ + cp LICENSE.md "$(PREFIX)/share/licenses/tomo@$(TOMO_VERSION)"; \ rm -f "$(PREFIX)/bin/$(EXE_FILE)"; \ cp build/bin/$(EXE_FILE) "$(PREFIX)/bin/"; \ cp man/man1/* "$(PREFIX)/man/man1/"; \ @@ -258,7 +259,7 @@ uninstall: exit 0; \ fi; \ rm -rvf "$(PREFIX)/bin/tomo" "$(PREFIX)/bin/tomo"[0-9]* "$(PREFIX)/bin/tomo_v"* "$(PREFIX)/include/tomo_v"* \ - "$(PREFIX)/lib/libtomo_v*" "$(PREFIX)/lib/tomo_$(TOMO_VERSION)" "$(PREFIX)/share/licenses/tomo_$(TOMO_VERSION)"; \ + "$(PREFIX)/lib/libtomo_v*" "$(PREFIX)/lib/tomo@$(TOMO_VERSION)" "$(PREFIX)/share/licenses/tomo@$(TOMO_VERSION)"; \ sh link_versions.sh endif diff --git a/docs/libraries.md b/docs/libraries.md index 79477070..fc1eb4ea 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -151,8 +151,7 @@ that can be used by other Tomo projects. You can build a library by running If you additionally add the `-I` flag, Tomo will copy the entire directory (excluding files and directories that begin with `.` such as `.git`) into -`~/.local/lib/tomo_vX.Y/` (where `X` and `Y` are the major/minor -version of the compiler). +`~/.local/lib/tomo@vTOMO_VERSION/LIBRARY_NAME@LIBRARY_VERSION`. ### Using Shared Libraries @@ -167,13 +166,13 @@ read from the source files during compilation. When you build and install a library, its version is determined from a `CHANGES.md` file at the top level of the library directory (see: [Versions](versions.md)). The library's version number is added to the file -path where the library is installed, so if the library `foo` has version +path where the library is installed, so if the library `mylib` has version `v1.2`, then it will be installed to -`~/.local/lib/tomo_vX.Y/foo_v1.2/`. When using a library, you must +`~/.local/lib/tomo@TOMO_VERSION/mylib@v1.2/`. When using a library, you must explicitly supply either the exact version in the `use` statement like this: -`use foo_v1.2`, or provide a `modules.ini` file that lists version information -and other details about modules being used. For each module, you should provide -a `[modulename]` section with a `version=` field. +`use mylib@v1.2`, or provide a `modules.ini` file that lists version +information and other details about modules being used. For each module, you +should provide a `[modulename]` section with a `version=` field. ```tomo # File: foo.tm diff --git a/link_versions.sh b/link_versions.sh index 47a70490..8dadbb52 100644 --- a/link_versions.sh +++ b/link_versions.sh @@ -1,17 +1,5 @@ #!/bin/sh TOMO_PREFIX="$(awk -F= '/PREFIX/{print $2}' config.mk)" cd "$TOMO_PREFIX/bin" - -commands="$(ls | awk -F '[v.]' ' - /^tomo_v/{ - if ($2 >= max_major) max_major=$2; - if ($3 >= max_minor[$2]) max_minor[$2] = $3; - link_tomo=1 - } - END { - for (major in max_minor) { - if (max_major > 0) print "ln -fs tomo_v"major"."max_minor[major]" tomo"major - } - if (link_tomo) print "ln -fs tomo_v"max_major"."max_minor[max_major]" tomo" - }')" -eval "$commands" +top_version="$(printf '%s\n' 'tomo@'* | sort -r | head -1)" +ln -fs "$top_version" tomo diff --git a/local-tomo b/local-tomo index bdf1e0da..76864837 100755 --- a/local-tomo +++ b/local-tomo @@ -1,11 +1,11 @@ #!/bin/sh version=$(awk '/^## / {print $2; exit}' CHANGES.md) here="$(realpath "$(dirname "$0")")" -if [ ! -e "$here/build/bin/tomo_$version" ]; then +if [ ! -e "$here/build/bin/tomo@$version" ]; then echo "Tomo hasn't been compiled yet! Run \`make\` to compile it!" exit 1; fi PATH="$here/build/bin${PATH:+:$PATH}" \ TOMO_PATH="$here/build" \ -tomo_"$version" "$@" +tomo@"$version" "$@" diff --git a/src/compile/files.c b/src/compile/files.c index b916f23f..03c08bf6 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -194,7 +194,7 @@ Text_t compile_file(env_t *env, ast_t *ast) { const char *name = file_base_name(ast->file->filename); return Texts(env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", - "#include \n" + "#include \n" "#include \"", name, ".tm.h\"\n\n", includes, env->code->local_typedefs, "\n", env->code->lambdas, "\n", env->code->staticdefs, "\n", top_level_code, "public void ", diff --git a/src/compile/headers.c b/src/compile/headers.c index f132b312..1dcf7abb 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -158,7 +158,7 @@ Text_t compile_file_header(env_t *env, Path_t header_path, ast_t *ast) { Text_t header = Texts("#pragma once\n", env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, - "#include \n"); + "#include \n"); compile_typedef_info_t info = {.env = env, .header = &header, .header_path = header_path}; visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn = (void *)_make_typedefs, &info}); @@ -183,8 +183,8 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) case USE_MODULE: { module_info_t mod = get_used_module_info(ast); glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; + if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/compile/statements.c b/src/compile/statements.c index c6ceccd9..638f1341 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -196,8 +196,8 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { } else if (use->what == USE_MODULE) { module_info_t mod = get_used_module_info(ast); glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; + if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/modules.c b/src/modules.c index df6bade3..36952ec8 100644 --- a/src/modules.c +++ b/src/modules.c @@ -28,7 +28,7 @@ const char *get_library_version(Path_t lib_dir) { Path_t changes_file = Path$child(lib_dir, Text("CHANGES.md")); OptionalText_t changes = Path$read(changes_file); if (changes.length <= 0) { - return "v0.0"; + return "v0"; } const char *changes_str = Text$as_c_string(Texts(Text("\n"), changes)); const char *version_line = strstr(changes_str, "\n## "); @@ -41,7 +41,7 @@ Text_t get_library_name(Path_t lib_dir) { Text_t name = Path$base_name(lib_dir); name = Text$without_prefix(name, Text("tomo-")); name = Text$without_suffix(name, Text("-tomo")); - Text_t suffix = Texts(Text("_"), Text$from_str(get_library_version(lib_dir))); + Text_t suffix = Texts(Text("@"), Text$from_str(get_library_version(lib_dir))); if (!Text$ends_with(name, suffix, NULL)) name = Texts(name, suffix); return name; } @@ -102,7 +102,7 @@ module_info_t get_used_module_info(ast_t *use) { const char *name = Match(use, Use)->path; module_info_t *info = new (module_info_t, .name = name); Path_t tomo_default_modules = - Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo_" TOMO_VERSION "/modules.ini")); + Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/modules.ini")); read_modules_ini(tomo_default_modules, info); read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info); read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info); @@ -111,8 +111,8 @@ module_info_t get_used_module_info(ast_t *use) { } bool try_install_module(module_info_t mod, bool ask_confirmation) { - Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo_" TOMO_VERSION "/", Text$from_str(mod.name), - "_", Text$from_str(mod.version))); + Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/", Text$from_str(mod.name), + "@", Text$from_str(mod.version))); if (Path$exists(dest)) return true; print("No such path: ", dest); diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c index b21d0cbc..ea939f62 100644 --- a/src/stdlib/stacktrace.c +++ b/src/stdlib/stacktrace.c @@ -98,7 +98,7 @@ void print_stacktrace(FILE *out, int offset) { cwd[cwd_len++] = '/'; cwd[cwd_len] = '\0'; - const char *install_dir = String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/"); + const char *install_dir = String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/"); static void *stack[1024]; int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0])); diff --git a/src/tomo.c b/src/tomo.c index e1743d47..e086166c 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -91,7 +91,7 @@ static OptionalText_t show_codegen = NONE_TEXT, " -D_BSD_SOURCE" #endif " -DGC_THREADS"), - ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo_" TOMO_VERSION), ldflags = Text(""), + ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo@" TOMO_VERSION), ldflags = Text(""), optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER); static Text_t config_summary, @@ -167,7 +167,7 @@ int main(int argc, char *argv[]) { if (getenv("TOMO_PATH")) TOMO_PATH = getenv("TOMO_PATH"); - cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "' ", cflags); + cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "' ", cflags); // Set up environment variables: const char *PATH = getenv("PATH"); @@ -186,7 +186,7 @@ int main(int argc, char *argv[]) { // Run a tool: if ((streq(argv[1], "-r") || streq(argv[1], "--run")) && argc >= 3) { if (strcspn(argv[2], "/;$") == strlen(argv[2])) { - const char *program = String("'", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", argv[2], "/", argv[2]); + const char *program = String("'", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", argv[2], "/", argv[2]); execv(program, &argv[2]); } print_err("This is not an installed tomo program: ", argv[2]); @@ -218,7 +218,7 @@ int main(int argc, char *argv[]) { " --source-mapping|-m : toggle source mapping in generated code\n" " --changelog: show the Tomo changelog\n" " --run|-r: run a program from ", - TOMO_PATH, "/share/tomo_" TOMO_VERSION "/installed\n"); + TOMO_PATH, "/share/tomo@" TOMO_VERSION "/installed\n"); Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage); cli_arg_t tomo_args[] = { {"run", &run_files, List$info(&Path$info), .short_flag = 'r'}, // @@ -292,7 +292,7 @@ int main(int argc, char *argv[]) { // Uninstall libraries: for (int64_t i = 0; i < (int64_t)uninstall_libraries.length; i++) { Text_t *u = (Text_t *)(uninstall_libraries.data + i * uninstall_libraries.stride); - xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", *u, " '", TOMO_PATH, "'/bin/", *u, + xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", *u, " '", TOMO_PATH, "'/bin/", *u, " '", TOMO_PATH, "'/man/man1/", *u, ".1"); print("Uninstalled ", *u); } @@ -509,7 +509,7 @@ void build_library(Path_t lib_dir) { void install_library(Path_t lib_dir) { Text_t lib_name = get_library_name(lib_dir); - Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION)), lib_name); + Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION)), lib_name); print("Installing ", lib_dir, " into ", dest); if (!Path$equal_values(lib_dir, dest)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_name); @@ -529,7 +529,7 @@ void install_library(Path_t lib_dir) { "' " ">/dev/null 2>/dev/null")); (void)result; - print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", lib_name); + print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", lib_name); } void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs, compile_mode_t mode) { @@ -690,14 +690,14 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l } case USE_MODULE: { module_info_t mod = get_used_module_info(stmt_ast); - const char *full_name = mod.version ? String(mod.name, "_", mod.version) : mod.name; - Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", Text$from_str(full_name), - "' '", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", Text$from_str(full_name), "/lib", + const char *full_name = mod.version ? String(mod.name, "@", mod.version) : mod.name; + Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), + "' '", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), "/lib", Text$from_str(full_name), SHARED_SUFFIX "'"); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); List_t children = - Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); + Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); for (int64_t i = 0; i < (int64_t)children.length; i++) { Path_t *child = (Path_t *)(children.data + i * children.stride); Table_t discarded = {.entries = EMPTY_LIST, .fallback = to_compile}; diff --git a/src/typecheck.c b/src/typecheck.c index b89f7eca..37f4fcab 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -231,8 +231,8 @@ static env_t *load_module(env_t *env, ast_t *use_ast) { case USE_MODULE: { module_info_t mod = get_used_module_info(use_ast); glob_t tm_files; - const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) + const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; + if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(use_ast, "Couldn't find or install library: ", folder); } -- cgit v1.2.3 From 75b7ee2a08aefbdb2e2202718326fadd96ee0ef0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 15:11:46 -0500 Subject: Fix for undefined behavior on structs/enums with padding --- CHANGES.md | 6 ++++++ src/types.c | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 212fe54c..b4910bb8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Version History +## v2025-11-29.2 + +### Bugfixes + +- Fix for undefined behavior on enums and structs with padding. + ## v2025-11-29 ### Syntax changes diff --git a/src/types.c b/src/types.c index 04784735..6ed24f6c 100644 --- a/src/types.c +++ b/src/types.c @@ -459,15 +459,25 @@ PUREFUNC bool is_packed_data(type_t *t) { || t->tag == FunctionType) { return true; } else if (t->tag == StructType) { + size_t offset = 0; for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { if (!is_packed_data(field->type)) return false; + size_t align = type_align(field->type); + if (align > 0 && offset % align != 0) return false; + offset += type_size(field->type); } - return true; + size_t overall_align = type_align(t); + return overall_align == 0 || (offset % overall_align == 0); } else if (t->tag == EnumType) { + size_t offset = sizeof(int32_t); for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { if (!is_packed_data(tag->type)) return false; + size_t align = type_align(tag->type); + if (align > 0 && offset % align != 0) return false; + offset += type_size(tag->type); } - return true; + size_t overall_align = type_align(t); + return overall_align == 0 || (offset % overall_align == 0); } else { return false; } -- cgit v1.2.3 From d60962ab5de970a9ce0893df4154b8a0b3ea646a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 15:42:04 -0500 Subject: Fix syntax in learnxiny.tm --- examples/learnxiny.tm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index a03db6c9..748f8957 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -99,7 +99,7 @@ func main() break x # This is the same as `stop x` # Tables are efficient hash maps - table := {"one"=1, "two"=2} + table := {"one": 1, "two": 2} assert table["two"] == 2 # The value returned is optional because none will be returned if the key @@ -130,19 +130,19 @@ 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 := {"three": 3; fallback=table} assert table2["two"]! == 2 assert table2["three"]! == 3 # Tables can also be created with comprehension loops: - assert {x=10*x for x in 5} == {1=10, 2=20, 3=30, 4=40, 5=50} + assert {x: 10*x for x in 5} == {1: 10, 2: 20, 3: 30, 4: 40, 5: 50} # If no default is provided and a missing key is looked up, the program # will print an error message and halt. # Any types can be used in tables, for example, a table mapping lists to # strings: - table3 := {[10, 20]="one", [30, 40, 50]="two"} + table3 := {[10, 20]: "one", [30, 40, 50]: "two"} assert table3[[10, 20]]! == "one" # So far, the datastructures that have been discussed are all *immutable*, @@ -248,7 +248,7 @@ func demo_structs() assert "$alice" == 'Person(name="Alice", age=30)' == yes - table := {alice="first", bob="second"} + table := {alice: "first", bob: "second"} assert table[alice]! == "first" -- cgit v1.2.3 From 97047cb95a88228ddefbc83b4c50b05eaf048272 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 15:56:02 -0500 Subject: Update docs --- docs/bytes.md | 2 +- docs/enums.md | 12 +++----- docs/functions.md | 22 ++++++--------- docs/integers.md | 31 +++++++++------------ docs/iterators.md | 17 ++++-------- docs/langs.md | 4 +-- docs/lists.md | 46 +++++++++++-------------------- docs/nums.md | 31 ++++++++------------- docs/operators.md | 76 +++++++++++++++++++-------------------------------- docs/optionals.md | 9 ++---- docs/paths.md | 6 ++-- docs/pointers.md | 18 ++++-------- docs/reductions.md | 56 +++++++++++++------------------------ docs/serialization.md | 12 ++++---- docs/structs.md | 23 +++++----------- docs/tables.md | 42 ++++++++++------------------ docs/text.md | 6 ++-- 17 files changed, 151 insertions(+), 262 deletions(-) diff --git a/docs/bytes.md b/docs/bytes.md index 196d5762..f18071e2 100644 --- a/docs/bytes.md +++ b/docs/bytes.md @@ -4,7 +4,7 @@ Byte values have the type `Byte`, which corresponds to an unsigned 8-bit integer ranging from 0 to 255. It is generally recommended to use `Int8` instead of `Byte` when performing math operations, however, `Byte`s are used in API methods for `Text` and `Path` that deal with raw binary data, such as -`Path.read_bytes()` and `Text.bytes()`. Byte literals can be written using +`Path.read_bytes()` and `Text.utf8()`. Byte literals can be written using the `Byte()` constructor: `Byte(5)`. # API diff --git a/docs/enums.md b/docs/enums.md index 27ebdb86..85e82f36 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -34,10 +34,8 @@ an `else` block to handle all unmatched patterns. Tags can also be quickly checked using the `.TagName` field: ```tomo ->> a.AnInteger -= yes ->> a.TwoWords -= no +assert a.AnInteger != none +assert a.TwoWords == none ``` ## Reducing Boilerplate @@ -62,10 +60,8 @@ func increment(arg:ArgumentType -> ReturnType) ... ->> increment(AnInt(5)) -= AnInt(6) ->> increment(SomeText("HI")) -= Nothiing +assert increment(AnInt(5)) == AnInt(6) +assert increment(SomeText("HI")) == Nothiing ``` This lets us have overlapping tag names for different types, but smartly infer diff --git a/docs/functions.md b/docs/functions.md index 72279c11..0b836bcc 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -27,11 +27,9 @@ Default arguments are used to fill in arguments that were not provided at the callsite: ```tomo ->> increment(5) -= 6 +assert increment(5) == 6 ->> increment(5, 10) -= 15 +assert increment(5, 10) == 15 ``` **Note:** Default arguments are re-evaluated at the callsite for each function @@ -50,11 +48,9 @@ any unbound arguments, in order: func foo(x:Int, y:Text, z:Num) return "x=$x y=$y z=$z" ->> foo(x=1, y="hi", z=2.5) -= "x=1 y=hi z=2.5" - ->> foo(z=2.5, 1, "hi") -= "x=1 y=hi z=2.5" +func main() + assert foo(x=1, y="hi", z=2.5) == "x=1 y=hi z=2.5" + assert foo(z=2.5, 1, "hi") == "x=1 y=hi z=2.5" ``` As an implementation detail, all function calls are compiled to normal @@ -157,10 +153,10 @@ func create_adder(n:Int -> func(i:Int -> Int)) n = -1 // This does not affect the adder return adder -... -add10 := create_adder(10) ->> add10(5) -= 15 + +func main() + add10 := create_adder(10) + assert add10(5) == 15 ``` Under the hood, all user functions that are passed around in Tomo are passed as diff --git a/docs/integers.md b/docs/integers.md index 6be96880..877ab39d 100644 --- a/docs/integers.md +++ b/docs/integers.md @@ -37,8 +37,7 @@ The simplest form of integer literal is a string of digits, which is inferred to have type `Int` (unbounded size). ```tomo ->>> 123456789012345678901234567890 -= 123456789012345678901234567890 : Int +i := 123456789012345678901234567890 ``` Underscores may also be used to visually break up the integer for readability: @@ -79,12 +78,10 @@ quotient := numerator / denominator remainder := numerator mod denominator # Modulus always gives a non-negative result: ->> remainder >= 0 -= yes +assert remainder >= 0 # The numerator can be reconstructed sensibly: ->> numerator == denominator * quotient + remainder -= yes +assert numerator == denominator * quotient + remainder ``` Importantly, these invariants hold for both positive and negative numerators @@ -95,25 +92,23 @@ negative numbers are involved. Integer division rounds _down_ instead of rounding _towards zero_, and modulus never gives negative results: ```tomo ->> quotient := -1 / 5 -= -1 +quotient := -1 / 5 +assert quotient == -1 ->> remainder := -1 mod 5 -= 4 +remainder := -1 mod 5 +assert remainder == 4 ->> -1 == 5 * -1 + 4 -= yes +assert -1 == 5 * -1 + 4 ``` ```tomo ->> quotient := 16 / -5 -= -3 +quotient := 16 / -5 +assert quotient == -3 ->> remainder := -1 mod 5 -= 1 +remainder := -1 mod 5 +assert remainder == 1 ->> 16 == -5 * -3 + 1 -= yes +assert 16 == -5 * -3 + 1 ``` # API diff --git a/docs/iterators.md b/docs/iterators.md index 9337d859..d1ff7b4b 100644 --- a/docs/iterators.md +++ b/docs/iterators.md @@ -14,15 +14,11 @@ successively gets one line from a file at a time until the file is exhausted: line three ") ->> iter := (./test.txt).each_line() ->> iter() -= "line one" : Text? ->> iter() -= "line two" : Text? ->> iter() -= "line three" : Text? ->> iter() -= none : Text? +iter := (./test.txt).each_line() +assert iter() == "line one" +assert iter() == "line two" +assert iter() == "line three" +assert iter() == none for line in (./test.txt).each_line() pass @@ -44,7 +40,6 @@ func primes_up_to(limit:Int) n += 1 return (n - 1)? ->> [p for p in primes_up_to(11)] -= [2, 3, 5, 7, 11] +assert [p for p in primes_up_to(11)] == [2, 3, 5, 7, 11] ``` diff --git a/docs/langs.md b/docs/langs.md index 8f440932..f8784fbc 100644 --- a/docs/langs.md +++ b/docs/langs.md @@ -29,8 +29,8 @@ situations where a malicious user might set their username to something like ``. ``` ->> username := Text.read_line("Choose a username: ") -= "" +username := Text.read_line("Choose a username: ") +assert username == "" page := $HTML" Hello $username! How are you? diff --git a/docs/lists.md b/docs/lists.md index d12a0b5b..2700fe81 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -30,17 +30,15 @@ Lists can also use comprehensions, where you specify how to dynamically create all the elements by iteration instead of manually specifying each: ```tomo ->> [i*10 for i in (3).to(8)] -= [30, 40, 50, 60, 70, 80] ->> [i*10 for i in (3).to(8) if i != 4] -= [30, 50, 60, 70, 80] +assert [i*10 for i in (3).to(8)] == [30, 40, 50, 60, 70, 80] +assert [i*10 for i in (3).to(8) if i != 4] == [30, 50, 60, 70, 80] ``` Comprehensions can be combined with regular items or other comprehensions: ```tomo ->> [-1, i*10 for i in (3).to(8), i for i in 3] -= [-1, 30, 40, 50, 60, 70, 80, 1, 2, 3] +nums := [-1, i*10 for i in (3).to(8), i for i in 3] +assert nums == [-1, 30, 40, 50, 60, 70, 80, 1, 2, 3] ``` ## Length @@ -48,8 +46,7 @@ Comprehensions can be combined with regular items or other comprehensions: List length can be accessed by the `.length` field: ```tomo ->> [10, 20, 30].length -= 3 +assert [10, 20, 30].length == 3 ``` ## Indexing @@ -61,20 +58,15 @@ last item, `-2` is the second-to-last, and so on. ```tomo list := [10, 20, 30, 40] ->> list[1] -= 10? +assert list[1] == 10? ->> list[2] -= 20? +assert list[2] == 20? ->> list[999] -= none +assert list[999] == none ->> list[-1] -= 40? +assert list[-1] == 40? ->> list[-2] -= 30? +assert list[-2] == 30? ``` If a list index of `0` or any value larger than the length of the list is @@ -103,8 +95,7 @@ has the items from one appended to the other. This should not be confused with the addition operator `+`, which does not work with lists. ```tomo ->> [1, 2] ++ [3, 4] -= [1, 2, 3, 4] +assert [1, 2] ++ [3, 4] == [1, 2, 3, 4] ``` ## Implementation Details @@ -172,24 +163,20 @@ nums[4] = 40 // Constant time operation, but increments the reference count: tmp := nums ->> tmp -= [10, 20, 30, 40] +assert tmp == [10, 20, 30, 40] // Now, a mutation will trigger a copy-on-write, // which resets the reference count to zero: nums[4] = 999 ->> nums -= [10, 20, 30, 999] +assert nums == [10, 20, 30, 999] // Because of the copy-on-write, `tmp` is unchanged: ->> tmp -= [10, 20, 30, 40] +assert tmp == [10, 20, 30, 40] // Since the reference count has been reset, we can do more // mutations without triggering another copy-on-write: nums[4] = -1 ->> nums -= [10, 20, 30, -1] +assert nums == [10, 20, 30, -1] ``` List reference counting is _approximate_, but will only ever err on the side @@ -211,8 +198,7 @@ nums := @[10, 20, 30] tmp := nums nums.insert(40) ->> tmp -= @[10, 20, 30, 40] +assert tmp == @[10, 20, 30, 40] ``` Having multiple pointers to the same heap-allocated list does not cause the diff --git a/docs/nums.md b/docs/nums.md index 1bd9b52d..7502d8bc 100644 --- a/docs/nums.md +++ b/docs/nums.md @@ -41,46 +41,39 @@ very liberal use of type coercion and implicit `none` checks when values are required to be non-none. Here are a few examples: ```tomo ->> x := 0.0 -= 0 : Num +zero := 0.0 +assert zero == 0 y := 1.0 # Division might produce none: ->> x / y -= 0 : Num? ->> x / x -= none : Num? +assert zero / y == 0 +assert zero / zero == none # Optional types and none values propagate: ->> x/y + 1 + 2 -= 3 : Num? ->> x/x + 1 + 2 -= none : Num? +assert zero/y + 1 + 2 == 3 +assert zero/zero + 1 + 2 == none # Optional Nums can be handled explicitly using `or` and `!`: ->> x/x or -123 -= -123 : Num +assert zero/zero or -123 == -123 -# This would raise a runtime error if `x` and `y` were zero: ->> (x/y)! -= 0 : Num +# This would raise a runtime error if `zero` and `y` were zero: +assert (zero/y)! == 0 # Assigning to a non-optional variable will do an implicit check for none and # raise a runtime error if the value is none, essentially the same as an # implicit `!`: -x = x/y +zero = zero/y func doop(x:Num -> Num) # If a function's return type is non-optional and an optional value is # used in a return statement, an implicit none check will be inserted and # will error if the value is none: - return x / 2 + return zero / 2 # Function arguments are also implicitly checked for none if the given value # is optional and the function needs a non-optional value: ->> doop(x/y) -= 0 : Num +assert doop(zero/y) == 0 ``` Hopefully the end result of this system is one where users can take advantage diff --git a/docs/operators.md b/docs/operators.md index fdc2aee5..02559067 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -25,19 +25,16 @@ between the two objects. The `<>` operator exposes this signed comparison value to the user. ```tomo ->> 0 <> 99 -= -1[32] ->> 5 <> 5 -= 0[32] ->> 99 <> 0 -= 1[32] +assert 0 <> 99 == -1[32] +assert 5 <> 5 == 0[32] +assert 99 <> 0 == 1[32] ``` It's particularly handy for using the list `sort()` method, which takes a function that returns a signed integer: ```tomo ->> foos.sort(func(a,b:&Foo): a.length <> b.length) +foos.sort(func(a,b:&Foo): a.length <> b.length) ``` ## Reducers @@ -50,15 +47,12 @@ the need for several polymorphic functions used in other languages like reducers in action: ```tomo ->> nums := [10, 20, 30] ->> (+: nums)! -= 60 ->> (or: n > 15 for n in nums)! -= yes - ->> texts := ["one", "two", "three"] ->> (++: texts)! -= "onetwothree" +nums := [10, 20, 30] +assert (+: nums)! == 60 +assert (or: n > 15 for n in nums)! == yes + +texts := ["one", "two", "three"] +assert (++: texts)! == "onetwothree" ``` The simplest form of a reducer is an infix operator surrounded by parentheses, @@ -75,8 +69,8 @@ first option is to not account for it, in which case you'll get a runtime error if you use a reducer on something that has no values: ```tomo ->> nums : [Int] = [] ->> (+: nums)! +nums : [Int] = [] +result := (+: nums)! Error: this collection was empty! ``` @@ -85,8 +79,7 @@ If you want to handle this case, you can either wrap it in a conditional statement or you can provide a fallback option with `else` like this: ```tomo ->> (+: nums) or 0 -= 0 +assert (+: nums) or 0 == 0 ``` The `else` clause must be a value of the same type that would be returned. @@ -102,21 +95,17 @@ struct Foo(x,y:Int) func is_even(f:Foo -> Bool) return (f.x + f.y) mod 2 == 0 ->> foos := [Foo(1, 2), Foo(-10, 20)] +foos := [Foo(1, 2), Foo(-10, 20)] ->> (+.x: foos) -= -9 +assert (+.x: foos) == -9 // Shorthand for: ->> (+: f.x for f in foos) -= -9 +assert (+: f.x for f in foos) == -9 ->> (or).is_even() foos -= yes +assert (or).is_even() foos == yes // Shorthand for: ->> (or) f.is_even() for f in foos +assert ((or) f.is_even() for f in foos) == yes ->> (+.x.abs(): foos) -= 11 +assert (+.x.abs(): foos) == 11 ``` ## `_min_` and `_max_` @@ -126,10 +115,8 @@ Tomo introduces a new pair of operators that may be unfamiliar: `_min_` and larger or smaller of two elements: ```tomo ->> 3 _max_ 5 -= 5 ->> "XYZ" _min_ "ABC" -= "ABC" +assert 3 _max_ 5 == 5 +assert "XYZ" _min_ "ABC" == "ABC" ``` Initially, this might seem like a fairly useless operator, but there are two @@ -144,22 +131,18 @@ Here's some examples: ```tomo // Get the largest absolute value number: ->> 3 _max_.abs() -15 -= -15 +assert 3 _max_.abs() -15 == -15 struct Person(name:Text, age:Int) // Get the oldest of two people: ->> Person("Alice", 33) _max_.age Person("Bob", 20) -= Person(name="Alice", age=33) +assert Person("Alice", 33) _max_.age Person("Bob", 20) == Person(name="Alice", age=33) // Get the longest of two lists: ->> [10, 20, 30, 40] _max_.length [99, 1] -= [10, 20, 30, 40] +assert [10, 20, 30, 40] _max_.length [99, 1] == [10, 20, 30, 40] // Get the list with the highest value in the last position: ->> [10, 20, 999] _max_[-1] [99, 1] -= [10, 20, 999] +assert [10, 20, 999] _max_[-1] [99, 1] == [10, 20, 999] ``` The keyed comparison can chain together multiple field accesses, list index @@ -173,12 +156,9 @@ This means that you get get the minimum or maximum element from an iterable object using them: ```tomo ->> nums := [10, -20, 30, -40] ->> (_max_: nums) -= 30 - ->> (_max_.abs(): nums) -= -40 +nums := [10, -20, 30, -40] +assert (_max_: nums) == 30 +assert (_max_.abs(): nums) == -40 ``` ## Operator Overloading diff --git a/docs/optionals.md b/docs/optionals.md index 131daf19..326d77b5 100644 --- a/docs/optionals.md +++ b/docs/optionals.md @@ -90,14 +90,11 @@ an `Abort` type like `fail()` or `exit()`: ```tomo maybe_x : Int? = 5 ->> maybe_x or -1 -= 5 : Int ->> maybe_x or fail("No value!") -= 5 : Int +assert (maybe_x or -1) == 5 +assert (maybe_x or fail("No value!")) == 5 maybe_x = none ->> maybe_x or -1 -= -1 : Int +assert (maybe_x or -1) == -1 >> maybe_x or fail("No value!") # Failure! diff --git a/docs/paths.md b/docs/paths.md index 2fa55b13..810cb6df 100644 --- a/docs/paths.md +++ b/docs/paths.md @@ -13,10 +13,8 @@ syntax. A path literal begins with either `(/`, `(./`, `(../`, or `(~/` and cont until a matching closing parenethesis: ```tomo ->> (/tmp) -= (/tmp) ->> (~/path with/(parens) is/ok/) -= (~/path with/(parens) is/ok/) +assert (/tmp) == (/tmp) +assert (~/path with/(parens) is/ok/) == (~/path with/(parens) is/ok/) ``` ### Interpolation diff --git a/docs/pointers.md b/docs/pointers.md index 254db07e..fb7668f1 100644 --- a/docs/pointers.md +++ b/docs/pointers.md @@ -18,16 +18,14 @@ func no_mutation_possible(nums:[Int]) ... my_nums := [0, 1, 2] no_mutation_possible(my_nums) ->> my_nums -= [0, 1, 2] +assert my_nums == [0, 1, 2] func do_mutation(nums:@[Int]) nums[1] = 10 // The mutates the value at the given pointer's location ... my_nums := @[0, 1, 2] do_mutation(my_nums) ->> my_nums -= @[10, 1, 2] +assert my_nums == @[10, 1, 2] ``` ## Dereferencing @@ -37,8 +35,7 @@ memory location using the `[]` postfix operator (with no value inside). ```tomo nums := @[10, 20] ->> nums[] -= [10, 20] +assert nums[] == [10, 20] ``` ## Equality and Comparisons @@ -52,12 +49,10 @@ doing a mutation to the other. ```tomo x := @[10, 20, 30] y := @[10, 20, 30] ->> x == y -= no +assert x != y z := x ->> x == z -= yes +assert x == z ``` Pointers are ordered by memory address, which is somewhat arbitrary, but @@ -112,8 +107,7 @@ inside of any datastructures as elements or members. ```tomo nums := @[10, 20, 30] ->> nums.first(func(x:&Int): x / 2 == 10) -= 2 : Int? +assert nums.first(func(x:&Int): x / 2 == 10) == 2 ``` Normal `@` pointers can be promoted to immutable view pointers automatically, diff --git a/docs/reductions.md b/docs/reductions.md index abd612b0..929e5d00 100644 --- a/docs/reductions.md +++ b/docs/reductions.md @@ -7,8 +7,7 @@ infix operator followed by a colon, followed by a collection: ```tomo nums := [10, 20, 30] sum := (+: nums) ->> sum -= 60 : Int? +assert sum == 60 ``` Reductions return an optional value which will be a null value if the thing @@ -21,15 +20,12 @@ provide a fallback value: nums : [Int] = [] sum := (+: nums) ->> sum -= none : Int? +assert sum == none ->> sum or 0 -= 0 +assert sum or 0 == 0 ->> nums = [10, 20] ->> (+: nums)! -= 30 +nums = [10, 20] +assert (+: nums)! == 30 ``` Reductions can be used as an alternative to generic functions like `sum()`, @@ -38,20 +34,16 @@ Reductions can be used as an alternative to generic functions like `sum()`, ```tomo # Sum: ->> (+: [10, 20, 30])! -= 60 +assert (+: [10, 20, 30])! == 60 # Product: ->> (*: [2, 3, 4])! -= 24 +assert (*: [2, 3, 4])! == 24 # Any: ->> (or: [no, yes, no])! -= yes +assert (or: [no, yes, no])! == yes # All: ->> (and: [no, yes, no])! -= no +assert (and: [no, yes, no])! == no ``` ## Minimum and Maximum @@ -61,12 +53,10 @@ a collection using the `_min_` and `_max_` infix operators. ```tomo # Get the maximum value: ->> (_max_: [10, 30, 20])! -= 30 +assert (_max_: [10, 30, 20])! == 30 # Get the minimum value: ->> (_min_: [10, 30, 20])! -= 10 +assert (_min_: [10, 30, 20])! == 10 ``` Reducers also support field and method call suffixes, which makes it very easy @@ -76,28 +66,22 @@ feature_. ```tomo # Get the longest text: ->> (_max_.length: ["z", "aaaaa", "mmm"])! -= "aaaaa" +assert (_max_.length: ["z", "aaaaa", "mmm"])! == "aaaaa" # Get the number with the biggest absolute value: ->> (_max_.abs(): [1, -2, 3, -4])! -= -4 +assert (_max_.abs(): [1, -2, 3, -4])! == -4 ``` You can also use suffixes on other operators: ```tomo texts := ["x", "y", "z"] ->> (==: texts) -= no ->> (==.length: texts) -= yes ->> (+.length: texts) -= 3 +assert (==: texts) == no +assert (==.length: texts) == yes +assert (+.length: texts) == 3 nums := [1, 2, -3] ->> (+.abs(): nums) -= 6 +assert (+.abs(): nums) == 6 ``` ## Comprehensions @@ -108,10 +92,8 @@ while filtering out values or while applying a transformation: ```tomo # Sum the lengths of these texts: ->> (+: t.length for t in ["a", "bc", "def"])! -= 6 +assert (+: t.length for t in ["a", "bc", "def"])! == 6 # Sum the primes between 1-100: ->> (+: i for i in 100 if i.is_prime())! -= 1060 +assert (+: i for i in 100 if i.is_prime())! == 1060 ``` diff --git a/docs/serialization.md b/docs/serialization.md index 287cbda7..8c72cb83 100644 --- a/docs/serialization.md +++ b/docs/serialization.md @@ -67,12 +67,14 @@ struct Cycle(name:Text, next:@Cycle?=none) c := @Cycle("A") c.next = @Cycle("B", next=c) ->> c +say("$c") +# @Cycle(name="A", next=@Cycle(name="B", next=@~1)) +bytes : [Byte] = c +say("$bytes") +# [0x02, 0x02, 0x41, 0x01, 0x04, 0x02, 0x42, 0x01, 0x02] +roundtrip : @Cycle = bytes +say("$roundtrip") # @Cycle(name="A", next=@Cycle(name="B", next=@~1)) ->> bytes : [Byte] = c -# [0x02, 0x02, 0x41, 0x01, 0x04, 0x02, 0x42, 0x01, 0x02] : [Byte] ->> roundtrip : @Cycle = bytes -# @Cycle(name="A", next=@Cycle(name="B", next=@~1)) : @Cycle assert roundtrip.next.next == roundtrip ``` diff --git a/docs/structs.md b/docs/structs.md index 1dfa49c9..8f280fab 100644 --- a/docs/structs.md +++ b/docs/structs.md @@ -6,10 +6,9 @@ types that can be accessed by fields: ```tomo struct Foo(name:Text, age:Int) ... ->> my_foo := Foo("Bob", age=10) -= Foo(name="Bob", age=10) ->> my_foo.name -= "Bob" +my_foo := Foo("Bob", age=10) +assert my_foo == Foo(name="Bob", age=10) +assert my_foo.name == "Bob" ``` Structs are value types and comparisons on them operate on the member values @@ -49,11 +48,8 @@ struct Password(raw_password_text:Text; secret) struct User(username:Text, password:Password) ... user := User("Stanley", Password("Swordfish")) ->> user -= User(username="Stanley", password=Password(...)) - ->> "$user" == 'User(username="Stanley", password=Password(...))' -= yes +assert user == User("Stanley", Password("Swordfish")) +assert "You are: $user" == 'You are: User(username="Stanley", password=Password(...))' ``` Designing APIs so they take secrecy-protected structs instead of raw data @@ -62,17 +58,12 @@ your logs! Secrecy-protected values still work the same as any other struct, they just don't divulge their contents when converting to strings: ```tomo ->> user.password == Password("Swordfish") -= yes +assert user.password == Password("Swordfish") ``` You can also access the fields directly, but hopefully this extra amount of friction reduces the chances of accidentally divulging sensitive content: ```tomo ->> user.password -= Password(...) - ->> user.password.raw_password_text -= "Swordfish" +assert user.password.raw_password_text == "Swordfish" ``` diff --git a/docs/tables.md b/docs/tables.md index eaf0083e..00e3e8c0 100644 --- a/docs/tables.md +++ b/docs/tables.md @@ -40,10 +40,8 @@ optional value: ```tomo table := {"A": 1, "B": 2} ->> table["A"] -= 1? ->> table["missing"] -= none +assert table["A"] == 1 +assert table["missing"] == none ``` As with all optional values, you can use the `!` postfix operator to assert @@ -51,11 +49,9 @@ 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 +assert table["A"]! == 1 ->> table["missing"] or -1 -= -1 +assert (table["missing"] or -1) == -1 ``` ### Fallback Tables @@ -66,18 +62,15 @@ is not found in the table itself: ```tomo t := {"A": 10} t2 := {"B": 20; fallback=t} ->> t2["A"] -= 10? +assert t2["A"] == 10 ``` The fallback is available by the `.fallback` field, which returns an optional table value: ```tomo ->> t2.fallback -= {"A": 10}? ->> t.fallback -= none +assert t2.fallback == {"A": 10} +assert t.fallback == none ``` ### Default Values @@ -87,13 +80,10 @@ present in the table or its fallback (if any). ```tomo counts := &{"foo": 12; default=0} ->> counts["foo"] -= 12 ->> counts["baz"] -= 0 +assert counts["foo"] == 12 +assert counts["baz"] == 0 counts["baz"] += 1 ->> counts["baz"] -= 1 +assert counts["baz"] == 1 ``` When values are accessed from a table with a default value, the return type @@ -108,8 +98,7 @@ You can assign a new key/value mapping or overwrite an existing one using t := {"A": 1, "B": 2} t["B"] = 222 t["C"] = 333 ->> t -= {"A": 1, "B": 222, "C": 333} +assert t == {"A": 1, "B": 222, "C": 333} ``` ## Length @@ -117,8 +106,7 @@ t["C"] = 333 Table length can be accessed by the `.length` field: ```tomo ->> {"A": 10, "B": 20}.length -= 2 +assert {"A": 10, "B": 20}.length == 2 ``` ## Accessing Keys and Values @@ -128,10 +116,8 @@ constant-time immutable slice of the internal data from the table: ```tomo t := {"A": 10, "B": 20} ->> t.keys -= ["A", "B"] ->> t.values -= [10, 20] +assert t.keys == ["A", "B"] +assert t.values == [10, 20] ``` ## Iteration diff --git a/docs/text.md b/docs/text.md index 2df27811..b41140f7 100644 --- a/docs/text.md +++ b/docs/text.md @@ -247,10 +247,8 @@ when you think of "letters" in a string. If you have text with an emoji that has several joining modifiers attached to it, that text has a length of 1. ```tomo ->> "hello".length -= 5 ->> "👩🏽‍🚀".length -= 1 +assert "hello".length == 5 +assert "👩🏽‍🚀".length == 1 ``` ### Iteration -- cgit v1.2.3 From 7409d20a9500acfb854285dbae49b6a19330e7a2 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 29 Nov 2025 16:06:12 -0500 Subject: Update version docs --- docs/versions.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/versions.md b/docs/versions.md index eb617f43..0af7595c 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -33,6 +33,10 @@ Major version change including: Bugfixes and new features... ``` +The versions here use a `vMAJOR.MINOR` semantic versioning style, but this is +not required. Versions can be any string literal, for example: +`edition-2025-11-29` or `1.2.3` or `6.1-EliteOcelot`. + When you build the compiler or a library, if this file exists, it will be used to determine the current version (the top-most level 2 header). @@ -40,16 +44,16 @@ to determine the current version (the top-most level 2 header). The version for the Tomo language itself will come into play in a few ways: -1. The compiler will be installed to `tomo_vX.Y` (where `X` is the major - version number and `Y` is the minor version number). +1. The compiler will be installed to `tomo@TOMO_VERSION` (where `TOMO_VERSION` + is the verion of the Tomo compiler). 2. A symbolic link will be installed from `tomo` to the largest version of Tomo that is installed on your machine (e.g. `~/.local/bin/tomo -> - ~/.local/bin/tomo_v2.12`). + ~/.local/bin/tomo@v2025-11-29.1`). 3. Each version of Tomo will build and install its own shared library file - (e.g. `~/.local/lib/libtomo_v1.2.so`) and headers (e.g. - `~/.local/include/tomo_v1.2/tomo.h`). + (e.g. `~/.local/lib/libtomo@v2025-11-29.1.so`) and headers (e.g. + `~/.local/include/tomo@v2025-11-29.1/tomo.h`). 4. Tomo libraries will be installed to a separate subdirectory for each version - of the compiler (e.g. `~/.local/lib/tomo_v1.2/`). + of the compiler (e.g. `~/.local/lib/tomo@v2025-11-29.1/`). ## Tomo Program Versions @@ -57,7 +61,7 @@ When you write a Tomo program (say, `foo.tm`) and run it, Tomo will automatically add support for parsing a version number out of an accompanying `CHANGES.md` file in the same directory. You can use the `--version` flag to print the version number and exit. For example, if I run `tomo foo.tm -- ---version`, it will print `v0.0` if no `CHANGES.md` file exists, otherwise it +--version`, it will print `v0` if no `CHANGES.md` file exists, otherwise it will compile the program with the most recent version number from that file and print it instead. Similarly, if you run `tomo -e foo.tm` to build `foo` as a standalone executable and then run `./foo --version`, it will print the version @@ -68,7 +72,7 @@ number and exit without running the program. Tomo libraries also have version numbers. When you install a library, its version number will be used to determine its installation location and how it's used in code. You must either explicitly import the library with its version -number (e.g. `use foo_v1.2`) or include a `modules.ini` configuration file that +number (e.g. `use foo@v1.2`) or include a `modules.ini` configuration file that maps a shorthand alias to a specific version of a library. For example, if the `modules.ini` file has a `[foo]` section with `version=v1.2`, you can put `use foo` to use v1.2 of the `foo` library (assuming you have it installed). -- cgit v1.2.3 From d302aaec38b9d295d39c4d87b53ee610bc9e0e07 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 30 Nov 2025 13:08:57 -0500 Subject: Handle some text method edge cases with empty text better. --- src/stdlib/text.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/stdlib/text.c b/src/stdlib/text.c index e51af49c..b4b27fed 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -763,8 +763,10 @@ static Text_t Text$from_components(List_t graphemes, Table_t unique_clusters) { public OptionalText_t Text$from_strn(const char *str, size_t len) { int64_t ascii_span = 0; - for (size_t i = 0; i < len && isascii(str[i]); i++) + for (size_t i = 0; i < len && isascii(str[i]); i++) { ascii_span++; + if (str[i] == 0) return NONE_TEXT; + } if (ascii_span == (int64_t)len) { // All ASCII char *copy = GC_MALLOC_ATOMIC(len); @@ -786,12 +788,15 @@ OptionalText_t Text$from_strn(const char *str, size_t len) { uint32_t buf[256]; size_t u32_len = sizeof(buf) / sizeof(buf[0]); uint32_t *u32s = u8_to_u32(pos, (size_t)(next - pos), buf, &u32_len); + if (u32s == NULL) return NONE_TEXT; uint32_t buf2[256]; size_t u32_normlen = sizeof(buf2) / sizeof(buf2[0]); uint32_t *u32s_normalized = u32_normalize(UNINORM_NFC, u32s, u32_len, buf2, &u32_normlen); + if (u32s_normalized == NULL) return NONE_TEXT; int32_t g = get_synthetic_grapheme(u32s_normalized, (int64_t)u32_normlen); + if (g == 0) return NONE_TEXT; List$insert(&graphemes, &g, I(0), sizeof(int32_t)); Table$get_or_setdefault(&unique_clusters, int32_t, uint8_t, g, (uint8_t)unique_clusters.entries.length, Table$info(&Int32$info, &Byte$info)); -- cgit v1.2.3 From 4d8aa867c7f4661167a4742fbdd865ed2449503e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 30 Nov 2025 14:12:01 -0500 Subject: Add `base` parameter to integer parsing functions --- CHANGES.md | 11 +++++ Makefile | 2 +- api/api.md | 6 ++- api/integers.md | 6 ++- api/integers.yaml | 12 +++++ man/man3/tomo-Int.3 | 4 +- man/man3/tomo-Int.parse.3 | 8 +++- src/compile/functions.c | 3 +- src/environment.c | 18 +++---- src/stdlib/bigint.c | 119 +++++++++++++++++++++++++++++++--------------- src/stdlib/bigint.h | 2 +- src/stdlib/bytes.c | 4 +- src/stdlib/bytes.h | 2 +- src/stdlib/cli.c | 10 ++-- src/stdlib/intX.c.h | 4 +- src/stdlib/intX.h | 2 +- src/typecheck.c | 3 +- test/integers.tm | 21 ++++++++ 18 files changed, 169 insertions(+), 68 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b4910bb8..a60c854a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # Version History +## v2025-11-30 + +### API changes + +- Added `base` parameter to various `Int.parse()` methods to allow explicitly + setting the numeric base from 1-36. + +### Bugfixes + +- Fixed various issues around parsing integers. + ## v2025-11-29.2 ### Bugfixes diff --git a/Makefile b/Makefile index 2bfde7a1..ad521916 100644 --- a/Makefile +++ b/Makefile @@ -226,7 +226,7 @@ check-utilities: check-c-compiler @which debugedit 2>/dev/null >/dev/null \ || printf '\033[33;1m%s\033[m\n' "I couldn't find 'debugedit' on your system! Try installing the package 'debugedit' with your package manager. (It's not required though)" -install-files: build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) check-utilities +install-files: $(INCLUDE_SYMLINK) build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) check-utilities @if ! echo "$$PATH" | tr ':' '\n' | grep -qx "$(PREFIX)/bin"; then \ echo $$PATH; \ printf "\033[31;1mError: '$(PREFIX)/bin' is not in your \$$PATH variable!\033[m\n" >&2; \ diff --git a/api/api.md b/api/api.md index f52691d3..6a7d218b 100644 --- a/api/api.md +++ b/api/api.md @@ -662,7 +662,7 @@ assert nums[] == [5, 6, 7, 8, 9, 10] ## Int.parse ```tomo -Int.parse : func(text: Text, remainder: &Text? = none -> Int?) +Int.parse : func(text: Text, base: Int? = none, remainder: &Text? = none -> Int?) ``` Converts a text representation of an integer into an integer. @@ -670,6 +670,7 @@ Converts a text representation of an integer into an integer. Argument | Type | Description | Default ---------|------|-------------|--------- text | `Text` | The text containing the integer. | - +base | `Int?` | The numeric base to use when parsing the integer. If unspecified, the integer's base will be inferred from the text prefix. After any "+" or "-" sign, if the text begins with "0x", the base will be assumed to be 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the base will be assumed to be 10. | `none` remainder | `&Text?` | If non-none, this argument will be set to the remainder of the text after the matching part. If none, parsing will only succeed if the entire text matches. | `none` **Return:** The integer represented by the text. If the given text contains a value outside of the representable range or if the entire text can't be parsed as an integer, `none` will be returned. @@ -690,6 +691,9 @@ assert Int.parse("asdf") == none # Outside valid range: assert Int8.parse("9999999") == none +# Explicitly specifying base: +assert Int.parse("10", base=16) == 16 + ``` ## Int.prev_prime diff --git a/api/integers.md b/api/integers.md index 6af66b0d..ef3a6a60 100644 --- a/api/integers.md +++ b/api/integers.md @@ -255,7 +255,7 @@ assert nums[] == [5, 6, 7, 8, 9, 10] ## Int.parse ```tomo -Int.parse : func(text: Text, remainder: &Text? = none -> Int?) +Int.parse : func(text: Text, base: Int? = none, remainder: &Text? = none -> Int?) ``` Converts a text representation of an integer into an integer. @@ -263,6 +263,7 @@ Converts a text representation of an integer into an integer. Argument | Type | Description | Default ---------|------|-------------|--------- text | `Text` | The text containing the integer. | - +base | `Int?` | The numeric base to use when parsing the integer. If unspecified, the integer's base will be inferred from the text prefix. After any "+" or "-" sign, if the text begins with "0x", the base will be assumed to be 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the base will be assumed to be 10. | `none` remainder | `&Text?` | If non-none, this argument will be set to the remainder of the text after the matching part. If none, parsing will only succeed if the entire text matches. | `none` **Return:** The integer represented by the text. If the given text contains a value outside of the representable range or if the entire text can't be parsed as an integer, `none` will be returned. @@ -283,6 +284,9 @@ assert Int.parse("asdf") == none # Outside valid range: assert Int8.parse("9999999") == none +# Explicitly specifying base: +assert Int.parse("10", base=16) == 16 + ``` ## Int.prev_prime diff --git a/api/integers.yaml b/api/integers.yaml index 70709b04..b3c6b579 100644 --- a/api/integers.yaml +++ b/api/integers.yaml @@ -280,6 +280,15 @@ Int.parse: type: 'Text' description: > The text containing the integer. + base: + type: 'Int?' + default: 'none' + description: > + The numeric base to use when parsing the integer. If unspecified, the + integer's base will be inferred from the text prefix. After any "+" or + "-" sign, if the text begins with "0x", the base will be assumed to be + 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the + base will be assumed to be 10. remainder: type: '&Text?' default: 'none' @@ -300,6 +309,9 @@ Int.parse: # Outside valid range: assert Int8.parse("9999999") == none + # Explicitly specifying base: + assert Int.parse("10", base=16) == 16 + Int.prev_prime: short: get the previous prime description: > diff --git a/man/man3/tomo-Int.3 b/man/man3/tomo-Int.3 index 2476a27b..186c0aae 100644 --- a/man/man3/tomo-Int.3 +++ b/man/man3/tomo-Int.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int 3 2025-11-29 "Tomo man-pages" +.TH Int 3 2025-11-30 "Tomo man-pages" .SH NAME Int \- a Tomo type .SH LIBRARY @@ -99,7 +99,7 @@ For more, see: .TP -.BI Int.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Int?) +.BI Int.parse\ :\ func(text:\ Text,\ base:\ Int?\ =\ none,\ remainder:\ &Text?\ =\ none\ ->\ Int?) Converts a text representation of an integer into an integer. For more, see: diff --git a/man/man3/tomo-Int.parse.3 b/man/man3/tomo-Int.parse.3 index df3888db..53713c78 100644 --- a/man/man3/tomo-Int.parse.3 +++ b/man/man3/tomo-Int.parse.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.parse 3 2025-11-29 "Tomo man-pages" +.TH Int.parse 3 2025-11-30 "Tomo man-pages" .SH NAME Int.parse \- convert text to integer .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Int.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Int?) +.BI Int.parse\ :\ func(text:\ Text,\ base:\ Int?\ =\ none,\ remainder:\ &Text?\ =\ none\ ->\ Int?) .fi .SH DESCRIPTION Converts a text representation of an integer into an integer. @@ -23,6 +23,7 @@ lb lb lbx lb l l l l. Name Type Description Default text Text The text containing the integer. - +base Int? The numeric base to use when parsing the integer. If unspecified, the integer's base will be inferred from the text prefix. After any "+" or "-" sign, if the text begins with "0x", the base will be assumed to be 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the base will be assumed to be 10. none remainder &Text? If non-none, this argument will be set to the remainder of the text after the matching part. If none, parsing will only succeed if the entire text matches. none .TE .SH RETURN @@ -42,6 +43,9 @@ assert Int.parse("asdf") == none # Outside valid range: assert Int8.parse("9999999") == none + +# Explicitly specifying base: +assert Int.parse("10", base=16) == 16 .EE .SH SEE ALSO .BR Tomo-Int (3) diff --git a/src/compile/functions.c b/src/compile/functions.c index cce93e3d..63d7d23d 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -6,6 +6,7 @@ #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/nums.h" +#include "../stdlib/optionals.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" #include "../stdlib/util.h" @@ -703,7 +704,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static definition = Texts(definition, wrapper); } else if (cache && cache->tag == Int) { assert(args); - OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NULL); + OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NONE_INT, NULL); Text_t pop_code = EMPTY_TEXT; if (cache->tag == Int && cache_size.has_value && cache_size.value > 0) { // FIXME: this currently just deletes the first entry, but this diff --git a/src/environment.c b/src/environment.c index 43d22c3d..7e04fe79 100644 --- a/src/environment.c +++ b/src/environment.c @@ -93,7 +93,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), - {"parse", "Bool$parse", "func(text:Text, remainder:&Text? = none -> Bool?)"}), + {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}), MAKE_TYPE( // "Byte", Type(ByteType), Text("Byte_t"), Text("Byte$info"), {"get_bit", "Byte$get_bit", "func(x:Byte, bit_index:Int -> Bool)"}, // @@ -101,7 +101,7 @@ env_t *global_env(bool source_mapping) { {"is_between", "Byte$is_between", "func(x:Byte, low:Byte, high:Byte -> Bool)"}, // {"max", "Byte$max", "Byte"}, // {"min", "Byte$min", "Byte"}, // - {"parse", "Byte$parse", "func(text:Text, remainder:&Text? = none -> Byte?)"}, // + {"parse", "Byte$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Byte?)"}, // {"to", "Byte$to", "func(first:Byte, last:Byte, step:Int8?=none -> func(->Byte?))"}), MAKE_TYPE( // "Int", Type(BigIntType), Text("Int_t"), Text("Int$info"), {"abs", "Int$abs", "func(x:Int -> Int)"}, // @@ -126,7 +126,7 @@ env_t *global_env(bool source_mapping) { {"next_prime", "Int$next_prime", "func(x:Int -> Int)"}, // {"octal", "Int$octal", "func(i:Int, digits=0, prefix=yes -> Text)"}, // {"onward", "Int$onward", "func(first:Int,step=1 -> func(->Int?))"}, // - {"parse", "Int$parse", "func(text:Text, remainder:&Text? = none -> Int?)"}, // + {"parse", "Int$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int?)"}, // {"plus", "Int$plus", "func(x,y:Int -> Int)"}, // {"power", "Int$power", "func(base:Int,exponent:Int -> Int)"}, // #if __GNU_MP_VERSION >= 6 @@ -145,7 +145,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int64$clamped", "func(x,low,high:Int64 -> Int64)"}, // {"divided_by", "Int64$divided_by", "func(x,y:Int64 -> Int64)"}, // {"gcd", "Int64$gcd", "func(x,y:Int64 -> Int64)"}, // - {"parse", "Int64$parse", "func(text:Text, remainder:&Text? = none -> Int64?)"}, // + {"parse", "Int64$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int64?)"}, // {"get_bit", "Int64$get_bit", "func(x:Int64, bit_index:Int -> Bool)"}, // {"hex", "Int64$hex", "func(i:Int64, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int64$is_between", "func(x:Int64,low:Int64,high:Int64 -> Bool)"}, // @@ -167,7 +167,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int32$clamped", "func(x,low,high:Int32 -> Int32)"}, // {"divided_by", "Int32$divided_by", "func(x,y:Int32 -> Int32)"}, // {"gcd", "Int32$gcd", "func(x,y:Int32 -> Int32)"}, // - {"parse", "Int32$parse", "func(text:Text, remainder:&Text? = none -> Int32?)"}, // + {"parse", "Int32$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int32?)"}, // {"get_bit", "Int32$get_bit", "func(x:Int32, bit_index:Int -> Bool)"}, // {"hex", "Int32$hex", "func(i:Int32, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int32$is_between", "func(x:Int32,low:Int32,high:Int32 -> Bool)"}, // @@ -189,7 +189,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int16$clamped", "func(x,low,high:Int16 -> Int16)"}, // {"divided_by", "Int16$divided_by", "func(x,y:Int16 -> Int16)"}, // {"gcd", "Int16$gcd", "func(x,y:Int16 -> Int16)"}, // - {"parse", "Int16$parse", "func(text:Text, remainder:&Text? = none -> Int16?)"}, // + {"parse", "Int16$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int16?)"}, // {"get_bit", "Int16$get_bit", "func(x:Int16, bit_index:Int -> Bool)"}, // {"hex", "Int16$hex", "func(i:Int16, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int16$is_between", "func(x:Int16,low:Int16,high:Int16 -> Bool)"}, // @@ -211,7 +211,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Int8$clamped", "func(x,low,high:Int8 -> Int8)"}, // {"divided_by", "Int8$divided_by", "func(x,y:Int8 -> Int8)"}, // {"gcd", "Int8$gcd", "func(x,y:Int8 -> Int8)"}, // - {"parse", "Int8$parse", "func(text:Text, remainder:&Text? = none -> Int8?)"}, // + {"parse", "Int8$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int8?)"}, // {"get_bit", "Int8$get_bit", "func(x:Int8, bit_index:Int -> Bool)"}, // {"hex", "Int8$hex", "func(i:Int8, digits=0, uppercase=yes, prefix=yes -> Text)"}, // {"is_between", "Int8$is_between", "func(x:Int8,low:Int8,high:Int8 -> Bool)"}, // @@ -245,7 +245,7 @@ env_t *global_env(bool source_mapping) { C(SQRT1_2), {"INF", "(Num_t)(INFINITY)", "Num"}, // {"TAU", "(Num_t)(2.*M_PI)", "Num"}, // {"mix", "Num$mix", "func(amount,x,y:Num -> Num)"}, // - {"parse", "Num$parse", "func(text:Text, remainder:&Text? = none -> Num?)"}, // + {"parse", "Num$parse", "func(text:Text, remainder:&Text?=none -> Num?)"}, // {"abs", "fabs", "func(n:Num -> Num)"}, // F_opt(acos), F_opt(acosh), F_opt(asin), F(asinh), F(atan), F_opt(atanh), F(cbrt), F(ceil), F_opt(cos), F(cosh), F(erf), F(erfc), F(exp), F(exp2), F(expm1), F(floor), F(j0), F(j1), F_opt(log), F_opt(log10), @@ -274,7 +274,7 @@ env_t *global_env(bool source_mapping) { {"INF", "(Num32_t)(INFINITY)", "Num32"}, // {"TAU", "(Num32_t)(2.f*M_PI)", "Num32"}, // {"mix", "Num32$mix", "func(amount,x,y:Num32 -> Num32)"}, // - {"parse", "Num32$parse", "func(text:Text, remainder:&Text? = none -> Num32?)"}, // + {"parse", "Num32$parse", "func(text:Text, remainder:&Text?=none -> Num32?)"}, // {"abs", "fabsf", "func(n:Num32 -> Num32)"}, // {"modulo", "Num32$mod", "func(x,y:Num32 -> Num32)"}, // {"modulo1", "Num32$mod1", "func(x,y:Num32 -> Num32)"}, // diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c index 46957c2d..2d145bd5 100644 --- a/src/stdlib/bigint.c +++ b/src/stdlib/bigint.c @@ -393,55 +393,98 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) { } public -Int_t Int$from_str(const char *str) { - mpz_t i; - int result; - if (strncmp(str, "0x", 2) == 0) { - result = mpz_init_set_str(i, str + 2, 16); - } else if (strncmp(str, "0o", 2) == 0) { - result = mpz_init_set_str(i, str + 2, 8); - } else if (strncmp(str, "0b", 2) == 0) { - result = mpz_init_set_str(i, str + 2, 2); - } else { - result = mpz_init_set_str(i, str, 10); - } - if (result != 0) return NONE_INT; - return Int$from_mpz(i); -} +Int_t Int$from_str(const char *str) { return Int$parse(Text$from_str(str), NONE_INT, NULL); } public -OptionalInt_t Int$parse(Text_t text, Text_t *remainder) { +OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) { const char *str = Text$as_c_string(text); - mpz_t i; - int result; - if (strncmp(str, "0x", 2) == 0) { - str += 2; - const char *end = str + strspn(str, "0123456789abcdefABCDEF"); - if (remainder) *remainder = Text$from_str(end); - else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 16); + bool negative = (*str == '-'); + if (negative || *str == '+') str += 1; + const char *end = str; + int32_t base32; + if (base.small != 0) { + base32 = Int32$from_int(base, false); + switch (base32) { + case 16: + if (strncmp(str, "0x", 2) == 0) { + base16_prefix: + str += 2; + } + end = str + strspn(str, "0123456789abcdefABCDEF"); + break; + case 10: + base10: + end = str + strspn(str, "0123456789"); + break; + case 8: + if (strncmp(str, "0o", 2) == 0) { + base8_prefix: + str += 2; + } + end = str + strspn(str, "01234567"); + break; + case 2: + if (strncmp(str, "0b", 2) == 0) { + base2_prefix: + str += 2; + } + end = str + strspn(str, "01"); + break; + case 1: { + str += strspn(str, "0"); + size_t n = strspn(str, "1"); + end = str + n; + if (remainder) *remainder = Text$from_str(end); + else if (*end != '\0') return NONE_INT; + return Int$from_int64((int64_t)n); + } + default: { + if (base32 < 1 || base32 > 36) { + if (remainder) *remainder = text; + return NONE_INT; + } + for (; *end; end++) { + char c = *end; + int32_t digit; + if ('0' <= c && c <= '9') { + digit = (c - (int)'0'); + } else if ('a' <= c && c <= 'z') { + digit = (c - (int)'a'); + } else if ('A' <= c && c <= 'Z') { + digit = (c - (int)'A'); + } else { + break; + } + if (digit >= base32) break; + } + } + } + } else if (strncmp(str, "0x", 2) == 0) { + base32 = 16; + goto base16_prefix; } else if (strncmp(str, "0o", 2) == 0) { - str += 2; - const char *end = str + strspn(str, "01234567"); - if (remainder) *remainder = Text$from_str(end); - else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 8); + base32 = 8; + goto base8_prefix; } else if (strncmp(str, "0b", 2) == 0) { - str += 2; - const char *end = str + strspn(str, "01"); - if (remainder) *remainder = Text$from_str(end); - else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 2); + base32 = 2; + goto base2_prefix; } else { - const char *end = str + strspn(str, "0123456789"); - if (remainder) *remainder = Text$from_str(end); - else if (*end != '\0') return NONE_INT; - result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 10); + base32 = 10; + goto base10; } + + if (remainder) *remainder = Text$from_str(end); + else if (*end != '\0') return NONE_INT; + + mpz_t i; + int result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), base32); if (result != 0) { if (remainder) *remainder = text; return NONE_INT; } + if (negative) { + mpz_neg(i, i); + } return Int$from_mpz(i); } diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h index a00bdf2f..9ce4c800 100644 --- a/src/stdlib/bigint.h +++ b/src/stdlib/bigint.h @@ -23,7 +23,7 @@ Text_t Int$octal(Int_t i, Int_t digits, bool prefix); PUREFUNC Closure_t Int$to(Int_t first, Int_t last, OptionalInt_t step); PUREFUNC Closure_t Int$onward(Int_t first, Int_t step); OptionalInt_t Int$from_str(const char *str); -OptionalInt_t Int$parse(Text_t text, Text_t *remainder); +OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder); Int_t Int$abs(Int_t x); Int_t Int$power(Int_t base, Int_t exponent); Int_t Int$gcd(Int_t x, Int_t y); diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c index ab689ae4..4416d804 100644 --- a/src/stdlib/bytes.c +++ b/src/stdlib/bytes.c @@ -33,8 +33,8 @@ public CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { return low <= x && x <= high; } public -OptionalByte_t Byte$parse(Text_t text, Text_t *remainder) { - OptionalInt_t full_int = Int$parse(text, remainder); +OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder) { + OptionalInt_t full_int = Int$parse(text, base, remainder); if (full_int.small != 0L && Int$compare_value(full_int, I(0)) >= 0 && Int$compare_value(full_int, I(255)) <= 0) { return (OptionalByte_t){.has_value = true, .value = Byte$from_int(full_int, true)}; } else { diff --git a/src/stdlib/bytes.h b/src/stdlib/bytes.h index 2f948177..6581f300 100644 --- a/src/stdlib/bytes.h +++ b/src/stdlib/bytes.h @@ -18,7 +18,7 @@ Byte_t Byte$from_int(Int_t i, bool truncate); Byte_t Byte$from_int64(int64_t i, bool truncate); Byte_t Byte$from_int32(int32_t i, bool truncate); Byte_t Byte$from_int16(int16_t i, bool truncate); -OptionalByte_t Byte$parse(Text_t text, Text_t *remainder); +OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder); Closure_t Byte$to(Byte_t first, Byte_t last, OptionalInt8_t step); MACROLIKE Byte_t Byte$from_int8(int8_t i) { return (Byte_t)i; } diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 04538796..18da5c5e 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -238,23 +238,23 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty if (parsed.small == 0) print_err("Could not parse argument for ", flag, ": ", arg); *(Int_t *)dest = parsed; } else if (type == &Int64$info) { - OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NULL); + OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NONE_INT, NULL); if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg); *(Int64_t *)dest = parsed.value; } else if (type == &Int32$info) { - OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NULL); + OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NONE_INT, NULL); if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg); *(Int32_t *)dest = parsed.value; } else if (type == &Int16$info) { - OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NULL); + OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NONE_INT, NULL); if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg); *(Int16_t *)dest = parsed.value; } else if (type == &Int8$info) { - OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NULL); + OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NONE_INT, NULL); if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg); *(Int8_t *)dest = parsed.value; } else if (type == &Byte$info) { - OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NULL); + OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NONE_INT, NULL); if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg); *(Byte_t *)dest = parsed.value; } else if (type == &Bool$info) { diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h index 0e665591..0910c7f1 100644 --- a/src/stdlib/intX.c.h +++ b/src/stdlib/intX.c.h @@ -188,8 +188,8 @@ Closure_t NAMESPACED(onward)(INT_T first, INT_T step) { return (Closure_t){.fn = _next_int, .userdata = range}; } public -PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder) { - OptionalInt_t full_int = Int$parse(text, remainder); +PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder) { + OptionalInt_t full_int = Int$parse(text, base, remainder); if (full_int.small == 0L) return (OPT_T){.has_value = false}; if (Int$compare_value(full_int, I(NAMESPACED(min))) < 0) { return (OPT_T){.has_value = false}; diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h index 0f4632c2..1c8b4a05 100644 --- a/src/stdlib/intX.h +++ b/src/stdlib/intX.h @@ -46,7 +46,7 @@ List_t NAMESPACED(bits)(INTX_T x); bool NAMESPACED(get_bit)(INTX_T x, Int_t bit_index); Closure_t NAMESPACED(to)(INTX_T first, INTX_T last, OPT_T step); Closure_t NAMESPACED(onward)(INTX_T first, INTX_T step); -PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder); +PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder); CONSTFUNC bool NAMESPACED(is_between)(const INTX_T x, const INTX_T low, const INTX_T high); CONSTFUNC INTX_T NAMESPACED(clamped)(INTX_T x, INTX_T min, INTX_T max); MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) { return (INTX_T)b; } diff --git a/src/typecheck.c b/src/typecheck.c index 37f4fcab..e432759b 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -14,6 +14,7 @@ #include "naming.h" #include "parse/files.h" #include "parse/types.h" +#include "stdlib/optionals.h" #include "stdlib/paths.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -1659,7 +1660,7 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) { case None: return true; case Int: { DeclareMatch(info, ast, Int); - Int_t int_val = Int$parse(Text$from_str(info->str), NULL); + Int_t int_val = Int$parse(Text$from_str(info->str), NONE_INT, NULL); if (int_val.small == 0) return false; // Failed to parse return (Int$compare_value(int_val, I(BIGGEST_SMALL_INT)) <= 0); } diff --git a/test/integers.tm b/test/integers.tm index 1b1f7569..67175f7a 100644 --- a/test/integers.tm +++ b/test/integers.tm @@ -105,3 +105,24 @@ func main() assert Int64(6).get_bit(2) == yes assert Int64(6).get_bit(3) == yes assert Int64(6).get_bit(4) == no + + assert Int.parse("123") == 123 + assert Int.parse("0x10") == 16 + assert Int.parse("0o10") == 8 + assert Int.parse("0b10") == 2 + assert Int.parse("abc") == none + + assert Int.parse("-123") == -123 + assert Int.parse("-0x10") == -16 + assert Int.parse("-0o10") == -8 + assert Int.parse("-0b10") == -2 + + for base in (2).to(36) + assert Int.parse("10", base=base) == base + + assert Int.parse("111", base=1) == 3 + + assert Int.parse("z", base=36) == 35 + assert Int.parse("Z", base=36) == 35 + assert Int.parse("-z", base=36) == -35 + assert Int.parse("-Z", base=36) == -35 -- cgit v1.2.3 From f7d5a022b3f63b196a9106afba66b7e10fa849ce Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 30 Nov 2025 14:20:10 -0500 Subject: Update makefile rules to reflect how CHANGES.md is used. --- Makefile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ad521916..32192554 100644 --- a/Makefile +++ b/Makefile @@ -167,15 +167,17 @@ src/stdlib/int64.o src/stdlib/int32.o src/stdlib/int16.o src/stdlib/int8.o: src/ # Num implementations depend on the shared header: src/stdlib/num32.o src/stdlib/num64.o: src/stdlib/numX.c.h -# Specifically src/tomo.c needs to recompile if CHANGES.md changes: -src/tomo.o: src/tomo.c src/ast.h src/environment.h src/types.h config.mk src/changes.md.h - @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@ - @$(CC) $(CFLAGS) -c $< -o $@ +# These files all depend on the current tomo version: +src/compile/files.o src/compile/headers.o src/compile/statements.o src/config.o src/environment.o \ + src/modules.o src/stdlib/stacktrace.o src/stdlib/stdlib.o src/tomo.o src/typecheck.o: CHANGES.md src/changes.md.h: CHANGES.md @$(ECHO) "Embedding changes.md" xxd -i $< > $@ +# The main Tomo executable embeds the changelog: +src/tomo.o: src/changes.md.h + %: %.tm ./local-tomo -e $< -- cgit v1.2.3 From a6c7d8ecbd2d67472774ac9600333cfe97454aaf Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Dec 2025 22:47:57 -0500 Subject: Clean up CLI --help output --- src/compile/cli.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compile/cli.c b/src/compile/cli.c index a66fb47d..5130b97b 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -74,16 +74,17 @@ static Text_t generate_usage(env_t *env, type_t *fn_type) { type_t *t = get_arg_type(main_env, arg); OptionalText_t flag = flagify(arg->name, arg->default_val != NULL); assert(flag.tag != TEXT_NONE); - OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL); Text_t flags = Texts("\x1b[1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m"); if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); if (arg->default_val || value_type(t)->tag == BoolType) { if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) usage = Texts(usage, "[", flags, "]"); else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + else if (t->tag == EnumType) usage = Texts(usage, "[", flags, " val]"); else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); + } else if (t->tag == EnumType) { + usage = Texts(usage, "\x1b[1m", flag, "\x1b[m"); } else { usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); } @@ -103,7 +104,7 @@ static Text_t generate_help(env_t *env, type_t *fn_type) { assert(flag.tag != TEXT_NONE); OptionalText_t alias_flag = flagify(arg->alias, true); Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m"); - if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m"); + if (alias_flag.tag != TEXT_NONE) flags = Texts("\x1b[33;1m", alias_flag, "\x1b[0;2m,\x1b[m ", flags); if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m"); if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) @@ -137,7 +138,7 @@ Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_t "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), " "Text$from_str(argv[0]), Text(\" \")", usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n", - "Text_t help = Texts(usage, Text(\"\\n\\n\"", quoted_text(help), "));\n"); + "Text_t help = Texts(usage, Text(\"\\n\"", quoted_text(help), "\"\\n\"));\n"); for (arg_t *arg = fn_info->args; arg; arg = arg->next) { code = Texts(code, compile_declaration(arg->type, Texts("_$", Text$from_str(arg->name))), " = ", -- cgit v1.2.3 From ce26a80bdb61bb8928c49f42bca84c6da4df72c5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Dec 2025 23:43:43 -0500 Subject: Fix CLI arg parsing --- src/stdlib/cli.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index 18da5c5e..e30f7ced 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -325,7 +325,8 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c // Case: --flag values... if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: ", flag); List_t values = List$slice(*args, I(i + 2), I(-1)); - *args = parse_arg_list(values, flag, dest, type, false); + List_t remaining_args = parse_arg_list(values, flag, dest, type, false); + *args = List$concat(List$to(*args, I(i)), remaining_args, sizeof(const char *)); return true; } else if (starts_with(arg + 2, flag) && arg[2 + strlen(flag)] == '=') { // Case: --flag=... @@ -341,7 +342,8 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c } else { values = List(arg_value); } - *args = parse_arg_list(values, flag, dest, type, false); + List_t remaining_args = parse_arg_list(values, flag, dest, type, false); + *args = List$concat(List$to(*args, I(i)), remaining_args, sizeof(const char *)); return true; } } else if (short_flag && arg[0] == '-' && arg[1] != '-' && strchr(arg + 1, short_flag)) { -- cgit v1.2.3 From 129f2c794cd388b99d573697965117e12b89f2fe Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Dec 2025 13:40:20 -0500 Subject: Fix for memory pointer visualization --- src/stdlib/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c index 2ae47c36..fd396463 100644 --- a/src/stdlib/memory.c +++ b/src/stdlib/memory.c @@ -17,7 +17,7 @@ public Text_t Memory$as_text(const void *p, bool colorize, const TypeInfo_t *info) { (void)info; if (!p) return Text("Memory"); - Text_t text = Text$from_str(String("Memory<", *(void **)p, ">")); + Text_t text = Text$from_str(String("Memory<", (void *)p, ">")); return colorize ? Texts(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text; } -- cgit v1.2.3 From a13b39f1e1ea220a868d99508796d06492a40611 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Dec 2025 13:54:37 -0500 Subject: Allow discarding Empty() values --- CHANGES.md | 4 ++++ src/compile/statements.c | 2 -- src/typecheck.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a60c854a..7081a1bf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Version History +## v2025-12-06 + +- You can now discard Empty values. + ## v2025-11-30 ### API changes diff --git a/src/compile/statements.c b/src/compile/statements.c index 638f1341..4bb8b432 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -218,8 +218,6 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { } case Metadata: return EMPTY_TEXT; default: - // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ", - // is_discardable(env, ast)); if (!is_discardable(env, ast)) code_err(ast, "The ", type_to_text(get_type(env, ast)), " result of this statement cannot be discarded"); return Texts("(void)", compile(env, ast), ";"); diff --git a/src/typecheck.c b/src/typecheck.c index e432759b..6f8fa0ba 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1539,7 +1539,7 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { default: break; } type_t *t = get_type(env, ast); - return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); + return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType || t == EMPTY_TYPE); } type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) { -- cgit v1.2.3 From 48491f94c96615e8055bcf72ed9009b1d921467f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Dec 2025 14:55:00 -0500 Subject: Use `foo!` as sugar for `foo.FirstTag!` for enum values. Also, give better error messages for this kind of `!` assertion. --- CHANGES.md | 3 +++ src/ast.h | 6 +++--- src/compile/enums.c | 6 +----- src/compile/optionals.c | 56 ++++++++++++++++++++++++++++++++++++++++++------ src/compile/statements.c | 4 +++- src/typecheck.c | 12 +++++++++-- src/types.c | 4 ++-- 7 files changed, 71 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7081a1bf..8468e4a7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,9 @@ ## v2025-12-06 - You can now discard Empty values. +- For an enum `Foo(A,B,C)`, the syntax `f!` now desugars to `f.A!` using the + first tag defined in the enum. +- Error messages are more helpful for `foo.Whatever!` enum field accessing. ## v2025-11-30 diff --git a/src/ast.h b/src/ast.h index 5307fc2c..b852da2a 100644 --- a/src/ast.h +++ b/src/ast.h @@ -23,9 +23,9 @@ #define LiteralCode(code, ...) \ new (ast_t, .tag = InlineCCode, \ .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, code)), __VA_ARGS__}) -#define WrapLiteralCode(ast, code, ...) \ - new (ast_t, .tag = InlineCCode, .file = (ast)->file, .start = (ast)->start, .end = (ast)->end, \ - .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(ast, TextLiteral, code)), __VA_ARGS__}) +#define WrapLiteralCode(_ast, code, ...) \ + new (ast_t, .tag = InlineCCode, .file = (_ast)->file, .start = (_ast)->start, .end = (_ast)->end, \ + .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(_ast, TextLiteral, code)), __VA_ARGS__}) #define Match(x, _tag) \ ((x)->tag == _tag ? &(x)->__data._tag \ : (errx(1, __FILE__ ":%d This was supposed to be a " #_tag "\n", __LINE__), &(x)->__data._tag)) diff --git a/src/compile/enums.c b/src/compile/enums.c index 56d6432a..853625ca 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -158,11 +158,7 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); if (tag->type != NULL && Match(tag->type, StructType)->fields) { Text_t member = compile_maybe_incref( - env, - WrapAST(ast, InlineCCode, - .chunks = new (ast_list_t, WrapAST(ast, TextLiteral, Texts("_e.", tag->name))), - .type = tag->type), - tag->type); + env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index ffe16248..5ad67602 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -115,12 +115,54 @@ public Text_t compile_non_optional(env_t *env, ast_t *ast) { ast_t *value = Match(ast, NonOptional)->value; if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true); - type_t *t = get_type(env, value); - Text_t value_code = compile(env, value); + type_t *value_t = get_type(env, value); + if (value_t->tag == PointerType) { + // Dereference pointers automatically + return compile_non_optional(env, WrapAST(ast, NonOptional, WrapAST(ast, Index, .indexed = value))); + } int64_t line = get_line_number(ast->file, ast->start); - return Texts( - "({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (", - check_none(t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ", - (int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ", - "\"This was expected to be a value, but it's `none`\\n\");\n", optional_into_nonnone(t, Text("opt")), "; })"); + if (value_t->tag == EnumType) { + // For this case: + // enum Foo(FirstField, SecondField(msg:Text)) + // e := ... + // e! + // We desugar into `e.FirstField!` using the first enum field + tag_t *first_tag = Match(value_t, EnumType)->tags; + if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum"); + return compile_non_optional( + env, WrapAST(ast, NonOptional, WrapAST(value, FieldAccess, .fielded = value, .field = first_tag->name))); + } else if (value->tag == FieldAccess + && value_type(get_type(env, Match(value, FieldAccess)->fielded))->tag == EnumType) { + type_t *enum_t = value_type(get_type(env, Match(value, FieldAccess)->fielded)); + DeclareMatch(e, enum_t, EnumType); + DeclareMatch(f, value, FieldAccess); + for (tag_t *tag = e->tags; tag; tag = tag->next) { + if (streq(f->field, tag->name)) { + Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); + return Texts("({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, true), ";", + "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", + "fail_source(", quoted_str(f->fielded->file->filename), ", ", + (int64_t)(f->fielded->start - f->fielded->file->text), ", ", + (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", + tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), + ", \"\\n\");\n}\n", + Match(tag->type, StructType)->fields + ? compile_maybe_incref( + env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), + tag->type) + : Text("EMPTY_STRUCT"), + "; })"); + } + } + code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t)); + } else { + Text_t value_code = compile(env, value); + return Texts("({ ", compile_declaration(value_t, Text("opt")), " = ", value_code, "; ", "if unlikely (", + check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", + quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ", + (int64_t)(value->end - value->file->text), ", ", + "\"This was expected to be a value, but it's `none`\\n\");\n", + optional_into_nonnone(value_t, Text("opt")), "; })"); + } } diff --git a/src/compile/statements.c b/src/compile/statements.c index 4bb8b432..01fb1a0b 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -219,7 +219,9 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { case Metadata: return EMPTY_TEXT; default: if (!is_discardable(env, ast)) - code_err(ast, "The ", type_to_text(get_type(env, ast)), " result of this statement cannot be discarded"); + code_err( + ast, "The ", type_to_text(get_type(env, ast)), + " value of this statement is implicitly ignored. \n Use `_ := ` if you want to explicitly discard it."); return Texts("(void)", compile(env, ast), ";"); } } diff --git a/src/typecheck.c b/src/typecheck.c index 6f8fa0ba..41de1a6b 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -755,7 +755,12 @@ type_t *get_type(env_t *env, ast_t *ast) { } case NonOptional: { ast_t *value = Match(ast, NonOptional)->value; - type_t *t = get_type(env, value); + type_t *t = value_type(get_type(env, value)); + if (t->tag == EnumType) { + tag_t *first_tag = Match(t, EnumType)->tags; + if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum"); + return first_tag->type; + } if (t->tag != OptionalType) code_err(value, "This value is not optional. Only optional values can use the '!' operator."); return Match(t, OptionalType)->type; @@ -1539,7 +1544,10 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { default: break; } type_t *t = get_type(env, ast); - return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType || t == EMPTY_TYPE); + if (t->tag == StructType) { + return (Match(t, StructType)->fields == NULL); + } + return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); } type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) { diff --git a/src/types.c b/src/types.c index 6ed24f6c..b34d8bf9 100644 --- a/src/types.c +++ b/src/types.c @@ -63,7 +63,7 @@ Text_t type_to_text(type_t *t) { } case StructType: { DeclareMatch(struct_, t, StructType); - return Text$from_str(struct_->name); + return Text$replace(Text$from_str(struct_->name), Text("$"), Text(".")); } case PointerType: { DeclareMatch(ptr, t, PointerType); @@ -73,7 +73,7 @@ Text_t type_to_text(type_t *t) { case EnumType: { DeclareMatch(enum_, t, EnumType); if (enum_->name != NULL && strncmp(enum_->name, "enum$", strlen("enum$")) != 0) - return Text$from_str(enum_->name); + return Text$replace(Text$from_str(enum_->name), Text("$"), Text(".")); Text_t text = Text("enum("); for (tag_t *tag = enum_->tags; tag; tag = tag->next) { text = Texts(text, Text$from_str(tag->name)); -- cgit v1.2.3 From 43b0a91664fc0b9a5222805c68c3505fd9634689 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 16:14:18 -0500 Subject: Remove debug code --- src/naming.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/naming.c b/src/naming.c index 9c0f09e4..d7bb0fb9 100644 --- a/src/naming.c +++ b/src/naming.c @@ -94,16 +94,12 @@ Text_t valid_c_name(const char *name) { return Text$from_str(name); } -#include "stdlib/stdlib.h" public Text_t CONSTFUNC namespace_name(env_t *env, namespace_t *ns, Text_t name) { - if (Text$has(name, Text("\n"))) fail("WTF??"); for (; ns; ns = ns->parent) { - if (strchr(ns->name, '\n')) fail("WTF"); name = Texts(ns->name, "$", name); } if (env->id_suffix.length > 0) name = Texts(name, env->id_suffix); - if (Text$has(env->id_suffix, Text("\n"))) fail("WTF?????"); return name; } -- cgit v1.2.3 From 85d1507d8b7e0139135e040b7b4b23c02097a155 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 18:44:58 -0500 Subject: Consolidate logic for enums with and without tags with fields. --- CHANGES.md | 2 ++ src/compile/enums.c | 48 +++++++------------------------------- src/compile/headers.c | 61 +++++++++++++++---------------------------------- src/compile/optionals.c | 30 ++++++++++-------------- src/compile/whens.c | 8 ++----- src/typecheck.c | 17 ++------------ src/types.c | 10 +------- src/types.h | 1 - test/enums.tm | 6 ++--- 9 files changed, 48 insertions(+), 135 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8468e4a7..3e94c85d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ - For an enum `Foo(A,B,C)`, the syntax `f!` now desugars to `f.A!` using the first tag defined in the enum. - Error messages are more helpful for `foo.Whatever!` enum field accessing. +- Simplified logic for enums so there is less difference between enums that + have tags with member fields and those without. ## v2025-11-30 diff --git a/src/compile/enums.c b/src/compile/enums.c index 853625ca..24da79b8 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -12,8 +12,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { // Compile member types and constructors: Text_t member_typeinfos = EMPTY_TEXT; for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - const char *tag_name = String(name, "$", tag->name); type_t *tag_type = Table$str_get(*env->types, tag_name); assert(tag_type && tag_type->tag == StructType); @@ -38,9 +36,7 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { for (tag_ast_t *tag = tags; tag; tag = tag->next) { const char *tag_type_name = String(name, "$", tag->name); type_t *tag_type = Table$str_get(*env->types, tag_type_name); - if (tag_type && Match(tag_type, StructType)->fields) - typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, "); - else typeinfo = Texts(typeinfo, "{\"", tag->name, "\"}, "); + typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, "); } typeinfo = Texts(typeinfo, "}}}};\n"); return Texts(member_typeinfos, typeinfo); @@ -49,8 +45,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) { Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) { Text_t constructors = EMPTY_TEXT; for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t arg_sig = EMPTY_TEXT; for (arg_ast_t *field = tag->fields; field; field = field->next) { type_t *field_t = get_arg_ast_type(env, field); @@ -76,25 +70,16 @@ Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { Text_t all_defs = EMPTY_TEXT; Text_t none_name = namespace_name(env, env->namespace, Texts(name, "$none")); - Text_t enum_name = namespace_name(env, env->namespace, Texts(name, "$$enum")); Text_t enum_tags = Texts("{ ", none_name, "=0, "); assert(Table$str_get(*env->types, name)); - bool has_any_tags_with_fields = false; for (tag_ast_t *tag = tags; tag; tag = tag->next) { Text_t tag_name = namespace_name(env, env->namespace, Texts(name, "$tag$", tag->name)); enum_tags = Texts(enum_tags, tag_name); if (tag->next) enum_tags = Texts(enum_tags, ", "); - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); } enum_tags = Texts(enum_tags, " }"); - if (!has_any_tags_with_fields) { - Text_t enum_def = Texts("enum ", enum_name, " ", enum_tags, ";\n"); - Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info")); - return Texts(enum_def, "extern const TypeInfo_t ", info, ";\n"); - } - Text_t struct_name = namespace_name(env, env->namespace, Texts(name, "$$struct")); Text_t enum_def = Texts("struct ", struct_name, " {\n" @@ -103,7 +88,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { " $tag;\n" "union {\n"); for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; Text_t field_def = compile_struct_header(env, NewAST(tag->file, tag->start, tag->end, StructDef, .name = Text$as_c_string(Texts(name, "$", tag->name)), .fields = tag->fields)); @@ -117,8 +101,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) { Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info")); all_defs = Texts(all_defs, "extern const TypeInfo_t ", info, ";\n"); for (tag_ast_t *tag = tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t arg_sig = EMPTY_TEXT; for (arg_ast_t *field = tag->fields; field; field = field->next) { type_t *field_t = get_arg_ast_type(env, field); @@ -140,11 +122,8 @@ Text_t compile_empty_enum(type_t *t) { tag_t *tag = enum_->tags; assert(tag); assert(tag->type); - if (Match(tag->type, StructType)->fields) - return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), - "})"); - else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, "})"); - else return Texts("((", compile_type(t), ")", tag->tag_value, ")"); + return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), + "})"); } public @@ -156,22 +135,11 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - if (tag->type != NULL && Match(tag->type, StructType)->fields) { - Text_t member = compile_maybe_incref( - env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); - return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", - compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", - promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); - } else if (fielded_t->tag == PointerType) { - Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false); - return Texts("((", fielded, ")->$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } else if (enum_has_fields(value_t)) { - Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ").$tag == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } else { - Text_t fielded = compile(env, f->fielded); - return Texts("((", fielded, ") == ", tag_name, " ? OPTIONAL_EMPTY_STRUCT : NONE_EMPTY_STRUCT)"); - } + Text_t member = + compile_maybe_incref(env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type); + return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ", + promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })"); } } code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(value_t)); diff --git a/src/compile/headers.c b/src/compile/headers.c index 1dcf7abb..dc57da77 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -79,28 +79,16 @@ static void _make_typedefs(compile_typedef_info_t *info, ast_t *ast) { *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); } else if (ast->tag == EnumDef) { DeclareMatch(def, ast, EnumDef); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); - } + Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct")); + Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - if (has_any_tags_with_fields) { - Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - - for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t tag_struct = - namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct")); - Text_t tag_type = - namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); - } - } else { - Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$enum")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type")); - *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n"); + for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { + Text_t tag_struct = + namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct")); + Text_t tag_type = + namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } } else if (ast->tag == LangDef) { DeclareMatch(def, ast, LangDef); @@ -124,29 +112,16 @@ static void add_type_headers(type_ast_t *type_ast, void *userdata) { // Force the type to get defined: (void)parse_type_ast(info->env, type_ast); DeclareMatch(enum_, type_ast, EnumTypeAST); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { - has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); - } - const char *name = String("enum$", (int64_t)(type_ast->start - type_ast->file->text)); - if (has_any_tags_with_fields) { - Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); - - for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { - if (!tag->fields) continue; - Text_t tag_struct = - namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct")); - Text_t tag_type = - namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type")); - *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); - } - } else { - Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$enum")); - Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); - *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n"); + Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct")); + Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); + + for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) { + Text_t tag_struct = + namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct")); + Text_t tag_type = namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type")); + *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } *info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags)); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 5ad67602..b9a3742e 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -103,10 +103,7 @@ Text_t check_none(type_t *t, Text_t value) { else if (t->tag == BoolType) return Texts("((", value, ") == NONE_BOOL)"); else if (t->tag == TextType) return Texts("((", value, ").tag == TEXT_NONE)"); else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) return Texts("!(", value, ").has_value"); - else if (t->tag == EnumType) { - if (enum_has_fields(t)) return Texts("((", value, ").$tag == 0)"); - else return Texts("((", value, ") == 0)"); - } + else if (t->tag == EnumType) return Texts("((", value, ").$tag == 0)"); print_err("Optional check not implemented for: ", type_to_text(t)); return EMPTY_TEXT; } @@ -139,20 +136,17 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name)); - return Texts("({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", - compile_to_pointer_depth(env, f->fielded, 0, true), ";", - "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", - "fail_source(", quoted_str(f->fielded->file->filename), ", ", - (int64_t)(f->fielded->start - f->fielded->file->text), ", ", - (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", - tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), - ", \"\\n\");\n}\n", - Match(tag->type, StructType)->fields - ? compile_maybe_incref( - env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), - tag->type) - : Text("EMPTY_STRUCT"), - "; })"); + return Texts( + "({ ", compile_declaration(enum_t, Text("_test_enum")), " = ", + compile_to_pointer_depth(env, f->fielded, 0, true), ";", + "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(", + quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text), + ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", + tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), + ", \"\\n\");\n}\n", + compile_maybe_incref( + env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type), + "; })"); } } code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t)); diff --git a/src/compile/whens.c b/src/compile/whens.c index 122c581c..618a667c 100644 --- a/src/compile/whens.c +++ b/src/compile/whens.c @@ -43,11 +43,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { DeclareMatch(enum_t, subject_t, EnumType); - Text_t code; - if (enum_has_fields(subject_t)) - code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); - else code = Texts("switch(", compile(env, when->subject), ") {\n"); - + Text_t code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n"); for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { if (clause->pattern->tag == Var) { const char *clause_tag_name = Match(clause->pattern, Var)->name; @@ -132,7 +128,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) { } else { code = Texts(code, "default: errx(1, \"Invalid tag!\");\n"); } - code = Texts(code, "\n}", enum_has_fields(subject_t) ? Text(")") : EMPTY_TEXT, "\n"); + code = Texts(code, "\n}", Text(")"), "\n"); return code; } diff --git a/src/typecheck.c b/src/typecheck.c index 41de1a6b..af726a14 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -125,11 +125,6 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { Table$str_set(env->types, enum_name, enum_type); - bool has_any_tags_with_fields = false; - for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) { - has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields; - } - for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) { arg_t *fields = NULL; for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) { @@ -151,14 +146,11 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { set_binding(ns_env, tag_ast->name, constructor_t, tagged_name); binding_t binding = {.type = constructor_t, .code = tagged_name}; List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding)); - } else if (has_any_tags_with_fields) { // Empty singleton value: + } else { // Empty singleton value: Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(enum_name, "$$type")), "){", namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)), "})"); set_binding(ns_env, tag_ast->name, enum_type, code); - } else { - Text_t code = namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)); - set_binding(ns_env, tag_ast->name, enum_type, code); } Table$str_set(env->types, String(enum_name, "$", tag_ast->name), tag_type); @@ -441,9 +433,7 @@ void bind_statement(env_t *env, ast_t *statement) { ns_env->current_type = type; tag_t *tags = NULL; int64_t next_tag = 1; - bool has_any_tags_with_fields = false; for (tag_ast_t *tag_ast = def->tags; tag_ast; tag_ast = tag_ast->next) { - has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields; arg_t *fields = NULL; for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) { type_t *field_t = get_arg_ast_type(env, field_ast); @@ -496,13 +486,10 @@ void bind_statement(env_t *env, ast_t *statement) { set_binding(ns_env, tag->name, constructor_t, tagged_name); binding_t binding = {.type = constructor_t, .code = tagged_name}; List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding)); - } else if (has_any_tags_with_fields) { // Empty singleton value: + } else { // Empty singleton value: Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(def->name, "$$type")), "){", namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)), "})"); set_binding(ns_env, tag->name, type, code); - } else { - Text_t code = namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)); - set_binding(ns_env, tag->name, type, code); } Table$str_set(env->types, String(def->name, "$", tag->name), tag->type); } diff --git a/src/types.c b/src/types.c index b34d8bf9..e3bf326c 100644 --- a/src/types.c +++ b/src/types.c @@ -701,8 +701,7 @@ type_t *get_field_type(type_t *t, const char *field_name) { DeclareMatch(e, t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { if (!streq(field_name, tag->name)) continue; - if (tag->type != NULL && Match(tag->type, StructType)->fields) return Type(OptionalType, tag->type); - else return Type(OptionalType, EMPTY_TYPE); + return Type(OptionalType, tag->type); } return NULL; } @@ -835,10 +834,3 @@ type_t *_make_function_type(type_t *ret, int n, arg_t args[n]) { } return Type(FunctionType, .ret = ret, .args = &arg_pointers[0]); } - -PUREFUNC bool enum_has_fields(type_t *t) { - for (tag_t *e_tag = Match(t, EnumType)->tags; e_tag; e_tag = e_tag->next) { - if (e_tag->type != NULL && Match(e_tag->type, StructType)->fields) return true; - } - return false; -} diff --git a/src/types.h b/src/types.h index 76632c66..66e6ba12 100644 --- a/src/types.h +++ b/src/types.h @@ -166,4 +166,3 @@ PUREFUNC type_t *non_optional(type_t *t); type_t *get_field_type(type_t *t, const char *field_name); PUREFUNC type_t *get_iterated_type(type_t *t); type_t *_make_function_type(type_t *ret, int n, arg_t args[n]); -PUREFUNC bool enum_has_fields(type_t *t); diff --git a/test/enums.tm b/test/enums.tm index 80f66ed8..aab1b234 100644 --- a/test/enums.tm +++ b/test/enums.tm @@ -98,17 +98,17 @@ func main() do e := OnlyTags.A - assert e.A == EMPTY + assert e.A == OnlyTags.A.A assert e.B == none do e := Foo.Zero - assert e.Zero == EMPTY + assert e.Zero == Foo.Zero.Zero assert e.One == none assert e.Two == none ep := @Foo.Zero - assert ep.Zero == EMPTY + assert ep.Zero == Foo.Zero.Zero assert ep.One == none assert ep.Two == none -- cgit v1.2.3 From 09942b27ae6fd7e7d394b2d251ef77c8a6f69e07 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 19:02:13 -0500 Subject: Rename `Empty()` -> `Present()` for set-like tables --- CHANGES.md | 3 ++- src/compile/cli.c | 2 +- src/compile/lists.c | 2 +- src/compile/tables.c | 10 +++++----- src/environment.c | 8 ++++---- src/environment.h | 2 +- src/stdlib/datatypes.h | 14 +++++++------- src/stdlib/structs.c | 11 ----------- src/stdlib/structs.h | 2 -- src/stdlib/tables.c | 12 ++++++++++++ src/stdlib/tables.h | 2 ++ src/typecheck.c | 10 +++++----- src/types.c | 2 +- 13 files changed, 41 insertions(+), 39 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3e94c85d..4689e5aa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,12 +2,13 @@ ## v2025-12-06 -- You can now discard Empty values. +- You can now discard empty struct values. - For an enum `Foo(A,B,C)`, the syntax `f!` now desugars to `f.A!` using the first tag defined in the enum. - Error messages are more helpful for `foo.Whatever!` enum field accessing. - Simplified logic for enums so there is less difference between enums that have tags with member fields and those without. +- Rename `Empty()` to `Present()` for set-like tables. ## v2025-11-30 diff --git a/src/compile/cli.c b/src/compile/cli.c index 5130b97b..63a467ca 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -38,7 +38,7 @@ static Text_t get_flag_options(type_t *t, Text_t separator) { } else if (t->tag == ListType) { Text_t item_option = get_flag_options(Match(t, ListType)->item_type, separator); return Texts(item_option, "1 ", item_option, "2..."); - } else if (t->tag == TableType && Match(t, TableType)->value_type == EMPTY_TYPE) { + } else if (t->tag == TableType && Match(t, TableType)->value_type == PRESENT_TYPE) { Text_t item_option = get_flag_options(Match(t, TableType)->key_type, separator); return Texts(item_option, "1 ", item_option, "2..."); } else if (t->tag == TableType) { diff --git a/src/compile/lists.c b/src/compile/lists.c index 97b0b85d..31255c1e 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -254,7 +254,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), ", &Empty$$info))"); + return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Present$$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/compile/tables.c b/src/compile/tables.c index 54276c3b..e624f9fb 100644 --- a/src/compile/tables.c +++ b/src/compile/tables.c @@ -13,7 +13,7 @@ static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) { return WrapAST( entry, MethodCall, .name = "set", .self = subject, .args = new (arg_ast_t, .value = e->key, - .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "EMPTY")))); + .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "PRESENT")))); } Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { @@ -51,10 +51,10 @@ Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { for (ast_list_t *entry = table->entries; entry; entry = entry->next) { DeclareMatch(e, entry->ast, TableEntry); - code = Texts( - code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ", - compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "EMPTY"), value_t), - "}"); + code = Texts(code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ", + compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "PRESENT"), + value_t), + "}"); } return Texts(code, ")"); } diff --git a/src/environment.c b/src/environment.c index 7e04fe79..c5932e30 100644 --- a/src/environment.c +++ b/src/environment.c @@ -19,7 +19,7 @@ type_t *PATH_TYPE = NULL; public type_t *PATH_TYPE_TYPE = NULL; public -type_t *EMPTY_TYPE = NULL; +type_t *PRESENT_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { ast_t *ast = parse_file_str(def_str); @@ -70,7 +70,7 @@ env_t *global_env(bool source_mapping) { PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)"); PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])"); - EMPTY_TYPE = declare_type(env, "struct Empty()"); + PRESENT_TYPE = declare_type(env, "struct Present()"); typedef struct { const char *name, *code, *type_str; @@ -90,7 +90,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Void", Type(VoidType), Text("void"), Text("Void$info")), MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")), MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")), - MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")), + MAKE_TYPE("Present", PRESENT_TYPE, Text("Present$$type"), Text("Present$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}), @@ -530,7 +530,7 @@ env_t *global_env(bool source_mapping) { struct { const char *name, *code, *type_str; } global_vars[] = { - {"EMPTY", "EMPTY", "Empty"}, + {"PRESENT", "PRESENT", "Present"}, {"TOMO_VERSION", "TOMO_VERSION_TEXT", "Text"}, {"USE_COLOR", "USE_COLOR", "Bool"}, {"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text?)"}, diff --git a/src/environment.h b/src/environment.h index 6389cc7a..f48271b5 100644 --- a/src/environment.h +++ b/src/environment.h @@ -87,4 +87,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; extern type_t *PATH_TYPE_TYPE; -extern type_t *EMPTY_TYPE; +extern type_t *PRESENT_TYPE; diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index d51db8ab..fbd2ce73 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -69,18 +69,18 @@ typedef struct table_s { struct table_s *fallback; } Table_t; -typedef struct Empty$$struct { -} Empty$$type; +typedef struct Present$$struct { +} Present$$type; -#define EMPTY_STRUCT ((Empty$$type){}) +#define PRESENT_STRUCT ((Present$$type){}) typedef struct { bool has_value; - Empty$$type value; -} $OptionalEmpty$$type; + Present$$type value; +} $OptionalPresent$$type; -#define NONE_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = false}) -#define OPTIONAL_EMPTY_STRUCT (($OptionalEmpty$$type){.has_value = true}) +#define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false}) +#define OPTIONAL_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = true}) typedef struct { void *fn, *userdata; diff --git a/src/stdlib/structs.c b/src/stdlib/structs.c index 89b5581b..da8f1461 100644 --- a/src/stdlib/structs.c +++ b/src/stdlib/structs.c @@ -215,14 +215,3 @@ void Struct$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo } } } - -public -const TypeInfo_t Empty$$info = {.size = 0, - .align = 0, - .tag = StructInfo, - .metamethods = Struct$metamethods, - .StructInfo.name = "Empty", - .StructInfo.num_fields = 0}; - -public -const Empty$$type EMPTY = {}; diff --git a/src/stdlib/structs.h b/src/stdlib/structs.h index 83904377..a582e9fb 100644 --- a/src/stdlib/structs.h +++ b/src/stdlib/structs.h @@ -15,8 +15,6 @@ PUREFUNC bool PackedData$equal(const void *x, const void *y, const TypeInfo_t *t PUREFUNC Text_t Struct$as_text(const void *obj, bool colorize, const TypeInfo_t *type); void Struct$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); void Struct$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *type); -extern const TypeInfo_t Empty$$info; -extern const Empty$$type EMPTY; #define Struct$metamethods \ { \ diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index 6e774c53..a801957f 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -18,6 +18,7 @@ #include "metamethods.h" #include "pointers.h" #include "siphash.h" +#include "structs.h" #include "tables.h" #include "text.h" #include "types.h" @@ -757,3 +758,14 @@ void Table$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_ *(Table_t *)outval = t; } + +public +const TypeInfo_t Present$$info = {.size = 0, + .align = 0, + .tag = StructInfo, + .metamethods = Struct$metamethods, + .StructInfo.name = "Present", + .StructInfo.num_fields = 0}; + +public +const Present$$type PRESENT = {}; diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h index cc2b3b91..cf1c3625 100644 --- a/src/stdlib/tables.h +++ b/src/stdlib/tables.h @@ -130,6 +130,8 @@ void Table$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_ #define Table$length(t) ((t).entries.length) +extern const TypeInfo_t Present$$info; +extern const Present$$type PRESENT; extern const TypeInfo_t CStrToVoidStarTable; #define Table$metamethods \ diff --git a/src/typecheck.c b/src/typecheck.c index af726a14..139f0655 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -74,7 +74,7 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) { if (has_stack_memory(key_type)) code_err(key_type_ast, "Tables can't have stack references because the list may outlive the stack frame."); - type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : EMPTY_TYPE; + type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : PRESENT_TYPE; if (!val_type) code_err(ast, "I can't figure out what the value type for this entry is."); if (table_type->value && has_stack_memory(val_type)) @@ -809,7 +809,7 @@ type_t *get_type(env_t *env, ast_t *ast) { DeclareMatch(e, entry_ast, TableEntry); type_t *key_t = get_type(scope, e->key); - type_t *value_t = e->value ? get_type(scope, e->value) : EMPTY_TYPE; + type_t *value_t = e->value ? get_type(scope, e->value) : PRESENT_TYPE; type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t; if (!key_merged) ambiguous_key_type = true; @@ -842,7 +842,7 @@ type_t *get_type(env_t *env, ast_t *ast) { } else if (comp->expr->tag == TableEntry) { DeclareMatch(e, comp->expr, TableEntry); return Type(TableType, .key_type = get_type(scope, e->key), - .value_type = e->value ? get_type(scope, e->value) : EMPTY_TYPE, .env = env); + .value_type = e->value ? get_type(scope, e->value) : PRESENT_TYPE, .env = env); } else { return Type(ListType, .item_type = get_type(scope, comp->expr)); } @@ -964,7 +964,7 @@ type_t *get_type(env_t *env, ast_t *ast) { 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(TableType, .key_type = item_type, .value_type = EMPTY_TYPE); + return Type(TableType, .key_type = item_type, .value_type = PRESENT_TYPE); else code_err(ast, "There is no '", call->name, "' method for lists"); } case TableType: { @@ -1718,7 +1718,7 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { if (entry->ast->tag != TableEntry) continue; // TODO: fix this DeclareMatch(e, entry->ast, TableEntry); if (!can_compile_to_type(env, e->key, key_type) - || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, EMPTY_TYPE))) + || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, PRESENT_TYPE))) return false; } return true; diff --git a/src/types.c b/src/types.c index e3bf326c..5c52992b 100644 --- a/src/types.c +++ b/src/types.c @@ -47,7 +47,7 @@ Text_t type_to_text(type_t *t) { } case TableType: { DeclareMatch(table, t, TableType); - return (table->value_type && table->value_type != EMPTY_TYPE) + return (table->value_type && table->value_type != PRESENT_TYPE) ? Texts("{", type_to_text(table->key_type), ":", type_to_text(table->value_type), "}") : Texts("{", type_to_text(table->key_type), "}"); } -- cgit v1.2.3 From efbdf7b13e4b559958ed5b1b9ca9d772ae77d702 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 19:14:41 -0500 Subject: Document sets --- docs/tables.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/tables.md b/docs/tables.md index 00e3e8c0..12b2eb35 100644 --- a/docs/tables.md +++ b/docs/tables.md @@ -136,6 +136,31 @@ Table iteration operates over the value of the table when the loop began, so modifying the table during iteration is safe and will not result in the loop iterating over any of the new values. +## Sets + +For an interface similar to Python's Sets, Tomo tables can be used with an +empty struct as its value type. For convenience, if a value or value is +omitted, Tomo will assign a default value type of `struct Present()` (an empty +struct). This way, the values stored in the table take up no space, but you +still have an easy way to represent Set-like data. + +```tomo +nums := {10, 20, 30, 10} +assert nums.items == [10, 20, 30] +assert nums[10] == Present() +assert nums[99] == none +``` + +The following set-theoretic operations are available for tables: + +- Set union: (AKA `or`) `{10, 20, 30}.with({30, 40})` -> `{10, 20, 30, 40}` +- Set intersection (AKA `and`) `{10, 20, 30}.intersection({30, 40})` -> `{10, + 20, 30, 40}` +- Set difference (AKA, `xor`, disjunctive union, symmetric difference) `{10, + 20, 30}.difference({30, 40})` -> `{10, 20, 40}` +- Set subtraction (AKA, `-`, asymmetric difference) `{10, 20, 30}.without({30, + 40})` -> `{10, 20}` + # API [API documentation](../api/tables.md) -- cgit v1.2.3 From 544b1fb6a70d55bf368b827136cf0f37a26e8288 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 21:42:45 -0500 Subject: Change Paths to be an enum of their different types. --- CHANGES.md | 1 + src/compile/expressions.c | 1 - src/compile/optionals.c | 11 +-- src/compile/types.c | 3 - src/environment.c | 13 +-- src/environment.h | 1 - src/stdlib/datatypes.h | 42 ++++++++- src/stdlib/optionals.h | 2 +- src/stdlib/paths.c | 226 ++++++++++++++++++++++------------------------ src/stdlib/paths.h | 7 +- src/tomo.c | 3 +- src/types.c | 2 - test/paths.tm | 10 +- 13 files changed, 167 insertions(+), 155 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4689e5aa..1baafc87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ - Simplified logic for enums so there is less difference between enums that have tags with member fields and those without. - Rename `Empty()` to `Present()` for set-like tables. +- Paths are now an `enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))` ## v2025-11-30 diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 8b339352..c3918de3 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -57,7 +57,6 @@ Text_t compile_empty(type_t *t) { if (t->tag == OptionalType) return compile_none(t); if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_ABSOLUTE"); switch (t->tag) { case BigIntType: return Text("I(0)"); diff --git a/src/compile/optionals.c b/src/compile/optionals.c index b9a3742e..83f387e2 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -15,18 +15,14 @@ Text_t optional_into_nonnone(type_t *t, Text_t value) { switch (t->tag) { case IntType: case ByteType: return Texts(value, ".value"); - case StructType: - if (t == PATH_TYPE || t == PATH_TYPE_TYPE) return value; - return Texts(value, ".value"); + case StructType: return Texts(value, ".value"); default: return value; } } public Text_t promote_to_optional(type_t *t, Text_t code) { - if (t == PATH_TYPE || t == PATH_TYPE_TYPE) { - return code; - } else if (t->tag == IntType) { + if (t->tag == IntType) { switch (Match(t, IntType)->bits) { case TYPE_IBITS8: return Texts("((OptionalInt8_t){.has_value=true, .value=", code, "})"); case TYPE_IBITS16: return Texts("((OptionalInt16_t){.has_value=true, .value=", code, "})"); @@ -53,7 +49,6 @@ Text_t compile_none(type_t *t) { if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type"); if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_NONE"); switch (t->tag) { case BigIntType: return Text("NONE_INT"); @@ -92,8 +87,6 @@ Text_t check_none(type_t *t, Text_t value) { // NOTE: these use statement expressions ({...;}) because some compilers // complain about excessive parens around equality comparisons if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) return Texts("(", value, " == NULL)"); - else if (t == PATH_TYPE) return Texts("((", value, ").type == PATHTYPE_NONE)"); - else if (t == PATH_TYPE_TYPE) return Texts("((", value, ") == PATHTYPE_NONE)"); else if (t->tag == BigIntType) return Texts("((", value, ").small == 0)"); else if (t->tag == ClosureType) return Texts("((", value, ").fn == NULL)"); else if (t->tag == NumType) diff --git a/src/compile/types.c b/src/compile/types.c index 2b345b41..aac27f4c 100644 --- a/src/compile/types.c +++ b/src/compile/types.c @@ -12,7 +12,6 @@ public Text_t compile_type(type_t *t) { if (t == PATH_TYPE) return Text("Path_t"); - else if (t == PATH_TYPE_TYPE) return Text("PathType_t"); switch (t->tag) { case ReturnType: errx(1, "Shouldn't be compiling ReturnType to a type"); @@ -73,7 +72,6 @@ Text_t compile_type(type_t *t) { case TableType: return Texts("Optional", compile_type(nonnull)); case StructType: { if (nonnull == PATH_TYPE) return Text("OptionalPath_t"); - if (nonnull == PATH_TYPE_TYPE) return Text("OptionalPathType_t"); DeclareMatch(s, nonnull, StructType); return namespace_name(s->env, s->env->namespace->parent, Texts("$Optional", s->name, "$$type")); } @@ -90,7 +88,6 @@ public Text_t compile_type_info(type_t *t) { if (t == NULL) compiler_err(NULL, NULL, NULL, "Attempt to compile a NULL type"); if (t == PATH_TYPE) return Text("&Path$info"); - else if (t == PATH_TYPE_TYPE) return Text("&PathType$info"); switch (t->tag) { case BoolType: diff --git a/src/environment.c b/src/environment.c index c5932e30..bdbc8a4f 100644 --- a/src/environment.c +++ b/src/environment.c @@ -17,8 +17,6 @@ type_t *TEXT_TYPE = NULL; public type_t *PATH_TYPE = NULL; public -type_t *PATH_TYPE_TYPE = NULL; -public type_t *PRESENT_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { @@ -67,8 +65,9 @@ env_t *global_env(bool source_mapping) { (void)bind_type(env, "Int", Type(BigIntType)); (void)bind_type(env, "Int32", Type(IntType, .bits = TYPE_IBITS32)); (void)bind_type(env, "Memory", Type(MemoryType)); - PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)"); - PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])"); + PATH_TYPE = declare_type( + env, + "enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))"); PRESENT_TYPE = declare_type(env, "struct Present()"); @@ -291,11 +290,6 @@ env_t *global_env(bool source_mapping) { #undef F_opt #undef F #undef C - MAKE_TYPE( // - "PathType", PATH_TYPE_TYPE, Text("PathType_t"), Text("PathType$info"), // - {"Relative", "PATHTYPE_RELATIVE", "PathType"}, // - {"Absolute", "PATHTYPE_ABSOLUTE", "PathType"}, // - {"Home", "PATHTYPE_HOME", "PathType"}), MAKE_TYPE( // "Path", PATH_TYPE, Text("Path_t"), Text("Path$info"), // {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // @@ -310,6 +304,7 @@ env_t *global_env(bool source_mapping) { {"child", "Path$child", "func(path:Path, child:Text -> Path)"}, // {"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, // + {"components", "Path$components", "func(path:Path -> [Text])"}, // {"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, // {"current_dir", "Path$current_dir", "func(->Path)"}, // diff --git a/src/environment.h b/src/environment.h index f48271b5..88d36d42 100644 --- a/src/environment.h +++ b/src/environment.h @@ -86,5 +86,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); #define code_err(ast, ...) compiler_err((ast)->file, (ast)->start, (ast)->end, __VA_ARGS__) extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; -extern type_t *PATH_TYPE_TYPE; extern type_t *PRESENT_TYPE; diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index fbd2ce73..49c4d835 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -75,8 +75,8 @@ typedef struct Present$$struct { #define PRESENT_STRUCT ((Present$$type){}) typedef struct { - bool has_value; Present$$type value; + bool has_value; } $OptionalPresent$$type; #define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false}) @@ -111,14 +111,46 @@ typedef struct Text_s { }; } Text_t; -typedef enum PathEnum { PATHTYPE_NONE, PATHTYPE_RELATIVE, PATHTYPE_ABSOLUTE, PATHTYPE_HOME } PathType_t; -#define OptionalPathType_t PathType_t +typedef struct Path$AbsolutePath$$struct { + List_t components; +} Path$AbsolutePath$$type; typedef struct { - PathType_t type; + Path$AbsolutePath$$type value; + bool has_value; +} $OptionalPath$AbsolutePath$$type; + +typedef struct Path$RelativePath$$struct { + List_t components; +} Path$RelativePath$$type; + +typedef struct { + Path$RelativePath$$type value; + bool has_value; +} $OptionalPath$RelativePath$$type; + +typedef struct Path$HomePath$$struct { List_t components; +} Path$HomePath$$type; + +typedef struct { + Path$HomePath$$type value; + bool has_value; +} $OptionalPath$HomePath$$type; + +#define Path$tagged$AbsolutePath(comps) ((Path_t){.$tag = Path$tag$AbsolutePath, .AbsolutePath.components = comps}) +#define Path$tagged$RelativePath(comps) ((Path_t){.$tag = Path$tag$RelativePath, .RelativePath.components = comps}) +#define Path$tagged$HomePath(comps) ((Path_t){.$tag = Path$tag$HomePath, .HomePath.components = comps}) + +typedef struct { + enum { Path$tag$none, Path$tag$AbsolutePath, Path$tag$RelativePath, Path$tag$HomePath } $tag; + union { + Path$RelativePath$$type RelativePath; + Path$AbsolutePath$$type AbsolutePath; + Path$HomePath$$type HomePath; + List_t components; + }; } Path_t; -#define OptionalPath_t Path_t #define OptionalBool_t uint8_t #define OptionalList_t List_t diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h index d067ec94..985d6611 100644 --- a/src/stdlib/optionals.h +++ b/src/stdlib/optionals.h @@ -15,7 +15,7 @@ #define NONE_TABLE ((OptionalTable_t){.entries.data = NULL}) #define NONE_CLOSURE ((OptionalClosure_t){.fn = NULL}) #define NONE_TEXT ((OptionalText_t){.tag = TEXT_NONE}) -#define NONE_PATH ((Path_t){.type = PATHTYPE_NONE}) +#define NONE_PATH ((Path_t){.$tag = Path$tag$none}) PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type); PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type); diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 6c99cf0b..9fb37ee3 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -29,11 +29,8 @@ #include "types.h" #include "util.h" -// Use inline version of the siphash code for performance: -#include "siphash-internals.h" - -static const Path_t HOME_PATH = {.type = PATHTYPE_HOME}, ROOT_PATH = {.type = PATHTYPE_ABSOLUTE}, - CURDIR_PATH = {.type = PATHTYPE_RELATIVE}; +static const Path_t HOME_PATH = Path$tagged$HomePath(EMPTY_LIST), ROOT_PATH = Path$tagged$AbsolutePath(EMPTY_LIST), + CURDIR_PATH = Path$tagged$RelativePath(EMPTY_LIST); static void clean_components(List_t *components) { for (int64_t i = 0; i < (int64_t)components->length;) { @@ -62,40 +59,41 @@ Path_t Path$from_str(const char *str) { if (strchr(str, ';') != NULL) fail("Path has illegal character (semicolon): ", str); - Path_t result = {.components = {}}; + Path_t result = {}; if (str[0] == '/') { - result.type = PATHTYPE_ABSOLUTE; + result.$tag = Path$tag$AbsolutePath; str += 1; } else if (str[0] == '~' && str[1] == '/') { - result.type = PATHTYPE_HOME; + result.$tag = Path$tag$HomePath; str += 2; } else if (str[0] == '.' && str[1] == '/') { - result.type = PATHTYPE_RELATIVE; + result.$tag = Path$tag$RelativePath; str += 2; } else { - result.type = PATHTYPE_RELATIVE; + result.$tag = Path$tag$RelativePath; } + List_t components = EMPTY_LIST; while (str && *str) { size_t component_len = strcspn(str, "/"); if (component_len > 0) { if (component_len == 1 && str[0] == '.') { // ignore /./ - } else if (component_len == 2 && strncmp(str, "..", 2) == 0 && result.components.length > 1 + } else if (component_len == 2 && strncmp(str, "..", 2) == 0 && components.length > 1 && !Text$equal_values( Text(".."), - *(Text_t *)(result.components.data - + result.components.stride * ((int64_t)result.components.length - 1)))) { + *(Text_t *)(components.data + components.stride * ((int64_t)components.length - 1)))) { // Pop off /foo/baz/.. -> /foo - List$remove_at(&result.components, I((int64_t)result.components.length), I(1), sizeof(Text_t)); + List$remove_at(&components, I((int64_t)components.length), I(1), sizeof(Text_t)); } else { Text_t component = Text$from_strn(str, component_len); - List$insert_value(&result.components, component, I(0), sizeof(Text_t)); + List$insert_value(&components, component, I(0), sizeof(Text_t)); } str += component_len; } str += strspn(str, "/"); } + result.components = components; return result; } @@ -104,12 +102,12 @@ Path_t Path$from_text(Text_t text) { return Path$from_str(Text$as_c_string(text) public Path_t Path$expand_home(Path_t path) { - if (path.type == PATHTYPE_HOME) { + if (path.$tag == Path$tag$HomePath) { Path_t pwd = Path$from_str(getenv("HOME")); - List_t components = List$concat(pwd.components, path.components, sizeof(Text_t)); - assert(components.length == path.components.length + pwd.components.length); + List_t components = List$concat(pwd.AbsolutePath.components, path.HomePath.components, sizeof(Text_t)); + assert(components.length == path.HomePath.components.length + pwd.AbsolutePath.components.length); clean_components(&components); - path = (Path_t){.type = PATHTYPE_ABSOLUTE, .components = components}; + path = Path$tagged$AbsolutePath(components); } return path; } @@ -120,7 +118,7 @@ Path_t Path$_concat(int n, Path_t items[n]) { Path_t result = items[0]; LIST_INCREF(result.components); for (int i = 1; i < n; i++) { - if (items[i].type != PATHTYPE_RELATIVE) + if (items[i].$tag != Path$tag$RelativePath) fail("Cannot concatenate an absolute or home-based path onto another path: (", items[i], ")"); List$insert_all(&result.components, items[i].components, I(0), sizeof(Text_t)); } @@ -130,10 +128,12 @@ Path_t Path$_concat(int n, Path_t items[n]) { public Path_t Path$resolved(Path_t path, Path_t relative_to) { - if (path.type == PATHTYPE_RELATIVE - && !(relative_to.type == PATHTYPE_RELATIVE && relative_to.components.length == 0)) { - Path_t result = {.type = relative_to.type}; - result.components = relative_to.components; + if (path.$tag == Path$tag$RelativePath + && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) { + Path_t result = { + .$tag = relative_to.$tag, + .components = relative_to.components, + }; LIST_INCREF(result.components); List$insert_all(&result.components, path.components, I(0), sizeof(Text_t)); clean_components(&result.components); @@ -144,11 +144,11 @@ Path_t Path$resolved(Path_t path, Path_t relative_to) { public Path_t Path$relative_to(Path_t path, Path_t relative_to) { - if (path.type != relative_to.type) + if (path.$tag != relative_to.$tag) fail("Cannot create a path relative to a different path with a mismatching type: (", path, ") relative to (", relative_to, ")"); - Path_t result = {.type = PATHTYPE_RELATIVE}; + Path_t result = Path$tagged$RelativePath(EMPTY_LIST); int64_t shared = 0; for (; shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length; shared++) { Text_t *p = (Text_t *)(path.components.data + shared * path.components.stride); @@ -568,15 +568,15 @@ Path_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_by public Path_t Path$parent(Path_t path) { - if (path.type == PATHTYPE_ABSOLUTE && path.components.length == 0) { + if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) { return path; } else if (path.components.length > 0 && !Text$equal_values( *(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1)), Text(".."))) { - return (Path_t){.type = path.type, .components = List$slice(path.components, I(1), I(-2))}; + return (Path_t){.$tag = path.$tag, .components = List$slice(path.components, I(1), I(-2))}; } else { - Path_t result = {.type = path.type, .components = path.components}; + Path_t result = {.$tag = path.$tag, .components = path.components}; LIST_INCREF(result.components); List$insert_value(&result.components, Text(".."), I(0), sizeof(Text_t)); return result; @@ -587,8 +587,8 @@ public PUREFUNC Text_t Path$base_name(Path_t path) { if (path.components.length >= 1) return *(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1)); - else if (path.type == PATHTYPE_HOME) return Text("~"); - else if (path.type == PATHTYPE_RELATIVE) return Text("."); + else if (path.$tag == Path$tag$HomePath) return Text("~"); + else if (path.$tag == Path$tag$RelativePath) return Text("."); else return EMPTY_TEXT; } @@ -618,7 +618,7 @@ public Path_t Path$child(Path_t path, Text_t name) { if (Text$has(name, Text("/")) || Text$has(name, Text(";"))) fail("Path name has invalid characters: ", name); Path_t result = { - .type = path.type, + .$tag = path.$tag, .components = path.components, }; LIST_INCREF(result.components); @@ -638,7 +638,7 @@ Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) { fail("Path extension has invalid characters: ", extension); Path_t result = { - .type = path.type, + .$tag = path.$tag, .components = path.components, }; LIST_INCREF(result.components); @@ -759,56 +759,20 @@ Path_t Path$current_dir(void) { return Path$from_str(cwd); } -public -PUREFUNC uint64_t Path$hash(const void *obj, const TypeInfo_t *type) { - (void)type; - Path_t *path = (Path_t *)obj; - siphash sh; - siphashinit(&sh, (uint64_t)path->type); - for (int64_t i = 0; i < (int64_t)path->components.length; i++) { - uint64_t item_hash = Text$hash(path->components.data + i * path->components.stride, &Text$info); - siphashadd64bits(&sh, item_hash); - } - return siphashfinish_last_part(&sh, (uint64_t)path->components.length); -} - -public -PUREFUNC int32_t Path$compare(const void *va, const void *vb, const TypeInfo_t *type) { - (void)type; - Path_t *a = (Path_t *)va, *b = (Path_t *)vb; - int diff = ((int)a->type - (int)b->type); - if (diff != 0) return diff; - return List$compare(&a->components, &b->components, List$info(&Text$info)); -} - -public -PUREFUNC bool Path$equal(const void *va, const void *vb, const TypeInfo_t *type) { - (void)type; - Path_t *a = (Path_t *)va, *b = (Path_t *)vb; - if (a->type != b->type) return false; - return List$equal(&a->components, &b->components, List$info(&Text$info)); -} - -public -PUREFUNC bool Path$equal_values(Path_t a, Path_t b) { - if (a.type != b.type) return false; - return List$equal(&a.components, &b.components, List$info(&Text$info)); -} - public int Path$print(FILE *f, Path_t path) { if (path.components.length == 0) { - if (path.type == PATHTYPE_ABSOLUTE) return fputs("/", f); - else if (path.type == PATHTYPE_RELATIVE) return fputs(".", f); - else if (path.type == PATHTYPE_HOME) return fputs("~", f); + if (path.$tag == Path$tag$AbsolutePath) return fputs("/", f); + else if (path.$tag == Path$tag$RelativePath) return fputs(".", f); + else if (path.$tag == Path$tag$HomePath) return fputs("~", f); } int n = 0; - if (path.type == PATHTYPE_ABSOLUTE) { + if (path.$tag == Path$tag$AbsolutePath) { n += fputc('/', f); - } else if (path.type == PATHTYPE_HOME) { + } else if (path.$tag == Path$tag$HomePath) { n += fputs("~/", f); - } else if (path.type == PATHTYPE_RELATIVE) { + } else if (path.$tag == Path$tag$RelativePath) { if (!Text$equal_values(*(Text_t *)path.components.data, Text(".."))) n += fputs("./", f); } @@ -829,9 +793,9 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) { if (!obj) return Text("Path"); Path_t *path = (Path_t *)obj; Text_t text = Text$join(Text("/"), path->components); - if (path->type == PATHTYPE_HOME) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text); - else if (path->type == PATHTYPE_ABSOLUTE) text = Text$concat(Text("/"), text); - else if (path->type == PATHTYPE_RELATIVE + if (path->$tag == Path$tag$HomePath) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text); + else if (path->$tag == Path$tag$AbsolutePath) text = Text$concat(Text("/"), text); + else if (path->$tag == Path$tag$RelativePath && (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text("..")))) text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text); @@ -841,52 +805,80 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) { } public -CONSTFUNC bool Path$is_none(const void *obj, const TypeInfo_t *type) { - (void)type; - return ((Path_t *)obj)->type == PATHTYPE_NONE; -} +const TypeInfo_t Path$AbsolutePath$$info = { + .size = sizeof(Path$AbsolutePath$$type), + .align = __alignof__(Path$AbsolutePath$$type), + .metamethods = Struct$metamethods, + .tag = StructInfo, + .StructInfo = + { + .name = "AbsolutePath", + .num_fields = 1, + .fields = (NamedType_t[1]){{ + .name = "components", + .type = List$info(&Text$info), + }}, + }, +}; public -void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) { - (void)type; - Path_t *path = (Path_t *)obj; - fputc((int)path->type, out); - List$serialize(&path->components, out, pointers, List$info(&Text$info)); -} +const TypeInfo_t Path$RelativePath$$info = { + .size = sizeof(Path$RelativePath$$type), + .align = __alignof__(Path$RelativePath$$type), + .metamethods = Struct$metamethods, + .tag = StructInfo, + .StructInfo = + { + .name = "RelativePath", + .num_fields = 1, + .fields = (NamedType_t[1]){{ + .name = "components", + .type = List$info(&Text$info), + }}, + }, +}; public -void Path$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type) { - (void)type; - Path_t path = {}; - path.type = fgetc(in); - List$deserialize(in, &path.components, pointers, List$info(&Text$info)); - *(Path_t *)obj = path; -} - -public -const TypeInfo_t Path$info = {.size = sizeof(Path_t), - .align = __alignof__(Path_t), - .tag = OpaqueInfo, - .metamethods = { - .as_text = Path$as_text, - .hash = Path$hash, - .compare = Path$compare, - .equal = Path$equal, - .is_none = Path$is_none, - .serialize = Path$serialize, - .deserialize = Path$deserialize, - }}; - -public -const TypeInfo_t PathType$info = { - .size = sizeof(PathType_t), - .align = __alignof__(PathType_t), - .metamethods = PackedDataEnum$metamethods, +const TypeInfo_t Path$HomePath$$info = { + .size = sizeof(Path$HomePath$$type), + .align = __alignof__(Path$HomePath$$type), + .metamethods = Struct$metamethods, + .tag = StructInfo, + .StructInfo = + { + .name = "HomePath", + .num_fields = 1, + .fields = (NamedType_t[1]){{ + .name = "components", + .type = List$info(&Text$info), + }}, + }, +}; + +public +const TypeInfo_t Path$info = { + .size = sizeof(Path_t), + .align = __alignof__(Path_t), .tag = EnumInfo, .EnumInfo = { - .name = "PathType", + .name = "Path", .num_tags = 3, - .tags = ((NamedType_t[3]){{.name = "Relative"}, {.name = "Absolute"}, {.name = "Home"}}), + .tags = + (NamedType_t[3]){ + {.name = "AbsolutePath", &Path$AbsolutePath$$info}, + {.name = "RelativePath", &Path$RelativePath$$info}, + {.name = "HomePath", &Path$HomePath$$info}, + }, + }, + .metamethods = + { + .as_text = Path$as_text, + .compare = Enum$compare, + .equal = Enum$equal, + .hash = Enum$hash, + .is_none = Enum$is_none, + .serialize = Enum$serialize, + .deserialize = Enum$deserialize, }, }; diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 677631b2..7a175099 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -12,6 +12,9 @@ Path_t Path$from_str(const char *str); Path_t Path$from_text(Text_t text); // int Path$print(FILE *f, Path_t path); +// UNSAFE: this works because each type of path has a .components in the same place +#define Path$components(path) ((path).components) +// END UNSAFE const char *Path$as_c_string(Path_t path); #define Path(str) Path$from_str(str) Path_t Path$_concat(int n, Path_t items[n]); @@ -69,5 +72,7 @@ bool Path$is_none(const void *obj, const TypeInfo_t *type); void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); void Path$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type); +extern const TypeInfo_t Path$AbsolutePath$$info; +extern const TypeInfo_t Path$RelativePath$$info; +extern const TypeInfo_t Path$HomePath$$info; extern const TypeInfo_t Path$info; -extern const TypeInfo_t PathType$info; diff --git a/src/tomo.c b/src/tomo.c index e086166c..04f0289a 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -28,6 +28,7 @@ #include "stdlib/c_strings.h" #include "stdlib/cli.h" #include "stdlib/datatypes.h" +#include "stdlib/enums.h" #include "stdlib/lists.h" #include "stdlib/optionals.h" #include "stdlib/paths.h" @@ -511,7 +512,7 @@ void install_library(Path_t lib_dir) { Text_t lib_name = get_library_name(lib_dir); Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION)), lib_name); print("Installing ", lib_dir, " into ", dest); - if (!Path$equal_values(lib_dir, dest)) { + if (!Enum$equal(&lib_dir, &dest, &Path$info)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_name); xsystem(as_owner, "rm -rf '", dest, "'"); if (verbose) whisper("Moving files to ", dest); diff --git a/src/types.c b/src/types.c index 5c52992b..46df5c64 100644 --- a/src/types.c +++ b/src/types.c @@ -516,7 +516,6 @@ PUREFUNC size_t unpadded_struct_size(type_t *t) { PUREFUNC size_t type_size(type_t *t) { if (t == PATH_TYPE) return sizeof(Path_t); - if (t == PATH_TYPE_TYPE) return sizeof(PathType_t); #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" @@ -605,7 +604,6 @@ PUREFUNC size_t type_size(type_t *t) { PUREFUNC size_t type_align(type_t *t) { if (t == PATH_TYPE) return __alignof__(Path_t); - if (t == PATH_TYPE_TYPE) return __alignof__(PathType_t); #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" diff --git a/test/paths.tm b/test/paths.tm index bcda8e1f..d7630fc3 100644 --- a/test/paths.tm +++ b/test/paths.tm @@ -103,8 +103,8 @@ func main() say("Globbing:") >> (./*.tm).glob() - assert (./foo).type == Relative - assert (/foo).type == Absolute - assert (~/foo).type == Home - assert (/foo/baz).components == ["foo", "baz"] - assert Path(type=Relative, ["foo", "baz"]) == (./foo/baz) + assert (./foo).RelativePath + assert (/foo).AbsolutePath + assert (~/foo).HomePath + assert (/foo/baz).components() == ["foo", "baz"] + assert Path.RelativePath(["foo", "baz"]) == (./foo/baz) -- cgit v1.2.3 From 19c8450aa0a9ea008a3e5fd4ec44f7c3761db663 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 22:53:45 -0500 Subject: Switch paths to use Result return values instead of fail() --- CHANGES.md | 11 ++++ api/api.md | 41 +++++++------ api/paths.md | 41 +++++++------ api/paths.yaml | 41 +++++++------ man/man3/tomo-Path.3 | 18 +++--- man/man3/tomo-Path.append.3 | 8 +-- man/man3/tomo-Path.append_bytes.3 | 8 +-- man/man3/tomo-Path.create_directory.3 | 6 +- man/man3/tomo-Path.parent.3 | 6 +- man/man3/tomo-Path.relative_to.3 | 7 ++- man/man3/tomo-Path.remove.3 | 6 +- man/man3/tomo-Path.set_owner.3 | 6 +- man/man3/tomo-Path.write.3 | 6 +- man/man3/tomo-Path.write_bytes.3 | 6 +- src/environment.c | 27 +++++---- src/environment.h | 1 + src/stdlib/datatypes.h | 35 +++++++++++ src/stdlib/optionals.h | 2 +- src/stdlib/paths.c | 111 ++++++++++++++++++---------------- src/stdlib/paths.h | 22 +++---- src/stdlib/result.c | 65 ++++++++++++++++++++ src/stdlib/result.h | 9 +++ src/stdlib/tomo.h | 1 + test/paths.tm | 10 +-- 24 files changed, 317 insertions(+), 177 deletions(-) create mode 100644 src/stdlib/result.c create mode 100644 src/stdlib/result.h diff --git a/CHANGES.md b/CHANGES.md index 1baafc87..51c4dcb9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,17 @@ have tags with member fields and those without. - Rename `Empty()` to `Present()` for set-like tables. - Paths are now an `enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))` +- Added `enum Result(Success, Failure(message:Text))` type for indicating + success or failure. +- Some path methods now use `Result` return types instead of failing: + - `Path.append()` + - `Path.append_bytes()` + - `Path.create_directory()` + - `Path.remove()` + - `Path.set_owner()` + - `Path.write()` + - `Path.write_bytes()` +- `Path.parent()` returns `none` if path is `(/)` (file root) ## v2025-11-30 diff --git a/api/api.md b/api/api.md index 6a7d218b..2be5e4e9 100644 --- a/api/api.md +++ b/api/api.md @@ -2556,7 +2556,7 @@ assert (./not-a-file).accessed() == none ## Path.append ```tomo -Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Void) +Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Result) ``` Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -2567,18 +2567,18 @@ path | `Path` | The path of the file to append to. | - text | `Text` | The text to append to the file. | - permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** ```tomo -(./log.txt).append("extra line$(\n)") +(./log.txt).append("extra line\n")! ``` ## Path.append_bytes ```tomo -Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Void) +Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Result) ``` Appends the given bytes to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -2589,12 +2589,12 @@ path | `Path` | The path of the file to append to. | - bytes | `[Byte]` | The bytes to append to the file. | - permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** ```tomo -(./log.txt).append_bytes([104, 105]) +(./log.txt).append_bytes([104, 105])! ``` ## Path.base_name @@ -2781,7 +2781,7 @@ assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"] ## Path.create_directory ```tomo -Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Void) +Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Result) ``` Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. @@ -2793,7 +2793,7 @@ path | `Path` | The path of the directory to create. | - permissions | `` | The permissions to set on the new directory. | `Int32(0o755)` recursive | `` | If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. | `yes` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -3159,7 +3159,7 @@ assert (/non/existent/file).owner() == none ## Path.parent ```tomo -Path.parent : func(path: Path -> Path) +Path.parent : func(path: Path -> Path?) ``` Returns the parent directory of the file or directory at the specified path. @@ -3168,7 +3168,7 @@ Argument | Type | Description | Default ---------|------|-------------|--------- path | `Path` | The path of the file or directory. | - -**Return:** The path of the parent directory. +**Return:** The path of the parent directory or `none` if the path is `(/)` (the file root). **Example:** @@ -3232,18 +3232,19 @@ Argument | Type | Description | Default path | `Path` | The path to convert. | - relative_to | `` | The base path for the relative path. | `(./)` -**Return:** The relative path. +**Return:** A relative path from the reference point to the given path. **Example:** ```tomo -assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt) +assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt) +assert (/tmp/foo).relative_to((/tmp)) == (./foo) ``` ## Path.remove ```tomo -Path.remove : func(path: Path, ignore_missing = no -> Void) +Path.remove : func(path: Path, ignore_missing = no -> Result) ``` Removes the file or directory at the specified path. A runtime error is raised if something goes wrong. @@ -3253,7 +3254,7 @@ Argument | Type | Description | Default path | `Path` | The path to remove. | - ignore_missing | `` | Whether to ignore errors if the file or directory does not exist. | `no` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -3286,7 +3287,7 @@ assert (./path/to/file.txt).resolved(relative_to=(/foo)) == (/foo/path/to/file.t ## Path.set_owner ```tomo -Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Void) +Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Result) ``` Set the owning user and/or group for a path. @@ -3298,7 +3299,7 @@ owner | `Text?` | If non-none, the new user to assign to be the owner of the fil group | `Text?` | If non-none, the new group to assign to be the owner of the file. | `none` follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes` -**Return:** Nothing. If a path does not exist, a failure will be raised. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -3374,7 +3375,7 @@ created.remove() ## Path.write ```tomo -Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Void) +Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Result) ``` Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. @@ -3385,7 +3386,7 @@ path | `Path` | The path of the file to write to. | - text | `Text` | The text to write to the file. | - permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -3396,7 +3397,7 @@ permissions | `` | The permissions to set on the file if it is created. | `Int3 ## Path.write_bytes ```tomo -Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Void) +Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Result) ``` Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. @@ -3407,7 +3408,7 @@ path | `Path` | The path of the file to write to. | - bytes | `[Byte]` | A list of bytes to write to the file. | - permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** diff --git a/api/paths.md b/api/paths.md index 4beabdc2..435932e3 100644 --- a/api/paths.md +++ b/api/paths.md @@ -28,7 +28,7 @@ assert (./not-a-file).accessed() == none ## Path.append ```tomo -Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Void) +Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Result) ``` Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -39,18 +39,18 @@ path | `Path` | The path of the file to append to. | - text | `Text` | The text to append to the file. | - permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** ```tomo -(./log.txt).append("extra line$(\n)") +(./log.txt).append("extra line\n")! ``` ## Path.append_bytes ```tomo -Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Void) +Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Result) ``` Appends the given bytes to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -61,12 +61,12 @@ path | `Path` | The path of the file to append to. | - bytes | `[Byte]` | The bytes to append to the file. | - permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** ```tomo -(./log.txt).append_bytes([104, 105]) +(./log.txt).append_bytes([104, 105])! ``` ## Path.base_name @@ -253,7 +253,7 @@ assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"] ## Path.create_directory ```tomo -Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Void) +Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Result) ``` Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. @@ -265,7 +265,7 @@ path | `Path` | The path of the directory to create. | - permissions | `` | The permissions to set on the new directory. | `Int32(0o755)` recursive | `` | If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. | `yes` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -631,7 +631,7 @@ assert (/non/existent/file).owner() == none ## Path.parent ```tomo -Path.parent : func(path: Path -> Path) +Path.parent : func(path: Path -> Path?) ``` Returns the parent directory of the file or directory at the specified path. @@ -640,7 +640,7 @@ Argument | Type | Description | Default ---------|------|-------------|--------- path | `Path` | The path of the file or directory. | - -**Return:** The path of the parent directory. +**Return:** The path of the parent directory or `none` if the path is `(/)` (the file root). **Example:** @@ -704,18 +704,19 @@ Argument | Type | Description | Default path | `Path` | The path to convert. | - relative_to | `` | The base path for the relative path. | `(./)` -**Return:** The relative path. +**Return:** A relative path from the reference point to the given path. **Example:** ```tomo -assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt) +assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt) +assert (/tmp/foo).relative_to((/tmp)) == (./foo) ``` ## Path.remove ```tomo -Path.remove : func(path: Path, ignore_missing = no -> Void) +Path.remove : func(path: Path, ignore_missing = no -> Result) ``` Removes the file or directory at the specified path. A runtime error is raised if something goes wrong. @@ -725,7 +726,7 @@ Argument | Type | Description | Default path | `Path` | The path to remove. | - ignore_missing | `` | Whether to ignore errors if the file or directory does not exist. | `no` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -758,7 +759,7 @@ assert (./path/to/file.txt).resolved(relative_to=(/foo)) == (/foo/path/to/file.t ## Path.set_owner ```tomo -Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Void) +Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Result) ``` Set the owning user and/or group for a path. @@ -770,7 +771,7 @@ owner | `Text?` | If non-none, the new user to assign to be the owner of the fil group | `Text?` | If non-none, the new group to assign to be the owner of the file. | `none` follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes` -**Return:** Nothing. If a path does not exist, a failure will be raised. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -846,7 +847,7 @@ created.remove() ## Path.write ```tomo -Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Void) +Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Result) ``` Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. @@ -857,7 +858,7 @@ path | `Path` | The path of the file to write to. | - text | `Text` | The text to write to the file. | - permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** @@ -868,7 +869,7 @@ permissions | `` | The permissions to set on the file if it is created. | `Int3 ## Path.write_bytes ```tomo -Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Void) +Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Result) ``` Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. @@ -879,7 +880,7 @@ path | `Path` | The path of the file to write to. | - bytes | `[Byte]` | A list of bytes to write to the file. | - permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)` -**Return:** Nothing. +**Return:** Either `Success` or `Failure(reason)`. **Example:** diff --git a/api/paths.yaml b/api/paths.yaml index 65d63671..02b8fbe8 100644 --- a/api/paths.yaml +++ b/api/paths.yaml @@ -27,9 +27,9 @@ Path.append: Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. return: - type: 'Void' + type: 'Result' description: > - Nothing. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' @@ -45,7 +45,7 @@ Path.append: description: > The permissions to set on the file if it is being created. example: | - (./log.txt).append("extra line$(\n)") + (./log.txt).append("extra line\n")! Path.append_bytes: short: append bytes to a file @@ -53,9 +53,9 @@ Path.append_bytes: Appends the given bytes to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. return: - type: 'Void' + type: 'Result' description: > - Nothing. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' @@ -71,7 +71,7 @@ Path.append_bytes: description: > The permissions to set on the file if it is being created. example: | - (./log.txt).append_bytes([104, 105]) + (./log.txt).append_bytes([104, 105])! Path.base_name: short: base name of a file @@ -260,9 +260,9 @@ Path.create_directory: any of the parent directories do not exist, they will be created as needed. note: > return: - type: 'Void' + type: 'Result' description: > - Nothing. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' @@ -604,9 +604,9 @@ Path.parent: description: > Returns the parent directory of the file or directory at the specified path. return: - type: 'Path' + type: 'Path?' description: > - The path of the parent directory. + The path of the parent directory or `none` if the path is `(/)` (the file root). args: path: type: 'Path' @@ -666,7 +666,7 @@ Path.relative_to: return: type: 'Path' description: > - The relative path. + A relative path from the reference point to the given path. args: path: type: 'Path' @@ -677,16 +677,17 @@ Path.relative_to: description: > The base path for the relative path. example: | - assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt) + assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt) + assert (/tmp/foo).relative_to((/tmp)) == (./foo) Path.remove: short: remove a file or directory description: > Removes the file or directory at the specified path. A runtime error is raised if something goes wrong. return: - type: 'Void' + type: 'Result' description: > - Nothing. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' @@ -725,9 +726,9 @@ Path.set_owner: description: > Set the owning user and/or group for a path. return: - type: 'Void' + type: 'Result' description: > - Nothing. If a path does not exist, a failure will be raised. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' @@ -818,9 +819,9 @@ Path.write: it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. return: - type: 'Void' + type: 'Result' description: > - Nothing. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' @@ -844,9 +845,9 @@ Path.write_bytes: it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. return: - type: 'Void' + type: 'Result' description: > - Nothing. + Either `Success` or `Failure(reason)`. args: path: type: 'Path' diff --git a/man/man3/tomo-Path.3 b/man/man3/tomo-Path.3 index f383196e..ae9b6d51 100644 --- a/man/man3/tomo-Path.3 +++ b/man/man3/tomo-Path.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path 3 2025-11-29 "Tomo man-pages" +.TH Path 3 2025-12-07 "Tomo man-pages" .SH NAME Path \- a Tomo type .SH LIBRARY @@ -19,7 +19,7 @@ For more, see: .TP -.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void) +.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result) Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. For more, see: @@ -27,7 +27,7 @@ For more, see: .TP -.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void) +.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result) Appends the given bytes to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. For more, see: @@ -99,7 +99,7 @@ For more, see: .TP -.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Void) +.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Result) Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. For more, see: @@ -235,7 +235,7 @@ For more, see: .TP -.BI Path.parent\ :\ func(path:\ Path\ ->\ Path) +.BI Path.parent\ :\ func(path:\ Path\ ->\ Path?) Returns the parent directory of the file or directory at the specified path. For more, see: @@ -267,7 +267,7 @@ For more, see: .TP -.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Void) +.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Result) Removes the file or directory at the specified path. A runtime error is raised if something goes wrong. For more, see: @@ -283,7 +283,7 @@ For more, see: .TP -.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Void) +.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Result) Set the owning user and/or group for a path. For more, see: @@ -315,7 +315,7 @@ For more, see: .TP -.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Void) +.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Result) Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. For more, see: @@ -323,7 +323,7 @@ For more, see: .TP -.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Void) +.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Result) Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. For more, see: diff --git a/man/man3/tomo-Path.append.3 b/man/man3/tomo-Path.append.3 index b065d55b..d11169b0 100644 --- a/man/man3/tomo-Path.append.3 +++ b/man/man3/tomo-Path.append.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.append 3 2025-11-29 "Tomo man-pages" +.TH Path.append 3 2025-12-07 "Tomo man-pages" .SH NAME Path.append \- append to a file .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void) +.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result) .fi .SH DESCRIPTION Appends the given text to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -27,11 +27,11 @@ text Text The text to append to the file. - permissions Int32 The permissions to set on the file if it is being created. Int32(0o644) .TE .SH RETURN -Nothing. +Either `Success` or `Failure(reason)`. .SH EXAMPLES .EX -(./log.txt).append("extra line$(\[rs]n)") +(./log.txt).append("extra line\[rs]n")! .EE .SH SEE ALSO .BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.append_bytes.3 b/man/man3/tomo-Path.append_bytes.3 index 48f795c3..c89cf08c 100644 --- a/man/man3/tomo-Path.append_bytes.3 +++ b/man/man3/tomo-Path.append_bytes.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.append_bytes 3 2025-11-29 "Tomo man-pages" +.TH Path.append_bytes 3 2025-12-07 "Tomo man-pages" .SH NAME Path.append_bytes \- append bytes to a file .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void) +.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result) .fi .SH DESCRIPTION Appends the given bytes to the file at the specified path, creating the file if it doesn't already exist. Failure to write will result in a runtime error. @@ -27,11 +27,11 @@ bytes [Byte] The bytes to append to the file. - permissions Int32 The permissions to set on the file if it is being created. Int32(0o644) .TE .SH RETURN -Nothing. +Either `Success` or `Failure(reason)`. .SH EXAMPLES .EX -(./log.txt).append_bytes([104, 105]) +(./log.txt).append_bytes([104, 105])! .EE .SH SEE ALSO .BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.create_directory.3 b/man/man3/tomo-Path.create_directory.3 index 539aae36..639e3f4a 100644 --- a/man/man3/tomo-Path.create_directory.3 +++ b/man/man3/tomo-Path.create_directory.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.create_directory 3 2025-11-29 "Tomo man-pages" +.TH Path.create_directory 3 2025-12-07 "Tomo man-pages" .SH NAME Path.create_directory \- make a directory .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Void) +.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Result) .fi .SH DESCRIPTION Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed. @@ -27,7 +27,7 @@ permissions The permissions to set on the new directory. Int32(0o755) recursive If set to \fByes\fR, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to \fByes\fR, this function behaves like \fBmkdir -p\fR. yes .TE .SH RETURN -Nothing. +Either `Success` or `Failure(reason)`. .SH NOTES diff --git a/man/man3/tomo-Path.parent.3 b/man/man3/tomo-Path.parent.3 index 7af4f98f..57c4a4a5 100644 --- a/man/man3/tomo-Path.parent.3 +++ b/man/man3/tomo-Path.parent.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.parent 3 2025-11-29 "Tomo man-pages" +.TH Path.parent 3 2025-12-07 "Tomo man-pages" .SH NAME Path.parent \- get parent directory .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.parent\ :\ func(path:\ Path\ ->\ Path) +.BI Path.parent\ :\ func(path:\ Path\ ->\ Path?) .fi .SH DESCRIPTION Returns the parent directory of the file or directory at the specified path. @@ -25,7 +25,7 @@ Name Type Description path Path The path of the file or directory. .TE .SH RETURN -The path of the parent directory. +The path of the parent directory or `none` if the path is `(/)` (the file root). .SH EXAMPLES .EX diff --git a/man/man3/tomo-Path.relative_to.3 b/man/man3/tomo-Path.relative_to.3 index a3bf2136..d7f547fb 100644 --- a/man/man3/tomo-Path.relative_to.3 +++ b/man/man3/tomo-Path.relative_to.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.relative_to 3 2025-11-29 "Tomo man-pages" +.TH Path.relative_to 3 2025-12-07 "Tomo man-pages" .SH NAME Path.relative_to \- apply a relative path to another .SH LIBRARY @@ -26,11 +26,12 @@ path Path The path to convert. - relative_to The base path for the relative path. (./) .TE .SH RETURN -The relative path. +A relative path from the reference point to the given path. .SH EXAMPLES .EX -assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt) +assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt) +assert (/tmp/foo).relative_to((/tmp)) == (./foo) .EE .SH SEE ALSO .BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.remove.3 b/man/man3/tomo-Path.remove.3 index a98b4483..70ef4a56 100644 --- a/man/man3/tomo-Path.remove.3 +++ b/man/man3/tomo-Path.remove.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.remove 3 2025-11-29 "Tomo man-pages" +.TH Path.remove 3 2025-12-07 "Tomo man-pages" .SH NAME Path.remove \- remove a file or directory .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Void) +.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Result) .fi .SH DESCRIPTION Removes the file or directory at the specified path. A runtime error is raised if something goes wrong. @@ -26,7 +26,7 @@ path Path The path to remove. - ignore_missing Whether to ignore errors if the file or directory does not exist. no .TE .SH RETURN -Nothing. +Either `Success` or `Failure(reason)`. .SH EXAMPLES .EX diff --git a/man/man3/tomo-Path.set_owner.3 b/man/man3/tomo-Path.set_owner.3 index b0b24e71..1cba764a 100644 --- a/man/man3/tomo-Path.set_owner.3 +++ b/man/man3/tomo-Path.set_owner.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.set_owner 3 2025-11-29 "Tomo man-pages" +.TH Path.set_owner 3 2025-12-07 "Tomo man-pages" .SH NAME Path.set_owner \- set the owner .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Void) +.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Result) .fi .SH DESCRIPTION Set the owning user and/or group for a path. @@ -28,7 +28,7 @@ group Text? If non-none, the new group to assign to be the owner of the file. n follow_symlinks Bool Whether to follow symbolic links. yes .TE .SH RETURN -Nothing. If a path does not exist, a failure will be raised. +Either `Success` or `Failure(reason)`. .SH EXAMPLES .EX diff --git a/man/man3/tomo-Path.write.3 b/man/man3/tomo-Path.write.3 index 6d9e1043..565935c5 100644 --- a/man/man3/tomo-Path.write.3 +++ b/man/man3/tomo-Path.write.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.write 3 2025-11-29 "Tomo man-pages" +.TH Path.write 3 2025-12-07 "Tomo man-pages" .SH NAME Path.write \- write to a file .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Void) +.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Result) .fi .SH DESCRIPTION Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. @@ -27,7 +27,7 @@ text Text The text to write to the file. - permissions The permissions to set on the file if it is created. Int32(0o644) .TE .SH RETURN -Nothing. +Either `Success` or `Failure(reason)`. .SH EXAMPLES .EX diff --git a/man/man3/tomo-Path.write_bytes.3 b/man/man3/tomo-Path.write_bytes.3 index 6534d18e..c11e97ed 100644 --- a/man/man3/tomo-Path.write_bytes.3 +++ b/man/man3/tomo-Path.write_bytes.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path.write_bytes 3 2025-11-29 "Tomo man-pages" +.TH Path.write_bytes 3 2025-12-07 "Tomo man-pages" .SH NAME Path.write_bytes \- write bytes to a file .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Void) +.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Result) .fi .SH DESCRIPTION Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised. @@ -27,7 +27,7 @@ bytes [Byte] A list of bytes to write to the file. - permissions The permissions to set on the file if it is created. Int32(0o644) .TE .SH RETURN -Nothing. +Either `Success` or `Failure(reason)`. .SH EXAMPLES .EX diff --git a/src/environment.c b/src/environment.c index bdbc8a4f..f84b6697 100644 --- a/src/environment.c +++ b/src/environment.c @@ -18,6 +18,8 @@ public type_t *PATH_TYPE = NULL; public type_t *PRESENT_TYPE = NULL; +public +type_t *RESULT_TYPE = NULL; static type_t *declare_type(env_t *env, const char *def_str) { ast_t *ast = parse_file_str(def_str); @@ -68,6 +70,7 @@ env_t *global_env(bool source_mapping) { PATH_TYPE = declare_type( env, "enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))"); + RESULT_TYPE = declare_type(env, "enum Result(Success, Failure(reason:Text))"); PRESENT_TYPE = declare_type(env, "struct Present()"); @@ -90,6 +93,7 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")), MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")), MAKE_TYPE("Present", PRESENT_TYPE, Text("Present$$type"), Text("Present$$info")), + MAKE_TYPE("Result", RESULT_TYPE, Text("Result_t"), Text("Result$$info")), MAKE_TYPE( // "Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"), {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}), @@ -293,8 +297,9 @@ env_t *global_env(bool source_mapping) { MAKE_TYPE( // "Path", PATH_TYPE, Text("Path_t"), Text("Path$info"), // {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // - {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"}, // - {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, // + {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, // + {"append_bytes", "Path$append_bytes", + "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, // {"base_name", "Path$base_name", "func(path:Path -> Text)"}, // {"by_line", "Path$by_line", "func(path:Path -> func(->Text?)?)"}, // {"can_execute", "Path$can_execute", "func(path:Path -> Bool)"}, // @@ -306,7 +311,7 @@ env_t *global_env(bool source_mapping) { {"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, // {"components", "Path$components", "func(path:Path -> [Text])"}, // {"create_directory", "Path$create_directory", - "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, // + "func(path:Path, permissions=Int32(0o755), recursive=yes -> Result)"}, // {"current_dir", "Path$current_dir", "func(->Path)"}, // {"exists", "Path$exists", "func(path:Path -> Bool)"}, // {"expand_home", "Path$expand_home", "func(path:Path -> Path)"}, // @@ -324,21 +329,21 @@ env_t *global_env(bool source_mapping) { {"lines", "Path$lines", "func(path:Path -> [Text]?)"}, // {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Int64?)"}, // {"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, // - {"parent", "Path$parent", "func(path:Path -> Path)"}, // + {"parent", "Path$parent", "func(path:Path -> Path?)"}, // {"read", "Path$read", "func(path:Path -> Text?)"}, // {"read_bytes", "Path$read_bytes", "func(path:Path, limit:Int?=none -> [Byte]?)"}, // {"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"}, // - {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, // + {"remove", "Path$remove", "func(path:Path, ignore_missing=no -> Result)"}, // {"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"}, // - {"set_owner", "Path$set_owner", // - "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes)"}, // + {"set_owner", "Path$set_owner", + "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes -> Result)"}, // {"sibling", "Path$sibling", "func(path:Path, name:Text -> Path)"}, // {"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"}, // - {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644))"}, // - {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, // - {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path)"}, // - {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path)"}), + {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, // + {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, // + {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path?)"}, // + {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path?)"}), MAKE_TYPE( // "Text", TEXT_TYPE, Text("Text_t"), Text("Text$info"), // {"as_c_string", "Text$as_c_string", "func(text:Text -> CString)"}, // diff --git a/src/environment.h b/src/environment.h index 88d36d42..ba036f2e 100644 --- a/src/environment.h +++ b/src/environment.h @@ -87,3 +87,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; extern type_t *PRESENT_TYPE; +extern type_t *RESULT_TYPE; diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index 49c4d835..3cd99f38 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -152,6 +152,41 @@ typedef struct { }; } Path_t; +#define $OptionalPath$$type Path_t +#define OptionalPath_t Path_t + +typedef struct Result$Success$$struct { +} Result$Success$$type; + +typedef struct { + Result$Success$$type value; + bool has_value; +} $OptionalResult$Success$$type; + +typedef struct Result$Failure$$struct { + Text_t reason; +} Result$Failure$$type; + +typedef struct { + Result$Failure$$type value; + bool has_value; +} $OptionalResult$Failure$$type; + +#define Result$Success ((Result$$type){.$tag = Result$tag$Success}) +#define SuccessResult Result$Success +#define Result$tagged$Failure(msg) ((Result$$type){.$tag = Result$tag$Failure, .Failure.reason = msg}) +#define FailureResult(...) Result$tagged$Failure(Texts(__VA_ARGS__)) + +typedef struct Result$$struct { + enum { Result$tag$none, Result$tag$Success, Result$tag$Failure } $tag; + union { + Result$Success$$type Success; + Result$Failure$$type Failure; + }; +} Result$$type; + +#define Result_t Result$$type + #define OptionalBool_t uint8_t #define OptionalList_t List_t #define OptionalTable_t Table_t diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h index 985d6611..700a4ada 100644 --- a/src/stdlib/optionals.h +++ b/src/stdlib/optionals.h @@ -15,7 +15,7 @@ #define NONE_TABLE ((OptionalTable_t){.entries.data = NULL}) #define NONE_CLOSURE ((OptionalClosure_t){.fn = NULL}) #define NONE_TEXT ((OptionalText_t){.tag = TEXT_NONE}) -#define NONE_PATH ((Path_t){.$tag = Path$tag$none}) +#define NONE_PATH ((OptionalPath_t){.$tag = Path$tag$none}) PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type); PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type); diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 9fb37ee3..ed8383fd 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -128,8 +128,10 @@ Path_t Path$_concat(int n, Path_t items[n]) { public Path_t Path$resolved(Path_t path, Path_t relative_to) { - if (path.$tag == Path$tag$RelativePath - && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) { + if (path.$tag == Path$tag$HomePath) { + return Path$expand_home(path); + } else if (path.$tag == Path$tag$RelativePath + && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) { Path_t result = { .$tag = relative_to.$tag, .components = relative_to.components, @@ -144,16 +146,18 @@ Path_t Path$resolved(Path_t path, Path_t relative_to) { public Path_t Path$relative_to(Path_t path, Path_t relative_to) { - if (path.$tag != relative_to.$tag) - fail("Cannot create a path relative to a different path with a mismatching type: (", path, ") relative to (", - relative_to, ")"); + if (path.$tag != relative_to.$tag) { + path = Path$resolved(path, Path$current_dir()); + relative_to = Path$resolved(relative_to, Path$current_dir()); + } Path_t result = Path$tagged$RelativePath(EMPTY_LIST); int64_t shared = 0; - for (; shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length; shared++) { + while (shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length) { Text_t *p = (Text_t *)(path.components.data + shared * path.components.stride); Text_t *r = (Text_t *)(relative_to.components.data + shared * relative_to.components.stride); if (!Text$equal_values(*p, *r)) break; + shared += 1; } for (int64_t i = shared; i < (int64_t)relative_to.components.length; i++) @@ -163,7 +167,6 @@ Path_t Path$relative_to(Path_t path, Path_t relative_to) { Text_t *p = (Text_t *)(path.components.data + i * path.components.stride); List$insert(&result.components, p, I(0), sizeof(Text_t)); } - // clean_components(&result.components); return result; } @@ -277,7 +280,7 @@ OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks) { return (OptionalInt64_t){.value = (int64_t)sb.st_ctime}; } -static void _write(Path_t path, List_t bytes, int mode, int permissions) { +static Result_t _write(Path_t path, List_t bytes, int mode, int permissions) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); int fd = open(path_str, mode, permissions); @@ -287,36 +290,38 @@ static void _write(Path_t path, List_t bytes, int mode, int permissions) { // be closed by GC finalizers. GC_gcollect(); fd = open(path_str, mode, permissions); - if (fd == -1) fail("Could not write to file: ", path_str, "\n", strerror(errno)); } + if (fd == -1) return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")"); } if (bytes.stride != 1) List$compact(&bytes, 1); ssize_t written = write(fd, bytes.data, (size_t)bytes.length); - if (written != (ssize_t)bytes.length) fail("Could not write to file: ", path_str, "\n", strerror(errno)); + if (written != (ssize_t)bytes.length) + return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")"); close(fd); + return SuccessResult; } public -void Path$write(Path_t path, Text_t text, int permissions) { +Result_t Path$write(Path_t path, Text_t text, int permissions) { List_t bytes = Text$utf8(text); - _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); + return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); } public -void Path$write_bytes(Path_t path, List_t bytes, int permissions) { - _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); +Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions) { + return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions); } public -void Path$append(Path_t path, Text_t text, int permissions) { +Result_t Path$append(Path_t path, Text_t text, int permissions) { List_t bytes = Text$utf8(text); - _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); + return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } public -void Path$append_bytes(Path_t path, List_t bytes, int permissions) { - _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); +Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) { + return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } public @@ -347,8 +352,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { memcpy(content, mem, (size_t)sb.st_size); content[sb.st_size] = '\0'; close(fd); - if (count.small && (int64_t)sb.st_size < target_count) - fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)sb.st_size, ")"); + if (count.small && (int64_t)sb.st_size < target_count) return NONE_LIST; int64_t len = count.small ? target_count : (int64_t)sb.st_size; return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len}; } else { @@ -376,8 +380,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { len += (size_t)just_read; } close(fd); - if (count.small != 0 && (int64_t)len < target_count) - fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)len, ")"); + if (count.small != 0 && (int64_t)len < target_count) return NONE_LIST; return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len}; } } @@ -408,23 +411,24 @@ OptionalText_t Path$group(Path_t path, bool follow_symlinks) { } public -void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) { +Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) { uid_t owner_id = (uid_t)-1; if (owner.tag == TEXT_NONE) { struct passwd *pwd = getpwnam(Text$as_c_string(owner)); - if (pwd == NULL) fail("Not a valid user: ", owner); + if (pwd == NULL) return FailureResult("Not a valid user: ", owner); owner_id = pwd->pw_uid; } gid_t group_id = (gid_t)-1; if (group.tag == TEXT_NONE) { struct group *grp = getgrnam(Text$as_c_string(group)); - if (grp == NULL) fail("Not a valid group: ", group); + if (grp == NULL) return FailureResult("Not a valid group: ", group); group_id = grp->gr_gid; } const char *path_str = Path$as_c_string(path); int result = follow_symlinks ? chown(path_str, owner_id, group_id) : lchown(path_str, owner_id, group_id); - if (result < 0) fail("Could not set owner!"); + if (result < 0) return FailureResult("Could not set owner!"); + return SuccessResult; } static int _remove_files(const char *path, const struct stat *sbuf, int type, struct FTW *ftwb) { @@ -446,29 +450,30 @@ static int _remove_files(const char *path, const struct stat *sbuf, int type, st } public -void Path$remove(Path_t path, bool ignore_missing) { +Result_t Path$remove(Path_t path, bool ignore_missing) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); struct stat sb; if (lstat(path_str, &sb) != 0) { - if (!ignore_missing) fail("Could not remove file: ", path_str, " (", strerror(errno), ")"); - return; + if (!ignore_missing) return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")"); + return SuccessResult; } if ((sb.st_mode & S_IFMT) == S_IFREG || (sb.st_mode & S_IFMT) == S_IFLNK) { if (unlink(path_str) != 0 && !ignore_missing) - fail("Could not remove file: ", path_str, " (", strerror(errno), ")"); + return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")"); } else if ((sb.st_mode & S_IFMT) == S_IFDIR) { const int num_open_fd = 10; if (nftw(path_str, _remove_files, num_open_fd, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0) - fail("Could not remove directory: %s (%s)", path_str, strerror(errno)); + return FailureResult("Could not remove directory: ", path_str, " (", strerror(errno), ")"); } else { - fail("Could not remove path: ", path_str, " (not a file or directory)"); + return FailureResult("Could not remove path: ", path_str, " (not a file or directory)"); } + return SuccessResult; } public -void Path$create_directory(Path_t path, int permissions, bool recursive) { +Result_t Path$create_directory(Path_t path, int permissions, bool recursive) { retry: path = Path$expand_home(path); const char *c_path = Path$as_c_string(path); @@ -478,19 +483,20 @@ retry: Path$create_directory(Path$parent(path), permissions, recursive); goto retry; } else if (errno != EEXIST) { - fail("Could not create directory: ", c_path, " (", strerror(errno), ")"); + return FailureResult("Could not create directory: ", c_path, " (", strerror(errno), ")"); } } + return SuccessResult; } -static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) { +static OptionalList_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) { path = Path$expand_home(path); struct dirent *dir; List_t children = EMPTY_LIST; const char *path_str = Path$as_c_string(path); size_t path_len = strlen(path_str); DIR *d = opendir(path_str); - if (!d) fail("Could not open directory: ", path, " (", strerror(errno), ")"); + if (!d) return NONE_LIST; if (path_str[path_len - 1] == '/') --path_len; @@ -511,18 +517,22 @@ static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter } public -List_t Path$children(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, (mode_t)-1); } +OptionalList_t Path$children(Path_t path, bool include_hidden) { + return _filtered_children(path, include_hidden, (mode_t)-1); +} public -List_t Path$files(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, S_IFREG); } +OptionalList_t Path$files(Path_t path, bool include_hidden) { + return _filtered_children(path, include_hidden, S_IFREG); +} public -List_t Path$subdirectories(Path_t path, bool include_hidden) { +OptionalList_t Path$subdirectories(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, S_IFDIR); } public -Path_t Path$unique_directory(Path_t path) { +OptionalPath_t Path$unique_directory(Path_t path) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); size_t len = strlen(path_str); @@ -532,12 +542,12 @@ Path_t Path$unique_directory(Path_t path) { buf[len] = '\0'; if (buf[len - 1] == '/') buf[--len] = '\0'; char *created = mkdtemp(buf); - if (!created) fail("Failed to create temporary directory: ", path_str, " (", strerror(errno), ")"); + if (!created) return NONE_PATH; return Path$from_str(created); } public -Path_t Path$write_unique_bytes(Path_t path, List_t bytes) { +OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) { path = Path$expand_home(path); const char *path_str = Path$as_c_string(path); size_t len = strlen(path_str); @@ -553,23 +563,23 @@ Path_t Path$write_unique_bytes(Path_t path, List_t bytes) { ++suffixlen; int fd = mkstemps(buf, suffixlen); - if (fd == -1) fail("Could not write to unique file: ", buf, "\n", strerror(errno)); + if (fd == -1) return NONE_PATH; if (bytes.stride != 1) List$compact(&bytes, 1); ssize_t written = write(fd, bytes.data, (size_t)bytes.length); - if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, "\n", strerror(errno)); + if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, " (", strerror(errno), ")"); close(fd); return Path$from_str(buf); } public -Path_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); } +OptionalPath_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); } public -Path_t Path$parent(Path_t path) { +OptionalPath_t Path$parent(Path_t path) { if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) { - return path; + return NONE_PATH; } else if (path.components.length > 0 && !Text$equal_values( *(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1)), @@ -631,11 +641,10 @@ public Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); } public -Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) { - if (path.components.length == 0) fail("A path with no components can't have an extension!"); +OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) { + if (path.components.length == 0) return NONE_PATH; - if (Text$has(extension, Text("/")) || Text$has(extension, Text(";"))) - fail("Path extension has invalid characters: ", extension); + if (Text$has(extension, Text("/")) || Text$has(extension, Text(";"))) return NONE_PATH; Path_t result = { .$tag = path.$tag, diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 7a175099..4e52866e 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -34,24 +34,24 @@ bool Path$can_execute(Path_t path); OptionalInt64_t Path$modified(Path_t path, bool follow_symlinks); OptionalInt64_t Path$accessed(Path_t path, bool follow_symlinks); OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks); -void Path$write(Path_t path, Text_t text, int permissions); -void Path$write_bytes(Path_t path, List_t bytes, int permissions); -void Path$append(Path_t path, Text_t text, int permissions); -void Path$append_bytes(Path_t path, List_t bytes, int permissions); +Result_t Path$write(Path_t path, Text_t text, int permissions); +Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions); +Result_t Path$append(Path_t path, Text_t text, int permissions); +Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions); OptionalText_t Path$read(Path_t path); OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t limit); -void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); +Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); OptionalText_t Path$owner(Path_t path, bool follow_symlinks); OptionalText_t Path$group(Path_t path, bool follow_symlinks); -void Path$remove(Path_t path, bool ignore_missing); -void Path$create_directory(Path_t path, int permissions, bool recursive); +Result_t Path$remove(Path_t path, bool ignore_missing); +Result_t Path$create_directory(Path_t path, int permissions, bool recursive); List_t Path$children(Path_t path, bool include_hidden); List_t Path$files(Path_t path, bool include_hidden); List_t Path$subdirectories(Path_t path, bool include_hidden); -Path_t Path$unique_directory(Path_t path); -Path_t Path$write_unique(Path_t path, Text_t text); -Path_t Path$write_unique_bytes(Path_t path, List_t bytes); -Path_t Path$parent(Path_t path); +OptionalPath_t Path$unique_directory(Path_t path); +OptionalPath_t Path$write_unique(Path_t path, Text_t text); +OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes); +OptionalPath_t Path$parent(Path_t path); Text_t Path$base_name(Path_t path); Text_t Path$extension(Path_t path, bool full); bool Path$has_extension(Path_t path, Text_t extension); diff --git a/src/stdlib/result.c b/src/stdlib/result.c new file mode 100644 index 00000000..8fd2ca1e --- /dev/null +++ b/src/stdlib/result.c @@ -0,0 +1,65 @@ +// Result (Success/Failure) type info +#include +#include +#include +#include +#include + +#include "enums.h" +#include "structs.h" +#include "text.h" +#include "util.h" + +public +const TypeInfo_t Result$Success$$info = { + .size = sizeof(Result$Success$$type), + .align = __alignof__(Result$Success$$type), + .tag = StructInfo, + .StructInfo = + { + .name = "Success", + .num_fields = 0, + }, + .metamethods = Struct$metamethods, +}; + +public +const TypeInfo_t Result$Failure$$info = { + .size = sizeof(Result$Failure$$type), + .align = __alignof__(Result$Failure$$type), + .tag = StructInfo, + .StructInfo = + { + .name = "Failure", + .num_fields = 1, + .fields = + (NamedType_t[1]){ + {.name = "reason", .type = &Text$info}, + }, + }, + .metamethods = Struct$metamethods, +}; + +public +const TypeInfo_t Result$$info = { + .size = sizeof(Result_t), + .align = __alignof__(Result_t), + .tag = EnumInfo, + .EnumInfo = + { + .name = "Result", + .num_tags = 2, + .tags = + (NamedType_t[2]){ + { + .name = "Success", + .type = &Result$Success$$info, + }, + { + .name = "Failure", + .type = &Result$Failure$$info, + }, + }, + }, + .metamethods = Enum$metamethods, +}; diff --git a/src/stdlib/result.h b/src/stdlib/result.h new file mode 100644 index 00000000..328480e7 --- /dev/null +++ b/src/stdlib/result.h @@ -0,0 +1,9 @@ +#pragma once + +// Result type for Success/Failure + +#include "types.h" + +extern const TypeInfo_t Result$Success$$info; +extern const TypeInfo_t Result$Failure$$info; +extern const TypeInfo_t Result$$info; diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h index 1ff065b9..ff8f90a2 100644 --- a/src/stdlib/tomo.h +++ b/src/stdlib/tomo.h @@ -25,6 +25,7 @@ #include "paths.h" // IWYU pragma: export #include "pointers.h" // IWYU pragma: export #include "print.h" // IWYU pragma: export +#include "result.h" // IWYU pragma: export #include "siphash.h" // IWYU pragma: export #include "stacktrace.h" // IWYU pragma: export #include "structs.h" // IWYU pragma: export diff --git a/test/paths.tm b/test/paths.tm index d7630fc3..a0ae4384 100644 --- a/test/paths.tm +++ b/test/paths.tm @@ -17,8 +17,8 @@ func main() assert optional_path == (./foo) >> tmpfile := (tmpdir++(./one.txt)) - >> tmpfile.write("Hello world") - >> tmpfile.append("!") + >> tmpfile.write("Hello world")! + >> tmpfile.append("!")! assert tmpfile.read() == "Hello world!" assert tmpfile.read_bytes()! == [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21] assert tmpdir.files().has(tmpfile) @@ -35,11 +35,11 @@ func main() else pass - >> tmpfile.remove() + >> tmpfile.remove()! assert tmpdir.files().has(tmpfile) == no - >> tmpdir.remove() + >> tmpdir.remove()! >> p := (/foo/baz.x/qux.tar.gz) assert p.base_name() == "qux.tar.gz" @@ -59,12 +59,12 @@ func main() assert (~/.foo.baz.qux).extension() == "baz.qux" - assert (/).parent() == (/) assert (~/x/.).parent() == (~) assert (~/x).parent() == (~) assert (.).parent() == (..) assert (..).parent() == (../..) assert (../foo).parent() == (..) + assert (/).parent() == none # Concatenation tests: say("Basic relative path concatenation:") -- cgit v1.2.3 From 890d16fae503a5ffbb9f3c3e70e02938b4f55756 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 22:56:01 -0500 Subject: Remove dead code --- src/stdlib/metamethods.c | 6 ------ src/stdlib/metamethods.h | 1 - 2 files changed, 7 deletions(-) diff --git a/src/stdlib/metamethods.c b/src/stdlib/metamethods.c index 3eff2dd3..70b8e4e1 100644 --- a/src/stdlib/metamethods.c +++ b/src/stdlib/metamethods.c @@ -85,12 +85,6 @@ void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type) { fclose(input); } -public -int generic_print(const void *obj, bool colorize, const TypeInfo_t *type) { - Text_t text = generic_as_text(obj, colorize, type); - return Text$print(stdout, text) + fputc('\n', stdout); -} - __attribute__((noreturn)) public void cannot_serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) { (void)obj, (void)out, (void)pointers; diff --git a/src/stdlib/metamethods.h b/src/stdlib/metamethods.h index 05d91c5c..7db041e7 100644 --- a/src/stdlib/metamethods.h +++ b/src/stdlib/metamethods.h @@ -16,6 +16,5 @@ void _serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t List_t generic_serialize(const void *x, const TypeInfo_t *type); void _deserialize(FILE *input, void *outval, List_t *pointers, const TypeInfo_t *type); void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type); -int generic_print(const void *obj, bool colorize, const TypeInfo_t *type); void cannot_serialize(const void *, FILE *, Table_t *, const TypeInfo_t *type); void cannot_deserialize(FILE *, void *, List_t *, const TypeInfo_t *type); -- cgit v1.2.3 From 4c5d15115d2d6c08c080d7d4a39efe039658d616 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 22:57:28 -0500 Subject: Add comments --- src/stdlib/paths.h | 1 + src/stdlib/text.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 4e52866e..881a3c78 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -11,6 +11,7 @@ Path_t Path$from_str(const char *str); Path_t Path$from_text(Text_t text); +// This function is defined as an extern in `src/stdlib/print.h` // int Path$print(FILE *f, Path_t path); // UNSAFE: this works because each type of path has a .components in the same place #define Path$components(path) ((path).components) diff --git a/src/stdlib/text.h b/src/stdlib/text.h index fba8b08f..9ad7441c 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -50,6 +50,7 @@ static inline Text_t Text_from_text(Text_t t) { return t; } Text_t Text$_concat(int n, Text_t items[n]); #define Text$concat(...) Text$_concat(sizeof((Text_t[]){__VA_ARGS__}) / sizeof(Text_t), (Text_t[]){__VA_ARGS__}) #define Texts(...) Text$concat(MAP_LIST(convert_to_text, __VA_ARGS__)) +// This function is defined as an extern in `src/stdlib/print.h` // int Text$print(FILE *stream, Text_t t); Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int); Text_t Text$from(Text_t text, Int_t first); -- cgit v1.2.3 From 08c47e1fabd1a2fb43c18828db8ad845f7db3a99 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 23:08:08 -0500 Subject: More correct handling for sleep() --- src/environment.c | 2 +- src/stdlib/stdlib.c | 10 ++++++++-- src/stdlib/stdlib.h | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/environment.c b/src/environment.c index f84b6697..cf662749 100644 --- a/src/environment.c +++ b/src/environment.c @@ -541,7 +541,7 @@ env_t *global_env(bool source_mapping) { {"print", "say", "func(text:Text, newline=yes)"}, {"say", "say", "func(text:Text, newline=yes)"}, {"setenv", "setenv_text", "func(name:Text, value:Text?)"}, - {"sleep", "sleep_num", "func(seconds:Num)"}, + {"sleep", "sleep_seconds", "func(seconds:Num)"}, }; for (size_t i = 0; i < sizeof(global_vars) / sizeof(global_vars[0]); i++) { diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index 8ec9e90b..defb263c 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -205,11 +206,16 @@ cleanup: } public -void sleep_num(double seconds) { +void sleep_seconds(double seconds) { + if (seconds < 0) fail("Cannot sleep for a negative amount of time: ", seconds); + else if (isnan(seconds)) fail("Cannot sleep for a time that is NaN"); struct timespec ts; ts.tv_sec = (time_t)seconds; ts.tv_nsec = (long)((seconds - (double)ts.tv_sec) * 1e9); - nanosleep(&ts, NULL); + while (nanosleep(&ts, NULL) != 0) { + if (errno == EINTR) continue; + fail("Failed to sleep for the requested time (", strerror(errno), ")"); + } } public diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 392b5f23..3afe3529 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -79,6 +79,6 @@ Text_t ask(Text_t prompt, bool bold, bool force_tty); _Noreturn void tomo_exit(Text_t text, int32_t status); Closure_t spawn(Closure_t fn); -void sleep_num(double seconds); +void sleep_seconds(double seconds); OptionalText_t getenv_text(Text_t name); void setenv_text(Text_t name, Text_t value); -- cgit v1.2.3 From 86228917b98a3ef4019e9e18fcafacc948ffcfd1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 7 Dec 2025 23:12:33 -0500 Subject: Better error handling for setenv() --- src/stdlib/stdlib.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index defb263c..f4e6d678 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -226,8 +226,16 @@ OptionalText_t getenv_text(Text_t name) { public void setenv_text(Text_t name, OptionalText_t value) { - if (value.tag == TEXT_NONE) unsetenv(Text$as_c_string(name)); - else setenv(Text$as_c_string(name), Text$as_c_string(value), 1); + int status; + if (value.tag == TEXT_NONE) { + status = unsetenv(Text$as_c_string(name)); + } else { + status = setenv(Text$as_c_string(name), Text$as_c_string(value), 1); + } + if (status != 0) { + if (errno == EINVAL) fail("Invalid environment variable name: ", Text$quoted(name, false, Text("\""))); + else fail("Failed to set environment variable (", strerror(errno)); + } } typedef struct cleanup_s { -- cgit v1.2.3 From 5a194a3e0dc01d7ba0d9ad81030284ebda13cbd6 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 8 Dec 2025 02:08:47 -0500 Subject: Add checks for unused variables --- CHANGES.md | 1 + src/ast.c | 25 ++--- src/ast.h | 5 +- src/compile/files.c | 3 +- src/compile/functions.c | 282 ++++++++++++++++++++++++++++++++++-------------- src/compile/headers.c | 6 +- test/import.tm | 2 + test/paths.tm | 2 +- 8 files changed, 227 insertions(+), 99 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 51c4dcb9..d1ca7769 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ - `Path.write()` - `Path.write_bytes()` - `Path.parent()` returns `none` if path is `(/)` (file root) +- Added check for unused variables. ## v2025-11-30 diff --git a/src/ast.c b/src/ast.c index b2730d21..e87ca005 100644 --- a/src/ast.c +++ b/src/ast.c @@ -443,19 +443,19 @@ CONSTFUNC ast_e binop_tag(ast_e tag) { } } -static void ast_visit_list(ast_list_t *ast_list, void (*visitor)(ast_t *, void *), void *userdata) { +static void ast_visit_list(ast_list_t *ast_list, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) { for (ast_list_t *ast = ast_list; ast; ast = ast->next) ast_visit(ast->ast, visitor, userdata); } -static void ast_visit_args(arg_ast_t *args, void (*visitor)(ast_t *, void *), void *userdata) { +static void ast_visit_args(arg_ast_t *args, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) { for (arg_ast_t *arg = args; arg; arg = arg->next) ast_visit(arg->value, visitor, userdata); } -void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) { +void ast_visit(ast_t *ast, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) { if (!ast) return; - visitor(ast, userdata); + if (visitor(ast, userdata) == VISIT_STOP) return; switch (ast->tag) { case Unknown: @@ -686,26 +686,25 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) { static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) { if (ast == NULL) return; - void (*visit)(type_ast_t *, void *) = ((Closure_t *)userdata)->fn; + visit_behavior_t (*visit)(type_ast_t *, void *) = ((Closure_t *)userdata)->fn; void *visitor_userdata = ((Closure_t *)userdata)->userdata; + if (visit(ast, visitor_userdata) == VISIT_STOP) return; + switch (ast->tag) { case UnknownTypeAST: - case VarTypeAST: visit(ast, visitor_userdata); break; + case VarTypeAST: break; case PointerTypeAST: { _recursive_type_ast_visit(Match(ast, PointerTypeAST)->pointed, userdata); - visit(ast, visitor_userdata); break; } case ListTypeAST: { _recursive_type_ast_visit(Match(ast, ListTypeAST)->item, userdata); - visit(ast, visitor_userdata); break; } case TableTypeAST: { DeclareMatch(table, ast, TableTypeAST); _recursive_type_ast_visit(table->key, userdata); _recursive_type_ast_visit(table->value, userdata); - visit(ast, visitor_userdata); break; } case FunctionTypeAST: { @@ -713,12 +712,10 @@ static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) { for (arg_ast_t *arg = fn->args; arg; arg = arg->next) _recursive_type_ast_visit(arg->type, userdata); _recursive_type_ast_visit(fn->ret, userdata); - visit(ast, visitor_userdata); break; } case OptionalTypeAST: { _recursive_type_ast_visit(Match(ast, OptionalTypeAST)->type, userdata); - visit(ast, visitor_userdata); break; } case EnumTypeAST: { @@ -727,14 +724,13 @@ static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) { _recursive_type_ast_visit(field->type, userdata); } } - visit(ast, visitor_userdata); break; } default: errx(1, "Invalid type AST"); } } -static void _type_ast_visit(ast_t *ast, void *userdata) { +static visit_behavior_t _type_ast_visit(ast_t *ast, void *userdata) { switch (ast->tag) { case Declare: { _recursive_type_ast_visit(Match(ast, Declare)->type, userdata); @@ -777,9 +773,10 @@ static void _type_ast_visit(ast_t *ast, void *userdata) { } default: break; } + return VISIT_PROCEED; } -void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata) { +void type_ast_visit(ast_t *ast, visit_behavior_t (*visitor)(type_ast_t *, void *), void *userdata) { Closure_t fn = {.fn = visitor, .userdata = userdata}; ast_visit(ast, _type_ast_visit, &fn); } diff --git a/src/ast.h b/src/ast.h index b852da2a..b6930ab7 100644 --- a/src/ast.h +++ b/src/ast.h @@ -488,6 +488,7 @@ void visit_topologically(ast_list_t *ast, Closure_t fn); CONSTFUNC bool is_update_assignment(ast_t *ast); CONSTFUNC ast_e binop_tag(ast_e tag); CONSTFUNC bool is_binary_operation(ast_t *ast); -void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata); -void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata); +typedef enum { VISIT_STOP, VISIT_PROCEED } visit_behavior_t; +void ast_visit(ast_t *ast, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata); +void type_ast_visit(ast_t *ast, visit_behavior_t (*visitor)(type_ast_t *, void *), void *userdata); OptionalText_t ast_metadata(ast_t *ast, const char *key); diff --git a/src/compile/files.c b/src/compile/files.c index 03c08bf6..27c2e041 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -152,7 +152,7 @@ typedef struct { Text_t *code; } compile_info_t; -static void add_type_infos(type_ast_t *type_ast, void *userdata) { +static visit_behavior_t add_type_infos(type_ast_t *type_ast, void *userdata) { if (type_ast && type_ast->tag == EnumTypeAST) { compile_info_t *info = (compile_info_t *)userdata; // Force the type to get defined: @@ -164,6 +164,7 @@ static void add_type_infos(type_ast_t *type_ast, void *userdata) { compile_enum_constructors(info->env, String("enum$", (int64_t)(type_ast->start - type_ast->file->text)), Match(type_ast, EnumTypeAST)->tags)); } + return VISIT_PROCEED; } public diff --git a/src/compile/functions.c b/src/compile/functions.c index 63d7d23d..ec37c0ad 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -3,6 +3,7 @@ #include "../ast.h" #include "../environment.h" #include "../naming.h" +#include "../stdlib/c_strings.h" #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/nums.h" @@ -247,85 +248,6 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { } } -public -Text_t compile_lambda(env_t *env, ast_t *ast) { - DeclareMatch(lambda, ast, Lambda); - Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); - - env_t *body_scope = fresh_scope(env); - body_scope->deferred = NULL; - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); - } - - body_scope->fn = ast; - - Table_t closed_vars = get_closed_vars(env, lambda->args, ast); - if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata - Text_t def = Text("typedef struct {"); - for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (has_stack_memory(entry->b->type)) - code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), - " stack memory in the variable `", entry->name, - "`, but the function may outlive the stack memory"); - if (entry->b->type->tag == ModuleType) continue; - set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); - def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); - } - def = Texts(def, "} ", name, "$userdata_t;"); - env->code->local_typedefs = Texts(env->code->local_typedefs, def); - } - - type_t *ret_t = get_function_return_type(env, ast); - Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); - } - - Text_t userdata; - if (Table$length(closed_vars) == 0) { - code = Texts(code, "void *_)"); - userdata = Text("NULL"); - } else { - userdata = Texts("new(", name, "$userdata_t"); - for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (entry->b->type->tag == ModuleType) continue; - binding_t *b = get_binding(env, entry->name); - assert(b); - Text_t binding_code = b->code; - if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); - else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); - else userdata = Texts(userdata, ", ", binding_code); - } - userdata = Texts(userdata, ")"); - code = Texts(code, name, "$userdata_t *userdata)"); - } - - Text_t body = EMPTY_TEXT; - for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { - if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType - || get_type(body_scope, stmt->ast)->tag == ReturnType) - body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); - else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); - bind_statement(body_scope, stmt->ast); - } - if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) - body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); - - env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); - return Texts("((Closure_t){", name, ", ", userdata, "})"); -} - static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t *env, ast_t *ast) { if (ast == NULL) return; @@ -595,6 +517,206 @@ Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) { return closed_vars; } +static visit_behavior_t find_used_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Var: { + const char *name = Match(ast, Var)->name; + Table$str_set(vars, name, ast); + return VISIT_STOP; + } + case Assign: { + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) { + ast_t *index = Match(var, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + var = Match(var, Index)->indexed; + } else if (var->tag == FieldAccess) { + var = Match(var, FieldAccess)->fielded; + } else { + break; + } + } + } + for (ast_list_t *val = Match(ast, Assign)->values; val; val = val->next) { + ast_visit(val->ast, find_used_variables, userdata); + } + return VISIT_STOP; + } + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *lhs = operands.lhs; + for (;;) { + if (lhs->tag == Index) { + ast_t *index = Match(lhs, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + lhs = Match(lhs, Index)->indexed; + } else if (lhs->tag == FieldAccess) { + lhs = Match(lhs, FieldAccess)->fielded; + } else { + break; + } + } + ast_visit(operands.rhs, find_used_variables, userdata); + return VISIT_STOP; + } + case Declare: { + ast_visit(Match(ast, Declare)->value, find_used_variables, userdata); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static visit_behavior_t find_assigned_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Assign: + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + } + return VISIT_STOP; + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *var = operands.lhs; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + return VISIT_STOP; + } + case Declare: { + ast_t *var = Match(ast, Declare)->var; + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { + Table_t used_vars = EMPTY_TABLE; + ast_visit(body, find_used_variables, &used_vars); + Table_t assigned_vars = EMPTY_TABLE; + ast_visit(body, find_assigned_variables, &assigned_vars); + + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + if (arg_type->tag == PointerType) { + Table$str_remove(&assigned_vars, arg->name); + } + } + + Table_t unused = Table$without(assigned_vars, used_vars, Table$info(&CString$info, &Present$$info)); + for (int64_t i = 0; i < (int64_t)unused.entries.length; i++) { + struct { + const char *name; + } *entry = unused.entries.data + i * unused.entries.stride; + if (streq(entry->name, "_")) continue; + ast_t *var = Table$str_get(assigned_vars, entry->name); + code_err(var, "This variable was assigned to, but never read from."); + } +} + +public +Text_t compile_lambda(env_t *env, ast_t *ast) { + DeclareMatch(lambda, ast, Lambda); + Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); + + env_t *body_scope = fresh_scope(env); + body_scope->deferred = NULL; + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + body_scope->fn = ast; + + Table_t closed_vars = get_closed_vars(env, lambda->args, ast); + if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata + Text_t def = Text("typedef struct {"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (has_stack_memory(entry->b->type)) + code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), + " stack memory in the variable `", entry->name, + "`, but the function may outlive the stack memory"); + if (entry->b->type->tag == ModuleType) continue; + set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); + def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); + } + def = Texts(def, "} ", name, "$userdata_t;"); + env->code->local_typedefs = Texts(env->code->local_typedefs, def); + } + + type_t *ret_t = get_function_return_type(env, ast); + Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); + } + + Text_t userdata; + if (Table$length(closed_vars) == 0) { + code = Texts(code, "void *_)"); + userdata = Text("NULL"); + } else { + userdata = Texts("new(", name, "$userdata_t"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (entry->b->type->tag == ModuleType) continue; + binding_t *b = get_binding(env, entry->name); + assert(b); + Text_t binding_code = b->code; + if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); + else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); + else userdata = Texts(userdata, ", ", binding_code); + } + userdata = Texts(userdata, ")"); + code = Texts(code, name, "$userdata_t *userdata)"); + } + + Text_t body = EMPTY_TEXT; + for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType + || get_type(body_scope, stmt->ast)->tag == ReturnType) + body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); + else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); + bind_statement(body_scope, stmt->ast); + } + if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) + body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); + + env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); + + check_unused_vars(env, lambda->args, lambda->body); + + return Texts("((Closure_t){", name, ", ", userdata, "})"); +} + public Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) { bool is_private = false; @@ -785,6 +907,8 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static } } + check_unused_vars(env, args, body); + return definition; } diff --git a/src/compile/headers.c b/src/compile/headers.c index dc57da77..e90556a1 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -104,8 +104,8 @@ static void _define_types_and_funcs(compile_typedef_info_t *info, ast_t *ast) { compile_statement_namespace_header(info->env, info->header_path, ast)); } -static void add_type_headers(type_ast_t *type_ast, void *userdata) { - if (!type_ast) return; +static visit_behavior_t add_type_headers(type_ast_t *type_ast, void *userdata) { + if (!type_ast) return VISIT_STOP; if (type_ast->tag == EnumTypeAST) { compile_typedef_info_t *info = (compile_typedef_info_t *)userdata; @@ -126,6 +126,8 @@ static void add_type_headers(type_ast_t *type_ast, void *userdata) { *info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags)); } + + return VISIT_PROCEED; } public diff --git a/test/import.tm b/test/import.tm index cb642ac6..e05d7e4f 100644 --- a/test/import.tm +++ b/test/import.tm @@ -9,9 +9,11 @@ func returns_imported_type(->ImportedType) func main() >> empty : [vectors.Vec2] + assert empty == [] assert returns_vec() == Vec2(x=1, y=2) >> imported : [ImportedType] + assert imported == [] assert returns_imported_type() == ImportedType("Hello") assert needs_initializing == 999999999999999999 diff --git a/test/paths.tm b/test/paths.tm index a0ae4384..6622847e 100644 --- a/test/paths.tm +++ b/test/paths.tm @@ -30,7 +30,7 @@ func main() assert (./does-not-exist.xxx).read() == none assert (./does-not-exist.xxx).read_bytes() == none - if lines := (./does-not-exist.xxx).by_line() then + if (./does-not-exist.xxx).by_line() fail("I could read lines in a nonexistent file") else pass -- cgit v1.2.3 From 02886fab651d3f64d2c8ded5597e6c075dc69b5f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 10 Dec 2025 14:49:21 -0500 Subject: Add docs on enum field access and `!` behavior --- docs/enums.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/docs/enums.md b/docs/enums.md index 85e82f36..5d6a2fb9 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -50,7 +50,7 @@ from a function with an explicit return type: ```tomo enum ArgumentType(AnInt(x:Int), SomeText(text:Text)) -enum ReturnType(Nothing, AnInt(x:Int)) +enum ReturnType(AnInt(x:Int), Nothing) func increment(arg:ArgumentType -> ReturnType) when arg is AnInt(x) @@ -123,3 +123,92 @@ func pad_text_wrapper(text:Text, width:Int, align:???) **Note:** Each enum type is distinct, regardless of whether the enum shares the same values with another enum, so you can't define another enum with the same values and use that in places where a different anonymous enum is expected. + + +## Result Type + +One very common pattern for enums is something which can either succeed or fail +with an informative message. For example, if you try to delete a file, you will +either succeed or fail, and if you fail, you might want to know that it was +because the file doesn't exist or if you don't have permission to delete it. +For this common pattern, Tomo includes a `Result` enum type in the standard +library: + +``` +enum Result(Success, Failure(reason:Text)) +``` + +You're free to define your own similar enum type or reuse this one as you see +fit. + + +## Field Access + +In some cases, a full `when` block is overkill when a value is assumed to have +a certain tag. In those cases, you can access the enum's tag value using field +access. The resulting value is `none` if the enum value is not the expected tag, +otherwise it will hold the struct contents of the enum value for the given tag. + +```tomo +func maybe_fail(should_fail:Bool -> Result) + if should_fail + return Failure("It failed") + else + return Success + +>> maybe_fail(yes).Failure +# Prints 'Failure("It failed")' +assert maybe_fail(yes).Failure!.text == "It failed" + +>> maybe_fail(no).Failure +# Prints 'none' +``` + +## Enum Assertions + +In general, it's best to always handle failure results close to the call site +where they occurred. However, sometimes, there's simply nothing you can do +beyond reporting the error to the user and closing the program. + +```tomo +result := (/tmp/log.txt).append(msg) +when result is Failure(msg) + fail(msg) +is Success + pass +``` + +For these cases, you can reduce the amount of code using a couple of +simplifications. Firstly, you can access `.Success` to get the optional empty +value of the Result enum (or `none` if there was a failure) and use `!` to +assert that the value is non-`none`. + +```tomo +(/tmp/log.txt).append(msg).Success! +``` + +Tomo is smart enough to give you a good error message in this case that will +look something like: + +``` +This was expected to be Success, but it was: +Failure("Could not write to file: /tmp/log.txt (Permission denied)") +``` + +You can further reduce the verbosity of this code by applying the `!` directly +to the Result enum value: + +```tomo +(/tmp/log.txt).append(msg)! +``` + +When the `!` operator is applied to an enum value, the effect is the same as +applying `.Success!` or whatever the first tag in the enum definition is. + +```tomo +enum Foo(A(member:Int), B) + +f := Foo.A(123) +assert f! == f.A! +assert f!.member == 123 +``` -- cgit v1.2.3