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