aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-12-11 13:50:01 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-12-11 13:52:46 -0500
commit7f8f2117799cdfa6b62909a9182b5adade1d0bd2 (patch)
tree1db466db870768e952f50572453660e090e434e0
parent630f910563b6f27dd34a4a0496a43d32539eadcb (diff)
parent02886fab651d3f64d2c8ded5597e6c075dc69b5f (diff)
Merge branch 'dev' into constructive-reals
-rw-r--r--CHANGES.md142
-rw-r--r--Makefile44
-rw-r--r--api/api.md132
-rw-r--r--api/builtins.md30
-rw-r--r--api/builtins.yaml33
-rw-r--r--api/integers.md6
-rw-r--r--api/integers.yaml12
-rw-r--r--api/paths.md71
-rw-r--r--api/paths.yaml73
-rw-r--r--api/text.md25
-rw-r--r--api/text.yaml28
-rw-r--r--docs/bytes.md2
-rw-r--r--docs/command-line-parsing.md38
-rw-r--r--docs/enums.md103
-rw-r--r--docs/functions.md22
-rw-r--r--docs/integers.md31
-rw-r--r--docs/iterators.md17
-rw-r--r--docs/langs.md4
-rw-r--r--docs/libraries.md13
-rw-r--r--docs/lists.md46
-rw-r--r--docs/nums.md31
-rw-r--r--docs/operators.md76
-rw-r--r--docs/optionals.md9
-rw-r--r--docs/paths.md6
-rw-r--r--docs/pointers.md18
-rw-r--r--docs/reductions.md56
-rw-r--r--docs/serialization.md12
-rw-r--r--docs/structs.md23
-rw-r--r--docs/tables.md67
-rw-r--r--docs/text.md6
-rw-r--r--docs/versions.md20
-rw-r--r--examples/learnxiny.tm10
-rw-r--r--link_versions.sh16
-rwxr-xr-xlocal-tomo4
-rw-r--r--man/man3/tomo-Bool.319
-rw-r--r--man/man3/tomo-Bool.parse.34
-rw-r--r--man/man3/tomo-Byte.351
-rw-r--r--man/man3/tomo-Byte.get_bit.314
-rw-r--r--man/man3/tomo-Byte.hex.36
-rw-r--r--man/man3/tomo-Byte.is_between.316
-rw-r--r--man/man3/tomo-Byte.parse.34
-rw-r--r--man/man3/tomo-Byte.to.36
-rw-r--r--man/man3/tomo-CString.327
-rw-r--r--man/man3/tomo-CString.as_text.312
-rw-r--r--man/man3/tomo-CString.join.314
-rw-r--r--man/man3/tomo-Float64.1_PI.34
-rw-r--r--man/man3/tomo-Float64.2_PI.34
-rw-r--r--man/man3/tomo-Float64.2_SQRTPI.34
-rw-r--r--man/man3/tomo-Float64.3523
-rw-r--r--man/man3/tomo-Float64.E.34
-rw-r--r--man/man3/tomo-Float64.INF.34
-rw-r--r--man/man3/tomo-Float64.LN10.34
-rw-r--r--man/man3/tomo-Float64.LN2.34
-rw-r--r--man/man3/tomo-Float64.LOG2E.34
-rw-r--r--man/man3/tomo-Float64.PI.34
-rw-r--r--man/man3/tomo-Float64.PI_2.34
-rw-r--r--man/man3/tomo-Float64.PI_4.34
-rw-r--r--man/man3/tomo-Float64.SQRT1_2.34
-rw-r--r--man/man3/tomo-Float64.SQRT2.34
-rw-r--r--man/man3/tomo-Float64.TAU.34
-rw-r--r--man/man3/tomo-Float64.abs.312
-rw-r--r--man/man3/tomo-Float64.acos.312
-rw-r--r--man/man3/tomo-Float64.acosh.312
-rw-r--r--man/man3/tomo-Float64.asin.312
-rw-r--r--man/man3/tomo-Float64.asinh.312
-rw-r--r--man/man3/tomo-Float64.atan.312
-rw-r--r--man/man3/tomo-Float64.atan2.314
-rw-r--r--man/man3/tomo-Float64.atanh.312
-rw-r--r--man/man3/tomo-Float64.cbrt.312
-rw-r--r--man/man3/tomo-Float64.ceil.312
-rw-r--r--man/man3/tomo-Float64.clamped.316
-rw-r--r--man/man3/tomo-Float64.copysign.314
-rw-r--r--man/man3/tomo-Float64.cos.312
-rw-r--r--man/man3/tomo-Float64.cosh.312
-rw-r--r--man/man3/tomo-Float64.erf.312
-rw-r--r--man/man3/tomo-Float64.erfc.312
-rw-r--r--man/man3/tomo-Float64.exp.312
-rw-r--r--man/man3/tomo-Float64.exp2.312
-rw-r--r--man/man3/tomo-Float64.expm1.312
-rw-r--r--man/man3/tomo-Float64.fdim.314
-rw-r--r--man/man3/tomo-Float64.floor.312
-rw-r--r--man/man3/tomo-Float64.hypot.314
-rw-r--r--man/man3/tomo-Float64.is_between.316
-rw-r--r--man/man3/tomo-Float64.isfinite.312
-rw-r--r--man/man3/tomo-Float64.isinf.312
-rw-r--r--man/man3/tomo-Float64.j0.312
-rw-r--r--man/man3/tomo-Float64.j1.312
-rw-r--r--man/man3/tomo-Float64.log.312
-rw-r--r--man/man3/tomo-Float64.log10.312
-rw-r--r--man/man3/tomo-Float64.log1p.312
-rw-r--r--man/man3/tomo-Float64.log2.312
-rw-r--r--man/man3/tomo-Float64.logb.312
-rw-r--r--man/man3/tomo-Float64.mix.316
-rw-r--r--man/man3/tomo-Float64.near.38
-rw-r--r--man/man3/tomo-Float64.nextafter.314
-rw-r--r--man/man3/tomo-Float64.parse.34
-rw-r--r--man/man3/tomo-Float64.percent.34
-rw-r--r--man/man3/tomo-Float64.rint.312
-rw-r--r--man/man3/tomo-Float64.round.312
-rw-r--r--man/man3/tomo-Float64.significand.312
-rw-r--r--man/man3/tomo-Float64.sin.312
-rw-r--r--man/man3/tomo-Float64.sinh.312
-rw-r--r--man/man3/tomo-Float64.sqrt.312
-rw-r--r--man/man3/tomo-Float64.tan.312
-rw-r--r--man/man3/tomo-Float64.tanh.312
-rw-r--r--man/man3/tomo-Float64.tgamma.312
-rw-r--r--man/man3/tomo-Float64.trunc.312
-rw-r--r--man/man3/tomo-Float64.with_precision.314
-rw-r--r--man/man3/tomo-Float64.y0.312
-rw-r--r--man/man3/tomo-Float64.y1.312
-rw-r--r--man/man3/tomo-Int.3131
-rw-r--r--man/man3/tomo-Int.abs.312
-rw-r--r--man/man3/tomo-Int.choose.314
-rw-r--r--man/man3/tomo-Int.clamped.316
-rw-r--r--man/man3/tomo-Int.factorial.312
-rw-r--r--man/man3/tomo-Int.get_bit.314
-rw-r--r--man/man3/tomo-Int.hex.34
-rw-r--r--man/man3/tomo-Int.is_between.316
-rw-r--r--man/man3/tomo-Int.is_prime.34
-rw-r--r--man/man3/tomo-Int.next_prime.312
-rw-r--r--man/man3/tomo-Int.octal.34
-rw-r--r--man/man3/tomo-Int.onward.34
-rw-r--r--man/man3/tomo-Int.parse.310
-rw-r--r--man/man3/tomo-Int.prev_prime.312
-rw-r--r--man/man3/tomo-Int.sqrt.312
-rw-r--r--man/man3/tomo-Int.to.36
-rw-r--r--man/man3/tomo-List.3219
-rw-r--r--man/man3/tomo-List.binary_search.34
-rw-r--r--man/man3/tomo-List.by.314
-rw-r--r--man/man3/tomo-List.clear.312
-rw-r--r--man/man3/tomo-List.counts.312
-rw-r--r--man/man3/tomo-List.find.314
-rw-r--r--man/man3/tomo-List.from.314
-rw-r--r--man/man3/tomo-List.has.314
-rw-r--r--man/man3/tomo-List.heap_pop.34
-rw-r--r--man/man3/tomo-List.heap_push.34
-rw-r--r--man/man3/tomo-List.heapify.34
-rw-r--r--man/man3/tomo-List.insert.34
-rw-r--r--man/man3/tomo-List.insert_all.34
-rw-r--r--man/man3/tomo-List.pop.34
-rw-r--r--man/man3/tomo-List.random.36
-rw-r--r--man/man3/tomo-List.remove_at.34
-rw-r--r--man/man3/tomo-List.remove_item.34
-rw-r--r--man/man3/tomo-List.reversed.312
-rw-r--r--man/man3/tomo-List.sample.36
-rw-r--r--man/man3/tomo-List.shuffle.36
-rw-r--r--man/man3/tomo-List.shuffled.36
-rw-r--r--man/man3/tomo-List.slice.316
-rw-r--r--man/man3/tomo-List.sort.34
-rw-r--r--man/man3/tomo-List.sorted.34
-rw-r--r--man/man3/tomo-List.to.314
-rw-r--r--man/man3/tomo-List.unique.312
-rw-r--r--man/man3/tomo-List.where.314
-rw-r--r--man/man3/tomo-Path.3347
-rw-r--r--man/man3/tomo-Path.accessed.34
-rw-r--r--man/man3/tomo-Path.append.310
-rw-r--r--man/man3/tomo-Path.append_bytes.310
-rw-r--r--man/man3/tomo-Path.base_name.312
-rw-r--r--man/man3/tomo-Path.by_line.320
-rw-r--r--man/man3/tomo-Path.can_execute.312
-rw-r--r--man/man3/tomo-Path.can_read.312
-rw-r--r--man/man3/tomo-Path.can_write.312
-rw-r--r--man/man3/tomo-Path.changed.34
-rw-r--r--man/man3/tomo-Path.child.314
-rw-r--r--man/man3/tomo-Path.children.36
-rw-r--r--man/man3/tomo-Path.create_directory.311
-rw-r--r--man/man3/tomo-Path.current_dir.34
-rw-r--r--man/man3/tomo-Path.exists.312
-rw-r--r--man/man3/tomo-Path.expand_home.312
-rw-r--r--man/man3/tomo-Path.extension.36
-rw-r--r--man/man3/tomo-Path.files.34
-rw-r--r--man/man3/tomo-Path.from_components.312
-rw-r--r--man/man3/tomo-Path.glob.312
-rw-r--r--man/man3/tomo-Path.group.34
-rw-r--r--man/man3/tomo-Path.has_extension.314
-rw-r--r--man/man3/tomo-Path.is_directory.34
-rw-r--r--man/man3/tomo-Path.is_file.34
-rw-r--r--man/man3/tomo-Path.is_socket.34
-rw-r--r--man/man3/tomo-Path.is_symlink.312
-rw-r--r--man/man3/tomo-Path.lines.335
-rw-r--r--man/man3/tomo-Path.modified.34
-rw-r--r--man/man3/tomo-Path.owner.34
-rw-r--r--man/man3/tomo-Path.parent.316
-rw-r--r--man/man3/tomo-Path.read.312
-rw-r--r--man/man3/tomo-Path.read_bytes.34
-rw-r--r--man/man3/tomo-Path.relative_to.39
-rw-r--r--man/man3/tomo-Path.remove.38
-rw-r--r--man/man3/tomo-Path.resolved.34
-rw-r--r--man/man3/tomo-Path.set_owner.38
-rw-r--r--man/man3/tomo-Path.sibling.314
-rw-r--r--man/man3/tomo-Path.subdirectories.34
-rw-r--r--man/man3/tomo-Path.unique_directory.312
-rw-r--r--man/man3/tomo-Path.write.38
-rw-r--r--man/man3/tomo-Path.write_bytes.38
-rw-r--r--man/man3/tomo-Path.write_unique.314
-rw-r--r--man/man3/tomo-Path.write_unique_bytes.314
-rw-r--r--man/man3/tomo-Table.399
-rw-r--r--man/man3/tomo-Table.clear.312
-rw-r--r--man/man3/tomo-Table.difference.314
-rw-r--r--man/man3/tomo-Table.get.314
-rw-r--r--man/man3/tomo-Table.get_or_set.316
-rw-r--r--man/man3/tomo-Table.has.314
-rw-r--r--man/man3/tomo-Table.intersection.314
-rw-r--r--man/man3/tomo-Table.remove.314
-rw-r--r--man/man3/tomo-Table.set.316
-rw-r--r--man/man3/tomo-Table.with.314
-rw-r--r--man/man3/tomo-Table.with_fallback.314
-rw-r--r--man/man3/tomo-Table.without.314
-rw-r--r--man/man3/tomo-Text.3339
-rw-r--r--man/man3/tomo-Text.as_c_string.312
-rw-r--r--man/man3/tomo-Text.at.314
-rw-r--r--man/man3/tomo-Text.by_line.312
-rw-r--r--man/man3/tomo-Text.by_split.34
-rw-r--r--man/man3/tomo-Text.by_split_any.34
-rw-r--r--man/man3/tomo-Text.caseless_equals.34
-rw-r--r--man/man3/tomo-Text.codepoint_names.312
-rw-r--r--man/man3/tomo-Text.ends_with.34
-rw-r--r--man/man3/tomo-Text.find.340
-rw-r--r--man/man3/tomo-Text.from.314
-rw-r--r--man/man3/tomo-Text.from_c_string.312
-rw-r--r--man/man3/tomo-Text.from_codepoint_names.312
-rw-r--r--man/man3/tomo-Text.from_utf16.312
-rw-r--r--man/man3/tomo-Text.from_utf32.312
-rw-r--r--man/man3/tomo-Text.from_utf8.312
-rw-r--r--man/man3/tomo-Text.has.314
-rw-r--r--man/man3/tomo-Text.join.314
-rw-r--r--man/man3/tomo-Text.left_pad.34
-rw-r--r--man/man3/tomo-Text.lines.312
-rw-r--r--man/man3/tomo-Text.lower.34
-rw-r--r--man/man3/tomo-Text.middle_pad.34
-rw-r--r--man/man3/tomo-Text.quoted.34
-rw-r--r--man/man3/tomo-Text.repeat.314
-rw-r--r--man/man3/tomo-Text.replace.316
-rw-r--r--man/man3/tomo-Text.reversed.312
-rw-r--r--man/man3/tomo-Text.right_pad.34
-rw-r--r--man/man3/tomo-Text.slice.34
-rw-r--r--man/man3/tomo-Text.split.34
-rw-r--r--man/man3/tomo-Text.split_any.34
-rw-r--r--man/man3/tomo-Text.starts_with.34
-rw-r--r--man/man3/tomo-Text.title.34
-rw-r--r--man/man3/tomo-Text.to.314
-rw-r--r--man/man3/tomo-Text.translate.314
-rw-r--r--man/man3/tomo-Text.trim.34
-rw-r--r--man/man3/tomo-Text.upper.34
-rw-r--r--man/man3/tomo-Text.utf16.312
-rw-r--r--man/man3/tomo-Text.utf32.312
-rw-r--r--man/man3/tomo-Text.utf8.312
-rw-r--r--man/man3/tomo-Text.width.312
-rw-r--r--man/man3/tomo-Text.without_prefix.314
-rw-r--r--man/man3/tomo-Text.without_suffix.314
-rw-r--r--man/man3/tomo-ask.32
-rw-r--r--man/man3/tomo-at_cleanup.338
-rw-r--r--man/man3/tomo-exit.34
-rw-r--r--man/man3/tomo-fail.310
-rw-r--r--man/man3/tomo-getenv.310
-rw-r--r--man/man3/tomo-print.32
-rw-r--r--man/man3/tomo-say.32
-rw-r--r--man/man3/tomo-setenv.314
-rw-r--r--man/man3/tomo-sleep.310
-rw-r--r--modules/core.ini18
-rwxr-xr-xscripts/mandoc_gen.py143
-rw-r--r--src/ast.c42
-rw-r--r--src/ast.h14
-rw-r--r--src/compile/assertions.c4
-rw-r--r--src/compile/assignments.c10
-rw-r--r--src/compile/cli.c159
-rw-r--r--src/compile/cli.h5
-rw-r--r--src/compile/comparisons.c8
-rw-r--r--src/compile/conditionals.c15
-rw-r--r--src/compile/enums.c42
-rw-r--r--src/compile/expressions.c45
-rw-r--r--src/compile/files.c6
-rw-r--r--src/compile/functions.c298
-rw-r--r--src/compile/headers.c73
-rw-r--r--src/compile/lists.c4
-rw-r--r--src/compile/optionals.c69
-rw-r--r--src/compile/pointers.c4
-rw-r--r--src/compile/promotions.c31
-rw-r--r--src/compile/statements.c11
-rw-r--r--src/compile/tables.c10
-rw-r--r--src/compile/types.c3
-rw-r--r--src/compile/whens.c20
-rw-r--r--src/environment.c76
-rw-r--r--src/environment.h4
-rw-r--r--src/modules.c15
-rw-r--r--src/naming.c4
-rw-r--r--src/parse/expressions.c2
-rw-r--r--src/parse/files.c41
-rw-r--r--src/parse/text.c12
-rw-r--r--src/parse/text.h4
-rw-r--r--src/stdlib/bigint.c116
-rw-r--r--src/stdlib/bigint.h2
-rw-r--r--src/stdlib/bytes.c4
-rw-r--r--src/stdlib/bytes.h2
-rw-r--r--src/stdlib/cli.c25
-rw-r--r--src/stdlib/datatypes.h86
-rw-r--r--src/stdlib/floatX.c.h1
-rw-r--r--src/stdlib/intX.c.h4
-rw-r--r--src/stdlib/intX.h2
-rw-r--r--src/stdlib/memory.c2
-rw-r--r--src/stdlib/metamethods.c6
-rw-r--r--src/stdlib/metamethods.h1
-rw-r--r--src/stdlib/optionals.h2
-rw-r--r--src/stdlib/paths.c361
-rw-r--r--src/stdlib/paths.h31
-rw-r--r--src/stdlib/print.c2
-rw-r--r--src/stdlib/result.c65
-rw-r--r--src/stdlib/result.h9
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c48
-rw-r--r--src/stdlib/stdlib.h10
-rw-r--r--src/stdlib/structs.c11
-rw-r--r--src/stdlib/structs.h2
-rw-r--r--src/stdlib/tables.c12
-rw-r--r--src/stdlib/tables.h2
-rw-r--r--src/stdlib/text.c27
-rw-r--r--src/stdlib/text.h2
-rw-r--r--src/stdlib/tomo.h1
-rw-r--r--src/tomo.c62
-rw-r--r--src/typecheck.c93
-rw-r--r--src/types.c132
-rw-r--r--src/types.h7
-rw-r--r--test/enums.tm37
-rw-r--r--test/import.tm2
-rw-r--r--test/integers.tm21
-rw-r--r--test/optionals.tm20
-rw-r--r--test/paths.tm22
-rw-r--r--test/text.tm8
-rw-r--r--test/values.tm55
329 files changed, 5577 insertions, 2068 deletions
diff --git a/CHANGES.md b/CHANGES.md
index a5f43353..ae82fde8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,40 +1,108 @@
# 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`
+## v2025-12-06
+
+- 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.
+- 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)
+- Added check for unused variables.
+
+## 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
+
+- Fix for undefined behavior on enums and structs with padding.
+
+## 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"
+ USAGE: "Usage text"
+ 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.
- Renamed `Num` -> `Float64` and `Num32` -> `Float32`
- Library installation has been cleaned up a bit.
+
+### Type Changes
+
- List indexing now gives an optional value.
- Added support for inline anonymous enums
-- 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}`).
-- 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]`
+- 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.
+- 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.
+- 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
@@ -43,16 +111,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`.
## v0.3
diff --git a/Makefile b/Makefile
index f4437d50..d4bb4e3a 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)
@@ -166,15 +167,17 @@ src/stdlib/int64.o src/stdlib/int32.o src/stdlib/int16.o src/stdlib/int8.o: src/
# Float implementations depend on the shared header:
src/stdlib/float32.o src/stdlib/float64.o: src/stdlib/floatX.c.h src/stdlib/floatX.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 $<
@@ -206,7 +209,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
@@ -226,7 +228,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; \
@@ -240,11 +242,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/"; \
@@ -259,7 +261,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/api/api.md b/api/api.md
index 51c30c2d..61ef9424 100644
--- a/api/api.md
+++ b/api/api.md
@@ -33,10 +33,34 @@ force_tty | `Bool` | Whether or not to force the use of /dev/tty. | `yes`
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.
@@ -142,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.
@@ -150,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.
@@ -1791,7 +1815,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.
@@ -1799,6 +1823,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.
@@ -1819,6 +1844,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
@@ -2528,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.
@@ -2539,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.
@@ -2561,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
@@ -2608,14 +2636,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
@@ -2753,17 +2781,19 @@ 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 -> 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.
+
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.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -3062,6 +3092,26 @@ path | `Path` | The path to check. | -
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
```tomo
@@ -3109,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.
@@ -3118,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:**
@@ -3182,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.
@@ -3203,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:**
@@ -3236,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.
@@ -3248,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:**
@@ -3324,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.
@@ -3335,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:**
@@ -3346,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.
@@ -3357,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:**
@@ -3885,6 +3936,31 @@ 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
```tomo
diff --git a/api/builtins.md b/api/builtins.md
index dfe62306..2dc2ba9e 100644
--- a/api/builtins.md
+++ b/api/builtins.md
@@ -33,10 +33,34 @@ force_tty | `Bool` | Whether or not to force the use of /dev/tty. | `yes`
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.
@@ -142,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.
@@ -150,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 cbbac5ff..aa2eef30 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: >
@@ -131,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/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/api/paths.md b/api/paths.md
index c69e91d9..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
@@ -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
@@ -253,17 +253,19 @@ 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 -> 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.
+
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.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -562,6 +564,26 @@ path | `Path` | The path to check. | -
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
```tomo
@@ -609,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.
@@ -618,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:**
@@ -682,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.
@@ -703,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:**
@@ -736,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.
@@ -748,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:**
@@ -824,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.
@@ -835,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:**
@@ -846,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.
@@ -857,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 532d9c71..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
@@ -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
@@ -241,10 +258,11 @@ 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'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -254,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()
@@ -580,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'
@@ -642,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'
@@ -653,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'
@@ -701,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'
@@ -794,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'
@@ -820,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/api/text.md b/api/text.md
index 9bd99529..928cb6ec 100644
--- a/api/text.md
+++ b/api/text.md
@@ -205,6 +205,31 @@ 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
```tomo
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/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/command-line-parsing.md b/docs/command-line-parsing.md
index 714e6e9c..e1af5419 100644
--- a/docs/command-line-parsing.md
+++ b/docs/command-line-parsing.md
@@ -223,3 +223,41 @@ 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 <n>"
+HELP: "
+ This is some custom help text.
+ You can use these flags:
+
+ --foo <n> The foo parameter
+ --help Show this message
+"
+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
+ 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`: 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/docs/enums.md b/docs/enums.md
index 27ebdb86..5d6a2fb9 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
@@ -52,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)
@@ -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
@@ -127,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
+```
diff --git a/docs/functions.md b/docs/functions.md
index b325ee9a..11e1ea63 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:Float64)
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
`<script>alert('pwned')</script>`.
```
->> username := Text.read_line("Choose a username: ")
-= "<script>alert('pwned')</script>"
+username := Text.read_line("Choose a username: ")
+assert username == "<script>alert('pwned')</script>"
page := $HTML"
<html><body>
Hello $username! How are you?
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/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 911144ef..4cd24c76 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 : Float64
+zero := 0.0
+assert zero == 0
y := 1.0
# Division might produce none:
->> x / y
-= 0 : Float64?
->> x / x
-= none : Float64?
+assert zero / y == 0
+assert zero / zero == none
# Optional types and none values propagate:
->> x/y + 1 + 2
-= 3 : Float64?
->> x/x + 1 + 2
-= none : Float64?
+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 : Float64
+assert zero/zero or -123 == -123
-# This would raise a runtime error if `x` and `y` were zero:
->> (x/y)!
-= 0 : Float64
+# 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:Float64 -> Float64)
# 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 : Float64
+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 6f2e3d5e..a6d6c338 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..12b2eb35 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
@@ -150,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)
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
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).
diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm
index 746e11a1..7af75880 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"
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/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 516d499f..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-11-09 "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 0007b5ad..ad92560e 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-11-09 "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
@@ -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.
@@ -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 ab35f5bb..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-11-09 "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 3fae008f..06e53fb0 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-11-09 "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
@@ -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`
@@ -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 cb7fb2e0..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-11-09 "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 d98e25dd..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-11-09 "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 0332c166..b9a8db95 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-11-09 "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
@@ -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.
@@ -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 70eb3d72..c44df80d 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-11-09 "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
@@ -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.
@@ -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-Float64.1_PI.3 b/man/man3/tomo-Float64.1_PI.3
index c5b57ecf..67f6b16a 100644
--- a/man/man3/tomo-Float64.1_PI.3
+++ b/man/man3/tomo-Float64.1_PI.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.1_PI 3 2025-11-09 "Tomo man-pages"
+.TH Float64.1_PI 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.1_PI \- 1/pi
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
The constant $\frac{1}{\pi}$.
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.2_PI.3 b/man/man3/tomo-Float64.2_PI.3
index 7bc0e51d..5a9edc50 100644
--- a/man/man3/tomo-Float64.2_PI.3
+++ b/man/man3/tomo-Float64.2_PI.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.2_PI 3 2025-11-09 "Tomo man-pages"
+.TH Float64.2_PI 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.2_PI \- 2*pi
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
The constant $2 \times \pi$.
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.2_SQRTPI.3 b/man/man3/tomo-Float64.2_SQRTPI.3
index e54e565f..28d600ce 100644
--- a/man/man3/tomo-Float64.2_SQRTPI.3
+++ b/man/man3/tomo-Float64.2_SQRTPI.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.2_SQRTPI 3 2025-11-09 "Tomo man-pages"
+.TH Float64.2_SQRTPI 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.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-Float64 (3)
diff --git a/man/man3/tomo-Float64.3 b/man/man3/tomo-Float64.3
new file mode 100644
index 00000000..02bea385
--- /dev/null
+++ b/man/man3/tomo-Float64.3
@@ -0,0 +1,523 @@
+'\" t
+.\" Copyright (c) 2025 Bruce Hill
+.\" All rights reserved.
+.\"
+.TH Float64 3 2025-12-11 "Tomo man-pages"
+.SH NAME
+Float64 \- a Tomo type
+.SH LIBRARY
+Tomo Standard Library
+.fi
+.SH METHODS
+
+.TP
+.BI Float64.1_PI\ :\ Float64
+The constant $\frac{1}{\pi}$.
+
+For more, see:
+.BR Tomo-Float64.1_PI (3)
+
+
+.TP
+.BI Float64.2_PI\ :\ Float64
+The constant $2 \times \pi$.
+
+For more, see:
+.BR Tomo-Float64.2_PI (3)
+
+
+.TP
+.BI Float64.2_SQRTPI\ :\ Float64
+The constant $2 \times \sqrt{\pi}$.
+
+For more, see:
+.BR Tomo-Float64.2_SQRTPI (3)
+
+
+.TP
+.BI Float64.E\ :\ Float64
+The base of the natural logarithm ($e$).
+
+For more, see:
+.BR Tomo-Float64.E (3)
+
+
+.TP
+.BI Float64.INF\ :\ Float64
+Positive infinity.
+
+For more, see:
+.BR Tomo-Float64.INF (3)
+
+
+.TP
+.BI Float64.LN10\ :\ Float64
+The natural logarithm of 10.
+
+For more, see:
+.BR Tomo-Float64.LN10 (3)
+
+
+.TP
+.BI Float64.LN2\ :\ Float64
+The natural logarithm of 2.
+
+For more, see:
+.BR Tomo-Float64.LN2 (3)
+
+
+.TP
+.BI Float64.LOG2E\ :\ Float64
+The base 2 logarithm of $e$
+
+For more, see:
+.BR Tomo-Float64.LOG2E (3)
+
+
+.TP
+.BI Float64.PI\ :\ Float64
+Pi ($\pi$).
+
+For more, see:
+.BR Tomo-Float64.PI (3)
+
+
+.TP
+.BI Float64.PI_2\ :\ Float64
+$\frac{\pi}{2}$
+
+For more, see:
+.BR Tomo-Float64.PI_2 (3)
+
+
+.TP
+.BI Float64.PI_4\ :\ Float64
+$\frac{\pi}{4}$
+
+For more, see:
+.BR Tomo-Float64.PI_4 (3)
+
+
+.TP
+.BI Float64.SQRT1_2\ :\ Float64
+$\sqrt{\frac{1}{2}}$
+
+For more, see:
+.BR Tomo-Float64.SQRT1_2 (3)
+
+
+.TP
+.BI Float64.SQRT2\ :\ Float64
+$\sqrt{2}$
+
+For more, see:
+.BR Tomo-Float64.SQRT2 (3)
+
+
+.TP
+.BI Float64.TAU\ :\ Float64
+Tau ($2 \times \pi$)
+
+For more, see:
+.BR Tomo-Float64.TAU (3)
+
+
+.TP
+.BI Float64.abs\ :\ func(n:\ Float64\ ->\ Float64)
+Calculates the absolute value of a number.
+
+For more, see:
+.BR Tomo-Float64.abs (3)
+
+
+.TP
+.BI Float64.acos\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the arc cosine of a number.
+
+For more, see:
+.BR Tomo-Float64.acos (3)
+
+
+.TP
+.BI Float64.acosh\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the inverse hyperbolic cosine of a number.
+
+For more, see:
+.BR Tomo-Float64.acosh (3)
+
+
+.TP
+.BI Float64.asin\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the arc sine of a number.
+
+For more, see:
+.BR Tomo-Float64.asin (3)
+
+
+.TP
+.BI Float64.asinh\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the inverse hyperbolic sine of a number.
+
+For more, see:
+.BR Tomo-Float64.asinh (3)
+
+
+.TP
+.BI Float64.atan\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the arc tangent of a number.
+
+For more, see:
+.BR Tomo-Float64.atan (3)
+
+
+.TP
+.BI Float64.atan2\ :\ func(x:\ Float64,\ y:\ Float64\ ->\ Float64)
+Computes the arc tangent of the quotient of two numbers.
+
+For more, see:
+.BR Tomo-Float64.atan2 (3)
+
+
+.TP
+.BI Float64.atanh\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the inverse hyperbolic tangent of a number.
+
+For more, see:
+.BR Tomo-Float64.atanh (3)
+
+
+.TP
+.BI Float64.cbrt\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the cube root of a number.
+
+For more, see:
+.BR Tomo-Float64.cbrt (3)
+
+
+.TP
+.BI Float64.ceil\ :\ func(x:\ Float64\ ->\ Float64)
+Rounds a number up to the nearest integer.
+
+For more, see:
+.BR Tomo-Float64.ceil (3)
+
+
+.TP
+.BI Float64.clamped\ :\ func(x:\ Float64,\ low:\ Float64,\ high:\ Float64\ ->\ Float64)
+Returns the given number clamped between two values so that it is within that range.
+
+For more, see:
+.BR Tomo-Float64.clamped (3)
+
+
+.TP
+.BI Float64.copysign\ :\ func(x:\ Float64,\ y:\ Float64\ ->\ Float64)
+Copies the sign of one number to another.
+
+For more, see:
+.BR Tomo-Float64.copysign (3)
+
+
+.TP
+.BI Float64.cos\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the cosine of a number (angle in radians).
+
+For more, see:
+.BR Tomo-Float64.cos (3)
+
+
+.TP
+.BI Float64.cosh\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the hyperbolic cosine of a number.
+
+For more, see:
+.BR Tomo-Float64.cosh (3)
+
+
+.TP
+.BI Float64.erf\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the error function of a number.
+
+For more, see:
+.BR Tomo-Float64.erf (3)
+
+
+.TP
+.BI Float64.erfc\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the complementary error function of a number.
+
+For more, see:
+.BR Tomo-Float64.erfc (3)
+
+
+.TP
+.BI Float64.exp\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the exponential function $e^x$ for a number.
+
+For more, see:
+.BR Tomo-Float64.exp (3)
+
+
+.TP
+.BI Float64.exp2\ :\ func(x:\ Float64\ ->\ Float64)
+Computes $2^x$ for a number.
+
+For more, see:
+.BR Tomo-Float64.exp2 (3)
+
+
+.TP
+.BI Float64.expm1\ :\ func(x:\ Float64\ ->\ Float64)
+Computes $e^x - 1$ for a number.
+
+For more, see:
+.BR Tomo-Float64.expm1 (3)
+
+
+.TP
+.BI Float64.fdim\ :\ func(x:\ Float64,\ y:\ Float64\ ->\ Float64)
+Computes the positive difference between two numbers.
+
+For more, see:
+.BR Tomo-Float64.fdim (3)
+
+
+.TP
+.BI Float64.floor\ :\ func(x:\ Float64\ ->\ Float64)
+Rounds a number down to the nearest integer.
+
+For more, see:
+.BR Tomo-Float64.floor (3)
+
+
+.TP
+.BI Float64.hypot\ :\ func(x:\ Float64,\ y:\ Float64\ ->\ Float64)
+Computes the Euclidean norm, $\sqrt{x^2 + y^2}$, of two numbers.
+
+For more, see:
+.BR Tomo-Float64.hypot (3)
+
+
+.TP
+.BI Float64.is_between\ :\ func(x:\ Float64,\ low:\ Float64,\ high:\ Float64\ ->\ Bool)
+Determines if a number is between two numbers (inclusive).
+
+For more, see:
+.BR Tomo-Float64.is_between (3)
+
+
+.TP
+.BI Float64.isfinite\ :\ func(n:\ Float64\ ->\ Bool)
+Checks if a number is finite.
+
+For more, see:
+.BR Tomo-Float64.isfinite (3)
+
+
+.TP
+.BI Float64.isinf\ :\ func(n:\ Float64\ ->\ Bool)
+Checks if a number is infinite.
+
+For more, see:
+.BR Tomo-Float64.isinf (3)
+
+
+.TP
+.BI Float64.j0\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the Bessel function of the first kind of order 0.
+
+For more, see:
+.BR Tomo-Float64.j0 (3)
+
+
+.TP
+.BI Float64.j1\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the Bessel function of the first kind of order 1.
+
+For more, see:
+.BR Tomo-Float64.j1 (3)
+
+
+.TP
+.BI Float64.log\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the natural logarithm (base $e$) of a number.
+
+For more, see:
+.BR Tomo-Float64.log (3)
+
+
+.TP
+.BI Float64.log10\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the base-10 logarithm of a number.
+
+For more, see:
+.BR Tomo-Float64.log10 (3)
+
+
+.TP
+.BI Float64.log1p\ :\ func(x:\ Float64\ ->\ Float64)
+Computes $\log(1 + x)$ for a number.
+
+For more, see:
+.BR Tomo-Float64.log1p (3)
+
+
+.TP
+.BI Float64.log2\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the base-2 logarithm of a number.
+
+For more, see:
+.BR Tomo-Float64.log2 (3)
+
+
+.TP
+.BI Float64.logb\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the binary exponent (base-2 logarithm) of a number.
+
+For more, see:
+.BR Tomo-Float64.logb (3)
+
+
+.TP
+.BI Float64.mix\ :\ func(amount:\ Float64,\ x:\ Float64,\ y:\ Float64\ ->\ Float64)
+Interpolates between two numbers based on a given amount.
+
+For more, see:
+.BR Tomo-Float64.mix (3)
+
+
+.TP
+.BI Float64.near\ :\ func(x:\ Float64,\ y:\ Float64,\ ratio:\ Float64\ =\ 1e-9,\ min_epsilon:\ Float64\ =\ 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-Float64.near (3)
+
+
+.TP
+.BI Float64.nextafter\ :\ func(x:\ Float64,\ y:\ Float64\ ->\ Float64)
+Computes the next representable value after a given number towards a specified direction.
+
+For more, see:
+.BR Tomo-Float64.nextafter (3)
+
+
+.TP
+.BI Float64.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Float64?)
+Converts a text representation of a number into a floating-point number.
+
+For more, see:
+.BR Tomo-Float64.parse (3)
+
+
+.TP
+.BI Float64.percent\ :\ func(n:\ Float64,\ precision:\ Float64\ =\ 0.01\ ->\ Text)
+Convert a number into a percentage text with a percent sign.
+
+For more, see:
+.BR Tomo-Float64.percent (3)
+
+
+.TP
+.BI Float64.rint\ :\ func(x:\ Float64\ ->\ Float64)
+Rounds a number to the nearest integer, with ties rounded to the nearest even integer.
+
+For more, see:
+.BR Tomo-Float64.rint (3)
+
+
+.TP
+.BI Float64.round\ :\ func(x:\ Float64\ ->\ Float64)
+Rounds a number to the nearest whole number integer.
+
+For more, see:
+.BR Tomo-Float64.round (3)
+
+
+.TP
+.BI Float64.significand\ :\ func(x:\ Float64\ ->\ Float64)
+Extracts the significand (or mantissa) of a number.
+
+For more, see:
+.BR Tomo-Float64.significand (3)
+
+
+.TP
+.BI Float64.sin\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the sine of a number (angle in radians).
+
+For more, see:
+.BR Tomo-Float64.sin (3)
+
+
+.TP
+.BI Float64.sinh\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the hyperbolic sine of a number.
+
+For more, see:
+.BR Tomo-Float64.sinh (3)
+
+
+.TP
+.BI Float64.sqrt\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the square root of a number.
+
+For more, see:
+.BR Tomo-Float64.sqrt (3)
+
+
+.TP
+.BI Float64.tan\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the tangent of a number (angle in radians).
+
+For more, see:
+.BR Tomo-Float64.tan (3)
+
+
+.TP
+.BI Float64.tanh\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the hyperbolic tangent of a number.
+
+For more, see:
+.BR Tomo-Float64.tanh (3)
+
+
+.TP
+.BI Float64.tgamma\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the gamma function of a number.
+
+For more, see:
+.BR Tomo-Float64.tgamma (3)
+
+
+.TP
+.BI Float64.trunc\ :\ func(x:\ Float64\ ->\ Float64)
+Truncates a number to the nearest integer towards zero.
+
+For more, see:
+.BR Tomo-Float64.trunc (3)
+
+
+.TP
+.BI Float64.with_precision\ :\ func(n:\ Float64,\ precision:\ Float64\ ->\ Float64)
+Round a number to the given precision level (specified as \fB10\fR, \fB.1\fR, \fB.001\fR etc).
+
+For more, see:
+.BR Tomo-Float64.with_precision (3)
+
+
+.TP
+.BI Float64.y0\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the Bessel function of the second kind of order 0.
+
+For more, see:
+.BR Tomo-Float64.y0 (3)
+
+
+.TP
+.BI Float64.y1\ :\ func(x:\ Float64\ ->\ Float64)
+Computes the Bessel function of the second kind of order 1.
+
+For more, see:
+.BR Tomo-Float64.y1 (3)
+
diff --git a/man/man3/tomo-Float64.E.3 b/man/man3/tomo-Float64.E.3
index 14a6d240..dd131dc5 100644
--- a/man/man3/tomo-Float64.E.3
+++ b/man/man3/tomo-Float64.E.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.E 3 2025-11-09 "Tomo man-pages"
+.TH Float64.E 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.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-Float64 (3)
diff --git a/man/man3/tomo-Float64.INF.3 b/man/man3/tomo-Float64.INF.3
index 93db39ae..bab84c98 100644
--- a/man/man3/tomo-Float64.INF.3
+++ b/man/man3/tomo-Float64.INF.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.INF 3 2025-11-09 "Tomo man-pages"
+.TH Float64.INF 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.INF \- infinity
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
Positive infinity.
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.LN10.3 b/man/man3/tomo-Float64.LN10.3
index 08716f05..e229e0ea 100644
--- a/man/man3/tomo-Float64.LN10.3
+++ b/man/man3/tomo-Float64.LN10.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.LN10 3 2025-11-09 "Tomo man-pages"
+.TH Float64.LN10 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.LN10 \- log(10)
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
The natural logarithm of 10.
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.LN2.3 b/man/man3/tomo-Float64.LN2.3
index 48efd9ec..edb172c8 100644
--- a/man/man3/tomo-Float64.LN2.3
+++ b/man/man3/tomo-Float64.LN2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.LN2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.LN2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.LN2 \- log(2)
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
The natural logarithm of 2.
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.LOG2E.3 b/man/man3/tomo-Float64.LOG2E.3
index 26745247..d92414a0 100644
--- a/man/man3/tomo-Float64.LOG2E.3
+++ b/man/man3/tomo-Float64.LOG2E.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.LOG2E 3 2025-11-09 "Tomo man-pages"
+.TH Float64.LOG2E 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.LOG2E \- log_2(e)
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
The base 2 logarithm of $e$
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.PI.3 b/man/man3/tomo-Float64.PI.3
index 98fe1468..957a7204 100644
--- a/man/man3/tomo-Float64.PI.3
+++ b/man/man3/tomo-Float64.PI.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.PI 3 2025-11-09 "Tomo man-pages"
+.TH Float64.PI 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.PI \- pi
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
Pi ($\pi$).
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.PI_2.3 b/man/man3/tomo-Float64.PI_2.3
index 3dafbcb4..2d3e1c3c 100644
--- a/man/man3/tomo-Float64.PI_2.3
+++ b/man/man3/tomo-Float64.PI_2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.PI_2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.PI_2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.PI_2 \- pi/2
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
$\frac{\pi}{2}$
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.PI_4.3 b/man/man3/tomo-Float64.PI_4.3
index bcf3ab78..9a0109a4 100644
--- a/man/man3/tomo-Float64.PI_4.3
+++ b/man/man3/tomo-Float64.PI_4.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.PI_4 3 2025-11-09 "Tomo man-pages"
+.TH Float64.PI_4 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.PI_4 \- pi/4
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
$\frac{\pi}{4}$
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.SQRT1_2.3 b/man/man3/tomo-Float64.SQRT1_2.3
index e40c68b4..78f3f205 100644
--- a/man/man3/tomo-Float64.SQRT1_2.3
+++ b/man/man3/tomo-Float64.SQRT1_2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.SQRT1_2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.SQRT1_2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.SQRT1_2 \- sqrt(1/2)
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
$\sqrt{\frac{1}{2}}$
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.SQRT2.3 b/man/man3/tomo-Float64.SQRT2.3
index 775a1a34..a830339a 100644
--- a/man/man3/tomo-Float64.SQRT2.3
+++ b/man/man3/tomo-Float64.SQRT2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.SQRT2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.SQRT2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.SQRT2 \- sqrt(2)
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
$\sqrt{2}$
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.TAU.3 b/man/man3/tomo-Float64.TAU.3
index bf351d7f..0ad0087e 100644
--- a/man/man3/tomo-Float64.TAU.3
+++ b/man/man3/tomo-Float64.TAU.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.TAU 3 2025-11-09 "Tomo man-pages"
+.TH Float64.TAU 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.TAU \- 2*pi
.SH LIBRARY
@@ -15,3 +15,5 @@ Tomo Standard Library
Tau ($2 \times \pi$)
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.abs.3 b/man/man3/tomo-Float64.abs.3
index 754fa488..b27e3610 100644
--- a/man/man3/tomo-Float64.abs.3
+++ b/man/man3/tomo-Float64.abs.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.abs 3 2025-11-09 "Tomo man-pages"
+.TH Float64.abs 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.abs \- absolute value
.SH LIBRARY
@@ -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 Float64 The number whose absolute value is to be computed. -
+lb lb lbx
+l l l.
+Name Type Description
+n Float64 The number whose absolute value is to be computed.
.TE
.SH RETURN
The absolute value of `n`.
@@ -31,3 +31,5 @@ The absolute value of `n`.
.EX
assert (-3.5).abs() == 3.5
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.acos.3 b/man/man3/tomo-Float64.acos.3
index d8bd0434..43031316 100644
--- a/man/man3/tomo-Float64.acos.3
+++ b/man/man3/tomo-Float64.acos.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.acos 3 2025-11-09 "Tomo man-pages"
+.TH Float64.acos 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.acos \- arc cosine
.SH LIBRARY
@@ -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 Float64 The number for which the arc cosine is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the arc cosine is to be calculated.
.TE
.SH RETURN
The arc cosine of `x` in radians.
@@ -31,3 +31,5 @@ The arc cosine of `x` in radians.
.EX
assert (0.0).acos() == 1.5708
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.acosh.3 b/man/man3/tomo-Float64.acosh.3
index 37bb19d2..79c426fb 100644
--- a/man/man3/tomo-Float64.acosh.3
+++ b/man/man3/tomo-Float64.acosh.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.acosh 3 2025-11-09 "Tomo man-pages"
+.TH Float64.acosh 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.acosh \- arc hyperbolic cosine
.SH LIBRARY
@@ -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 Float64 The number for which the inverse hyperbolic cosine is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the inverse hyperbolic cosine is to be calculated.
.TE
.SH RETURN
The inverse hyperbolic cosine of `x`.
@@ -31,3 +31,5 @@ The inverse hyperbolic cosine of `x`.
.EX
assert (1.0).acosh() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.asin.3 b/man/man3/tomo-Float64.asin.3
index f56fec93..2cff0652 100644
--- a/man/man3/tomo-Float64.asin.3
+++ b/man/man3/tomo-Float64.asin.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.asin 3 2025-11-09 "Tomo man-pages"
+.TH Float64.asin 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.asin \- arc sine
.SH LIBRARY
@@ -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 Float64 The number for which the arc sine is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the arc sine is to be calculated.
.TE
.SH RETURN
The arc sine of `x` in radians.
@@ -31,3 +31,5 @@ The arc sine of `x` in radians.
.EX
assert (0.5).asin() == 0.5236
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.asinh.3 b/man/man3/tomo-Float64.asinh.3
index 9ba2329b..b1d596fb 100644
--- a/man/man3/tomo-Float64.asinh.3
+++ b/man/man3/tomo-Float64.asinh.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.asinh 3 2025-11-09 "Tomo man-pages"
+.TH Float64.asinh 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.asinh \- arc hyperbolic sine
.SH LIBRARY
@@ -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 Float64 The number for which the inverse hyperbolic sine is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the inverse hyperbolic sine is to be calculated.
.TE
.SH RETURN
The inverse hyperbolic sine of `x`.
@@ -31,3 +31,5 @@ The inverse hyperbolic sine of `x`.
.EX
assert (0.0).asinh() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.atan.3 b/man/man3/tomo-Float64.atan.3
index adceaddf..550c8d99 100644
--- a/man/man3/tomo-Float64.atan.3
+++ b/man/man3/tomo-Float64.atan.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.atan 3 2025-11-09 "Tomo man-pages"
+.TH Float64.atan 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.atan \- arc tangent
.SH LIBRARY
@@ -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 Float64 The number for which the arc tangent is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the arc tangent is to be calculated.
.TE
.SH RETURN
The arc tangent of `x` in radians.
@@ -31,3 +31,5 @@ The arc tangent of `x` in radians.
.EX
assert (1.0).atan() == 0.7854
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.atan2.3 b/man/man3/tomo-Float64.atan2.3
index b9bfbeb8..3be0ee98 100644
--- a/man/man3/tomo-Float64.atan2.3
+++ b/man/man3/tomo-Float64.atan2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.atan2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.atan2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.atan2 \- arc tangent from 2 variables
.SH LIBRARY
@@ -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 Float64 The numerator. -
-y Float64 The denominator. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The numerator.
+y Float64 The denominator.
.TE
.SH RETURN
The arc tangent of `x/y` in radians.
@@ -32,3 +32,5 @@ The arc tangent of `x/y` in radians.
.EX
assert Float64.atan2(1, 1) == 0.7854
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.atanh.3 b/man/man3/tomo-Float64.atanh.3
index f67dc23f..c311cf36 100644
--- a/man/man3/tomo-Float64.atanh.3
+++ b/man/man3/tomo-Float64.atanh.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.atanh 3 2025-11-09 "Tomo man-pages"
+.TH Float64.atanh 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.atanh \- arc hyperbolic tangent.
.SH LIBRARY
@@ -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 Float64 The number for which the inverse hyperbolic tangent is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the inverse hyperbolic tangent is to be calculated.
.TE
.SH RETURN
The inverse hyperbolic tangent of `x`.
@@ -31,3 +31,5 @@ The inverse hyperbolic tangent of `x`.
.EX
assert (0.5).atanh() == 0.5493
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.cbrt.3 b/man/man3/tomo-Float64.cbrt.3
index 1cac7bc4..077990a3 100644
--- a/man/man3/tomo-Float64.cbrt.3
+++ b/man/man3/tomo-Float64.cbrt.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.cbrt 3 2025-11-09 "Tomo man-pages"
+.TH Float64.cbrt 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.cbrt \- cube root
.SH LIBRARY
@@ -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 Float64 The number for which the cube root is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the cube root is to be calculated.
.TE
.SH RETURN
The cube root of `x`.
@@ -31,3 +31,5 @@ The cube root of `x`.
.EX
assert (27.0).cbrt() == 3
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.ceil.3 b/man/man3/tomo-Float64.ceil.3
index 630e31a5..a46915cb 100644
--- a/man/man3/tomo-Float64.ceil.3
+++ b/man/man3/tomo-Float64.ceil.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.ceil 3 2025-11-09 "Tomo man-pages"
+.TH Float64.ceil 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.ceil \- ceiling function
.SH LIBRARY
@@ -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 Float64 The number to be rounded up. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number to be rounded up.
.TE
.SH RETURN
The smallest integer greater than or equal to `x`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.clamped.3 b/man/man3/tomo-Float64.clamped.3
index eba40be4..3aa78f9d 100644
--- a/man/man3/tomo-Float64.clamped.3
+++ b/man/man3/tomo-Float64.clamped.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.clamped 3 2025-11-09 "Tomo man-pages"
+.TH Float64.clamped 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.clamped \- clamp a number
.SH LIBRARY
@@ -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 Float64 The number to clamp. -
-low Float64 The lowest value the result can take. -
-high Float64 The highest value the result can take. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number to clamp.
+low Float64 The lowest value the result can take.
+high Float64 The highest value the result can take.
.TE
.SH RETURN
The first argument clamped between the other two arguments.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.copysign.3 b/man/man3/tomo-Float64.copysign.3
index 7100192d..3a6b5bba 100644
--- a/man/man3/tomo-Float64.copysign.3
+++ b/man/man3/tomo-Float64.copysign.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.copysign 3 2025-11-09 "Tomo man-pages"
+.TH Float64.copysign 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.copysign \- copy a number's sign
.SH LIBRARY
@@ -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 Float64 The number whose magnitude will be copied. -
-y Float64 The number whose sign will be copied. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number whose magnitude will be copied.
+y Float64 The number whose sign will be copied.
.TE
.SH RETURN
A number with the magnitude of `x` and the sign of `y`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.cos.3 b/man/man3/tomo-Float64.cos.3
index 9b9ada05..c9d3c0e8 100644
--- a/man/man3/tomo-Float64.cos.3
+++ b/man/man3/tomo-Float64.cos.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.cos 3 2025-11-09 "Tomo man-pages"
+.TH Float64.cos 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.cos \- cosine
.SH LIBRARY
@@ -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 Float64 The angle in radians. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The angle in radians.
.TE
.SH RETURN
The cosine of `x`.
@@ -31,3 +31,5 @@ The cosine of `x`.
.EX
assert (0.0).cos() == 1
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.cosh.3 b/man/man3/tomo-Float64.cosh.3
index 273171f0..eabcba45 100644
--- a/man/man3/tomo-Float64.cosh.3
+++ b/man/man3/tomo-Float64.cosh.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.cosh 3 2025-11-09 "Tomo man-pages"
+.TH Float64.cosh 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.cosh \- hyperbolic cosine
.SH LIBRARY
@@ -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 Float64 The number for which the hyperbolic cosine is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the hyperbolic cosine is to be calculated.
.TE
.SH RETURN
The hyperbolic cosine of `x`.
@@ -31,3 +31,5 @@ The hyperbolic cosine of `x`.
.EX
assert (0.0).cosh() == 1
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.erf.3 b/man/man3/tomo-Float64.erf.3
index 59a9e842..9d36375d 100644
--- a/man/man3/tomo-Float64.erf.3
+++ b/man/man3/tomo-Float64.erf.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.erf 3 2025-11-09 "Tomo man-pages"
+.TH Float64.erf 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.erf \- error function
.SH LIBRARY
@@ -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 Float64 The number for which the error function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the error function is to be calculated.
.TE
.SH RETURN
The error function of `x`.
@@ -31,3 +31,5 @@ The error function of `x`.
.EX
assert (0.0).erf() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.erfc.3 b/man/man3/tomo-Float64.erfc.3
index 214d3477..c14e2e35 100644
--- a/man/man3/tomo-Float64.erfc.3
+++ b/man/man3/tomo-Float64.erfc.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.erfc 3 2025-11-09 "Tomo man-pages"
+.TH Float64.erfc 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.erfc \- complimentary error function
.SH LIBRARY
@@ -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 Float64 The number for which the complementary error function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the complementary error function is to be calculated.
.TE
.SH RETURN
The complementary error function of `x`.
@@ -31,3 +31,5 @@ The complementary error function of `x`.
.EX
assert (0.0).erfc() == 1
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.exp.3 b/man/man3/tomo-Float64.exp.3
index 945d8284..ae2702e8 100644
--- a/man/man3/tomo-Float64.exp.3
+++ b/man/man3/tomo-Float64.exp.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.exp 3 2025-11-09 "Tomo man-pages"
+.TH Float64.exp 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.exp \- base-e exponentiation
.SH LIBRARY
@@ -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 Float64 The exponent. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The exponent.
.TE
.SH RETURN
The value of $e^x$.
@@ -31,3 +31,5 @@ The value of $e^x$.
.EX
assert (1.0).exp() == 2.7183
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.exp2.3 b/man/man3/tomo-Float64.exp2.3
index f6ab754a..95bb02c3 100644
--- a/man/man3/tomo-Float64.exp2.3
+++ b/man/man3/tomo-Float64.exp2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.exp2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.exp2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.exp2 \- base-2 exponentiation
.SH LIBRARY
@@ -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 Float64 The exponent. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The exponent.
.TE
.SH RETURN
The value of $2^x$.
@@ -31,3 +31,5 @@ The value of $2^x$.
.EX
assert (3.0).exp2() == 8
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.expm1.3 b/man/man3/tomo-Float64.expm1.3
index c92e4c23..4aa82378 100644
--- a/man/man3/tomo-Float64.expm1.3
+++ b/man/man3/tomo-Float64.expm1.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.expm1 3 2025-11-09 "Tomo man-pages"
+.TH Float64.expm1 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.expm1 \- base-e exponential minus 1
.SH LIBRARY
@@ -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 Float64 The exponent. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The exponent.
.TE
.SH RETURN
The value of $e^x - 1$.
@@ -31,3 +31,5 @@ The value of $e^x - 1$.
.EX
assert (1.0).expm1() == 1.7183
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.fdim.3 b/man/man3/tomo-Float64.fdim.3
index f7326d9a..54774806 100644
--- a/man/man3/tomo-Float64.fdim.3
+++ b/man/man3/tomo-Float64.fdim.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.fdim 3 2025-11-09 "Tomo man-pages"
+.TH Float64.fdim 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.fdim \- positive difference
.SH LIBRARY
@@ -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 Float64 The first number. -
-y Float64 The second number. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The first number.
+y Float64 The second number.
.TE
.SH RETURN
The positive difference $\max(0, x - y)$.
@@ -34,3 +34,5 @@ fd
assert (5.0).fdim(3) == 2
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.floor.3 b/man/man3/tomo-Float64.floor.3
index 42a79dec..86953b9e 100644
--- a/man/man3/tomo-Float64.floor.3
+++ b/man/man3/tomo-Float64.floor.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.floor 3 2025-11-09 "Tomo man-pages"
+.TH Float64.floor 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.floor \- floor function
.SH LIBRARY
@@ -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 Float64 The number to be rounded down. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number to be rounded down.
.TE
.SH RETURN
The largest integer less than or equal to `x`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.hypot.3 b/man/man3/tomo-Float64.hypot.3
index 19c197c6..5780f390 100644
--- a/man/man3/tomo-Float64.hypot.3
+++ b/man/man3/tomo-Float64.hypot.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.hypot 3 2025-11-09 "Tomo man-pages"
+.TH Float64.hypot 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.hypot \- Euclidean distance function
.SH LIBRARY
@@ -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 Float64 The first number. -
-y Float64 The second number. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The first number.
+y Float64 The second number.
.TE
.SH RETURN
The Euclidean norm of `x` and `y`.
@@ -32,3 +32,5 @@ The Euclidean norm of `x` and `y`.
.EX
assert Float64.hypot(3, 4) == 5
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.is_between.3 b/man/man3/tomo-Float64.is_between.3
index 9889299e..ffe62d2a 100644
--- a/man/man3/tomo-Float64.is_between.3
+++ b/man/man3/tomo-Float64.is_between.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.is_between 3 2025-11-09 "Tomo man-pages"
+.TH Float64.is_between 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.is_between \- check if a number is in a range
.SH LIBRARY
@@ -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 Float64 The integer to be checked. -
-low Float64 The lower bound to check (inclusive). -
-high Float64 The upper bound to check (inclusive). -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The integer to be checked.
+low Float64 The lower bound to check (inclusive).
+high Float64 The upper bound to check (inclusive).
.TE
.SH RETURN
`yes` if `low <= x and x <= high`, otherwise `no`
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.isfinite.3 b/man/man3/tomo-Float64.isfinite.3
index b72268f5..ef5c7a3b 100644
--- a/man/man3/tomo-Float64.isfinite.3
+++ b/man/man3/tomo-Float64.isfinite.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.isfinite 3 2025-11-09 "Tomo man-pages"
+.TH Float64.isfinite 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.isfinite \- check for finite number
.SH LIBRARY
@@ -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 Float64 The number to be checked. -
+lb lb lbx
+l l l.
+Name Type Description
+n Float64 The number to be checked.
.TE
.SH RETURN
`yes` if `n` is finite, `no` otherwise.
@@ -32,3 +32,5 @@ n Float64 The number to be checked. -
assert (1.0).isfinite() == yes
assert Float64.INF.isfinite() == no
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.isinf.3 b/man/man3/tomo-Float64.isinf.3
index 6701fdc0..e8c97daa 100644
--- a/man/man3/tomo-Float64.isinf.3
+++ b/man/man3/tomo-Float64.isinf.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.isinf 3 2025-11-09 "Tomo man-pages"
+.TH Float64.isinf 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.isinf \- check for infinite number
.SH LIBRARY
@@ -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 Float64 The number to be checked. -
+lb lb lbx
+l l l.
+Name Type Description
+n Float64 The number to be checked.
.TE
.SH RETURN
`yes` if `n` is infinite, `no` otherwise.
@@ -32,3 +32,5 @@ n Float64 The number to be checked. -
assert Float64.INF.isinf() == yes
assert (1.0).isinf() == no
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.j0.3 b/man/man3/tomo-Float64.j0.3
index 97ada7c7..94e3155f 100644
--- a/man/man3/tomo-Float64.j0.3
+++ b/man/man3/tomo-Float64.j0.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.j0 3 2025-11-09 "Tomo man-pages"
+.TH Float64.j0 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.j0 \- Bessel function
.SH LIBRARY
@@ -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 Float64 The number for which the Bessel function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 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`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.j1.3 b/man/man3/tomo-Float64.j1.3
index f5cf816e..f63ad696 100644
--- a/man/man3/tomo-Float64.j1.3
+++ b/man/man3/tomo-Float64.j1.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.j1 3 2025-11-09 "Tomo man-pages"
+.TH Float64.j1 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.j1 \- Bessel function
.SH LIBRARY
@@ -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 Float64 The number for which the Bessel function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 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`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.log.3 b/man/man3/tomo-Float64.log.3
index 7808ddf9..885a2d6d 100644
--- a/man/man3/tomo-Float64.log.3
+++ b/man/man3/tomo-Float64.log.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.log 3 2025-11-09 "Tomo man-pages"
+.TH Float64.log 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.log \- natural logarithm
.SH LIBRARY
@@ -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 Float64 The number for which the natural logarithm is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the natural logarithm is to be calculated.
.TE
.SH RETURN
The natural logarithm of `x`.
@@ -31,3 +31,5 @@ The natural logarithm of `x`.
.EX
assert Float64.E.log() == 1
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.log10.3 b/man/man3/tomo-Float64.log10.3
index 37d63100..07b4131b 100644
--- a/man/man3/tomo-Float64.log10.3
+++ b/man/man3/tomo-Float64.log10.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.log10 3 2025-11-09 "Tomo man-pages"
+.TH Float64.log10 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.log10 \- logarithm base-10
.SH LIBRARY
@@ -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 Float64 The number for which the base-10 logarithm is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the base-10 logarithm is to be calculated.
.TE
.SH RETURN
The base-10 logarithm of `x`.
@@ -31,3 +31,5 @@ The base-10 logarithm of `x`.
.EX
assert (100.0).log10() == 2
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.log1p.3 b/man/man3/tomo-Float64.log1p.3
index 0cbec8f7..6c19d416 100644
--- a/man/man3/tomo-Float64.log1p.3
+++ b/man/man3/tomo-Float64.log1p.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.log1p 3 2025-11-09 "Tomo man-pages"
+.TH Float64.log1p 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.log1p \- logarithm of 1 plus x
.SH LIBRARY
@@ -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 Float64 The number for which $\log(1 + x)$ is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which $\log(1 + x)$ is to be calculated.
.TE
.SH RETURN
The value of $\log(1 + x)$.
@@ -31,3 +31,5 @@ The value of $\log(1 + x)$.
.EX
assert (1.0).log1p() == 0.6931
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.log2.3 b/man/man3/tomo-Float64.log2.3
index c6934d7b..58b4299c 100644
--- a/man/man3/tomo-Float64.log2.3
+++ b/man/man3/tomo-Float64.log2.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.log2 3 2025-11-09 "Tomo man-pages"
+.TH Float64.log2 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.log2 \- logarithm base-2
.SH LIBRARY
@@ -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 Float64 The number for which the base-2 logarithm is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the base-2 logarithm is to be calculated.
.TE
.SH RETURN
The base-2 logarithm of `x`.
@@ -31,3 +31,5 @@ The base-2 logarithm of `x`.
.EX
assert (8.0).log2() == 3
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.logb.3 b/man/man3/tomo-Float64.logb.3
index ec165aac..8f474df9 100644
--- a/man/man3/tomo-Float64.logb.3
+++ b/man/man3/tomo-Float64.logb.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.logb 3 2025-11-09 "Tomo man-pages"
+.TH Float64.logb 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.logb \- exponent of a floating point value
.SH LIBRARY
@@ -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 Float64 The number for which the binary exponent is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the binary exponent is to be calculated.
.TE
.SH RETURN
The binary exponent of `x`.
@@ -31,3 +31,5 @@ The binary exponent of `x`.
.EX
assert (8.0).logb() == 3
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.mix.3 b/man/man3/tomo-Float64.mix.3
index 24ed92d4..c8afc56e 100644
--- a/man/man3/tomo-Float64.mix.3
+++ b/man/man3/tomo-Float64.mix.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.mix 3 2025-11-09 "Tomo man-pages"
+.TH Float64.mix 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.mix \- mix two numbers by an amount
.SH LIBRARY
@@ -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 Float64 The interpolation factor (between `0` and `1`). -
-x Float64 The starting number. -
-y Float64 The ending number. -
+lb lb lbx
+l l l.
+Name Type Description
+amount Float64 The interpolation factor (between \fB0\fR and \fB1\fR).
+x Float64 The starting number.
+y Float64 The ending number.
.TE
.SH RETURN
The interpolated number between `x` and `y` based on `amount`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.near.3 b/man/man3/tomo-Float64.near.3
index 85a1bad4..3cf40244 100644
--- a/man/man3/tomo-Float64.near.3
+++ b/man/man3/tomo-Float64.near.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.near 3 2025-11-09 "Tomo man-pages"
+.TH Float64.near 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.near \- check if two numbers are near each other
.SH LIBRARY
@@ -24,8 +24,8 @@ l l l l.
Name Type Description Default
x Float64 The first number. -
y Float64 The second number. -
-ratio Float64 The relative tolerance. Default is `1e-9`. 1e-9
-min_epsilon Float64 The absolute tolerance. Default is `1e-9`. 1e-9
+ratio Float64 The relative tolerance. Default is \fB1e-9\fR. 1e-9
+min_epsilon Float64 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-Float64 (3)
diff --git a/man/man3/tomo-Float64.nextafter.3 b/man/man3/tomo-Float64.nextafter.3
index 4d931ca4..8345d23e 100644
--- a/man/man3/tomo-Float64.nextafter.3
+++ b/man/man3/tomo-Float64.nextafter.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.nextafter 3 2025-11-09 "Tomo man-pages"
+.TH Float64.nextafter 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.nextafter \- next floating point number
.SH LIBRARY
@@ -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 Float64 The starting number. -
-y Float64 The direction towards which to find the next representable value. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The starting number.
+y Float64 The direction towards which to find the next representable value.
.TE
.SH RETURN
The next representable value after `x` in the direction of `y`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.parse.3 b/man/man3/tomo-Float64.parse.3
index 6829f53c..2ad4c1c4 100644
--- a/man/man3/tomo-Float64.parse.3
+++ b/man/man3/tomo-Float64.parse.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.parse 3 2025-11-09 "Tomo man-pages"
+.TH Float64.parse 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.parse \- convert text to number
.SH LIBRARY
@@ -37,3 +37,5 @@ remainder : Text
assert Float64.parse("1.5junk", &remainder) == 1.5
assert remainder == "junk"
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.percent.3 b/man/man3/tomo-Float64.percent.3
index 4a8e3c34..d93adbd6 100644
--- a/man/man3/tomo-Float64.percent.3
+++ b/man/man3/tomo-Float64.percent.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.percent 3 2025-11-09 "Tomo man-pages"
+.TH Float64.percent 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.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-Float64 (3)
diff --git a/man/man3/tomo-Float64.rint.3 b/man/man3/tomo-Float64.rint.3
index 8226d646..dace55c7 100644
--- a/man/man3/tomo-Float64.rint.3
+++ b/man/man3/tomo-Float64.rint.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.rint 3 2025-11-09 "Tomo man-pages"
+.TH Float64.rint 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.rint \- round to nearest integer
.SH LIBRARY
@@ -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 Float64 The number to be rounded. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number to be rounded.
.TE
.SH RETURN
The nearest integer value of `x`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.round.3 b/man/man3/tomo-Float64.round.3
index 872e2527..e422416e 100644
--- a/man/man3/tomo-Float64.round.3
+++ b/man/man3/tomo-Float64.round.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.round 3 2025-11-09 "Tomo man-pages"
+.TH Float64.round 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.round \- round to nearest integer
.SH LIBRARY
@@ -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 Float64 The number to be rounded. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number to be rounded.
.TE
.SH RETURN
The nearest integer value of `x`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.significand.3 b/man/man3/tomo-Float64.significand.3
index ce49cb1c..d06f9b21 100644
--- a/man/man3/tomo-Float64.significand.3
+++ b/man/man3/tomo-Float64.significand.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.significand 3 2025-11-09 "Tomo man-pages"
+.TH Float64.significand 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.significand \- get mantissa
.SH LIBRARY
@@ -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 Float64 The number from which to extract the significand. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number from which to extract the significand.
.TE
.SH RETURN
The significand of `x`.
@@ -31,3 +31,5 @@ The significand of `x`.
.EX
assert (1234.567).significand() == 0.1234567
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.sin.3 b/man/man3/tomo-Float64.sin.3
index 0f71ecf4..7aeb22a5 100644
--- a/man/man3/tomo-Float64.sin.3
+++ b/man/man3/tomo-Float64.sin.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.sin 3 2025-11-09 "Tomo man-pages"
+.TH Float64.sin 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.sin \- sine
.SH LIBRARY
@@ -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 Float64 The angle in radians. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The angle in radians.
.TE
.SH RETURN
The sine of `x`.
@@ -31,3 +31,5 @@ The sine of `x`.
.EX
assert (0.0).sin() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.sinh.3 b/man/man3/tomo-Float64.sinh.3
index 3bc477e8..1fa6bdcb 100644
--- a/man/man3/tomo-Float64.sinh.3
+++ b/man/man3/tomo-Float64.sinh.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.sinh 3 2025-11-09 "Tomo man-pages"
+.TH Float64.sinh 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.sinh \- hyperbolic sine
.SH LIBRARY
@@ -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 Float64 The number for which the hyperbolic sine is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the hyperbolic sine is to be calculated.
.TE
.SH RETURN
The hyperbolic sine of `x`.
@@ -31,3 +31,5 @@ The hyperbolic sine of `x`.
.EX
assert (0.0).sinh() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.sqrt.3 b/man/man3/tomo-Float64.sqrt.3
index eb857cf1..40d61477 100644
--- a/man/man3/tomo-Float64.sqrt.3
+++ b/man/man3/tomo-Float64.sqrt.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.sqrt 3 2025-11-09 "Tomo man-pages"
+.TH Float64.sqrt 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.sqrt \- square root
.SH LIBRARY
@@ -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 Float64 The number for which the square root is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the square root is to be calculated.
.TE
.SH RETURN
The square root of `x`.
@@ -31,3 +31,5 @@ The square root of `x`.
.EX
assert (16.0).sqrt() == 4
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.tan.3 b/man/man3/tomo-Float64.tan.3
index e0ba363a..f110b453 100644
--- a/man/man3/tomo-Float64.tan.3
+++ b/man/man3/tomo-Float64.tan.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.tan 3 2025-11-09 "Tomo man-pages"
+.TH Float64.tan 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.tan \- tangent
.SH LIBRARY
@@ -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 Float64 The angle in radians. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The angle in radians.
.TE
.SH RETURN
The tangent of `x`.
@@ -31,3 +31,5 @@ The tangent of `x`.
.EX
assert (0.0).tan() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.tanh.3 b/man/man3/tomo-Float64.tanh.3
index fc71ca7a..a0f6c7b8 100644
--- a/man/man3/tomo-Float64.tanh.3
+++ b/man/man3/tomo-Float64.tanh.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.tanh 3 2025-11-09 "Tomo man-pages"
+.TH Float64.tanh 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.tanh \- hyperbolic tangent
.SH LIBRARY
@@ -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 Float64 The number for which the hyperbolic tangent is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the hyperbolic tangent is to be calculated.
.TE
.SH RETURN
The hyperbolic tangent of `x`.
@@ -31,3 +31,5 @@ The hyperbolic tangent of `x`.
.EX
assert (0.0).tanh() == 0
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.tgamma.3 b/man/man3/tomo-Float64.tgamma.3
index f0132bed..5559d4c2 100644
--- a/man/man3/tomo-Float64.tgamma.3
+++ b/man/man3/tomo-Float64.tgamma.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.tgamma 3 2025-11-09 "Tomo man-pages"
+.TH Float64.tgamma 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.tgamma \- true gamma function
.SH LIBRARY
@@ -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 Float64 The number for which the gamma function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number for which the gamma function is to be calculated.
.TE
.SH RETURN
The gamma function of `x`.
@@ -31,3 +31,5 @@ The gamma function of `x`.
.EX
assert (1.0).tgamma() == 1
.EE
+.SH SEE ALSO
+.BR Tomo-Float64 (3)
diff --git a/man/man3/tomo-Float64.trunc.3 b/man/man3/tomo-Float64.trunc.3
index eb43275d..dfd8e387 100644
--- a/man/man3/tomo-Float64.trunc.3
+++ b/man/man3/tomo-Float64.trunc.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.trunc 3 2025-11-09 "Tomo man-pages"
+.TH Float64.trunc 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.trunc \- truncate a number
.SH LIBRARY
@@ -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 Float64 The number to be truncated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 The number to be truncated.
.TE
.SH RETURN
The integer part of `x` towards zero.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.with_precision.3 b/man/man3/tomo-Float64.with_precision.3
index 2699e0a6..a98064cf 100644
--- a/man/man3/tomo-Float64.with_precision.3
+++ b/man/man3/tomo-Float64.with_precision.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.with_precision 3 2025-11-09 "Tomo man-pages"
+.TH Float64.with_precision 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.with_precision \- round to a given precision
.SH LIBRARY
@@ -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 Float64 The number to be rounded to a given precision. -
-precision Float64 The precision to which the number should be rounded. -
+lb lb lbx
+l l l.
+Name Type Description
+n Float64 The number to be rounded to a given precision.
+precision Float64 The precision to which the number should be rounded.
.TE
.SH RETURN
The number, rounded to the given precision level.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.y0.3 b/man/man3/tomo-Float64.y0.3
index 95d67c3b..1724a29d 100644
--- a/man/man3/tomo-Float64.y0.3
+++ b/man/man3/tomo-Float64.y0.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.y0 3 2025-11-09 "Tomo man-pages"
+.TH Float64.y0 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.y0 \- Bessel function
.SH LIBRARY
@@ -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 Float64 The number for which the Bessel function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 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`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Float64.y1.3 b/man/man3/tomo-Float64.y1.3
index 2c5bce05..0896d1e7 100644
--- a/man/man3/tomo-Float64.y1.3
+++ b/man/man3/tomo-Float64.y1.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Float64.y1 3 2025-11-09 "Tomo man-pages"
+.TH Float64.y1 3 2025-12-11 "Tomo man-pages"
.SH NAME
Float64.y1 \- Bessel function
.SH LIBRARY
@@ -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 Float64 The number for which the Bessel function is to be calculated. -
+lb lb lbx
+l l l.
+Name Type Description
+x Float64 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`.
@@ -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-Float64 (3)
diff --git a/man/man3/tomo-Int.3 b/man/man3/tomo-Int.3
new file mode 100644
index 00000000..186c0aae
--- /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-30 "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,\ base:\ Int?\ =\ none,\ 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 714cb8df..71c60960 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-11-09 "Tomo man-pages"
+.TH Int.abs 3 2025-11-29 "Tomo man-pages"
.SH NAME
Int.abs \- absolute value
.SH LIBRARY
@@ -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`.
@@ -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 c18a8d54..1e5c705b 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-11-09 "Tomo man-pages"
+.TH Int.choose 3 2025-11-29 "Tomo man-pages"
.SH NAME
Int.choose \- binomial coefficient
.SH LIBRARY
@@ -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.
@@ -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 c72f5e1c..5d846a21 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-11-09 "Tomo man-pages"
+.TH Int.clamped 3 2025-11-29 "Tomo man-pages"
.SH NAME
Int.clamped \- clamp an integer
.SH LIBRARY
@@ -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.
@@ -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 396b1f8f..e7105287 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-11-09 "Tomo man-pages"
+.TH Int.factorial 3 2025-11-29 "Tomo man-pages"
.SH NAME
Int.factorial \- factorial
.SH LIBRARY
@@ -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.
@@ -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 d3d1d3ec..bf9f11b2 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-11-09 "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
@@ -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.
@@ -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 f1c2fc8e..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-11-09 "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 89340542..8087e0d0 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-11-09 "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
@@ -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`
@@ -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 ad5cdc66..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-11-09 "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 9380bc5f..ab2c4307 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-11-09 "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
@@ -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`.
@@ -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 e4e462ae..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-11-09 "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 106a84b9..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-11-09 "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 d2fc563f..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-09 "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,4 +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/man/man3/tomo-Int.prev_prime.3 b/man/man3/tomo-Int.prev_prime.3
index b66a0235..87f7be36 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-11-09 "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
@@ -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.
@@ -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 d1fd1023..6a3e7eaf 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-11-09 "Tomo man-pages"
+.TH Int.sqrt 3 2025-11-29 "Tomo man-pages"
.SH NAME
Int.sqrt \- square root
.SH LIBRARY
@@ -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`.
@@ -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 804f5064..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-11-09 "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..3da6389f
--- /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-12-11 "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:\ [Float64]?\ =\ none,\ random:\ func(->Float64)?\ =\ 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 a13ec08c..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-11-09 "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 28373446..463ea9ed 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-11-09 "Tomo man-pages"
+.TH List.by 3 2025-11-29 "Tomo man-pages"
.SH NAME
List.by \- slice by a step value
.SH LIBRARY
@@ -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.
@@ -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 6c2eeb3e..b4237e45 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-11-09 "Tomo man-pages"
+.TH List.clear 3 2025-11-29 "Tomo man-pages"
.SH NAME
List.clear \- clear a list
.SH LIBRARY
@@ -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.
@@ -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 b64a29f8..54698085 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-11-09 "Tomo man-pages"
+.TH List.counts 3 2025-11-29 "Tomo man-pages"
.SH NAME
List.counts \- count occurrences
.SH LIBRARY
@@ -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.
@@ -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 76c8c0fb..eece41a1 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-11-09 "Tomo man-pages"
+.TH List.find 3 2025-11-29 "Tomo man-pages"
.SH NAME
List.find \- find an element's index
.SH LIBRARY
@@ -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.
@@ -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 516dd140..e63a089c 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-11-09 "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
@@ -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.
@@ -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 cd04b66b..6406e294 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-11-09 "Tomo man-pages"
+.TH List.has 3 2025-11-29 "Tomo man-pages"
.SH NAME
List.has \- check for member
.SH LIBRARY
@@ -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.
@@ -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 d4662a45..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-11-09 "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 8a5d098a..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-11-09 "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 e1a26082..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-11-09 "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 5876bb80..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-11-09 "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 0c5e2b97..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-11-09 "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 c9623752..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-11-09 "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 44ae1377..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-11-09 "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 236a6d9e..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-11-09 "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 92aded13..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-11-09 "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 406963fd..f0450007 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-11-09 "Tomo man-pages"
+.TH List.reversed 3 2025-11-29 "Tomo man-pages"
.SH NAME
List.reversed \- get a reversed list
.SH LIBRARY
@@ -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.
@@ -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 c090d8b7..126e6ede 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-11-09 "Tomo man-pages"
+.TH List.sample 3 2025-12-11 "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 [Float64]? 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(->Float64)? 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(->Float64)? 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 ae280483..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-11-09 "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 caaa90f2..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-11-09 "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 6d560b21..5025733a 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-11-09 "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
@@ -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.
@@ -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 355d3bff..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-11-09 "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 264743f4..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-11-09 "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 658d2d63..5e3e3d77 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-11-09 "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
@@ -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.
@@ -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 efe1157d..80b9b9f5 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-11-09 "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
@@ -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.
@@ -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 837d30ea..9a02aaaf 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-11-09 "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
@@ -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 `yes` if the item's index should be returned or `no` 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.
@@ -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-Path.3 b/man/man3/tomo-Path.3
new file mode 100644
index 00000000..ae9b6d51
--- /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-12-07 "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)\ ->\ 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:
+.BR Tomo-Path.append (3)
+
+
+.TP
+.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:
+.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\ ->\ 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:
+.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\ ->\ Result)
+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\ ->\ Result)
+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)\ ->\ 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:
+.BR Tomo-Path.write (3)
+
+
+.TP
+.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:
+.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 866a6c47..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-11-09 "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 0073718b..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-09 "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,9 +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 ee63c089..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-09 "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,9 +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.base_name.3 b/man/man3/tomo-Path.base_name.3
index dbd09e8d..f557a971 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-11-09 "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
@@ -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.
@@ -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 476de256..46a47d8c 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-09 "Tomo man-pages"
+.TH Path.by_line 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.by_line \- iterate by line
.SH LIBRARY
@@ -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.
@@ -31,12 +31,14 @@ 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
+.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 2970f78b..53dee69c 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-11-09 "Tomo man-pages"
+.TH Path.can_execute 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.can_execute \- check execute permissions
.SH LIBRARY
@@ -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`.
@@ -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 785d3d54..b3c19a89 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-11-09 "Tomo man-pages"
+.TH Path.can_read 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.can_read \- check read permissions
.SH LIBRARY
@@ -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`.
@@ -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 19a3b944..78880c4b 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-11-09 "Tomo man-pages"
+.TH Path.can_write 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.can_write \- check write permissions
.SH LIBRARY
@@ -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`.
@@ -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 f942bf69..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-11-09 "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 1b4e0497..8e6c4859 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-11-09 "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
@@ -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.
@@ -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 9c83a366..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-11-09 "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 50e139df..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-09 "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)\ ->\ 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.
@@ -24,11 +24,16 @@ 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 \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
.SH EXAMPLES
.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 ac6eb326..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-11-09 "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 7bf91bd8..7d03e468 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-11-09 "Tomo man-pages"
+.TH Path.exists 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.exists \- check if a path exists
.SH LIBRARY
@@ -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.
@@ -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 c88f25bd..0f3ee0d0 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-11-09 "Tomo man-pages"
+.TH Path.expand_home 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.expand_home \- expand ~ to $HOME
.SH LIBRARY
@@ -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.
@@ -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 0185400a..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-11-09 "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 32c180fa..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-11-09 "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 eea6d2db..d20efdd2 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-11-09 "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
@@ -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.
@@ -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 d5feeef7..1e593035 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-11-09 "Tomo man-pages"
+.TH Path.glob 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.glob \- perform file globbing
.SH LIBRARY
@@ -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 `*`, `?`, or `{...}` -
+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.
@@ -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 4c746746..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-11-09 "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 7fca6987..390015da 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-11-09 "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
@@ -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 `.` 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.
@@ -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 089cf3b8..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-11-09 "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 be425328..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-11-09 "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 d371d1d9..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-11-09 "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 1f54231c..d38591d0 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-11-09 "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
@@ -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.
@@ -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
new file mode 100644
index 00000000..19e48a99
--- /dev/null
+++ b/man/man3/tomo-Path.lines.3
@@ -0,0 +1,35 @@
+'\" t
+.\" Copyright (c) 2025 Bruce Hill
+.\" All rights reserved.
+.\"
+.TH Path.lines 3 2025-11-29 "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
+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.
+
+.SH EXAMPLES
+.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 fd47e9fe..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-11-09 "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 f3a086e2..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-11-09 "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 1e81ab7c..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-09 "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.
@@ -19,15 +19,17 @@ 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.
+The path of the parent directory or `none` if the path is `(/)` (the file root).
.SH EXAMPLES
.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 88ad9e80..31b32fd2 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-11-09 "Tomo man-pages"
+.TH Path.read 3 2025-11-29 "Tomo man-pages"
.SH NAME
Path.read \- read file contents
.SH LIBRARY
@@ -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.
@@ -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 0c7d82da..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-11-09 "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 e528205a..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-09 "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,9 +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 da3179b4..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-09 "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,9 +26,11 @@ 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
(./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 efc76e28..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-11-09 "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 48a256d5..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-09 "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,9 +28,11 @@ 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
(./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 324f8f4d..10bc821c 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-11-09 "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
@@ -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.
@@ -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 f54f9a11..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-11-09 "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 0a1c8fe9..7f66fad5 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-11-09 "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
@@ -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 `XXXXXX`. -
+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.
@@ -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 ab0aef36..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-09 "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,9 +27,11 @@ 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
(./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 b3ea648b..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-09 "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,9 +27,11 @@ 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
(./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 400f4097..d7763bdb 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-11-09 "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
@@ -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 `XXXXXX` 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.
@@ -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 53cb7bfd..6bb4abd0 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-11-09 "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
@@ -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 `XXXXXX` 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.
@@ -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 be91c5ce..1e2816dd 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-11-09 "Tomo man-pages"
+.TH Table.clear 3 2025-11-29 "Tomo man-pages"
.SH NAME
Table.clear \- clear a table
.SH LIBRARY
@@ -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.
@@ -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 b216b283..2db0def3 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-11-09 "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
@@ -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.
@@ -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 ce0b1f95..87dc40ec 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-11-09 "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
@@ -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.
@@ -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 69d5a236..61c84305 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-11-09 "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
@@ -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.
@@ -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 c4b4a09b..45936f89 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-11-09 "Tomo man-pages"
+.TH Table.has 3 2025-11-29 "Tomo man-pages"
.SH NAME
Table.has \- check for a key
.SH LIBRARY
@@ -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.
@@ -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 c01f4336..bbd8497d 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-11-09 "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
@@ -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.
@@ -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 07fd042d..5f5b06b7 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-11-09 "Tomo man-pages"
+.TH Table.remove 3 2025-11-29 "Tomo man-pages"
.SH NAME
Table.remove \- remove a table entry
.SH LIBRARY
@@ -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.
@@ -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 bf8c39cd..ba904c29 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-11-09 "Tomo man-pages"
+.TH Table.set 3 2025-11-29 "Tomo man-pages"
.SH NAME
Table.set \- set a table entry
.SH LIBRARY
@@ -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.
@@ -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 d9a70f97..c2285343 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-11-09 "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
@@ -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.
@@ -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 ea8f7979..72dd0589 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-11-09 "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
@@ -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.
@@ -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 c9d1b0e9..80154a3a 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-11-09 "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
@@ -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.
@@ -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 fec6a393..5369905d 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-11-09 "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
@@ -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.
@@ -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 1908f6c0..a1d30371 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-11-09 "Tomo man-pages"
+.TH Text.at 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.at \- get a letter
.SH LIBRARY
@@ -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.
@@ -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 6f996ed1..2abc4c23 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-11-09 "Tomo man-pages"
+.TH Text.by_line 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.by_line \- iterate by line
.SH LIBRARY
@@ -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`.
@@ -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 188c3592..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-11-09 "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 dfce89a7..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-11-09 "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 1818851c..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-11-09 "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 80ac3d6f..320af3f6 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-11-09 "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
@@ -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]`).
@@ -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 aa34489a..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-11-09 "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
new file mode 100644
index 00000000..b145ae3c
--- /dev/null
+++ b/man/man3/tomo-Text.find.3
@@ -0,0 +1,40 @@
+'\" t
+.\" Copyright (c) 2025 Bruce Hill
+.\" All rights reserved.
+.\"
+.TH Text.find 3 2025-11-29 "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
+.SH SEE ALSO
+.BR Tomo-Text (3)
diff --git a/man/man3/tomo-Text.from.3 b/man/man3/tomo-Text.from.3
index 2d15ac55..53df8278 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-11-09 "Tomo man-pages"
+.TH Text.from 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.from \- slice from a starting index
.SH LIBRARY
@@ -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.
@@ -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 18a7c576..32e6e4f5 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-11-09 "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
@@ -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.
@@ -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 464e87e6..ac0bf01b 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-11-09 "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
@@ -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.
@@ -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 6e52230e..be046ddb 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-11-09 "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
@@ -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.
@@ -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 1f1689a6..7f78c191 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-11-09 "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
@@ -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.
@@ -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 9cd848bc..497a6107 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-11-09 "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
@@ -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.
@@ -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 7e56020b..3397cc2b 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-11-09 "Tomo man-pages"
+.TH Text.has 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.has \- check for substring
.SH LIBRARY
@@ -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.
@@ -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 699765f3..0eccb241 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-11-09 "Tomo man-pages"
+.TH Text.join 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.join \- concatenate with separator
.SH LIBRARY
@@ -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.
@@ -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 30150211..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-11-09 "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 a2397c40..7c53ed54 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-11-09 "Tomo man-pages"
+.TH Text.lines 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.lines \- get list of lines
.SH LIBRARY
@@ -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.
@@ -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 eae3cebe..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-11-09 "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 a9358619..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-11-09 "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 5142eef8..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-11-09 "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 3bfcce74..d917f96c 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-11-09 "Tomo man-pages"
+.TH Text.repeat 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.repeat \- repeat text
.SH LIBRARY
@@ -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.
@@ -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 16c7756b..8631f32a 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-11-09 "Tomo man-pages"
+.TH Text.replace 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.replace \- replace a substring
.SH LIBRARY
@@ -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.
@@ -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 6f06d4e6..821e1ab3 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-11-09 "Tomo man-pages"
+.TH Text.reversed 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.reversed \- get a reversed copy
.SH LIBRARY
@@ -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.
@@ -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 4c1a1efb..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-11-09 "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 f974e2af..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-11-09 "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 b80023a0..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-11-09 "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 28eb7783..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-11-09 "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 5de9a947..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-11-09 "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 bf4e768d..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-11-09 "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 7ca58bfa..2507311a 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-11-09 "Tomo man-pages"
+.TH Text.to 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.to \- slice to an end index
.SH LIBRARY
@@ -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.
@@ -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 e436a2ee..2cac6a47 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-11-09 "Tomo man-pages"
+.TH Text.translate 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.translate \- perform multiple replacements
.SH LIBRARY
@@ -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.
@@ -39,3 +39,5 @@ text := "A <tag> & an amperand".translate({
})
assert text == "A &lt;tag&gt; &amp; 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 fae9f3a5..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-11-09 "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 6ac7e072..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-11-09 "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 b8696254..f65d05d2 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-11-09 "Tomo man-pages"
+.TH Text.utf16 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.utf16 \- get UTF16 codepoints
.SH LIBRARY
@@ -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]`).
@@ -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 276e8dad..d43d31e7 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-11-09 "Tomo man-pages"
+.TH Text.utf32 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.utf32 \- get UTF32 codepoints
.SH LIBRARY
@@ -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]`).
@@ -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 8ace9dfb..5490c882 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-11-09 "Tomo man-pages"
+.TH Text.utf8 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.utf8 \- get UTF8 bytes
.SH LIBRARY
@@ -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.
@@ -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 17666301..ec6307e2 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-11-09 "Tomo man-pages"
+.TH Text.width 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.width \- get display width
.SH LIBRARY
@@ -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.
@@ -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 5c12c4e8..d1b902da 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-11-09 "Tomo man-pages"
+.TH Text.without_prefix 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.without_prefix \- remove prefix
.SH LIBRARY
@@ -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.
@@ -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 08053ab0..b905bee8 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-11-09 "Tomo man-pages"
+.TH Text.without_suffix 3 2025-11-29 "Tomo man-pages"
.SH NAME
Text.without_suffix \- remove suffix
.SH LIBRARY
@@ -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.
@@ -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 7eed621d..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-11-09 "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
new file mode 100644
index 00000000..556178c9
--- /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-29 "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
+l l l.
+Name Type Description
+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
diff --git a/man/man3/tomo-exit.3 b/man/man3/tomo-exit.3
index cb1ee064..865d8b72 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-11-09 "Tomo man-pages"
+.TH exit 3 2025-11-29 "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/man/man3/tomo-fail.3 b/man/man3/tomo-fail.3
index 14fa0cf8..27eb0660 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-11-09 "Tomo man-pages"
+.TH fail 3 2025-11-29 "Tomo man-pages"
.SH NAME
fail \- abort the program
.SH LIBRARY
@@ -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 1fa1f1e7..71f828eb 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-11-09 "Tomo man-pages"
+.TH getenv 3 2025-11-29 "Tomo man-pages"
.SH NAME
getenv \- get an environment variable
.SH LIBRARY
@@ -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-print.3 b/man/man3/tomo-print.3
index 68ac5060..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-11-09 "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 d6b7202f..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-11-09 "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 4e0c5ca3..56ab32bf 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-11-09 "Tomo man-pages"
+.TH setenv 3 2025-11-29 "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.
@@ -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. -
+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 783e0aee..a2e4a707 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-11-09 "Tomo man-pages"
+.TH sleep 3 2025-12-11 "Tomo man-pages"
.SH NAME
sleep \- wait for an interval
.SH LIBRARY
@@ -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 Float64 How many seconds to sleep for. -
+lb lb lbx
+l l l.
+Name Type Description
+seconds Float64 How many seconds to sleep for.
.TE
.SH RETURN
Nothing.
diff --git a/modules/core.ini b/modules/core.ini
index ddad0a09..debaf7d9 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.1
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.1
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
diff --git a/scripts/mandoc_gen.py b/scripts/mandoc_gen.py
index 8342e253..b9cf1cc2 100755
--- a/scripts/mandoc_gen.py
+++ b/scripts/mandoc_gen.py
@@ -71,54 +71,151 @@ 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")
+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.
+.\\"
+.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")
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")
+ 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 = arg_info['description'].replace('\n', ' ')
- add_line(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}")
- add_line(".TE")
+ 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:
- 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")
+
+ 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:
+ 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}")
+
+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():
- 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 "." 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
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)
diff --git a/src/ast.c b/src/ast.c
index 432ce2d4..e87ca005 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
}
@@ -441,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:
@@ -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);
@@ -683,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: {
@@ -710,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: {
@@ -724,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);
@@ -774,9 +773,22 @@ 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);
}
+
+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 aaa2a993..b6930ab7 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))
@@ -276,6 +279,7 @@ typedef enum {
Use,
InlineCCode,
ExplicitlyTyped,
+ Metadata,
} ast_e;
#define NUM_AST_TAGS (ExplicitlyTyped + 1)
@@ -287,6 +291,7 @@ struct ast_s {
struct {
} Unknown;
struct {
+ struct type_s *type;
} None;
struct {
bool b;
@@ -457,6 +462,9 @@ struct ast_s {
ast_t *ast;
struct type_s *type;
} ExplicitlyTyped;
+ struct {
+ Text_t key, value;
+ } Metadata;
} __data;
};
@@ -480,5 +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/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/assignments.c b/src/compile/assignments.c
index 84c43153..8c6af3ee 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/cli.c b/src/compile/cli.c
index e3d2329f..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) {
@@ -58,80 +58,87 @@ 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);
+ Text_t flags = Texts("\x1b[1m", 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 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");
}
- 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("\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))
+ 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\"", 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))), " = ",
@@ -144,14 +151,14 @@ 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(""),
"},\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
@@ -161,7 +168,6 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
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);
}
@@ -181,14 +187,21 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
}
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 fa60eccf..907554a6 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_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args);
+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, ast_t *ast, arg_t *args);
diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c
index bc927599..5e95459c 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/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/enums.c b/src/compile/enums.c
index ec7a1755..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,16 +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 (fielded_t->tag == PointerType) {
- Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false);
- return Texts("((", fielded, ")->$tag == ", tag_name, ")");
- } else if (enum_has_fields(value_t)) {
- Text_t fielded = compile(env, f->fielded);
- return Texts("((", fielded, ").$tag == ", tag_name, ")");
- } else {
- Text_t fielded = compile(env, f->fielded);
- return Texts("((", fielded, ") == ", tag_name, ")");
- }
+ 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/expressions.c b/src/compile/expressions.c
index c28185ce..f249993f 100644
--- a/src/compile/expressions.c
+++ b/src/compile/expressions.c
@@ -11,11 +11,41 @@
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 {
+ 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, 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));
+ 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);
}
@@ -27,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)");
@@ -66,7 +95,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/compile/files.c b/src/compile/files.c
index a4cc07fe..27c2e041 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;
}
}
@@ -151,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:
@@ -163,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
@@ -193,7 +195,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 <tomo_" TOMO_VERSION "/tomo.h>\n"
+ "#include <tomo@" TOMO_VERSION "/tomo.h>\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/functions.c b/src/compile/functions.c
index a14c0455..46acd780 100644
--- a/src/compile/functions.c
+++ b/src/compile/functions.c
@@ -3,9 +3,12 @@
#include "../ast.h"
#include "../environment.h"
#include "../naming.h"
+#include "../stdlib/c_strings.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/floats.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"
@@ -163,12 +166,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), ")");
@@ -246,85 +249,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;
@@ -594,6 +518,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;
@@ -703,7 +827,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
@@ -784,6 +908,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 f132b312..e90556a1 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);
@@ -116,41 +104,30 @@ 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;
// 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));
}
+
+ return VISIT_PROCEED;
}
public
@@ -158,7 +135,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 <tomo_" TOMO_VERSION "/tomo.h>\n");
+ "#include <tomo@" TOMO_VERSION "/tomo.h>\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 +160,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/lists.c b/src/compile/lists.c
index 1f3590a9..f39f61d8 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;
@@ -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/optionals.c b/src/compile/optionals.c
index 7da50b0b..9aca84d0 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.$tag == PATHTYPE_NONE)");
- else if (t == PATH_TYPE_TYPE) return Texts("((", value, ").$tag == 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 == FloatType)
@@ -103,10 +96,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;
}
@@ -115,12 +105,51 @@ 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",
+ 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));
+ } 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/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/compile/promotions.c b/src/compile/promotions.c
index 2a346668..4b5458c9 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:
@@ -128,6 +131,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 == FloatType) {
diff --git a/src/compile/statements.c b/src/compile/statements.c
index f554263c..81a10ddd 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");
@@ -216,11 +216,12 @@ 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));
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/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/compile/types.c b/src/compile/types.c
index 24790e46..a581fb61 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/compile/whens.c b/src/compile/whens.c
index 4f6a2a40..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;
@@ -83,8 +79,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 +99,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;
@@ -128,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/environment.c b/src/environment.c
index a5b30cb5..4cb7fbb0 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -17,9 +17,9 @@ type_t *TEXT_TYPE = NULL;
public
type_t *PATH_TYPE = NULL;
public
-type_t *PATH_TYPE_TYPE = NULL;
+type_t *PRESENT_TYPE = NULL;
public
-type_t *EMPTY_TYPE = NULL;
+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);
@@ -67,10 +67,12 @@ 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]))");
+ RESULT_TYPE = declare_type(env, "enum Result(Success, Failure(reason:Text))");
- EMPTY_TYPE = declare_type(env, "struct Empty()");
+ PRESENT_TYPE = declare_type(env, "struct Present()");
typedef struct {
const char *name, *code, *type_str;
@@ -90,10 +92,11 @@ 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("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?)"}),
+ {"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 +104,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 +129,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 +148,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 +170,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 +192,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 +214,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)"}, //
@@ -302,15 +305,11 @@ env_t *global_env(bool source_mapping) {
{"as_text", "Text$from_str", "func(str:CString -> Text)"},
{"join", "CString$join", "func(glue:CString, pieces:[CString] -> CString)"}),
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?)"}, //
- {"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)"}, //
@@ -320,7 +319,9 @@ 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))"}, //
+ {"components", "Path$components", "func(path:Path -> [Text])"}, //
+ {"create_directory", "Path$create_directory",
+ "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)"}, //
@@ -335,23 +336,24 @@ 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)"}, //
+ {"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)"}, //
@@ -362,6 +364,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?)"}, //
@@ -537,17 +540,18 @@ env_t *global_env(bool source_mapping) {
struct {
const char *name, *code, *type_str;
} global_vars[] = {
- {"USE_COLOR", "USE_COLOR", "Bool"},
+ {"PRESENT", "PRESENT", "Present"},
{"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)"},
- {"sleep", "sleep_float64", "func(seconds:Float64)"},
- {"EMPTY", "EMPTY", "Empty"},
+ {"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?)"},
+ {"sleep", "sleep_seconds", "func(seconds:Float64)"},
};
for (size_t i = 0; i < sizeof(global_vars) / sizeof(global_vars[0]); i++) {
diff --git a/src/environment.h b/src/environment.h
index 6389cc7a..ba036f2e 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -86,5 +86,5 @@ 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 *EMPTY_TYPE;
+extern type_t *PRESENT_TYPE;
+extern type_t *RESULT_TYPE;
diff --git a/src/modules.c b/src/modules.c
index 9ebdca09..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);
@@ -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;
@@ -152,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/naming.c b/src/naming.c
index 484e1998..d7bb0fb9 100644
--- a/src/naming.c
+++ b/src/naming.c
@@ -96,8 +96,9 @@ Text_t valid_c_name(const char *name) {
public
Text_t CONSTFUNC namespace_name(env_t *env, namespace_t *ns, Text_t name) {
- for (; ns; ns = ns->parent)
+ for (; ns; ns = ns->parent) {
name = Texts(ns->name, "$", name);
+ }
if (env->id_suffix.length > 0) name = Texts(name, env->id_suffix);
return name;
}
@@ -113,5 +114,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);
}
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 <string.h>
#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 <stdbool.h>
+
#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/stdlib/bigint.c b/src/stdlib/bigint.c
index 41e8e6db..2d145bd5 100644
--- a/src/stdlib/bigint.c
+++ b/src/stdlib/bigint.c
@@ -393,52 +393,98 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) {
}
public
-OptionalInt_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) {
- const char *end = str + 2 + strspn(str + 2, "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 + 2, (size_t)(end - (str + 2)))), 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) {
- const char *end = str + 2 + strspn(str + 2, "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 + 2, (size_t)(end - (str + 2)))), 8);
+ base32 = 8;
+ goto base8_prefix;
} else if (strncmp(str, "0b", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "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 + 2, (size_t)(end - (str + 2)))), 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 e50a6847..b57844a4 100644
--- a/src/stdlib/bigint.h
+++ b/src/stdlib/bigint.h
@@ -24,7 +24,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 8301a2c2..cc2fa0b8 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);
}
}
@@ -215,7 +215,10 @@ 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 || 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;
else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true;
else if (nonnull == &Int8$info) ((OptionalInt8_t *)dest)->has_value = true;
@@ -235,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) {
@@ -322,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=...
@@ -338,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)) {
@@ -397,7 +402,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
diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h
index c177a0a5..7c829cac 100644
--- a/src/stdlib/datatypes.h
+++ b/src/stdlib/datatypes.h
@@ -86,13 +86,18 @@ typedef struct table_s {
struct table_s *fallback;
} Table_t;
-typedef struct Empty$$struct {
-} Empty$$type;
+typedef struct Present$$struct {
+} Present$$type;
+
+#define PRESENT_STRUCT ((Present$$type){})
typedef struct {
+ Present$$type value;
bool has_value;
- Empty$$type value;
-} $OptionalEmpty$$type;
+} $OptionalPresent$$type;
+
+#define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false})
+#define OPTIONAL_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = true})
typedef struct {
void *fn, *userdata;
@@ -123,15 +128,82 @@ 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 {
+ Path$AbsolutePath$$type value;
+ bool has_value;
+} $OptionalPath$AbsolutePath$$type;
+
+typedef struct Path$RelativePath$$struct {
+ List_t components;
+} Path$RelativePath$$type;
typedef struct {
- PathType_t type;
+ 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$$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/floatX.c.h b/src/stdlib/floatX.c.h
index 0961631c..54477ee3 100644
--- a/src/stdlib/floatX.c.h
+++ b/src/stdlib/floatX.c.h
@@ -67,6 +67,7 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf
#elif FLOATX_C_H__BITS == 32
public
PUREFUNC Text_t NAMESPACED(value_as_text)(FLOAT_T x) { return Float64$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);
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 03aa7247..4d8f8e3d 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/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;
}
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);
diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h
index d067ec94..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){.type = PATHTYPE_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 810f98b1..ed8383fd 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,14 @@ 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$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,
+ };
LIST_INCREF(result.components);
List$insert_all(&result.components, path.components, I(0), sizeof(Text_t));
clean_components(&result.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.type != relative_to.type)
- 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 = {.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++) {
+ 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,43 +450,53 @@ 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) {
+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);
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) {
+ 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;
@@ -503,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);
@@ -524,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);
@@ -545,30 +563,30 @@ 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) {
- if (path.type == PATHTYPE_ABSOLUTE && path.components.length == 0) {
- return path;
+OptionalPath_t Path$parent(Path_t path) {
+ if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) {
+ 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)),
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;
@@ -579,8 +597,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;
}
@@ -610,7 +628,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);
@@ -623,14 +641,13 @@ 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 = {
- .type = path.type,
+ .$tag = path.$tag,
.components = path.components,
};
LIST_INCREF(result.components);
@@ -704,6 +721,28 @@ OptionalClosure_t Path$by_line(Path_t path) {
}
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 = EMPTY_LIST;
+ 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;
int status = glob(Path$as_c_string(path), GLOB_BRACE | GLOB_TILDE, NULL, &glob_result);
@@ -730,55 +769,19 @@ Path_t Path$current_dir(void) {
}
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);
}
@@ -799,9 +802,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);
@@ -811,52 +814,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 ce6de1c8..881a3c78 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -11,7 +11,11 @@
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)
+// 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]);
@@ -31,24 +35,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);
+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);
@@ -57,6 +61,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 *);
@@ -68,5 +73,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/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)
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 <err.h>
+#include <gc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/param.h>
+
+#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/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/stdlib/stdlib.c b/src/stdlib/stdlib.c
index c3ea1d36..f4e6d678 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <gc.h>
#include <locale.h>
+#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
@@ -72,6 +73,7 @@ void tomo_init(void) {
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigaction(SIGILL, &sigact, (struct sigaction *)NULL);
+ atexit(tomo_cleanup);
}
public
@@ -145,7 +147,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
@@ -204,11 +206,16 @@ cleanup:
}
public
-void sleep_float64(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
@@ -218,4 +225,37 @@ 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) {
+ 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 {
+ 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 e52b5cd1..3afe3529 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); \
@@ -30,11 +33,12 @@ void tomo_init(void);
else fputs("\n", stderr); \
fflush(stderr); \
raise(SIGABRT); \
- _exit(1); \
+ exit(1); \
})
#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); \
@@ -50,7 +54,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);
@@ -75,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);
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/stdlib/text.c b/src/stdlib/text.c
index f323d88d..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));
@@ -1057,8 +1062,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;
}
@@ -1107,6 +1112,19 @@ static bool _has_grapheme(TextIter_t *text, int32_t g) {
}
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;
TextIter_t text_state = NEW_TEXT_ITER_STATE(text), trim_state = NEW_TEXT_ITER_STATE(to_trim);
@@ -1135,6 +1153,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 +1175,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;
@@ -1605,6 +1625,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));
}
diff --git a/src/stdlib/text.h b/src/stdlib/text.h
index 37ed056a..8c0c43b3 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);
@@ -84,6 +85,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/src/stdlib/tomo.h b/src/stdlib/tomo.h
index b6166890..6a9ba621 100644
--- a/src/stdlib/tomo.h
+++ b/src/stdlib/tomo.h
@@ -26,6 +26,7 @@
#include "pointers.h" // IWYU pragma: export
#include "print.h" // IWYU pragma: export
#include "reals.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/src/tomo.c b/src/tomo.c
index fab725db..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"
@@ -91,7 +92,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,
@@ -116,6 +117,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;
@@ -166,7 +168,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");
@@ -185,7 +187,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]);
@@ -194,8 +196,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"
@@ -217,7 +219,7 @@ int main(int argc, char *argv[]) {
" --source-mapping|-m <yes|no>: 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'}, //
@@ -291,7 +293,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);
}
@@ -368,7 +370,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 +403,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,12 +422,13 @@ 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) {
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;
@@ -448,6 +453,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) {
@@ -475,7 +492,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,
@@ -493,9 +510,9 @@ 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 (!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);
@@ -513,7 +530,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) {
@@ -674,14 +691,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};
@@ -841,7 +858,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,10 +894,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,
- 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);
}
diff --git a/src/typecheck.c b/src/typecheck.c
index 10cb9cba..9ba7284e 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"
@@ -73,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))
@@ -124,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) {
@@ -150,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);
@@ -231,8 +224,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);
}
@@ -440,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);
@@ -495,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);
}
@@ -754,7 +742,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;
@@ -816,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;
@@ -849,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));
}
@@ -971,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: {
@@ -1218,6 +1211,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))
@@ -1376,7 +1370,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 +1394,11 @@ 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) {
+ 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)
@@ -1509,6 +1507,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
@@ -1527,10 +1526,14 @@ 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);
+ if (t->tag == StructType) {
+ return (Match(t, StructType)->fields == NULL);
+ }
return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType);
}
@@ -1586,8 +1589,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 =
@@ -1651,7 +1655,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);
}
@@ -1683,7 +1687,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;
@@ -1695,40 +1700,44 @@ 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 == FloatType && 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);
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;
- } else if (needed->tag == PointerType) {
- DeclareMatch(ptr, needed, 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 (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 492a2a22..aeadbceb 100644
--- a/src/types.c
+++ b/src/types.c
@@ -9,10 +9,20 @@
#include "environment.h"
#include "stdlib/datatypes.h"
#include "stdlib/integers.h"
+#include "stdlib/tables.h"
#include "stdlib/text.h"
#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)");
@@ -39,7 +49,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), "}");
}
@@ -47,19 +57,15 @@ 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);
- return Text$from_str(struct_->name);
+ return Text$replace(Text$from_str(struct_->name), Text("$"), Text("."));
}
case PointerType: {
DeclareMatch(ptr, t, PointerType);
@@ -69,7 +75,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));
@@ -227,23 +233,59 @@ 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 RealType: 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, visited)) return true;
+ }
+ return false;
+ }
+ default: return false;
+ }
+}
+
+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, visited);
+ case StructType: {
+ for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
+ 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_heap_memory(tag->type)) return true;
+ if (tag->type && _has_refcounts(tag->type, visited)) return true;
}
return false;
}
@@ -251,15 +293,45 @@ PUREFUNC bool has_heap_memory(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 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, 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, 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, visited)) return true;
+ }
+ return false;
+ }
default: return false;
}
}
+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) {
@@ -397,15 +469,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;
}
@@ -444,7 +526,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"
@@ -534,7 +615,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"
@@ -630,7 +710,8 @@ 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;
+ return Type(OptionalType, tag->type);
}
return NULL;
}
@@ -763,10 +844,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 a7a6ffcf..9f468c1e 100644
--- a/src/types.h
+++ b/src/types.h
@@ -140,6 +140,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);
@@ -152,8 +153,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_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);
@@ -168,4 +170,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 fe767ebf..aab1b234 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 == OnlyTags.A.A
+ assert e.B == none
+
+ do
+ e := Foo.Zero
+ assert e.Zero == Foo.Zero.Zero
+ assert e.One == none
+ assert e.Two == none
+
+ ep := @Foo.Zero
+ assert ep.Zero == Foo.Zero.Zero
+ 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")
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/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
diff --git a/test/optionals.tm b/test/optionals.tm
index f439d998..53741748 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")
diff --git a/test/paths.tm b/test/paths.tm
index bcda8e1f..6622847e 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)
@@ -30,16 +30,16 @@ 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
- >> 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:")
@@ -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)
diff --git a/test/text.tm b/test/text.tm
index 6631b94e..6c23042d 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
@@ -200,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
diff --git a/test/values.tm b/test/values.tm
new file mode 100644
index 00000000..9f86c012
--- /dev/null
+++ b/test/values.tm
@@ -0,0 +1,55 @@
+# Tests for ensuring immutable value nature in various contexts
+struct Inner(xs:[Int32])
+
+struct Outer(inner:Inner)
+
+enum HoldsList(HasList(xs:[Int32]))
+
+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]
+
+ do
+ x := HoldsList.HasList([10, 20, 30])
+ when x is HasList(list)
+ (&list)[1] = 99
+
+ assert x == HoldsList.HasList([10, 20, 30])