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 --- docs/command-line-parsing.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'docs') 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). -- 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 ++ 1 file changed, 2 insertions(+) (limited to 'docs') 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). -- 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 +++ 1 file changed, 3 insertions(+) (limited to 'docs') 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 -- 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. --- docs/libraries.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'docs') 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 -- 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(-) (limited to 'docs') 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(-) (limited to 'docs') 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 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(+) (limited to 'docs') 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 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(-) (limited to 'docs') 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