aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-09-06 14:47:45 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-09-06 14:47:45 -0400
commita8316252db95e3d77f9f0e9beb89cfcb4573d5b1 (patch)
treee5905bce9611e35ccb2f84481232fca0e657ff42
parenta0ac652cd1eebdc42425b34f1685f8cb20cb4eea (diff)
parent73246764f88f6f652316ee0c138a990d836698a7 (diff)
Merge branch 'main' into simplified-quotes
-rw-r--r--.gitignore3
-rw-r--r--CHANGES.md11
-rw-r--r--Makefile57
-rw-r--r--docs/libraries.md4
-rw-r--r--docs/tomo.1.md62
-rw-r--r--docs/versions.md2
-rw-r--r--examples/README.md19
-rw-r--r--examples/colorful/CHANGES.md13
-rw-r--r--examples/colorful/README.md70
-rw-r--r--examples/colorful/colorful.tm220
-rw-r--r--examples/colorful/modules.ini2
-rw-r--r--examples/colorful/test.colors9
-rw-r--r--examples/coroutines/ACO_LICENSE202
-rw-r--r--examples/coroutines/CHANGES.md5
-rw-r--r--examples/coroutines/README.md30
-rw-r--r--examples/coroutines/aco.c458
-rw-r--r--examples/coroutines/aco.h213
-rw-r--r--examples/coroutines/acoyield.S208
-rw-r--r--examples/coroutines/coroutines.tm67
-rw-r--r--examples/game/CHANGES.md5
-rw-r--r--examples/game/Makefile17
-rw-r--r--examples/game/README.md13
-rw-r--r--examples/game/box.tm7
-rw-r--r--examples/game/game.tm29
-rw-r--r--examples/game/map.txt18
-rw-r--r--examples/game/player.tm28
-rw-r--r--examples/game/raylib.tm63
-rw-r--r--examples/game/world.tm88
-rw-r--r--examples/hello.tm3
-rw-r--r--examples/http-server/CHANGES.md5
-rw-r--r--examples/http-server/README.md8
-rw-r--r--examples/http-server/connection-queue.tm25
-rw-r--r--examples/http-server/http-server.tm149
-rw-r--r--examples/http-server/modules.ini8
-rw-r--r--examples/http-server/sample-site/foo.html6
-rw-r--r--examples/http-server/sample-site/hello.txt1
-rw-r--r--examples/http-server/sample-site/index.html16
-rwxr-xr-xexamples/http-server/sample-site/random.tm17
-rw-r--r--examples/http-server/sample-site/styles.css11
-rw-r--r--examples/http/CHANGES.md5
-rw-r--r--examples/http/http.tm111
-rw-r--r--examples/ini/CHANGES.md5
-rw-r--r--examples/ini/ini.tm61
-rw-r--r--examples/ini/modules.ini2
-rw-r--r--examples/ini/test.ini8
-rw-r--r--examples/log/CHANGES.md5
-rw-r--r--examples/log/log.tm50
-rw-r--r--examples/vectors/CHANGES.md5
-rw-r--r--examples/vectors/vectors.tm136
-rw-r--r--examples/wrap/CHANGES.md5
-rw-r--r--examples/wrap/wrap.tm104
-rwxr-xr-xinstall_dependencies.sh26
-rw-r--r--lib/README.md19
-rw-r--r--lib/base64/CHANGES.md5
-rw-r--r--lib/base64/README.md3
-rw-r--r--lib/base64/base64.tm96
-rw-r--r--lib/commands/CHANGES.md5
-rw-r--r--lib/commands/README.md5
-rw-r--r--lib/commands/commands.c295
-rw-r--r--lib/commands/commands.tm90
-rw-r--r--lib/core/CHANGES.md5
-rw-r--r--lib/core/core.tm9
-rw-r--r--lib/json/CHANGES.md5
-rw-r--r--lib/json/README.md18
-rw-r--r--lib/json/json.tm173
-rw-r--r--lib/patterns/CHANGES.md8
-rw-r--r--lib/patterns/README.md444
-rw-r--r--lib/patterns/_test.tm256
-rw-r--r--lib/patterns/match_type.h9
-rw-r--r--lib/patterns/patterns.c1212
-rw-r--r--lib/patterns/patterns.tm66
-rw-r--r--lib/pthreads/CHANGES.md5
-rw-r--r--lib/pthreads/pthreads.tm134
-rw-r--r--lib/random/CHANGES.md5
-rw-r--r--lib/random/README.md196
-rw-r--r--lib/random/chacha.h192
-rw-r--r--lib/random/random.tm234
-rw-r--r--lib/random/sysrandom.h17
-rw-r--r--lib/shell/CHANGES.md5
-rw-r--r--lib/shell/README.md13
-rw-r--r--lib/shell/shell.tm44
-rw-r--r--lib/time/CHANGES.md5
-rw-r--r--lib/time/README.md5
-rw-r--r--lib/time/time.tm214
-rw-r--r--lib/time/time_defs.h34
-rw-r--r--lib/uuid/CHANGES.md5
-rw-r--r--lib/uuid/uuid.tm39
-rwxr-xr-xlocal-tomo5
-rw-r--r--man/man1/tomo.164
-rw-r--r--modules/core.ini35
-rw-r--r--modules/examples.ini27
-rw-r--r--src/ast.c151
-rw-r--r--src/ast.h24
-rw-r--r--src/compile/assertions.c35
-rw-r--r--src/compile/assignments.c7
-rw-r--r--src/compile/binops.c2
-rw-r--r--src/compile/comparisons.c14
-rw-r--r--src/compile/conditionals.c2
-rw-r--r--src/compile/doctests.c17
-rw-r--r--src/compile/enums.c20
-rw-r--r--src/compile/expressions.c5
-rw-r--r--src/compile/files.c49
-rw-r--r--src/compile/functions.c44
-rw-r--r--src/compile/headers.c6
-rw-r--r--src/compile/indexing.c4
-rw-r--r--src/compile/lists.c2
-rw-r--r--src/compile/loops.c4
-rw-r--r--src/compile/optionals.c11
-rw-r--r--src/compile/promotions.c12
-rw-r--r--src/compile/reductions.c7
-rw-r--r--src/compile/sets.c2
-rw-r--r--src/compile/statements.c81
-rw-r--r--src/compile/structs.c9
-rw-r--r--src/compile/tables.c2
-rw-r--r--src/compile/types.c4
-rw-r--r--src/config.h6
-rw-r--r--src/environment.c6
-rw-r--r--src/environment.h2
-rw-r--r--src/formatter/args.c57
-rw-r--r--src/formatter/args.h11
-rw-r--r--src/formatter/enums.c51
-rw-r--r--src/formatter/enums.h11
-rw-r--r--src/formatter/formatter.c884
-rw-r--r--src/formatter/formatter.h13
-rw-r--r--src/formatter/types.c45
-rw-r--r--src/formatter/types.h8
-rw-r--r--src/formatter/utils.c154
-rw-r--r--src/formatter/utils.h30
-rw-r--r--src/modules.c119
-rw-r--r--src/modules.h3
-rw-r--r--src/parse/binops.c31
-rw-r--r--src/parse/containers.c32
-rw-r--r--src/parse/context.c8
-rw-r--r--src/parse/context.h5
-rw-r--r--src/parse/controlflow.c24
-rw-r--r--src/parse/expressions.c28
-rw-r--r--src/parse/files.c18
-rw-r--r--src/parse/functions.c41
-rw-r--r--src/parse/statements.c10
-rw-r--r--src/parse/suffixes.c30
-rw-r--r--src/parse/text.c18
-rw-r--r--src/parse/typedefs.c38
-rw-r--r--src/parse/types.c20
-rw-r--r--src/parse/utils.c16
-rw-r--r--src/parse/utils.h6
-rw-r--r--src/stdlib/integers.c4
-rw-r--r--src/stdlib/integers.h1
-rw-r--r--src/stdlib/nums.c36
-rw-r--r--src/stdlib/nums.h10
-rw-r--r--src/stdlib/pointers.c2
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c3
-rw-r--r--src/stdlib/text.c50
-rw-r--r--src/stdlib/text.h15
-rw-r--r--src/tomo.c201
-rw-r--r--src/typecheck.c111
-rw-r--r--src/typecheck.h3
-rw-r--r--src/types.c5
-rw-r--r--test/_vectors.tm1
-rw-r--r--test/enums.tm3
-rw-r--r--test/import.tm2
161 files changed, 2262 insertions, 7122 deletions
diff --git a/.gitignore b/.gitignore
index 71fa5258..78087392 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,8 +14,7 @@
/src/changes.md.h
.build
-/build/bin
-/build/lib
+/build
*.o
*.so
*.dylib
diff --git a/CHANGES.md b/CHANGES.md
index 965a1aea..e2f69439 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,16 @@
# Version History
+## v0.4
+- Tomo libraries are now installed to `$TOMO_PATH/lib/tomo_vX.Y/module_vZ.W`
+ instead of `$TOMO_PATH/share/tomo_vX.Y/installed/module_vZ.W`
+- Core libraries are no longer shipped with the compiler, they have moved to
+ separate repositories.
+- Library installation has been cleaned up a bit.
+- Added a `--format` flag to the `tomo` binary that autoformats your code
+ (currently unstable, do not rely on it just yet).
+- Fixed bugs:
+ - `Int.parse()` had a memory bug.
+
## v0.3
- Added a versioning system based on `CHANGES.md` files and `modules.ini`
diff --git a/Makefile b/Makefile
index 983f41ae..86b5b76b 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,21 @@ else
include config.mk
+# Modified progress counter based on: https://stackoverflow.com/a/35320895
+ifndef NO_PROGRESS
+ifndef ECHO
+T := $(shell $(MAKE) ECHO="COUNTTHIS" $(MAKECMDGOALS) --no-print-directory \
+ -n | grep -c "COUNTTHIS")
+N := x
+C = $(words $N)$(eval N := x $N)
+ECHO = echo -e "[`expr $C '*' 100 / $T`%]"
+endif
+endif
+ifndef ECHO
+ECHO = echo
+endif
+# End of progress counter
+
CC=$(DEFAULT_C_COMPILER)
CCONFIG=-std=c2x -fPIC \
-fno-signed-zeros -fno-trapping-math \
@@ -71,7 +86,7 @@ O=-O3
TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print $$2; exit}' CHANGES.md)
GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h")
CFLAGS=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \
- -DTOMO_PREFIX='"$(PREFIX)"' -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \
+ -DTOMO_INSTALL='"$(PREFIX)"' -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \
-DTOMO_VERSION='"$(TOMO_VERSION)"' -DGIT_VERSION='"$(GIT_VERSION)"'
CFLAGS_PLACEHOLDER="$$(printf '\033[2m<flags...>\033[m\n')"
LDLIBS=-lgc -lm -lunistring -lgmp
@@ -95,13 +110,17 @@ else
endif
EXE_FILE=tomo_$(TOMO_VERSION)
-COMPILER_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/compile/*.c src/parse/*.c))
+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))
+TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/[!_]*.tm))
API_YAML=$(wildcard api/*.yaml)
API_MD=$(patsubst %.yaml,%.md,$(API_YAML))
-all: config.mk check-c-compiler check-libs build/lib/$(LIB_FILE) build/lib/$(AR_FILE) build/bin/$(EXE_FILE)
+all: config.mk check-c-compiler check-libs build/include/tomo_$(TOMO_VERSION) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) build/bin/$(EXE_FILE)
+ @$(ECHO) "All done!"
+
+build/include/tomo_$(TOMO_VERSION):
+ ln -s ../../src/stdlib $@
version:
@echo $(TOMO_VERSION)
@@ -116,12 +135,12 @@ check-libs: check-c-compiler
build/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS)
@mkdir -p build/bin
- @echo $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@
+ @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@
@$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
build/lib/$(LIB_FILE): $(STDLIB_OBJS)
@mkdir -p build/lib
- @echo $(CC) $^ $(CFLAGS_PLACEHOLDER) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@
+ @$(ECHO) $(CC) $^ $(CFLAGS_PLACEHOLDER) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@
@$(CC) $^ $(CFLAGS) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@
build/lib/$(AR_FILE): $(STDLIB_OBJS)
@@ -129,21 +148,22 @@ build/lib/$(AR_FILE): $(STDLIB_OBJS)
ar -rcs $@ $^
tags:
- ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h}
+ ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} src/formatter/*.{c,h}
config.mk: configure.sh
bash ./configure.sh
%.o: %.c src/ast.h src/environment.h src/types.h config.mk
- @echo $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@
+ @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@
@$(CC) $(CFLAGS) -c $< -o $@
# 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 $@
+ @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@
@$(CC) $(CFLAGS) -c $< -o $@
src/changes.md.h: CHANGES.md
+ @$(ECHO) "Embedding changes.md"
xxd -i $< > $@
%: %.tm
@@ -184,10 +204,12 @@ man/man1/tomo.1: docs/tomo.1.md
pandoc --lua-filter=docs/.pandoc/bold-code.lua -s $< -t man -o $@
examples:
- ./local-tomo -qIL examples/log examples/ini examples/vectors examples/http examples/wrap examples/colorful
- ./local-tomo -e examples/game/game.tm examples/http-server/http-server.tm
+ ./local-tomo -L modules/examples.ini
./local-tomo examples/learnxiny.tm
+core-libs:
+ ./local-tomo -L modules/core.ini
+
deps:
bash ./install_dependencies.sh
@@ -217,14 +239,7 @@ install-files: build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE)
cp man/man3/* "$(PREFIX)/man/man3/"; \
sh link_versions.sh
-install-libs: build/bin/$(EXE_FILE) check-utilities
- if ! [ -w "$(PREFIX)" ]; then \
- $(SUDO) -u $(OWNER) $(MAKE) install-libs; \
- exit 0; \
- fi; \
- ./local-tomo -qIL lib/patterns lib/json lib/time lib/commands lib/shell lib/random lib/base64 lib/pthreads lib/uuid lib/core
-
-install: install-files install-libs
+install: install-files
uninstall:
if ! [ -w "$(PREFIX)" ]; then \
@@ -232,10 +247,10 @@ 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)/share/tomo_$(TOMO_VERSION)"; \
+ "$(PREFIX)/lib/libtomo_v*" "$(PREFIX)/lib/tomo_$(TOMO_VERSION)"; \
sh link_versions.sh
endif
.SUFFIXES:
-.PHONY: all clean install install-files install-libs uninstall test tags examples deps check-utilities check-c-compiler check-libs version
+.PHONY: all clean install install-files uninstall test tags core-libs examples deps check-utilities check-c-compiler check-libs version
diff --git a/docs/libraries.md b/docs/libraries.md
index 69608ae3..79477070 100644
--- a/docs/libraries.md
+++ b/docs/libraries.md
@@ -151,7 +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/share/tomo_vX.Y/installed/` (where `X` and `Y` are the major/minor
+`~/.local/lib/tomo_vX.Y/` (where `X` and `Y` are the major/minor
version of the compiler).
### Using Shared Libraries
@@ -169,7 +169,7 @@ When you build and install a library, its version is determined from a
[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
`v1.2`, then it will be installed to
-`~/.local/share/tomo_vX.Y/installed/foo_v1.2/`. When using a library, you must
+`~/.local/lib/tomo_vX.Y/foo_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
diff --git a/docs/tomo.1.md b/docs/tomo.1.md
index 3bf5e779..79c94ddd 100644
--- a/docs/tomo.1.md
+++ b/docs/tomo.1.md
@@ -32,41 +32,59 @@ C code, which is then compiled using a C compiler of your choice.
# OPTIONS
-`-h`, `--help`
-: Print the usage and exit.
+`--changelog`
+: Print the compiler change log and exit.
-`-t`, `--transpile`
-: Transpile the input files to C code without compiling them.
+`--compile-exe`, `-e`
+: Compile the input file to an executable.
-`-c`, `--compile-obj`
+`--compile-obj`, `-c`
: Compile the input files to static objects, rather than running them.
-`-e`, `--compile-exe`
-: Compile the input file to an executable.
-
-`-L`, `--library`
-: Compile the input files to a shared library file and header.
+`--help`, `-h`
+: Print the usage and exit.
-`-I`, `--install`
+`--install`, `-I`
: Install the compiled executable or library.
-`-C` *<program>*, `--show-codegen` *<program>*
+`--library`, `-L`
+: Compile the input files to a shared library file and header.
+
+`--show-codegen` *<program>*, `-C` *<program>*
: Set a program (e.g. `cat` or `bat`) to display the generated code
-`-O` **level**, `--optimization` **level**
-: Set the optimization level.
+`--force-rebuild`, `-f`
+: Force rebuilding/recompiling.
-`-v`, `--verbose`
-: Print extra verbose output.
+`--format`
+: Autoformat a file and print it to standard output.
-`--version`
-: Print the compiler version and exit.
+`--format-inplace`
+: Autoformat a file in-place.
-`--changelog`
-: Print the compiler change log and exit.
+`--optimization` **level**, `-O` **level**
+: Set the optimization level.
`--prefix`
: Print the Tomo installation prefix and exit.
-`-r`, `--run`
-: Run an installed tomo program from `~/.local/share/tomo_vX.Y/installed`.
+`--quiet`, `-q`
+: Run in quiet mode.
+
+`--run`, `-r`
+: Run an installed tomo program from `~/.local/lib/tomo_vX.Y/`.
+
+`--source-mapping=`, `-m=` **<yes|no>**
+: Toggle whether source mapping should be enabled or disabled.
+
+`--transpile`, `-t`
+: Transpile the input files to C code without compiling them.
+
+`--uninstall`, `-u`
+: Uninstall a compiled executable or library.
+
+`--verbose`, `-v`
+: Print extra verbose output.
+
+`--version`
+: Print the compiler version and exit.
diff --git a/docs/versions.md b/docs/versions.md
index c68d1f81..eb617f43 100644
--- a/docs/versions.md
+++ b/docs/versions.md
@@ -49,7 +49,7 @@ The version for the Tomo language itself will come into play in a few ways:
(e.g. `~/.local/lib/libtomo_v1.2.so`) and headers (e.g.
`~/.local/include/tomo_v1.2/tomo.h`).
4. Tomo libraries will be installed to a separate subdirectory for each version
- of the compiler (e.g. `~/.local/share/tomo_v1.2/installed`).
+ of the compiler (e.g. `~/.local/lib/tomo_v1.2/`).
## Tomo Program Versions
diff --git a/examples/README.md b/examples/README.md
index 9e9291e5..d527d692 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -4,19 +4,20 @@ This folder contains some example programs and libraries.
## Example Programs
+- [hello.tm](hello.tm): A Hello World program.
- [learnxiny.tm](learnxiny.tm): A quick overview of language features in the
style of [learnxinyminutes.com](https://learnxinyminutes.com/).
-- [game](game/): An example game using raylib.
-- [http-server](http-server/): A multithreaded HTTP server.
-- [wrap](wrap/): A command-line program to wrap text.
+- [game](https://github.com/bruce-hill/tomo-game): An example game using raylib.
+- [http-server](https://github.com/bruce-hill/http-server): A multithreaded HTTP server.
+- [wrap](https://github.com/bruce-hill/wrap): A command-line program to wrap text.
## Example Libraries
Libraries can be installed with `tomo -IL ./library-folder`
-- [colorful](colorful/): A DSL useful for rendering terminal colors.
-- [coroutines](coroutines/): A library for stackful coroutines similar to Lua's. (Note: only works on x86_64)
-- [http](http/): An HTTP library to make basic synchronous HTTP requests.
-- [ini](ini/): An INI configuration file reader tool.
-- [log](log/): A logging utility.
-- [vectors](vectors/): A math vector library.
+- [colorful](https://github.com/bruce-hill/tomo-colorful): A DSL useful for rendering terminal colors.
+- [coroutines](https://github.com/bruce-hill/tomo-coroutines): A library for stackful coroutines similar to Lua's. (Note: only works on x86_64)
+- [http](https://github.com/bruce-hill/tomo-http): An HTTP library to make basic synchronous HTTP requests.
+- [ini](https://github.com/bruce-hill/tomo-ini): An INI configuration file reader tool.
+- [log](https://github.com/bruce-hill/tomo-log): A logging utility.
+- [vectors](https://github.com/bruce-hill/tomo-vectors): A math vector library.
diff --git a/examples/colorful/CHANGES.md b/examples/colorful/CHANGES.md
deleted file mode 100644
index 093cc077..00000000
--- a/examples/colorful/CHANGES.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Version History
-
-## v1.2
-
-- Version bump for patterns.
-
-## v1.1
-
-- Added syntax for `@(strikethrough:...)` or `@(s:...)`
-
-## v1.0
-
-Initial version
diff --git a/examples/colorful/README.md b/examples/colorful/README.md
deleted file mode 100644
index faded9b1..00000000
--- a/examples/colorful/README.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# Colorful Lang
-
-Colorful is a `lang` that lets you write colorful text for the terminal without
-having to stress about managing state for color highlighting.
-
-## Grammar
-
-The grammar looks like this:
-
-```
-colorful <- ("@(at)" / "@(lparen)" / "@(rparen)" # Escapes
- / "@(" attributes ":" colorful ")" # Colorful text
- / .)* # Plain text
-
-attributes <- (attribute ("," attribute)*)?
-
-attribute <- color # Color defaults to foreground
- / "fg=" color # Foreground color
- / "bg=" color # Background color
- / "ul=" color # Underline color
- / "b" / "bold"
- / "d" / "dim"
- / "u" / "underline"
- / "i" / "italic"
- / "B" / "blink"
- / "r" / "reverse"
- # These are rarely supported by terminals:
- / "fraktur"
- / "frame"
- / "encircle"
- / "overline"
- / "super" / "superscript"
- / "sub" / "subscript"
-
-color <- "black" / "red" / "green" / "yellow" / "blue" / "magenta" / "cyan" / "white"
- # All caps colors are "bright" colors (not always supported):
- / "BLACK" / "RED" / "GREEN" / "YELLOW" / "BLUE" / "MAGENTA" / "CYAN" / "WHITE"
- / "default"
- / "#" 6 hex # Values 0x000000-0xFFFFFF
- / "#" 3 hex # Values 0x000-0xFFF
- / 1-3 digit # Values 0-255
-```
-
-## Command Line Usage
-
-You can run `colorful` as a standalone executable to render colorful text with
-ANSI escape sequences so it looks nice on a terminal.
-
-```
-colorful [--help] [texts...] [--by-line] [--files ...]
-```
-
-## Library Usage
-
-`colorful` can also be used as a Tomo library:
-
-```ini
-# modules.ini
-[colorful]
-version=v1.0
-```
-
-```tomo
-use colorful
-
-$Colorful"
- @(blue:Welcome to the @(bold:party)!)
- We have @(green,bold:colors)!
-".print()
-```
diff --git a/examples/colorful/colorful.tm b/examples/colorful/colorful.tm
deleted file mode 100644
index 8a1d46b0..00000000
--- a/examples/colorful/colorful.tm
+++ /dev/null
@@ -1,220 +0,0 @@
-# Colorful language
-
-HELP := "
- colorful: A domain-specific language for writing colored text to the terminal
- Usage: colorful [args...] [--by-line] [--files files...]
-"
-
-CSI := "\033["
-
-use patterns
-
-lang Colorful
- convert(text:Text -> Colorful)
- text = text.translate({"@"="@(at)", "("="@(lparen)", ")"="@(rparen)"})
- return Colorful.from_text(text)
-
- convert(i:Int -> Colorful) return Colorful.from_text("$i")
- convert(n:Num -> Colorful) return Colorful.from_text("$n")
-
- func for_terminal(c:Colorful -> Text)
- return CSI ++ "m" ++ _for_terminal(c, _TermState())
-
- func print(c:Colorful, newline=yes)
- say(c.for_terminal(), newline=newline)
-
-
-func main(texts:[Text], files:[Path]=[], by_line=no)
- for i,text in texts
- colorful := Colorful.from_text(text)
- colorful.print(newline=no)
- if i == texts.length then say("")
- else say(" ", newline=no)
-
- if texts.length == 0 and files.length == 0
- files = [(/dev/stdin)]
-
- for file in files
- if by_line
- for line in file.by_line() or exit("Could not read file: $file")
- colorful := Colorful.from_text(line)
- colorful.print()
- else
- colorful := Colorful.from_text(file.read() or exit("Could not read file: $file"))
- colorful.print(newline=no)
-
-
-func _for_terminal(c:Colorful, state:_TermState -> Text)
- return c.text.map_pattern(recursive=no, $Pat"@(?)", func(m:PatternMatch) _add_ansi_sequences(m.captures[1], state))
-
-enum _Color(Default, Bright(color:Int16), Color8Bit(color:Int16), Color24Bit(color:Int32))
- func from_text(text:Text -> _Color?)
- if text.matches_pattern($Pat"#{3-6 hex}")
- hex := text.from(2)
- return none unless hex.length == 3 or hex.length == 6
- if hex.length == 3
- hex = hex[1]++hex[1]++hex[2]++hex[2]++hex[3]++hex[3]
- n := Int32.parse("0x" ++ hex) or return none
- return Color24Bit(n)
- else if text.matches_pattern($Pat"{1-3 digit}")
- n := Int16.parse(text) or return none
- if n >= 0 and n <= 255 return Color8Bit(n)
- else if text == "black" return _Color.Color8Bit(0)
- else if text == "red" return _Color.Color8Bit(1)
- else if text == "green" return _Color.Color8Bit(2)
- else if text == "yellow" return _Color.Color8Bit(3)
- else if text == "blue" return _Color.Color8Bit(4)
- else if text == "magenta" return _Color.Color8Bit(5)
- else if text == "cyan" return _Color.Color8Bit(6)
- else if text == "white" return _Color.Color8Bit(7)
- else if text == "default" return _Color.Default
- else if text == "BLACK" return _Color.Bright(0)
- else if text == "RED" return _Color.Bright(1)
- else if text == "GREEN" return _Color.Bright(2)
- else if text == "YELLOW" return _Color.Bright(3)
- else if text == "BLUE" return _Color.Bright(4)
- else if text == "MAGENTA" return _Color.Bright(5)
- else if text == "CYAN" return _Color.Bright(6)
- else if text == "WHITE" return _Color.Bright(7)
- return none
-
- func fg(c:_Color -> Text)
- when c is Color8Bit(color)
- if color >= 0 and color <= 7 return "$(30+color)"
- else if color >= 0 and color <= 255 return "38;5;$color"
- is Color24Bit(hex)
- if hex >= 0 and hex <= 0xFFFFFF
- return "38;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)"
- is Bright(color)
- if color <= 7 return "$(90+color)"
- is Default
- return "39"
- fail("Invalid foreground color: '$c'")
-
- func bg(c:_Color -> Text)
- when c is Color8Bit(color)
- if color >= 0 and color <= 7 return "$(40+color)"
- else if color >= 0 and color <= 255 return "48;5;$color"
- is Color24Bit(hex)
- if hex >= 0 and hex <= 0xFFFFFF
- return "48;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)"
- is Bright(color)
- if color <= 7 return "$(90+color)"
- is Default
- return "49"
- fail("Invalid background color: '$c'")
-
- func underline(c:_Color -> Text)
- when c is Color8Bit(color)
- if color >= 0 and color <= 255 return "58;5;$color"
- is Color24Bit(hex)
- if hex >= 0 and hex <= 0xFFFFFF
- return "58;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)"
- is Default
- return "59"
- is Bright(color)
- pass
- fail("Invalid underline color: '$c'")
-
-func _toggle(sequences:&[Text], cur,new:Bool, apply,unapply:Text; inline)
- if new and not cur
- sequences.insert(apply)
- else if cur and not new
- sequences.insert(unapply)
-
-func _toggle2(sequences:&[Text], cur1,cur2,new1,new2:Bool, apply1,apply2,unapply:Text; inline)
- return if new1 == cur1 and new2 == cur2
- if (cur1 and not new1) or (cur2 and not new2) # Gotta wipe at least one
- sequences.insert(unapply)
- cur1, cur2 = no, no # Wiped out
-
- if new1 and not cur1
- sequences.insert(apply1)
- if new2 and not cur2
- sequences.insert(apply2)
-
-struct _TermState(
- bold=no, dim=no, italic=no, underline=no, blink=no,
- reverse=no, conceal=no, strikethrough=no, fraktur=no, frame=no,
- encircle=no, overline=no, superscript=no, subscript=no,
- bg=_Color.Default, fg=_Color.Default, underline_color=_Color.Default,
-)
-
- func apply(old,new:_TermState -> Text)
- sequences : &[Text]
- _toggle2(sequences, old.bold, old.dim, new.bold, new.dim, "1", "2", "22")
- _toggle2(sequences, old.italic, old.fraktur, new.italic, new.fraktur, "3", "20", "23")
- _toggle(sequences, old.underline, new.underline, "4", "24")
- _toggle(sequences, old.blink, new.blink, "5", "25")
- _toggle(sequences, old.reverse, new.reverse, "7", "27")
- _toggle(sequences, old.conceal, new.conceal, "8", "28")
- _toggle(sequences, old.strikethrough, new.strikethrough, "9", "29")
- _toggle2(sequences, old.frame, old.encircle, new.frame, new.frame, "51", "52", "54")
- _toggle(sequences, old.overline, new.overline, "53", "55")
- _toggle2(sequences, old.subscript, old.subscript, new.superscript, new.superscript, "73", "74", "75")
-
- if new.bg != old.bg
- sequences.insert(new.bg.bg())
-
- if new.fg != old.fg
- sequences.insert(new.fg.fg())
-
- if new.underline_color != old.underline_color
- sequences.insert(new.underline_color.underline())
-
- if sequences.length == 0
- return ""
- return CSI ++ ";".join(sequences) ++ "m"
-
-func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text)
- if text == "lparen" return "("
- else if text == "rparen" return ")"
- else if text == "@" or text == "at" return "@"
- parts := (
- text.pattern_captures($Pat"{0+..}:{0+..}") or
- return "@("++_for_terminal(Colorful.from_text(text), prev_state)++")"
- )
- attributes := parts[1].split_pattern($Pat"{0+space},{0+space}")
- new_state := prev_state
- for attr in attributes
- if attr.starts_with("fg=")
- new_state.fg = _Color.from_text(attr.from(4))!
- else if attr.starts_with("bg=")
- new_state.bg = _Color.from_text(attr.from(4))!
- else if attr.starts_with("ul=")
- new_state.underline_color = _Color.from_text(attr.from(4))!
- else if color := _Color.from_text(attr)
- new_state.fg = color
- else if attr == "b" or attr == "bold"
- new_state.bold = yes
- else if attr == "d" or attr == "dim"
- new_state.dim = yes
- else if attr == "i" or attr == "italic"
- new_state.italic = yes
- else if attr == "u" or attr == "underline"
- new_state.underline = yes
- else if attr == "s" or attr == "strikethrough"
- new_state.strikethrough = yes
- else if attr == "B" or attr == "blink"
- new_state.blink = yes
- else if attr == "r" or attr == "reverse"
- new_state.reverse = yes
- else if attr == "fraktur"
- new_state.fraktur = yes
- else if attr == "frame"
- new_state.frame = yes
- else if attr == "encircle"
- new_state.encircle = yes
- else if attr == "overline"
- new_state.overline = yes
- else if attr == "super" or attr == "superscript"
- new_state.superscript = yes
- else if attr == "sub" or attr == "subscript"
- new_state.subscript = yes
- else
- fail("Invalid attribute: '$attr'")
-
- result := prev_state.apply(new_state)
- result ++= parts[2].map_pattern(recursive=no, $Pat"@(?)", func(m:PatternMatch) _add_ansi_sequences(m.captures[1], new_state))
- result ++= new_state.apply(prev_state)
- return result
diff --git a/examples/colorful/modules.ini b/examples/colorful/modules.ini
deleted file mode 100644
index 5e4b5b0a..00000000
--- a/examples/colorful/modules.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[patterns]
-version=v1.1
diff --git a/examples/colorful/test.colors b/examples/colorful/test.colors
deleted file mode 100644
index 314b8b05..00000000
--- a/examples/colorful/test.colors
+++ /dev/null
@@ -1,9 +0,0 @@
-This is some text that has @(bold:@(red:c)@(yellow:o)@(green:l)@(cyan:o)@(blue:r)@(magenta:s))!
-
-@(fg=#aaf,b:You can have @(red:nested) color directives and stuff (even in
-parens) will be handled @(i:right))
-
-@(dim:The output @(bold:ANSI) sequences will be optimal, even if you have
-nested stuff like bold and dim)
-
-(which is distinct from @(bold:BOLD) by itself)
diff --git a/examples/coroutines/ACO_LICENSE b/examples/coroutines/ACO_LICENSE
deleted file mode 100644
index ef4f82f0..00000000
--- a/examples/coroutines/ACO_LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [2018] [Sen Han <00hnes@gmail.com>]
- Copyright [2024] [Bruce Hill <bruce@bruce-hill.com>]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/examples/coroutines/CHANGES.md b/examples/coroutines/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/coroutines/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/coroutines/README.md b/examples/coroutines/README.md
deleted file mode 100644
index 644c0e07..00000000
--- a/examples/coroutines/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Tomo Coroutine Library
-
-This is a coroutine library built on top of a modified version of
-[libaco](https://libaco.org).
-
-## Example Usage
-
-```ini
-# modules.ini
-[coroutines]
-version=v1.0
-```
-
-```tomo
-use coroutines
-
-func main()
- co := Coroutine(func()
- say("I'm in the coroutine!")
- yield()
- say("I'm back in the coroutine!")
- )
- >> co
- say("I'm in the main func")
- >> co.resume()
- say("I'm back in the main func")
- >> co.resume()
- say("I'm back in the main func again")
- >> co.resume()
-```
diff --git a/examples/coroutines/aco.c b/examples/coroutines/aco.c
deleted file mode 100644
index 258efe28..00000000
--- a/examples/coroutines/aco.c
+++ /dev/null
@@ -1,458 +0,0 @@
-// Copyright 2018 Sen Han <00hnes@gmail.com>
-// Modifications copyright 2025 Bruce Hill <bruce@bruce-hill.com>
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#define _GNU_SOURCE
-
-#include "aco.h"
-#include <stdint.h>
-#include <stdio.h>
-
-#ifndef public
-#define public __attribute__((visibility("default")))
-#endif
-
-#define aco_size_t_safe_add_assert(a, b) aco_assert((a) + (b) >= (a))
-
-static void aco_default_protector_last_word(void *);
-
-void *(*aco_alloc_fn)(size_t) = malloc;
-void (*aco_dealloc_fn)(void *) = free;
-
-#define aco_alloc(size) \
- ({ \
- void *_ptr = aco_alloc_fn(size); \
- if (aco_unlikely((_ptr) == NULL)) { \
- fprintf(stderr, "Aborting: failed to allocate memory: %s:%d:%s\n", __FILE__, __LINE__, \
- __PRETTY_FUNCTION__); \
- abort(); \
- } \
- _ptr; \
- })
-
-// aco's Global Thread Local Storage variable `co`
-public
-__thread aco_t *aco_gtls_co;
-static __thread aco_cofuncp_t aco_gtls_last_word_fp = aco_default_protector_last_word;
-
-#ifdef __i386__
-static __thread void *aco_gtls_fpucw_mxcsr[2];
-#elif __x86_64__
-static __thread void *aco_gtls_fpucw_mxcsr[1];
-#else
-#error "platform not supporteded yet"
-#endif
-
-public
-void aco_runtime_test(void) {
-#ifdef __i386__
- _Static_assert(sizeof(void *) == 4, "require 'sizeof(void*) == 4'");
-#elif __x86_64__
- _Static_assert(sizeof(void *) == 8, "require 'sizeof(void*) == 8'");
- _Static_assert(sizeof(__uint128_t) == 16, "require 'sizeof(__uint128_t) == 16'");
-#else
-#error "platform not supporteded yet"
-#endif
- _Static_assert(sizeof(int) >= 4, "require 'sizeof(int) >= 4'");
- aco_assert(sizeof(int) >= 4);
- _Static_assert(sizeof(int) <= sizeof(size_t), "require 'sizeof(int) <= sizeof(size_t)'");
- aco_assert(sizeof(int) <= sizeof(size_t));
-}
-
-#ifdef __x86_64__
-static inline void aco_fast_memcpy(void *dst, const void *src, size_t sz) {
- if (((uintptr_t)src & 0x0f) != 0 || ((uintptr_t)dst & 0x0f) != 0 || (sz & 0x0f) != 0x08 || (sz >> 4) > 8) {
- memcpy(dst, src, sz);
- return;
- }
-
- __uint128_t xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7;
- switch (sz >> 4) {
- case 0: break;
- case 1:
- xmm0 = *((__uint128_t *)src + 0);
- *((__uint128_t *)dst + 0) = xmm0;
- break;
- case 2:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- break;
- case 3:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- xmm2 = *((__uint128_t *)src + 2);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- *((__uint128_t *)dst + 2) = xmm2;
- break;
- case 4:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- xmm2 = *((__uint128_t *)src + 2);
- xmm3 = *((__uint128_t *)src + 3);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- *((__uint128_t *)dst + 2) = xmm2;
- *((__uint128_t *)dst + 3) = xmm3;
- break;
- case 5:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- xmm2 = *((__uint128_t *)src + 2);
- xmm3 = *((__uint128_t *)src + 3);
- xmm4 = *((__uint128_t *)src + 4);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- *((__uint128_t *)dst + 2) = xmm2;
- *((__uint128_t *)dst + 3) = xmm3;
- *((__uint128_t *)dst + 4) = xmm4;
- break;
- case 6:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- xmm2 = *((__uint128_t *)src + 2);
- xmm3 = *((__uint128_t *)src + 3);
- xmm4 = *((__uint128_t *)src + 4);
- xmm5 = *((__uint128_t *)src + 5);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- *((__uint128_t *)dst + 2) = xmm2;
- *((__uint128_t *)dst + 3) = xmm3;
- *((__uint128_t *)dst + 4) = xmm4;
- *((__uint128_t *)dst + 5) = xmm5;
- break;
- case 7:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- xmm2 = *((__uint128_t *)src + 2);
- xmm3 = *((__uint128_t *)src + 3);
- xmm4 = *((__uint128_t *)src + 4);
- xmm5 = *((__uint128_t *)src + 5);
- xmm6 = *((__uint128_t *)src + 6);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- *((__uint128_t *)dst + 2) = xmm2;
- *((__uint128_t *)dst + 3) = xmm3;
- *((__uint128_t *)dst + 4) = xmm4;
- *((__uint128_t *)dst + 5) = xmm5;
- *((__uint128_t *)dst + 6) = xmm6;
- break;
- case 8:
- xmm0 = *((__uint128_t *)src + 0);
- xmm1 = *((__uint128_t *)src + 1);
- xmm2 = *((__uint128_t *)src + 2);
- xmm3 = *((__uint128_t *)src + 3);
- xmm4 = *((__uint128_t *)src + 4);
- xmm5 = *((__uint128_t *)src + 5);
- xmm6 = *((__uint128_t *)src + 6);
- xmm7 = *((__uint128_t *)src + 7);
- *((__uint128_t *)dst + 0) = xmm0;
- *((__uint128_t *)dst + 1) = xmm1;
- *((__uint128_t *)dst + 2) = xmm2;
- *((__uint128_t *)dst + 3) = xmm3;
- *((__uint128_t *)dst + 4) = xmm4;
- *((__uint128_t *)dst + 5) = xmm5;
- *((__uint128_t *)dst + 6) = xmm6;
- *((__uint128_t *)dst + 7) = xmm7;
- break;
- }
- *((uint64_t *)((uintptr_t)dst + sz - 8)) = *((uint64_t *)((uintptr_t)src + sz - 8));
-}
-#endif
-
-void aco_default_protector_last_word(void *_) {
- aco_t *co = aco_get_co();
- // do some log about the offending `co`
- fprintf(stderr, "error: aco_default_protector_last_word triggered\n");
- fprintf(stderr,
- "error: co:%p should call `aco_exit()` instead of direct "
- "`return` in co_fp:%p to finish its execution\n",
- co, (void *)co->fp);
- aco_assert(0);
-}
-
-public
-void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *)) {
- aco_alloc_fn = alloc;
- aco_dealloc_fn = dealloc;
-}
-
-public
-void aco_thread_init(aco_cofuncp_t last_word_co_fp) {
- aco_save_fpucw_mxcsr(aco_gtls_fpucw_mxcsr);
-
- if ((void *)last_word_co_fp != NULL) aco_gtls_last_word_fp = last_word_co_fp;
-}
-
-// This function `aco_funcp_protector` should never be
-// called. If it's been called, that means the offending
-// `co` didn't call aco_exit(co) instead of `return` to
-// finish its execution.
-public
-void aco_funcp_protector(void) {
- if ((void *)(aco_gtls_last_word_fp) != NULL) {
- aco_gtls_last_word_fp(NULL);
- } else {
- aco_default_protector_last_word(NULL);
- }
- aco_assert(0);
-}
-
-public
-aco_shared_stack_t *aco_shared_stack_new(size_t sz) { return aco_shared_stack_new2(sz, 1); }
-
-public
-aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled) {
- if (sz == 0) {
- sz = 1024 * 1024 * 2;
- }
- if (sz < 4096) {
- sz = 4096;
- }
- aco_assert(sz > 0);
-
- size_t u_pgsz = 0;
- if (guard_page_enabled) {
- // although gcc's Built-in Functions to Perform Arithmetic with
- // Overflow Checking is better, but it would require gcc >= 5.0
- long pgsz = sysconf(_SC_PAGESIZE);
- // pgsz must be > 0 && a power of two
- aco_assert(pgsz > 0 && (((pgsz - 1) & pgsz) == 0));
- u_pgsz = (size_t)((unsigned long)pgsz);
- // it should be always true in real life
- aco_assert(u_pgsz == (unsigned long)pgsz && ((u_pgsz << 1) >> 1) == u_pgsz);
- if (sz <= u_pgsz) {
- sz = u_pgsz << 1;
- } else {
- size_t new_sz;
- if ((sz & (u_pgsz - 1)) != 0) {
- new_sz = (sz & (~(u_pgsz - 1)));
- aco_assert(new_sz >= u_pgsz);
- aco_size_t_safe_add_assert(new_sz, (u_pgsz << 1));
- new_sz = new_sz + (u_pgsz << 1);
- aco_assert(sz / u_pgsz + 2 == new_sz / u_pgsz);
- } else {
- aco_size_t_safe_add_assert(sz, u_pgsz);
- new_sz = sz + u_pgsz;
- aco_assert(sz / u_pgsz + 1 == new_sz / u_pgsz);
- }
- sz = new_sz;
- aco_assert((sz / u_pgsz > 1) && ((sz & (u_pgsz - 1)) == 0));
- }
- }
-
- aco_shared_stack_t *p = aco_alloc(sizeof(aco_shared_stack_t));
- memset(p, 0, sizeof(aco_shared_stack_t));
-
- if (guard_page_enabled) {
- p->real_ptr = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (aco_unlikely(p->real_ptr == MAP_FAILED)) {
- fprintf(stderr, "Aborting: failed to allocate memory: %s:%d:%s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
- abort();
- }
- p->guard_page_enabled = true;
- aco_assert(0 == mprotect(p->real_ptr, u_pgsz, PROT_READ));
-
- p->ptr = (void *)(((uintptr_t)p->real_ptr) + u_pgsz);
- p->real_sz = sz;
- aco_assert(sz >= (u_pgsz << 1));
- p->sz = sz - u_pgsz;
- } else {
- // p->guard_page_enabled = 0;
- p->sz = sz;
- p->ptr = aco_alloc(sz);
- }
-
- p->owner = NULL;
-#ifdef ACO_USE_VALGRIND
- p->valgrind_stk_id = VALGRIND_STACK_REGISTER(p->ptr, (void *)((uintptr_t)p->ptr + p->sz));
-#endif
-#if defined(__i386__) || defined(__x86_64__)
- uintptr_t u_p = (uintptr_t)(p->sz - (sizeof(void *) << 1) + (uintptr_t)p->ptr);
- u_p = (u_p >> 4) << 4;
- p->align_highptr = (void *)u_p;
- p->align_retptr = (void *)(u_p - sizeof(void *));
- *((void **)(p->align_retptr)) = (void *)(aco_funcp_protector_asm);
- aco_assert(p->sz > (16 + (sizeof(void *) << 1) + sizeof(void *)));
- p->align_limit = p->sz - 16 - (sizeof(void *) << 1);
-#else
-#error "platform not supporteded yet"
-#endif
- return p;
-}
-
-public
-void aco_shared_stack_destroy(aco_shared_stack_t *sstk) {
- aco_assert(sstk != NULL && sstk->ptr != NULL);
-#ifdef ACO_USE_VALGRIND
- VALGRIND_STACK_DEREGISTER(sstk->valgrind_stk_id);
-#endif
- if (sstk->guard_page_enabled) {
- aco_assert(0 == munmap(sstk->real_ptr, sstk->real_sz));
- sstk->real_ptr = NULL;
- sstk->ptr = NULL;
- } else {
- if (aco_dealloc_fn != NULL) aco_dealloc_fn(sstk->ptr);
- sstk->ptr = NULL;
- }
- if (aco_dealloc_fn != NULL) aco_dealloc_fn(sstk);
-}
-
-public
-aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp,
- void *arg) {
- aco_t *p = aco_alloc(sizeof(aco_t));
- memset(p, 0, sizeof(aco_t));
-
- if (main_co != NULL) { // non-main co
- aco_assertptr(shared_stack);
- p->shared_stack = shared_stack;
-#ifdef __i386__
- // POSIX.1-2008 (IEEE Std 1003.1-2008) - General Information - Data Types - Pointer Types
- // http://pubs.opengroup.org/onlinepubs/9699919799.2008edition/functions/V2_chap02.html#tag_15_12_03
- p->reg[ACO_REG_IDX_RETADDR] = (void *)fp;
- // push retaddr
- p->reg[ACO_REG_IDX_SP] = p->shared_stack->align_retptr;
-#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- p->reg[ACO_REG_IDX_FPU] = aco_gtls_fpucw_mxcsr[0];
- p->reg[ACO_REG_IDX_FPU + 1] = aco_gtls_fpucw_mxcsr[1];
-#endif
-#elif __x86_64__
- p->reg[ACO_REG_IDX_RETADDR] = (void *)fp;
- p->reg[ACO_REG_IDX_SP] = p->shared_stack->align_retptr;
-#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- p->reg[ACO_REG_IDX_FPU] = aco_gtls_fpucw_mxcsr[0];
-#endif
-#else
-#error "platform not supporteded yet"
-#endif
- p->main_co = main_co;
- p->arg = arg;
- p->fp = fp;
- if (saved_stack_sz == 0) {
- saved_stack_sz = 64;
- }
- p->saved_stack.ptr = aco_alloc(saved_stack_sz);
- p->saved_stack.sz = saved_stack_sz;
-#if defined(__i386__) || defined(__x86_64__)
- p->saved_stack.valid_sz = 0;
-#else
-#error "platform not supporteded yet"
-#endif
- return p;
- } else { // main co
- p->main_co = NULL;
- p->arg = arg;
- p->fp = fp;
- p->shared_stack = NULL;
- p->saved_stack.ptr = NULL;
- return p;
- }
- aco_assert(0);
-}
-
-public
-aco_attr_no_asan void aco_resume(aco_t *resume_co) {
- aco_assert(resume_co != NULL && resume_co->main_co != NULL && !resume_co->is_finished);
- if (resume_co->shared_stack->owner != resume_co) {
- if (resume_co->shared_stack->owner != NULL) {
- aco_t *owner_co = resume_co->shared_stack->owner;
- aco_assert(owner_co->shared_stack == resume_co->shared_stack);
-#if defined(__i386__) || defined(__x86_64__)
- aco_assert(((uintptr_t)(owner_co->shared_stack->align_retptr) >= (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]))
- && ((uintptr_t)(owner_co->shared_stack->align_highptr)
- - (uintptr_t)(owner_co->shared_stack->align_limit)
- <= (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])));
- owner_co->saved_stack.valid_sz =
- (uintptr_t)(owner_co->shared_stack->align_retptr) - (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]);
- if (owner_co->saved_stack.sz < owner_co->saved_stack.valid_sz) {
- if (aco_dealloc_fn != NULL) aco_dealloc_fn(owner_co->saved_stack.ptr);
- owner_co->saved_stack.ptr = NULL;
- while (1) {
- owner_co->saved_stack.sz = owner_co->saved_stack.sz << 1;
- aco_assert(owner_co->saved_stack.sz > 0);
- if (owner_co->saved_stack.sz >= owner_co->saved_stack.valid_sz) {
- break;
- }
- }
- owner_co->saved_stack.ptr = aco_alloc(owner_co->saved_stack.sz);
- }
- // TODO: optimize the performance penalty of memcpy function call
- // for very short memory span
- if (owner_co->saved_stack.valid_sz > 0) {
-#ifdef __x86_64__
- aco_fast_memcpy(owner_co->saved_stack.ptr, owner_co->reg[ACO_REG_IDX_SP],
- owner_co->saved_stack.valid_sz);
-#else
- memcpy(owner_co->saved_stack.ptr, owner_co->reg[ACO_REG_IDX_SP], owner_co->saved_stack.valid_sz);
-#endif
- owner_co->saved_stack.ct_save++;
- }
- if (owner_co->saved_stack.valid_sz > owner_co->saved_stack.max_cpsz) {
- owner_co->saved_stack.max_cpsz = owner_co->saved_stack.valid_sz;
- }
- owner_co->shared_stack->owner = NULL;
- owner_co->shared_stack->align_validsz = 0;
-#else
-#error "platform not supporteded yet"
-#endif
- }
- aco_assert(resume_co->shared_stack->owner == NULL);
-#if defined(__i386__) || defined(__x86_64__)
- aco_assert(resume_co->saved_stack.valid_sz <= resume_co->shared_stack->align_limit - sizeof(void *));
- // TODO: optimize the performance penalty of memcpy function call
- // for very short memory span
- if (resume_co->saved_stack.valid_sz > 0) {
- void *dst = (void *)((uintptr_t)(resume_co->shared_stack->align_retptr) - resume_co->saved_stack.valid_sz);
-#ifdef __x86_64__
- aco_fast_memcpy(dst, resume_co->saved_stack.ptr, resume_co->saved_stack.valid_sz);
-#else
- memcpy(dst, resume_co->saved_stack.ptr, resume_co->saved_stack.valid_sz);
-#endif
- resume_co->saved_stack.ct_restore++;
- }
- if (resume_co->saved_stack.valid_sz > resume_co->saved_stack.max_cpsz) {
- resume_co->saved_stack.max_cpsz = resume_co->saved_stack.valid_sz;
- }
- resume_co->shared_stack->align_validsz = resume_co->saved_stack.valid_sz + sizeof(void *);
- resume_co->shared_stack->owner = resume_co;
-#else
-#error "platform not supporteded yet"
-#endif
- }
- aco_gtls_co = resume_co;
- aco_yield_asm(resume_co->main_co, resume_co);
- aco_gtls_co = resume_co->main_co;
-}
-
-public
-void aco_destroy(aco_t *co) {
- aco_assertptr(co);
- if (aco_is_main_co(co)) {
- if (aco_dealloc_fn != NULL) aco_dealloc_fn(co);
- } else {
- if (co->shared_stack->owner == co) {
- co->shared_stack->owner = NULL;
- co->shared_stack->align_validsz = 0;
- }
- if (aco_dealloc_fn != NULL) aco_dealloc_fn(co->saved_stack.ptr);
- co->saved_stack.ptr = NULL;
- if (aco_dealloc_fn != NULL) aco_dealloc_fn(co);
- }
-}
-
-public
-void aco_exit_fn(void *_) { aco_exit(); }
diff --git a/examples/coroutines/aco.h b/examples/coroutines/aco.h
deleted file mode 100644
index 05ba0bdb..00000000
--- a/examples/coroutines/aco.h
+++ /dev/null
@@ -1,213 +0,0 @@
-// A coroutine library
-// Copyright 2018 Sen Han <00hnes@gmail.com>
-// Modifications copyright 2025 Bruce Hill <bruce@bruce-hill.com>
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <limits.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <time.h>
-#include <unistd.h>
-
-#ifdef ACO_USE_VALGRIND
-#include <valgrind/valgrind.h>
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define ACO_VERSION_MAJOR 2
-#define ACO_VERSION_MINOR 0
-#define ACO_VERSION_PATCH 0
-
-#ifdef __i386__
-#define ACO_REG_IDX_RETADDR 0
-#define ACO_REG_IDX_SP 1
-#define ACO_REG_IDX_BP 2
-#define ACO_REG_IDX_ARG1 0
-#define ACO_REG_IDX_FPU 6
-#elif __x86_64__
-#define ACO_REG_IDX_RETADDR 4
-#define ACO_REG_IDX_SP 5
-#define ACO_REG_IDX_BP 7
-#define ACO_REG_IDX_EDI 8
-#define ACO_REG_IDX_FPU 8
-#else
-#error "platform not supported yet"
-#endif
-
-typedef struct {
- void *ptr;
- size_t sz;
- size_t valid_sz;
- // max copy size in bytes
- size_t max_cpsz;
- // copy from shared stack to this saved stack
- size_t ct_save;
- // copy from this saved stack to shared stack
- size_t ct_restore;
-} aco_saved_stack_t;
-
-struct aco_s;
-typedef struct aco_s aco_t;
-
-typedef struct {
- void *ptr;
- size_t sz;
- void *align_highptr;
- void *align_retptr;
- size_t align_validsz;
- size_t align_limit;
- aco_t *owner;
-
- bool guard_page_enabled;
- void *real_ptr;
- size_t real_sz;
-
-#ifdef ACO_USE_VALGRIND
- unsigned long valgrind_stk_id;
-#endif
-} aco_shared_stack_t;
-
-typedef void (*aco_cofuncp_t)(void *);
-
-struct aco_s {
- // cpu registers' state
-#ifdef __i386__
-#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- void *reg[6];
-#else
- void *reg[8];
-#endif
-#elif __x86_64__
-#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- void *reg[8];
-#else
- void *reg[9];
-#endif
-#else
-#error "platform not supported yet"
-#endif
- aco_t *main_co;
- void *arg;
- bool is_finished;
-
- aco_cofuncp_t fp;
-
- aco_saved_stack_t saved_stack;
- aco_shared_stack_t *shared_stack;
-};
-
-#define aco_likely(x) (__builtin_expect(!!(x), 1))
-
-#define aco_unlikely(x) (__builtin_expect(!!(x), 0))
-
-#define aco_assert(EX) ((aco_likely(EX)) ? ((void)0) : (abort()))
-
-#define aco_assertptr(ptr) ((aco_likely((ptr) != NULL)) ? ((void)0) : (abort()))
-
-#if defined(aco_attr_no_asan)
-#error "aco_attr_no_asan already defined"
-#endif
-#if defined(ACO_USE_ASAN)
-#if defined(__has_feature)
-#if __has_feature(__address_sanitizer__)
-#define aco_attr_no_asan __attribute__((__no_sanitize_address__))
-#endif
-#endif
-#if defined(__SANITIZE_ADDRESS__) && !defined(aco_attr_no_asan)
-#define aco_attr_no_asan __attribute__((__no_sanitize_address__))
-#endif
-#endif
-#ifndef aco_attr_no_asan
-#define aco_attr_no_asan
-#endif
-
-void aco_runtime_test(void);
-
-void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *));
-
-void aco_thread_init(aco_cofuncp_t last_word_co_fp);
-
-void aco_yield_asm(aco_t *from_co, aco_t *to_co) __asm__("aco_yield_asm"); // asm
-
-void aco_save_fpucw_mxcsr(void *p) __asm__("aco_save_fpucw_mxcsr"); // asm
-
-void aco_funcp_protector_asm(void) __asm__("aco_funcp_protector_asm"); // asm
-
-void aco_funcp_protector(void);
-
-aco_shared_stack_t *aco_shared_stack_new(size_t sz);
-
-aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled);
-
-void aco_shared_stack_destroy(aco_shared_stack_t *sstk);
-
-aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp, void *arg);
-
-// aco's Global Thread Local Storage variable `co`
-#ifdef __TINYC__
-#error "TinyCC doesn't support thread-local storage!"
-#else
-extern __thread aco_t *aco_gtls_co;
-#endif
-
-aco_attr_no_asan void aco_resume(aco_t *resume_co);
-
-// void aco_yield1(aco_t* yield_co);
-#define aco_yield1(yield_co) \
- do { \
- aco_assertptr((yield_co)); \
- aco_assertptr((yield_co)->main_co); \
- aco_yield_asm((yield_co), (yield_co)->main_co); \
- } while (0)
-
-#define aco_yield() aco_yield1(aco_gtls_co)
-
-#define aco_get_arg() (aco_gtls_co->arg)
-
-#define aco_get_co() \
- ({ \
- (void)0; \
- aco_gtls_co; \
- })
-
-void aco_destroy(aco_t *co);
-
-#define aco_is_main_co(co) ({ ((co)->main_co) == NULL; })
-
-#define aco_exit1(co) \
- do { \
- (co)->is_finished = true; \
- aco_assert((co)->shared_stack->owner == (co)); \
- (co)->shared_stack->owner = NULL; \
- (co)->shared_stack->align_validsz = 0; \
- aco_yield1((co)); \
- aco_assert(0); \
- } while (0)
-
-#define aco_exit() aco_exit1(aco_gtls_co)
-
-void aco_exit_fn(void *);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/examples/coroutines/acoyield.S b/examples/coroutines/acoyield.S
deleted file mode 100644
index 7bc87ff1..00000000
--- a/examples/coroutines/acoyield.S
+++ /dev/null
@@ -1,208 +0,0 @@
-.text
-.globl aco_yield_asm
-#if defined(__APPLE__)
-#else
-.type aco_yield_asm, @function
-#endif
-.intel_syntax noprefix
-aco_yield_asm:
-/*
- extern void aco_yield_asm(aco_t* from_co, aco_t* to_co);
-
- struct aco_t {
- void* reg[X];
- // ...
- }
-
- reference:
- https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
-
- pitfall:
- http://man7.org/linux/man-pages/man7/signal.7.html
- http://man7.org/linux/man-pages/man2/sigaltstack.2.html
-
- > $ man 7 signal
- > ...
- > By default, the signal handler is invoked on the normal process
- > stack. It is possible to arrange that the signal handler
- > uses an alternate stack; see sigaltstack(2) for a discussion of
- > how to do this and when it might be useful.
- > ...
-
- This is a BUG example:
- https://github.com/Tencent/libco/blob/v1.0/coctx_swap.S#L27
-
- proof of correctness:
- https://github.com/hnes/libaco
-
- mxcsr & fpu:
- fnstcw * m2byte
- Store FPU control word to m2byte without checking for
- pending unmasked floating-point exceptions.
-
- fldcw m2byte
- Load FPU control word from m2byte.
-
- stmxcsr m32
- Store contents of MXCSR register to m32
-
- ldmxcsr m32
- Load MXCSR register from m32.
-*/
-/*
- 0x00 --> 0xff
- eip esp ebp edi esi ebx fpucw16 mxcsr32
- 0 4 8 c 10 14 18 1c
-*/
-#ifdef __i386__
- mov eax,DWORD PTR [esp+0x4] // from_co
- mov edx,DWORD PTR [esp] // retaddr
- lea ecx,[esp+0x4] // esp
- mov DWORD PTR [eax+0x8],ebp //<ebp
- mov DWORD PTR [eax+0x4],ecx //<esp
- mov DWORD PTR [eax+0x0],edx //<retaddr
- mov DWORD PTR [eax+0xc],edi //<edi
- mov ecx,DWORD PTR [esp+0x8] // to_co
- mov DWORD PTR [eax+0x10],esi //<esi
- mov DWORD PTR [eax+0x14],ebx //<ebx
-#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- fnstcw WORD PTR [eax+0x18] //<fpucw
- stmxcsr DWORD PTR [eax+0x1c] //<mxcsr
-#endif
- mov edx,DWORD PTR [ecx+0x4] //>esp
- mov ebp,DWORD PTR [ecx+0x8] //>ebp
- mov eax,DWORD PTR [ecx+0x0] //>retaddr
- mov edi,DWORD PTR [ecx+0xc] //>edi
- mov esi,DWORD PTR [ecx+0x10] //>esi
- mov ebx,DWORD PTR [ecx+0x14] //>ebx
-#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- fldcw WORD PTR [ecx+0x18] //>fpucw
- ldmxcsr DWORD PTR [ecx+0x1c] //>mxcsr
-#endif
- xor ecx,ecx
- mov esp,edx
- mov edx,eax
-
- // Pass the user-provided argument as first argument:
-#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- mov eax,DWORD PTR [ecx+0x24]
-#else
- mov eax,DWORD PTR [ecx+0x28]
-#endif
-
- jmp edx
-#elif __x86_64__
-/*
- 0x00 --> 0xff
- r12 r13 r14 r15 rip rsp rbx rbp fpucw16 mxcsr32
- 0 8 10 18 20 28 30 38 40 44
-*/
- // rdi - from_co | rsi - to_co
- mov rdx,QWORD PTR [rsp] // retaddr
- lea rcx,[rsp+0x8] // rsp
- mov QWORD PTR [rdi+0x0], r12
- mov QWORD PTR [rdi+0x8], r13
- mov QWORD PTR [rdi+0x10],r14
- mov QWORD PTR [rdi+0x18],r15
- mov QWORD PTR [rdi+0x20],rdx // retaddr
- mov QWORD PTR [rdi+0x28],rcx // rsp
- mov QWORD PTR [rdi+0x30],rbx
- mov QWORD PTR [rdi+0x38],rbp
-#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- fnstcw WORD PTR [rdi+0x40]
- stmxcsr DWORD PTR [rdi+0x44]
-#endif
- mov r12,QWORD PTR [rsi+0x0]
- mov r13,QWORD PTR [rsi+0x8]
- mov r14,QWORD PTR [rsi+0x10]
- mov r15,QWORD PTR [rsi+0x18]
- mov rax,QWORD PTR [rsi+0x20] // retaddr
- mov rcx,QWORD PTR [rsi+0x28] // rsp
- mov rbx,QWORD PTR [rsi+0x30]
- mov rbp,QWORD PTR [rsi+0x38]
-#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- fldcw WORD PTR [rsi+0x40]
- ldmxcsr DWORD PTR [rsi+0x44]
-#endif
-
- // Pass the user-provided argument as first argument:
-#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
- mov rdi,QWORD PTR [rsi+0x48]
-#else
- mov rdi,QWORD PTR [rsi+0x50]
-#endif
-
- mov rsp,rcx
- jmp rax
-#else
- #error "platform not supported"
-#endif
-
-.globl aco_save_fpucw_mxcsr
-#if defined(__APPLE__)
-#else
-.type aco_save_fpucw_mxcsr, @function
-#endif
-.intel_syntax noprefix
-aco_save_fpucw_mxcsr:
-#ifdef __i386__
- mov eax,DWORD PTR [esp+0x4] // ptr
- fnstcw WORD PTR [eax]
- stmxcsr DWORD PTR [eax+0x4]
- ret
-#elif __x86_64__
- fnstcw WORD PTR [rdi]
- stmxcsr DWORD PTR [rdi+0x4]
- ret
-#else
- #error "platform not supported"
-#endif
-
-#if defined(__APPLE__)
-.globl _abort
-.globl _aco_funcp_protector
-#else
-.globl abort
-.globl aco_funcp_protector
-#endif
-
-.globl aco_funcp_protector_asm
-#if defined(__APPLE__)
-#else
-.type aco_funcp_protector_asm, @function
-#endif
-.intel_syntax noprefix
-aco_funcp_protector_asm:
-#ifdef __i386__
- and esp,0xfffffff0
- #if defined(__APPLE__)
- call _aco_funcp_protector
- call _abort
- #else
- #if defined(__pic__) || defined(__PIC__)
- call aco_funcp_protector@PLT
- call abort@PLT
- #else
- call aco_funcp_protector
- call abort
- #endif
- #endif
- ret
-#elif __x86_64__
- and rsp,0xfffffffffffffff0
- #if defined(__APPLE__)
- call _aco_funcp_protector
- call _abort
- #else
- #if defined(__pic__) || defined(__PIC__)
- call aco_funcp_protector@PLT
- call abort@PLT
- #else
- call aco_funcp_protector
- call abort
- #endif
- #endif
- ret
-#else
- #error "platform not supported"
-#endif
diff --git a/examples/coroutines/coroutines.tm b/examples/coroutines/coroutines.tm
deleted file mode 100644
index c4ef1a97..00000000
--- a/examples/coroutines/coroutines.tm
+++ /dev/null
@@ -1,67 +0,0 @@
-# This is a coroutine library that uses libaco (https://libaco.org)
-#
-# Lua programmers will recognize this as similar to Lua's stackful coroutines.
-#
-# Async/Await programmers will weep at its beauty and gnash their teeth and
-# rend their garments in despair at what they could have had.
-
-use ./aco.h
-use ./aco.c
-use ./acoyield.S
-
-func main()
- say("Example usage")
- co := Coroutine(func()
- say("I'm in the coroutine!")
- yield()
- say("I'm back in the coroutine!")
- )
- >> co
- say("I'm in the main func")
- >> co.resume()
- say("I'm back in the main func")
- >> co.resume()
- say("I'm back in the main func again")
- >> co.resume()
-
-struct aco_t(; extern, opaque)
-struct aco_shared_stack_t(; extern, opaque)
-
-_main_co : @aco_t? = none
-_shared_stack : @aco_shared_stack_t? = none
-
-struct Coroutine(co:@aco_t)
- convert(fn:func() -> Coroutine)
- if not _main_co
- _init()
-
- main_co := _main_co
- shared_stack := _shared_stack
- aco_ptr := C_code:@aco_t `
- aco_create(@main_co, @shared_stack, 0, (void*)@fn.fn, @fn.userdata)
- `
- return Coroutine(aco_ptr)
-
- func is_finished(co:Coroutine->Bool; inline)
- return C_code:Bool`((aco_t*)@co.co)->is_finished`
-
- func resume(co:Coroutine->Bool)
- if co.is_finished()
- return no
- C_code `aco_resume(@co.co);`
- return yes
-
-func _init()
- C_code `
- aco_set_allocator(GC_malloc, NULL);
- aco_thread_init(aco_exit_fn);
- `
- _main_co = C_code:@aco_t`aco_create(NULL, NULL, 0, NULL, NULL)`
-
- _shared_stack = C_code:@aco_shared_stack_t`aco_shared_stack_new(0)`
-
-func yield(; inline)
- C_code `
- aco_yield();
- `
-
diff --git a/examples/game/CHANGES.md b/examples/game/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/game/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/game/Makefile b/examples/game/Makefile
deleted file mode 100644
index 7cf46ce6..00000000
--- a/examples/game/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-game: game.tm box.tm color.tm player.tm world.tm
- tomo -e game.tm
-
-# Disable built-in makefile rules:
-%: %.c
-%.o: %.c
-%: %.o
-
-clean:
- rm -vf game *.tm.*
-
-play: game
- ./game
-
-.PHONY: play, clean
diff --git a/examples/game/README.md b/examples/game/README.md
deleted file mode 100644
index 475a8299..00000000
--- a/examples/game/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Example Game
-
-This is a simple example game that uses [raylib](https://www.raylib.com/) to
-demonstrate a project that spans multiple files and showcases some game
-programming concepts used in idiomatic Tomo code. It also showcases how to
-interact with an external C library. To run the game:
-
-```bash
-tomo game.tm
-```
-
-An example [Makefile](Makefile) is also provided if you want to use `make` to
-build the game and `make clean` to clean up the built files.
diff --git a/examples/game/box.tm b/examples/game/box.tm
deleted file mode 100644
index 41ae10e5..00000000
--- a/examples/game/box.tm
+++ /dev/null
@@ -1,7 +0,0 @@
-# Defines a struct representing boxes on the terrain
-use ./world.tm
-use ./raylib.tm
-
-struct Box(pos:Vector2, size=Vector2(50, 50), color=Color(0x80,0x80,0x80))
- func draw(b:Box)
- DrawRectangleV(b.pos, b.size, b.color)
diff --git a/examples/game/game.tm b/examples/game/game.tm
deleted file mode 100644
index f82e4f40..00000000
--- a/examples/game/game.tm
+++ /dev/null
@@ -1,29 +0,0 @@
-# This game demo uses Raylib to present a simple maze-type game
-use ./raylib.tm
-use ./world.tm
-
-func main(map=(./map.txt))
- InitWindow(1600, 900, CString("raylib [core] example - 2d camera"))
-
- map_contents := map.read() or exit("Could not find the game map: $map")
-
- world := @World(
- player=@Player(Vector2(0,0), Vector2(0,0)),
- goal=@Box(Vector2(0,0), Vector2(50,50), color=Color(0x10,0xa0,0x10)),
- boxes=@[],
- )
- world.load_map(map_contents)
-
- SetTargetFPS(60)
-
- while not WindowShouldClose()
- dt := GetFrameTime()
- world.update(dt)
-
- BeginDrawing()
- ClearBackground(Color(0xCC, 0xCC, 0xCC, 0xFF))
- world.draw()
- EndDrawing()
-
- CloseWindow()
-
diff --git a/examples/game/map.txt b/examples/game/map.txt
deleted file mode 100644
index 46fccd09..00000000
--- a/examples/game/map.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-################################
-#@ # # #
-# #### #### # # #### #####
-# # # # # #
-# ####### # ####### # # ###
-# # # # # # #
-# #### ########## # #### #
-# # # # # # #
-#### # # # # # # # # ##
-# # # # # # # #
-####### ########## # ########
-# # # # #
-# ########## #### # # # #
-# # # # # # # # #
-# ####### # # ########## #
-# # # # # # # ? #
-# # # # # # #
-################################
diff --git a/examples/game/player.tm b/examples/game/player.tm
deleted file mode 100644
index b34eadd0..00000000
--- a/examples/game/player.tm
+++ /dev/null
@@ -1,28 +0,0 @@
-# Defines a struct representing the player, which is controlled by WASD keys
-use ./world.tm
-use ./raylib.tm
-
-struct Player(pos,prev_pos:Vector2)
- WALK_SPEED := Num32(500.)
- ACCEL := Num32(0.3)
- FRICTION := Num32(0.99)
- SIZE := Vector2(30, 30)
- COLOR := Color(0x60, 0x60, 0xbF)
-
- func update(p:@Player)
- target_x := C_code:Num32`
- (Num32_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0))
- `
- target_y := C_code:Num32`
- (Num32_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0))
- `
- target_vel := Vector2(target_x, target_y).norm() * Player.WALK_SPEED
-
- vel := (p.pos - p.prev_pos)/World.DT
- vel *= Player.FRICTION
- vel = vel.mix(target_vel, Player.ACCEL)
-
- p.prev_pos, p.pos = p.pos, p.pos + World.DT*vel
-
- func draw(p:Player)
- DrawRectangleV(p.pos, Player.SIZE, Player.COLOR)
diff --git a/examples/game/raylib.tm b/examples/game/raylib.tm
deleted file mode 100644
index b2ba53d7..00000000
--- a/examples/game/raylib.tm
+++ /dev/null
@@ -1,63 +0,0 @@
-# Raylib wrapper for some functions and structs
-use -lraylib
-use <raylib.h>
-use <raymath.h>
-
-struct Color(r,g,b:Byte,a=Byte(255); extern)
-struct Rectangle(x,y,width,height:Num32; extern)
- func draw(r:Rectangle, color:Color)
- DrawRectangleRec(r, color)
-
-struct Vector2(x,y:Num32; extern)
- ZERO := Vector2(0, 0)
- func plus(a,b:Vector2->Vector2; inline)
- return Vector2(a.x+b.x, a.y+b.y)
- func minus(a,b:Vector2->Vector2; inline)
- return Vector2(a.x-b.x, a.y-b.y)
- func times(a,b:Vector2->Vector2; inline)
- return Vector2(a.x*b.x, a.y*b.y)
- func negative(v:Vector2->Vector2; inline)
- return Vector2(-v.x, -v.y)
- func dot(a,b:Vector2->Num32; inline)
- return ((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y))
- func cross(a,b:Vector2->Num32; inline)
- return a.x*b.y - a.y*b.x
- func scaled_by(v:Vector2, k:Num32->Vector2; inline)
- return Vector2(v.x*k, v.y*k)
- func divided_by(v:Vector2, divisor:Num32->Vector2; inline)
- return Vector2(v.x/divisor, v.y/divisor)
- func length(v:Vector2->Num32; inline)
- return (v.x*v.x + v.y*v.y).sqrt()
- func dist(a,b:Vector2->Num32; inline)
- return a.minus(b).length()
- func angle(v:Vector2->Num32; inline)
- return Num32.atan2(v.y, v.x)
- func norm(v:Vector2->Vector2; inline)
- if v.x == 0 and v.y == 0
- return v
- len := v.length()
- return Vector2(v.x/len, v.y/len)
- func rotated(v:Vector2, radians:Num32 -> Vector2)
- cos := radians.cos() or return v
- sin := radians.sin() or return v
- return Vector2(cos*v.x - sin*v.y, sin*v.x + cos*v.y)
- func mix(a,b:Vector2, amount:Num32 -> Vector2)
- return Vector2(
- amount.mix(a.x, b.x),
- amount.mix(a.y, b.y),
- )
-
-extern InitWindow : func(width:Int32, height:Int32, title:CString)
-extern SetTargetFPS : func(fps:Int32)
-extern WindowShouldClose : func(->Bool)
-extern GetFrameTime : func(->Num32)
-extern BeginDrawing : func()
-extern EndDrawing : func()
-extern CloseWindow : func()
-extern ClearBackground : func(color:Color)
-extern DrawRectangle : func(x,y,width,height:Int32, color:Color)
-extern DrawRectangleRec : func(rec:Rectangle, color:Color)
-extern DrawRectangleV : func(pos:Vector2, size:Vector2, color:Color)
-extern DrawText : func(text:CString, x,y:Int32, text_height:Int32, color:Color)
-extern GetScreenWidth : func(->Int32)
-extern GetScreenHeight : func(->Int32)
diff --git a/examples/game/world.tm b/examples/game/world.tm
deleted file mode 100644
index 0de8ea4b..00000000
--- a/examples/game/world.tm
+++ /dev/null
@@ -1,88 +0,0 @@
-# This file defines a World object for keeping track of everything, as well
-# as the collision logic.
-use ./player.tm
-use ./raylib.tm
-use ./box.tm
-
-# Return a displacement relative to `a` that will push it out of `b`
-func solve_overlap(a_pos:Vector2, a_size:Vector2, b_pos:Vector2, b_size:Vector2 -> Vector2)
- a_left := a_pos.x
- a_right := a_pos.x + a_size.x
- a_top := a_pos.y
- a_bottom := a_pos.y + a_size.y
-
- b_left := b_pos.x
- b_right := b_pos.x + b_size.x
- b_top := b_pos.y
- b_bottom := b_pos.y + b_size.y
-
- # Calculate the overlap in each dimension
- overlap_x := (a_right _min_ b_right) - (a_left _max_ b_left)
- overlap_y := (a_bottom _min_ b_bottom) - (a_top _max_ b_top)
-
- # If either axis is not overlapping, then there is no collision:
- if overlap_x <= 0 or overlap_y <= 0
- return Vector2(0, 0)
-
- if overlap_x < overlap_y
- if a_right > b_left and a_right < b_right
- return Vector2(-(overlap_x), 0)
- else if a_left < b_right and a_left > b_left
- return Vector2(overlap_x, 0)
- else
- if a_top < b_bottom and a_top > b_top
- return Vector2(0, overlap_y)
- else if a_bottom > b_top and a_bottom < b_bottom
- return Vector2(0, -overlap_y)
-
- return Vector2(0, 0)
-
-struct World(player:@Player, goal:@Box, boxes:@[@Box], dt_accum=Num32(0.0), won=no)
- DT := (Num32(1.)/Num32(60.))
- STIFFNESS := Num32(0.3)
-
- func update(w:@World, dt:Num32)
- w.dt_accum += dt
- while w.dt_accum > 0
- w.update_once()
- w.dt_accum -= World.DT
-
- func update_once(w:@World)
- w.player.update()
-
- if solve_overlap(w.player.pos, Player.SIZE, w.goal.pos, w.goal.size) != Vector2(0,0)
- w.won = yes
-
- # Resolve player overlapping with any boxes:
- for i in 3
- for b in w.boxes
- w.player.pos += World.STIFFNESS * solve_overlap(w.player.pos, Player.SIZE, b.pos, b.size)
-
- func draw(w:@World)
- for b in w.boxes
- b.draw()
- w.goal.draw()
- w.player.draw()
-
- if w.won
- DrawText(CString("WINNER"), GetScreenWidth()/Int32(2)-Int32(48*3), GetScreenHeight()/Int32(2)-Int32(24), 48, Color(0,0,0))
-
- func load_map(w:@World, map:Text)
- if map.has("[]")
- map = map.translate({"[]"="#", "@ "="@", " "=" "})
- w.boxes = @[]
- box_size := Vector2(50., 50.)
- for y,line in map.lines()
- for x,cell in line.split()
- if cell == "#"
- pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y)
- box := @Box(pos, size=box_size, color=Color(0x80,0x80,0x80))
- w.boxes.insert(box)
- else if cell == "@"
- pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y)
- pos += box_size/Num32(2) - Player.SIZE/Num32(2)
- w.player = @Player(pos,pos)
- else if cell == "?"
- pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y)
- w.goal.pos = pos
-
diff --git a/examples/hello.tm b/examples/hello.tm
new file mode 100644
index 00000000..09ee3ab4
--- /dev/null
+++ b/examples/hello.tm
@@ -0,0 +1,3 @@
+# A simple hello world program:
+func main()
+ say("Hello world!")
diff --git a/examples/http-server/CHANGES.md b/examples/http-server/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/http-server/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/http-server/README.md b/examples/http-server/README.md
deleted file mode 100644
index 78c8d793..00000000
--- a/examples/http-server/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# HTTP Server
-
-This is a simple multithreaded Tomo HTTP server that can be run like this:
-
-```
-tomo -e http-server.tm
-./http-server ./sample-site
-```
diff --git a/examples/http-server/connection-queue.tm b/examples/http-server/connection-queue.tm
deleted file mode 100644
index c56069e1..00000000
--- a/examples/http-server/connection-queue.tm
+++ /dev/null
@@ -1,25 +0,0 @@
-use pthreads
-
-func _assert_success(name:Text, val:Int32; inline)
- fail("$name() failed!") if val < 0
-
-struct ConnectionQueue(_connections:@[Int32]=@[], _mutex=pthread_mutex_t.new(), _cond=pthread_cond_t.new())
- func enqueue(queue:ConnectionQueue, connection:Int32)
- queue._mutex.lock()
- queue._connections.insert(connection)
- queue._mutex.unlock()
- queue._cond.signal()
-
-
- func dequeue(queue:ConnectionQueue -> Int32)
- conn : Int32?
-
- queue._mutex.lock()
-
- while queue._connections.length == 0
- queue._cond.wait(queue._mutex)
-
- conn = queue._connections.pop(1)
- queue._mutex.unlock()
- queue._cond.signal()
- return conn!
diff --git a/examples/http-server/http-server.tm b/examples/http-server/http-server.tm
deleted file mode 100644
index dbe57805..00000000
--- a/examples/http-server/http-server.tm
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/bin/env tomo
-
-# This file provides an HTTP server module and standalone executable
-
-use <stdio.h>
-use <stdlib.h>
-use <string.h>
-use <unistd.h>
-use <arpa/inet.h>
-use <err.h>
-
-use commands
-use pthreads
-use patterns
-
-use ./connection-queue.tm
-
-func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_threads=16)
- connections := ConnectionQueue()
- workers : &[@pthread_t]
- for i in num_threads
- workers.insert(pthread_t.new(func()
- repeat
- connection := connections.dequeue()
- request_text := C_code:Text```
- Text_t request = EMPTY_TEXT;
- char buf[1024] = {};
- for (ssize_t n; (n = read(@connection, buf, sizeof(buf) - 1)) > 0; ) {
- buf[n] = 0;
- request = Text$concat(request, Text$from_strn(buf, n));
- if (request.length > 1000000 || strstr(buf, "\r\n\r\n"))
- break;
- }
- request
- ```
-
- request := HTTPRequest.from_text(request_text) or skip
- response := handler(request).bytes()
- C_code `
- if (@response.stride != 1)
- List$compact(&@response, 1);
- write(@connection, @response.data, @response.length);
- close(@connection);
- `
- ))
-
-
- sock := C_code:Int32 `
- int s = socket(AF_INET, SOCK_STREAM, 0);
- if (s < 0) err(1, "Couldn't connect to socket!");
-
- int opt = 1;
- if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
- err(1, "Couldn't set socket option");
-
- struct sockaddr_in addr = {AF_INET, htons(@port), INADDR_ANY};
- if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0)
- err(1, "Couldn't bind to socket");
- if (listen(s, 8) < 0)
- err(1, "Couldn't listen on socket");
-
- s
- `
-
- repeat
- conn := C_code:Int32`accept(@sock, NULL, NULL)`
- stop if conn < 0
- connections.enqueue(conn)
-
- say("Shutting down...")
- for w in workers
- w.cancel()
-
-struct HTTPRequest(method:Text, path:Text, version:Text, headers:[Text], body:Text)
- func from_text(text:Text -> HTTPRequest?)
- m := text.pattern_captures($Pat'{word} {..} HTTP/{..}{crlf}{..}') or return none
- method := m[1]
- path := m[2].replace_pattern($Pat'{2+ /}', '/')
- version := m[3]
- rest := m[-1].pattern_captures($Pat'{..}{2 crlf}{0+ ..}') or return none
- headers := rest[1].split_pattern($Pat'{crlf}')
- body := rest[-1]
- return HTTPRequest(method, path, version, headers, body)
-
-struct HTTPResponse(body:Text, status=200, content_type="text/plain", headers:{Text=Text}={})
- func bytes(r:HTTPResponse -> [Byte])
- body_bytes := r.body.bytes()
- extra_headers := (++: "$k: $v\r\n" for k,v in r.headers) or ""
- return "
- HTTP/1.1 $(r.status) OK\r
- Content-Length: $(body_bytes.length + 2)\r
- Content-Type: $(r.content_type)\r
- Connection: close\r
- $extra_headers
- \r\n
- ".bytes() ++ body_bytes
-
-func _content_type(file:Path -> Text)
- when file.extension() is "html" return "text/html"
- is "tm" return "text/html"
- is "js" return "text/javascript"
- is "css" return "text/css"
- else return "text/plain"
-
-enum RouteEntry(ServeFile(file:Path), Redirect(destination:Text))
- func respond(entry:RouteEntry, request:HTTPRequest -> HTTPResponse)
- when entry is ServeFile(file)
- body := if file.can_execute()
- Command(Text(file)).get_output()!
- else
- file.read()!
- return HTTPResponse(body, content_type=_content_type(file))
- is Redirect(destination)
- return HTTPResponse("Found", 302, headers={"Location"=destination})
-
-func load_routes(directory:Path -> {Text=RouteEntry})
- routes : &{Text=RouteEntry}
- for file in (directory ++ (./*)).glob()
- skip unless file.is_file()
- contents := file.read() or skip
- server_path := "/" ++ "/".join(file.relative_to(directory).components)
- if file.base_name() == "index.html"
- canonical := server_path.without_suffix("index.html")
- routes[server_path] = Redirect(canonical)
- routes[canonical] = ServeFile(file)
- else if file.extension() == "html"
- canonical := server_path.without_suffix(".html")
- routes[server_path] = Redirect(canonical)
- routes[canonical] = ServeFile(file)
- else if file.extension() == "tm"
- canonical := server_path.without_suffix(".tm")
- routes[server_path] = Redirect(canonical)
- routes[canonical] = ServeFile(file)
- else
- routes[server_path] = ServeFile(file)
- return routes[]
-
-func main(directory:Path, port=Int32(8080))
- say("Serving on port $port")
- routes := load_routes(directory)
- say("Hosting: $routes")
-
- serve(port, func(request:HTTPRequest)
- if handler := routes[request.path]
- return handler.respond(request)
- else
- return HTTPResponse("Not found!", 404)
- )
-
diff --git a/examples/http-server/modules.ini b/examples/http-server/modules.ini
deleted file mode 100644
index 171e11d2..00000000
--- a/examples/http-server/modules.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[pthreads]
-version=v1.0
-
-[patterns]
-version=v1.0
-
-[commands]
-version=v1.0
diff --git a/examples/http-server/sample-site/foo.html b/examples/http-server/sample-site/foo.html
deleted file mode 100644
index 162a7146..00000000
--- a/examples/http-server/sample-site/foo.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!DOCTYPE HTML>
-<html>
- <body>
- This is the <b>foo</b> page.
- </body>
-</html>
diff --git a/examples/http-server/sample-site/hello.txt b/examples/http-server/sample-site/hello.txt
deleted file mode 100644
index e965047a..00000000
--- a/examples/http-server/sample-site/hello.txt
+++ /dev/null
@@ -1 +0,0 @@
-Hello
diff --git a/examples/http-server/sample-site/index.html b/examples/http-server/sample-site/index.html
deleted file mode 100644
index 8e1573bb..00000000
--- a/examples/http-server/sample-site/index.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE HTML>
-<html>
- <head>
- <title>HTTP Example</title>
- <link rel="stylesheet" href="styles.css">
- </head>
- <body>
- <p>
- Hello <b>world!</b>
- </p>
-
- <p>
- Try going to <a href="/random">/random</a> or <a href="/foo">/foo</a> or <a href="/hello.txt">/hello.txt</a>
- </p>
- </body>
-</html>
diff --git a/examples/http-server/sample-site/random.tm b/examples/http-server/sample-site/random.tm
deleted file mode 100755
index 153ac2af..00000000
--- a/examples/http-server/sample-site/random.tm
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/env tomo
-use random
-
-func main()
- say("
- <!DOCTYPE HTML>
- <html>
- <head>
- <title>Random Number</title>
- <link rel="stylesheet" href="styles.css">
- </head>
- <body>
- <h1>Random Number</h1>
- Your random number is: $(random.int(1,100))
- </body>
- </html>
- ")
diff --git a/examples/http-server/sample-site/styles.css b/examples/http-server/sample-site/styles.css
deleted file mode 100644
index f15d25de..00000000
--- a/examples/http-server/sample-site/styles.css
+++ /dev/null
@@ -1,11 +0,0 @@
-body{
- margin:40px auto;
- max-width:650px;
- line-height:1.6;
- font-size:18px;
- color:#444;
- padding:0 10px;
-}
-h1,h2,h3{
- line-height:1.2
-}
diff --git a/examples/http/CHANGES.md b/examples/http/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/http/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/http/http.tm b/examples/http/http.tm
deleted file mode 100644
index 8d111904..00000000
--- a/examples/http/http.tm
+++ /dev/null
@@ -1,111 +0,0 @@
-# A simple HTTP library built using CURL
-
-use -lcurl
-use <curl/curl.h>
-
-struct HTTPResponse(code:Int, body:Text)
-
-enum _Method(GET, POST, PUT, PATCH, DELETE)
-
-func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse)
- chunks : @[Text]
- save_chunk := func(chunk:CString, size:Int64, n:Int64)
- chunks.insert(C_code:Text`Text$from_strn(@chunk, @size*@n)`)
- return n*size
-
- C_code `
- CURL *curl = curl_easy_init();
- struct curl_slist *chunk = NULL;
- curl_easy_setopt(curl, CURLOPT_URL, @(CString(url)));
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, @save_chunk.fn);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, @save_chunk.userdata);
- `
-
- defer
- C_code `
- if (chunk)
- curl_slist_free_all(chunk);
- curl_easy_cleanup(curl);
- `
-
- when method is POST
- C_code `
- curl_easy_setopt(curl, CURLOPT_POST, 1L);
- `
- if posting := data
- C_code `
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(posting)));
- `
- is PUT
- C_code `
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
- `
- if putting := data
- C_code `
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(putting)));
- `
- is PATCH
- C_code `
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
- `
- if patching := data
- C_code `
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(patching)));
- `
- is DELETE
- C_code `
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
- `
- else
- pass
-
- for header in headers
- C_code `
- chunk = curl_slist_append(chunk, @(CString(header)));
- `
-
- C_code `
- if (chunk)
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
- `
-
- code := Int64(0)
- C_code ```
- CURLcode res = curl_easy_perform(curl);
- if (res != CURLE_OK)
- fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
-
- curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &@code);
- ```
- return HTTPResponse(Int(code), "".join(chunks))
-
-func get(url:Text, headers:[Text]=[] -> HTTPResponse)
- return _send(GET, url, none, headers)
-
-func post(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse)
- return _send(POST, url, data, headers)
-
-func put(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse)
- return _send(PUT, url, data, headers)
-
-func patch(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse)
- return _send(PATCH, url, data, headers)
-
-func delete(url:Text, data:Text?=none, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse)
- return _send(DELETE, url, data, headers)
-
-func main()
- say("GET:")
- say(get("https://httpbin.org/get").body)
- say("Waiting 1sec...")
- sleep(1)
- say("POST:")
- say(post("https://httpbin.org/post", `{"key": "value"}`).body)
- say("Waiting 1sec...")
- sleep(1)
- say("PUT:")
- say(put("https://httpbin.org/put", `{"key": "value"}`).body)
- say("Waiting 1sec...")
- sleep(1)
- say("DELETE:")
- say(delete("https://httpbin.org/delete").body)
diff --git a/examples/ini/CHANGES.md b/examples/ini/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/ini/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm
deleted file mode 100644
index 576d273f..00000000
--- a/examples/ini/ini.tm
+++ /dev/null
@@ -1,61 +0,0 @@
-
-use patterns
-
-_USAGE := "
- Usage: ini <filename> "[section[/key]]"
-"
-_HELP := "
- ini: A .ini config file reader tool.
- $_USAGE
-"
-
-func parse_ini(path:Path -> {Text={Text=Text}})
- text := path.read() or exit("Could not read INI file: \[31;1]$(path)\[]")
- sections : @{Text=@{Text=Text}}
- current_section : @{Text=Text}
-
- # Line wraps:
- text = text.replace_pattern($Pat`\\{1 nl}{0+space}`, " ")
-
- for line in text.lines()
- line = line.trim()
- skip if line.starts_with(";") or line.starts_with("#")
- if line.matches_pattern($Pat`[?]`)
- section_name := line.replace($Pat`[?]`, "@1").trim().lower()
- current_section = @{}
- sections[section_name] = current_section
- else if line.matches_pattern($Pat`{..}={..}`)
- key := line.replace_pattern($Pat`{..}={..}`, "@1").trim().lower()
- value := line.replace_pattern($Pat`{..}={..}`, "@2").trim()
- current_section[key] = value
-
- return {k=v[] for k,v in sections[]}
-
-func main(path:Path, key:Text?)
- keys := (key or "").split(`/`)
- if keys.length > 2
- exit("
- Too many arguments!
- $_USAGE
- ")
-
- data := parse_ini(path)
- if keys.length < 1 or keys[1] == '*'
- say("$data")
- return
-
- section := keys[1].lower()
- section_data := data[section] or exit("
- Invalid section name: \[31;1]$section\[]
- Valid names: \[1]$(", ".join([k.quoted() for k in data.keys]))\[]
- ")
- if keys.length < 2 or keys[2] == '*'
- say("$section_data")
- return
-
- section_key := keys[2].lower()
- value := section_data[section_key] or exit("
- Invalid key: \[31;1]$section_key\[]
- Valid keys: \[1]$(", ".join([s.quoted() for s in section_data.keys]))\[]
- ")
- say(value)
diff --git a/examples/ini/modules.ini b/examples/ini/modules.ini
deleted file mode 100644
index fb52a859..00000000
--- a/examples/ini/modules.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[patterns]
-version=v1.0
diff --git a/examples/ini/test.ini b/examples/ini/test.ini
deleted file mode 100644
index 782dc76f..00000000
--- a/examples/ini/test.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[ Book ]
-title = Dirk Gently's Holistic Detective Agency
-author = Douglas Adams
-published = 1987
-
-[ Protagonist ]
-name = Dirk Gently
-age = 42
diff --git a/examples/log/CHANGES.md b/examples/log/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/log/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/log/log.tm b/examples/log/log.tm
deleted file mode 100644
index 2798b7ae..00000000
--- a/examples/log/log.tm
+++ /dev/null
@@ -1,50 +0,0 @@
-use <time.h>
-use <stdio.h>
-
-timestamp_format := CString("%F %T")
-
-logfiles : @|Path|
-
-func _timestamp(->Text)
- c_str := C_code:CString`
- char *str = GC_MALLOC_ATOMIC(20);
- time_t t; time(&t);
- struct tm *tm_info = localtime(&t);
- strftime(str, 20, "%F %T", tm_info);
- str
- `
- return c_str.as_text()
-
-func info(text:Text, newline=yes)
- say("\[2]⚫ $text\[]", newline)
- for file in logfiles
- file.append("$(_timestamp()) [info] $text\n")
-
-func debug(text:Text, newline=yes)
- say("\[32]🟢 $text\[]", newline)
- for file in logfiles
- file.append("$(_timestamp()) [debug] $text\n")
-
-func warn(text:Text, newline=yes)
- say("\[33;1]🟡 $text\[]", newline)
- for file in logfiles
- file.append("$(_timestamp()) [warn] $text\n")
-
-func error(text:Text, newline=yes)
- say("\[31;1]🔴 $text\[]", newline)
- for file in logfiles
- file.append("$(_timestamp()) [error] $text\n")
-
-func add_logfile(file:Path)
- logfiles.add(file)
-
-func remove_logfile(file:Path)
- logfiles.remove(file)
-
-func main()
- add_logfile((./log.txt))
- >> info("Hello")
- >> debug("Hello")
- >> warn("Hello")
- >> error("Hello")
-
diff --git a/examples/vectors/CHANGES.md b/examples/vectors/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/vectors/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/vectors/vectors.tm b/examples/vectors/vectors.tm
deleted file mode 100644
index 1fd0c4fa..00000000
--- a/examples/vectors/vectors.tm
+++ /dev/null
@@ -1,136 +0,0 @@
-# A math vector library for 2D and 3D vectors of Nums or Ints
-
-struct Vec2(x,y:Num)
- ZERO := Vec2(0, 0)
- func plus(a,b:Vec2->Vec2; inline)
- return Vec2(a.x+b.x, a.y+b.y)
- func minus(a,b:Vec2->Vec2; inline)
- return Vec2(a.x-b.x, a.y-b.y)
- func times(a,b:Vec2->Vec2; inline)
- return Vec2(a.x*b.x, a.y*b.y)
- func negative(v:Vec2->Vec2; inline)
- return Vec2(-v.x, -v.y)
- func dot(a,b:Vec2->Num; inline)
- return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)
- func cross(a,b:Vec2->Num; inline)
- return a.x*b.y - a.y*b.x
- func scaled_by(v:Vec2, k:Num->Vec2; inline)
- return Vec2(v.x*k, v.y*k)
- func divided_by(v:Vec2, divisor:Num->Vec2; inline)
- return Vec2(v.x/divisor, v.y/divisor)
- func length(v:Vec2->Num; inline)
- return (v.x*v.x + v.y*v.y).sqrt()
- func dist(a,b:Vec2->Num; inline)
- return a.minus(b).length()
- func angle(v:Vec2->Num; inline)
- return Num.atan2(v.y, v.x)
- func norm(v:Vec2->Vec2; inline)
- if v.x == 0 and v.y == 0
- return v
- len := v.length()
- return Vec2(v.x/len, v.y/len)
- func rotated(v:Vec2, radians:Num -> Vec2)
- cos := radians.cos() or return v
- sin := radians.sin() or return v
- return Vec2(cos*v.x - sin*v.y, sin*v.x + cos*v.y)
- func mix(a,b:Vec2, amount:Num -> Vec2)
- return Vec2(
- amount.mix(a.x, b.x),
- amount.mix(a.y, b.y),
- )
-
-struct Vec3(x,y,z:Num)
- ZERO := Vec3(0, 0, 0)
- func plus(a,b:Vec3->Vec3; inline)
- return Vec3(a.x+b.x, a.y+b.y, a.z+b.z)
- func minus(a,b:Vec3->Vec3; inline)
- return Vec3(a.x-b.x, a.y-b.y, a.z-b.z)
- func times(a,b:Vec3->Vec3; inline)
- return Vec3(a.x*b.x, a.y*b.y, a.z*b.z)
- func negative(v:Vec3->Vec3; inline)
- return Vec3(-v.x, -v.y, -v.z)
- func dot(a,b:Vec3->Num; inline)
- return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z)
- func cross(a,b:Vec3->Vec3; inline)
- return Vec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x)
- func scaled_by(v:Vec3, k:Num->Vec3; inline)
- return Vec3(v.x*k, v.y*k, v.z*k)
- func divided_by(v:Vec3, divisor:Num->Vec3; inline)
- return Vec3(v.x/divisor, v.y/divisor, v.z/divisor)
- func length(v:Vec3->Num; inline)
- return (v.x*v.x + v.y*v.y + v.z*v.z).sqrt()
- func dist(a,b:Vec3->Num; inline)
- return a.minus(b).length()
- func norm(v:Vec3->Vec3; inline)
- if v.x == 0 and v.y == 0 and v.z == 0
- return v
- len := v.length()
- return Vec3(v.x/len, v.y/len, v.z/len)
- func mix(a,b:Vec3, amount:Num -> Vec3)
- return Vec3(
- amount.mix(a.x, b.x),
- amount.mix(a.y, b.y),
- amount.mix(a.z, b.z),
- )
-
-
-struct IVec2(x,y:Int)
- ZERO := IVec2(0, 0)
- func plus(a,b:IVec2->IVec2; inline)
- return IVec2(a.x+b.x, a.y+b.y)
- func minus(a,b:IVec2->IVec2; inline)
- return IVec2(a.x-b.x, a.y-b.y)
- func times(a,b:IVec2->IVec2; inline)
- return IVec2(a.x*b.x, a.y*b.y)
- func negative(v:IVec2->IVec2; inline)
- return IVec2(-v.x, -v.y)
- func dot(a,b:IVec2->Int; inline)
- return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)
- func cross(a,b:IVec2->Int; inline)
- return a.x*b.y - a.y*b.x
- func scaled_by(v:IVec2, k:Int->IVec2; inline)
- return IVec2(v.x*k, v.y*k)
- func divided_by(v:IVec2, divisor:Int->IVec2; inline)
- return IVec2(v.x/divisor, v.y/divisor)
- func length(v:IVec2->Num; inline)
- x := Num(v.x)
- y := Num(v.y)
- return Num.sqrt(x*x + y*y)
- func dist(a,b:IVec2->Num; inline)
- return a.minus(b).length()
- func angle(v:IVec2->Num; inline)
- return Num.atan2(Num(v.y), Num(v.x))
-
-struct IVec3(x,y,z:Int)
- ZERO := IVec3(0, 0, 0)
- func plus(a,b:IVec3->IVec3; inline)
- return IVec3(a.x+b.x, a.y+b.y, a.z+b.z)
- func minus(a,b:IVec3->IVec3; inline)
- return IVec3(a.x-b.x, a.y-b.y, a.z-b.z)
- func times(a,b:IVec3->IVec3; inline)
- return IVec3(a.x*b.x, a.y*b.y, a.z*b.z)
- func negative(v:IVec3->IVec3; inline)
- return IVec3(-v.x, -v.y, -v.z)
- func dot(a,b:IVec3->Int; inline)
- return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z)
- func cross(a,b:IVec3->IVec3; inline)
- return IVec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x)
- func scaled_by(v:IVec3, k:Int->IVec3; inline)
- return IVec3(v.x*k, v.y*k, v.z*k)
- func divided_by(v:IVec3, divisor:Int->IVec3; inline)
- return IVec3(v.x/divisor, v.y/divisor, v.z/divisor)
- func length(v:IVec3->Num; inline)
- x := Num(v.x)
- y := Num(v.y)
- z := Num(v.z)
- return Num.sqrt(x*x + y*y + z*z)
- func dist(a,b:IVec3->Num; inline)
- return a.minus(b).length()
-
-func main()
- >> Vec2(10, 20)
- >> Vec2(10, 20) + Vec2(100, 100)
- = Vec2(x=110, y=120)
- >> Vec3(10, 20, 30)
- >> Vec2.ZERO
-
diff --git a/examples/wrap/CHANGES.md b/examples/wrap/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/examples/wrap/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm
deleted file mode 100644
index 402d22af..00000000
--- a/examples/wrap/wrap.tm
+++ /dev/null
@@ -1,104 +0,0 @@
-use patterns
-
-HELP := "
- wrap: A tool for wrapping lines of text
-
- usage: wrap [--help] [files...] [--width=80] [--inplace=no] [--min_split=3] [--no-rewrap] [--hyphen='-']
- --help: Print this message and exit
- [files...]: The files to wrap (stdin is used if no files are provided)
- --width=N: The width to wrap the text
- --inplace: Whether or not to perform the modification in-place or print the output
- --min-split=N: The minimum amount of text on either end of a hyphenation split
- --rewrap|--no-rewrap: Whether to rewrap text that is already wrapped or only split long lines
- --hyphen='-': The text to use for hyphenation
-"
-
-UNICODE_HYPHEN := "\{hyphen}"
-
-func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN -> Text)
- if preserve_paragraphs
- paragraphs := text.split_pattern($Pat"{2+ nl}")
- if paragraphs.length > 1
- return "\n\n".join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs])
-
- return text.replace("$(hyphen)\n", "")
-
-func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text)
- if width <= 0
- fail("Width must be a positive integer, not $width")
-
- if 2*min_split - hyphen.length > width
- fail("
- Minimum word split length ($min_split) is too small for the given wrap width ($width)!
-
- I can't fit a $(2*min_split - hyphen.length)-wide word on a line without splitting it,
- ... and I can't split it without splitting into chunks smaller than $min_split.
- ")
-
- lines : @[Text]
- line := ""
- for word in text.split_pattern($Pat"{whitespace}")
- letters := word.split()
- skip if letters.length == 0
-
- while not _can_fit_word(line, letters, width)
- line_space := width - line.length
- if line != "" then line_space -= 1
-
- if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split
- # Split word with a hyphen:
- split := line_space - hyphen.length
- split = split _max_ min_split
- split = split _min_ (letters.length - min_split)
- if line != "" then line ++= " "
- line ++= ((++: letters.to(split)) or "") ++ hyphen
- letters = letters.from(split + 1)
- else if line == ""
- # Force split word without hyphenation:
- if line != "" then line ++= " "
- line ++= (++: letters.to(line_space)) or ""
- letters = letters.from(line_space + 1)
- else
- pass # Move to next line
-
- lines.insert(line)
- line = ""
-
- if letters.length > 0
- if line != "" then line ++= " "
- line ++= (++: letters) or ""
-
- if line != ""
- lines.insert(line)
-
- return "\n".join(lines)
-
-func _can_fit_word(line:Text, letters:[Text], width:Int -> Bool; inline)
- if line == ""
- return letters.length <= width
- else
- return line.length + 1 + letters.length <= width
-
-func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UNICODE_HYPHEN)
- if files.length == 0
- files = [(/dev/stdin)]
-
- for file in files
- text := file.read() or exit("Could not read file: $file")
-
- if rewrap
- text = unwrap(text)
-
- out := if file.is_file() and inplace
- file
- else
- (/dev/stdout)
-
- first := yes
- wrapped_paragraphs : @[Text]
- for paragraph in text.split_pattern($Pat"{2+ nl}")
- wrapped_paragraphs.insert(
- wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen)
- )
-
- out.write("\n\n".join(wrapped_paragraphs[]) ++ "\n")
diff --git a/install_dependencies.sh b/install_dependencies.sh
index 9b9026e9..5ce6bd74 100755
--- a/install_dependencies.sh
+++ b/install_dependencies.sh
@@ -48,19 +48,19 @@ fi
# Install packages
case "$PKG_MGR" in
- apt) $SUDO apt install libgc-dev libunistring-dev binutils libgmp-dev ;;
- dnf) $SUDO dnf install gc-devel libunistring-devel binutils gmp-devel ;;
- pacman) $SUDO pacman -S gc libunistring binutils gmp ;;
- yay|paru) $PKG_MGR -S gc libunistring binutils gmp ;;
- xbps) $SUDO xbps-install -S gc libunistring binutils gmp ;;
- pkg_add) $SUDO pkg_add boehm-gc libunistring binutils gmp ;;
- freebsd-pkg) $SUDO pkg install boehm-gc libunistring binutils gmp ;;
- brew) brew install bdw-gc libunistring binutils llvm gmp ;;
- macports) $SUDO port install boehm-gc libunistring binutils gmp ;;
- zypper) $SUDO zypper install gc-devel libunistring-devel binutils gmp-devel ;;
- nix) nix-env -iA nixpkgs.boehmgc.dev nixpkgs.libunistring nixpkgs.binutils nixpkgs.nixpkgs.gmp ;;
- spack) spack install boehm-gc libunistring binutils gmp ;;
- conda) conda install boehm-gc libunistring binutils gmp ;;
+ apt) $SUDO apt install libgc-dev libunistring-dev binutils libgmp-dev xxd ;;
+ dnf) $SUDO dnf install gc-devel libunistring-devel binutils gmp-devel xxd ;;
+ pacman) $SUDO pacman -S gc libunistring binutils gmp xxd ;;
+ yay|paru) $PKG_MGR -S gc libunistring binutils gmp xxd ;;
+ xbps) $SUDO xbps-install -S gc libunistring binutils gmp xxd ;;
+ pkg_add) $SUDO pkg_add boehm-gc libunistring binutils gmp xxd ;;
+ freebsd-pkg) $SUDO pkg install boehm-gc libunistring binutils gmp xxd ;;
+ brew) brew install bdw-gc libunistring binutils llvm gmp xxd ;;
+ macports) $SUDO port install boehm-gc libunistring binutils gmp xxd ;;
+ zypper) $SUDO zypper install gc-devel libunistring-devel binutils gmp-devel xxd ;;
+ nix) nix-env -iA nixpkgs.boehmgc.dev nixpkgs.libunistring nixpkgs.binutils nixpkgs.nixpkgs.gmp xxd ;;
+ spack) spack install boehm-gc libunistring binutils gmp xxd ;;
+ conda) conda install boehm-gc libunistring binutils gmp xxd ;;
*)
echo "Unknown package manager: $PKG_MGR" >&2
exit 1
diff --git a/lib/README.md b/lib/README.md
deleted file mode 100644
index 31de5b92..00000000
--- a/lib/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Core Libraries
-
-This folder contains some libraries that are installed by default when
-installing Tomo.
-
-## Libraries
-
-Libraries can be installed with `tomo -IL ./library-folder`
-
-- [base64](base64/): A base64 encoding/decoding library.
-- [commands](commands/): A library for running commands.
-- [core](core/): Bundling up commonly used libraries into a single library.
-- [json](json/): JSON parsing and encoding.
-- [patterns](patterns/): Pattern matching for text.
-- [pthreads](pthreads/): A POSIX threads library.
-- [random](random/): Pseudorandom number generators.
-- [shell](shell/): A DSL for running shell commands.
-- [time](time/): A module for working with dates and times.
-- [uuid](uuid/): A universally unique identifier library.
diff --git a/lib/base64/CHANGES.md b/lib/base64/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/base64/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/base64/README.md b/lib/base64/README.md
deleted file mode 100644
index 8db0f8ec..00000000
--- a/lib/base64/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Base64
-
-This is a library for encoding/decoding Base64 values.
diff --git a/lib/base64/base64.tm b/lib/base64/base64.tm
deleted file mode 100644
index bf512a83..00000000
--- a/lib/base64/base64.tm
+++ /dev/null
@@ -1,96 +0,0 @@
-# Base 64 encoding and decoding
-
-_enc := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".bytes()
-
-_EQUAL_BYTE := Byte(0x3D)
-
-_dec : [Byte] = [
- 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 62, 255, 255, 255, 63,
- 52, 53, 54, 55, 56, 57, 58, 59,
- 60, 61, 255, 255, 255, 255, 255, 255,
- 255, 0, 1, 2, 3, 4, 5, 6,
- 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 255, 255, 255, 255, 255,
- 255, 26, 27, 28, 29, 30, 31, 32,
- 33, 34, 35, 36, 37, 38, 39, 40,
- 41, 42, 43, 44, 45, 46, 47, 48,
- 49, 50, 51, 255, 255, 255, 255, 255,
-]
-
-lang Base64
- func parse(text:Text -> Base64?)
- return Base64.from_bytes(text.bytes())
-
- func from_bytes(bytes:[Byte] -> Base64?)
- output := &[Byte(0) for _ in bytes.length * 4 / 3 + 4]
- src := Int64(1)
- dest := Int64(1)
- while src + 2 <= Int64(bytes.length)
- chunk24 := (
- (Int32(bytes[src]) <<< 16) or (Int32(bytes[src+1]) <<< 8) or Int32(bytes[src+2])
- )
- src += 3
-
- output[dest] = _enc[1 + ((chunk24 >>> 18) and 0b111111)]
- output[dest+1] = _enc[1 + ((chunk24 >>> 12) and 0b111111)]
- output[dest+2] = _enc[1 + ((chunk24 >>> 6) and 0b111111)]
- output[dest+3] = _enc[1 + (chunk24 and 0b111111)]
- dest += 4
-
- if src + 1 == bytes.length
- chunk16 := (
- (Int32(bytes[src]) <<< 8) or Int32(bytes[src+1])
- )
- output[dest] = _enc[1 + ((chunk16 >>> 10) and 0b11111)]
- output[dest+1] = _enc[1 + ((chunk16 >>> 4) and 0b111111)]
- output[dest+2] = _enc[1 + ((chunk16 <<< 2)and 0b111111)]
- output[dest+3] = _EQUAL_BYTE
- else if src == bytes.length
- chunk8 := Int32(bytes[src])
- output[dest] = _enc[1 + ((chunk8 >>> 2) and 0b111111)]
- output[dest+1] = _enc[1 + ((chunk8 <<< 4) and 0b111111)]
- output[dest+2] = _EQUAL_BYTE
- output[dest+3] = _EQUAL_BYTE
-
- return Base64.from_text(Text.from_bytes(output[]) or return none)
-
- func decode_text(b64:Base64 -> Text?)
- return Text.from_bytes(b64.decode_bytes() or return none)
-
- func decode_bytes(b64:Base64 -> [Byte]?)
- bytes := b64.text.bytes()
- output := &[Byte(0) for _ in bytes.length/4 * 3]
- src := Int64(1)
- dest := Int64(1)
- while src + 3 <= Int64(bytes.length)
- chunk24 := (
- (Int32(_dec[1+bytes[src]]) <<< 18) or
- (Int32(_dec[1+bytes[src+1]]) <<< 12) or
- (Int32(_dec[1+bytes[src+2]]) <<< 6) or
- Int32(_dec[1+bytes[src+3]])
- )
- src += 4
-
- output[dest] = Byte((chunk24 >>> 16) and 0xFF)
- output[dest+1] = Byte((chunk24 >>> 8) and 0xFF)
- output[dest+2] = Byte(chunk24 and 0xFF)
- dest += 3
-
- while output[-1] == 0xFF
- output[] = output.to(-2)
-
- return output[]
-
-func main(input=(/dev/stdin), decode=no)
- if decode
- b := Base64.from_text(input.read()!)
- say(b.decode_text()!)
- else
- text := input.read()!
- say(Base64.parse(text)!.text)
diff --git a/lib/commands/CHANGES.md b/lib/commands/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/commands/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/commands/README.md b/lib/commands/README.md
deleted file mode 100644
index 040f4bd5..00000000
--- a/lib/commands/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Commands
-
-This module provides a way to run executable programs and get their output. You
-can also feed in text to the programs' `stdin`. Think of it as `popen()` on
-steroids.
diff --git a/lib/commands/commands.c b/lib/commands/commands.c
deleted file mode 100644
index 110ae696..00000000
--- a/lib/commands/commands.c
+++ /dev/null
@@ -1,295 +0,0 @@
-// Logic for running system commands
-
-#pragma once
-
-#include <errno.h>
-#include <gc.h>
-#include <poll.h>
-#include <pthread.h>
-#include <signal.h>
-#include <spawn.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-#include <sys/param.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-// This is a workaround fix for an issue on some systems that don't have `__GLIBC__` defined
-// and run into problems with <unistr.h>
-
-#ifndef __GLIBC__
-#define __GLIBC__ 2
-#include <unistr.h> // IWYU pragma: export
-#undef __GLIBC__
-#else
-#include <unistr.h> // IWYU pragma: export
-#endif
-
-#define READ_END 0
-#define WRITE_END 1
-
-static void xpipe(int fd[2]) {
- if (pipe(fd) != 0) fail("Failed to create pipe: ", strerror(errno));
-}
-
-int run_command(Text_t exe, List_t arg_list, Table_t env_table, OptionalList_t input_bytes, List_t *output_bytes,
- List_t *error_bytes) {
- pthread_testcancel();
-
- struct sigaction sa = {.sa_handler = SIG_IGN}, oldint, oldquit;
- sigaction(SIGINT, &sa, &oldint);
- sigaction(SIGQUIT, &sa, &oldquit);
- sigaddset(&sa.sa_mask, SIGCHLD);
- sigset_t old, reset;
- sigprocmask(SIG_BLOCK, &sa.sa_mask, &old);
- sigemptyset(&reset);
- if (oldint.sa_handler != SIG_IGN) sigaddset(&reset, SIGINT);
- if (oldquit.sa_handler != SIG_IGN) sigaddset(&reset, SIGQUIT);
- posix_spawnattr_t attr;
- posix_spawnattr_init(&attr);
- posix_spawnattr_setsigmask(&attr, &old);
- posix_spawnattr_setsigdefault(&attr, &reset);
- posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK);
-
- int child_inpipe[2], child_outpipe[2], child_errpipe[2];
- if (input_bytes.length >= 0) xpipe(child_inpipe);
- if (output_bytes) xpipe(child_outpipe);
- if (error_bytes) xpipe(child_errpipe);
-
- posix_spawn_file_actions_t actions;
- posix_spawn_file_actions_init(&actions);
- if (input_bytes.length >= 0) {
- posix_spawn_file_actions_adddup2(&actions, child_inpipe[READ_END], STDIN_FILENO);
- posix_spawn_file_actions_addclose(&actions, child_inpipe[WRITE_END]);
- }
- if (output_bytes) {
- posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
- posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
- }
- if (error_bytes) {
- posix_spawn_file_actions_adddup2(&actions, child_errpipe[WRITE_END], STDERR_FILENO);
- posix_spawn_file_actions_addclose(&actions, child_errpipe[READ_END]);
- }
-
- const char *exe_str = Text$as_c_string(exe);
-
- List_t arg_strs = {};
- List$insert_value(&arg_strs, exe_str, I(0), sizeof(char *));
- for (int64_t i = 0; i < arg_list.length; i++)
- List$insert_value(&arg_strs, Text$as_c_string(*(Text_t *)(arg_list.data + i * arg_list.stride)), I(0),
- sizeof(char *));
- List$insert_value(&arg_strs, NULL, I(0), sizeof(char *));
- char **args = arg_strs.data;
-
- extern char **environ;
- char **env = environ;
- if (env_table.entries.length > 0) {
- List_t env_list = {}; // List of const char*
- for (char **e = environ; *e; e++)
- List$insert(&env_list, e, I(0), sizeof(char *));
-
- for (int64_t i = 0; i < env_table.entries.length; i++) {
- struct {
- Text_t key, value;
- } *entry = env_table.entries.data + env_table.entries.stride * i;
- const char *env_entry = String(entry->key, "=", entry->value);
- List$insert(&env_list, &env_entry, I(0), sizeof(char *));
- }
- List$insert_value(&env_list, NULL, I(0), sizeof(char *));
- assert(env_list.stride == sizeof(char *));
- env = env_list.data;
- }
-
- pid_t pid;
- int ret = exe_str[0] == '/' ? posix_spawn(&pid, exe_str, &actions, &attr, args, env)
- : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
- if (ret != 0) return -1;
-
- posix_spawnattr_destroy(&attr);
- posix_spawn_file_actions_destroy(&actions);
-
- if (input_bytes.length >= 0) close(child_inpipe[READ_END]);
- if (output_bytes) close(child_outpipe[WRITE_END]);
- if (error_bytes) close(child_errpipe[WRITE_END]);
-
- struct pollfd pollfds[3] = {};
- if (input_bytes.length >= 0) pollfds[0] = (struct pollfd){.fd = child_inpipe[WRITE_END], .events = POLLOUT};
- if (output_bytes) pollfds[1] = (struct pollfd){.fd = child_outpipe[WRITE_END], .events = POLLIN};
- if (error_bytes) pollfds[2] = (struct pollfd){.fd = child_errpipe[WRITE_END], .events = POLLIN};
-
- if (input_bytes.length > 0 && input_bytes.stride != 1) List$compact(&input_bytes, sizeof(char));
- if (output_bytes) *output_bytes = (List_t){.atomic = 1, .stride = 1, .length = 0};
- if (error_bytes) *error_bytes = (List_t){.atomic = 1, .stride = 1, .length = 0};
-
- while (input_bytes.length > 0 || output_bytes || error_bytes) {
- (void)poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1); // Wait for data or readiness
- bool did_something = false;
- if (input_bytes.length >= 0 && pollfds[0].revents) {
- if (input_bytes.length > 0) {
- ssize_t written = write(child_inpipe[WRITE_END], input_bytes.data, (size_t)input_bytes.length);
- if (written > 0) {
- input_bytes.data += written;
- input_bytes.length -= (int64_t)written;
- did_something = true;
- } else if (written < 0) {
- close(child_inpipe[WRITE_END]);
- pollfds[0].events = 0;
- }
- }
- if (input_bytes.length <= 0) {
- close(child_inpipe[WRITE_END]);
- pollfds[0].events = 0;
- }
- }
- char buf[256];
- if (output_bytes && pollfds[1].revents) {
- ssize_t n = read(child_outpipe[READ_END], buf, sizeof(buf));
- did_something = did_something || (n > 0);
- if (n <= 0) {
- close(child_outpipe[READ_END]);
- pollfds[1].events = 0;
- } else if (n > 0) {
- if (output_bytes->free < n) {
- output_bytes->data = GC_REALLOC(output_bytes->data, (size_t)(output_bytes->length + n));
- output_bytes->free = 0;
- }
- memcpy(output_bytes->data + output_bytes->length, buf, (size_t)n);
- output_bytes->length += n;
- }
- }
- if (error_bytes && pollfds[2].revents) {
- ssize_t n = read(child_errpipe[READ_END], buf, sizeof(buf));
- did_something = did_something || (n > 0);
- if (n <= 0) {
- close(child_errpipe[READ_END]);
- pollfds[2].events = 0;
- } else if (n > 0) {
- if (error_bytes->free < n) {
- error_bytes->data = GC_REALLOC(error_bytes->data, (size_t)(error_bytes->length + n));
- error_bytes->free = 0;
- }
- memcpy(error_bytes->data + error_bytes->length, buf, (size_t)n);
- error_bytes->length += n;
- }
- }
- if (!did_something) break;
- }
-
- int status = 0;
- if (ret == 0) {
- while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
- if (WIFEXITED(status) || WIFSIGNALED(status)) break;
- else if (WIFSTOPPED(status)) kill(pid, SIGCONT);
- }
- }
-
- if (input_bytes.length >= 0) close(child_inpipe[WRITE_END]);
- if (output_bytes) close(child_outpipe[READ_END]);
- if (error_bytes) close(child_errpipe[READ_END]);
-
- sigaction(SIGINT, &oldint, NULL);
- sigaction(SIGQUIT, &oldquit, NULL);
- sigprocmask(SIG_SETMASK, &old, NULL);
-
- if (ret) errno = ret;
- return status;
-}
-
-typedef struct {
- pid_t pid;
- FILE *out;
-} child_info_t;
-
-static void _line_reader_cleanup(child_info_t *child) {
- if (child && child->out) {
- fclose(child->out);
- child->out = NULL;
- }
- if (child->pid) {
- kill(child->pid, SIGTERM);
- child->pid = 0;
- }
-}
-
-static Text_t _next_line(child_info_t *child) {
- if (!child || !child->out) return NONE_TEXT;
-
- char *line = NULL;
- size_t size = 0;
- ssize_t len = getline(&line, &size, child->out);
- if (len <= 0) {
- _line_reader_cleanup(child);
- return NONE_TEXT;
- }
-
- while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
- --len;
-
- if (u8_check((uint8_t *)line, (size_t)len) != NULL) fail("Invalid UTF8!");
-
- Text_t line_text = Text$from_strn(line, len);
- free(line);
- return line_text;
-}
-
-OptionalClosure_t command_by_line(Text_t exe, List_t arg_list, Table_t env_table) {
- posix_spawnattr_t attr;
- posix_spawnattr_init(&attr);
-
- int child_outpipe[2];
- xpipe(child_outpipe);
-
- posix_spawn_file_actions_t actions;
- posix_spawn_file_actions_init(&actions);
- posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
- posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
-
- const char *exe_str = Text$as_c_string(exe);
-
- List_t arg_strs = {};
- List$insert_value(&arg_strs, exe_str, I(0), sizeof(char *));
- for (int64_t i = 0; i < arg_list.length; i++)
- List$insert_value(&arg_strs, Text$as_c_string(*(Text_t *)(arg_list.data + i * arg_list.stride)), I(0),
- sizeof(char *));
- List$insert_value(&arg_strs, NULL, I(0), sizeof(char *));
- char **args = arg_strs.data;
-
- extern char **environ;
- char **env = environ;
- if (env_table.entries.length > 0) {
- List_t env_list = {}; // List of const char*
- for (char **e = environ; *e; e++)
- List$insert(&env_list, e, I(0), sizeof(char *));
-
- for (int64_t i = 0; i < env_table.entries.length; i++) {
- struct {
- Text_t key, value;
- } *entry = env_table.entries.data + env_table.entries.stride * i;
- const char *env_entry = String(entry->key, "=", entry->value);
- List$insert(&env_list, &env_entry, I(0), sizeof(char *));
- }
- List$insert_value(&env_list, NULL, I(0), sizeof(char *));
- assert(env_list.stride == sizeof(char *));
- env = env_list.data;
- }
-
- pid_t pid;
- int ret = exe_str[0] == '/' ? posix_spawn(&pid, exe_str, &actions, &attr, args, env)
- : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
- if (ret != 0) return NONE_CLOSURE;
-
- posix_spawnattr_destroy(&attr);
- posix_spawn_file_actions_destroy(&actions);
-
- close(child_outpipe[WRITE_END]);
-
- child_info_t *child_info = GC_MALLOC(sizeof(child_info_t));
- child_info->out = fdopen(child_outpipe[READ_END], "r");
- child_info->pid = pid;
- GC_register_finalizer(child_info, (void *)_line_reader_cleanup, NULL, NULL, NULL);
- return (Closure_t){.fn = (void *)_next_line, .userdata = child_info};
-}
-
-#undef READ_END
-#undef WRITE_END
diff --git a/lib/commands/commands.tm b/lib/commands/commands.tm
deleted file mode 100644
index b9b176af..00000000
--- a/lib/commands/commands.tm
+++ /dev/null
@@ -1,90 +0,0 @@
-# Functions for running system commands
-
-use ./commands.c
-
-extern run_command : func(exe:Text, args:[Text], env:{Text=Text}, input:[Byte]?, output:&[Byte]?, error:&[Byte]? -> Int32)
-extern command_by_line : func(exe:Text, args:[Text], env:{Text=Text} -> func(->Text?)?)
-
-enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed)
- func succeeded(e:ExitType -> Bool)
- when e is Exited(status) return (status == 0)
- else return no
-
- func or_fail(e:ExitType, message:Text?=none)
- if not e.succeeded()
- fail(message or "Program failed: $e")
-
-struct ProgramResult(output:[Byte], errors:[Byte], exit_type:ExitType)
- func or_fail(r:ProgramResult, message:Text?=none -> ProgramResult)
- when r.exit_type is Exited(status)
- if status != 0
- fail(message or "Program failed: $r")
- else fail(message or "Program failed: $r")
- return r
-
- func output_text(r:ProgramResult, trim_newline=yes -> Text?)
- when r.exit_type is Exited(status)
- if status == 0
- if text := Text.from_bytes(r.output)
- if trim_newline
- text = text.without_suffix("\n")
- return text
- else return none
- return none
-
- func error_text(r:ProgramResult -> Text?)
- when r.exit_type is Exited(status)
- if status == 0
- return Text.from_bytes(r.errors)
- else return none
- return none
-
- func succeeded(r:ProgramResult -> Bool)
- when r.exit_type is Exited(status)
- return (status == 0)
- else
- return no
-
-struct Command(command:Text, args:[Text]=[], env:{Text=Text}={})
- func from_path(path:Path, args:[Text]=[], env:{Text=Text}={} -> Command)
- return Command(Text(path), args, env)
-
- func result(command:Command, input="", input_bytes:[Byte]=[] -> ProgramResult)
- if input.length > 0
- (&input_bytes).insert_all(input.bytes())
-
- output : [Byte]
- errors : [Byte]
- status := run_command(command.command, command.args, command.env, input_bytes, &output, &errors)
-
- if C_code:Bool`WIFEXITED(@status)`
- return ProgramResult(output, errors, ExitType.Exited(C_code:Int32`WEXITSTATUS(@status)`))
-
- if C_code:Bool`WIFSIGNALED(@status)`
- return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32`WTERMSIG(@status)`))
-
- return ProgramResult(output, errors, ExitType.Failed)
-
- func run(command:Command, -> ExitType)
- status := run_command(command.command, command.args, command.env, none, none, none)
-
- if C_code:Bool`WIFEXITED(@status)`
- return ExitType.Exited(C_code:Int32`WEXITSTATUS(@status)`)
-
- if C_code:Bool`WIFSIGNALED(@status)`
- return ExitType.Signaled(C_code:Int32`WTERMSIG(@status)`)
-
- return ExitType.Failed
-
- func get_output(command:Command, input="", trim_newline=yes -> Text?)
- return command.result(input=input).output_text(trim_newline=trim_newline)
-
- func get_output_bytes(command:Command, input="", input_bytes:[Byte]=[] -> [Byte]?)
- result := command.result(input=input, input_bytes=input_bytes)
- when result.exit_type is Exited(status)
- if status == 0 return result.output
- return none
- else return none
-
- func by_line(command:Command -> func(->Text?)?)
- return command_by_line(command.command, command.args, command.env)
diff --git a/lib/core/CHANGES.md b/lib/core/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/core/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/core/core.tm b/lib/core/core.tm
deleted file mode 100644
index 5ed77756..00000000
--- a/lib/core/core.tm
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file just uses all the most commonly used standard
-# library modules so you don't have to import them one-by-one
-
-use patterns_v1.1
-use commands_v1.0
-use shell_v1.0
-use pthreads_v1.0
-use random_v1.0
-use time_v1.0
diff --git a/lib/json/CHANGES.md b/lib/json/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/json/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/json/README.md b/lib/json/README.md
deleted file mode 100644
index 33e2101a..00000000
--- a/lib/json/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# JSON
-
-This is a library for encoding/decoding JSON values.
-
-## Usage
-
-```tomo
->> j := JSON({"key1"=123, "key2"=[yes, {"ok"="inner"}, JSON.Null]})
-= JSON.Object({"key1"=Number(123), "key2"=Array([Boolean(yes), Object({"ok"=String("inner")}), Null])})
-
-say("$(j.encode())")
-say("$(j.pretty_print())")
-
-when JSON.parse("[1, null, true]") is Success(obj)
- >> obj
-is Failure(msg)
- fail("Failed to parse JSON: $msg")
-```
diff --git a/lib/json/json.tm b/lib/json/json.tm
deleted file mode 100644
index 35a139c5..00000000
--- a/lib/json/json.tm
+++ /dev/null
@@ -1,173 +0,0 @@
-# Base 64 encoding and decoding
-use patterns
-
-enum JSONDecodeResult(
- Success(json:JSON)
- Failure(reason:Text)
-)
- func invalid(text:Text -> JSONDecodeResult)
- return Failure("Unrecognized JSON: $(text.quoted())")
-
-extend Text
- func json_quoted(text:Text -> Text)
- return '"' ++ text.translate({
- "\\"="\\\\",
- '"'='\\"',
- "\f"="\\f",
- "\r"="\\r",
- "\n"="\\n",
- "\b"="\\b",
- "\t"="\\t",
- }) ++ '"'
-
-enum JSON(
- Object(items:{Text=JSON})
- Array(items:[JSON])
- Boolean(value:Bool)
- String(text:Text)
- Number(n:Num)
- Null
-)
- func encode(j:JSON -> Text)
- when j is Object(items)
- return "{" ++ ", ".join([
- '$(k.json_quoted()): $(v.encode())'
- for k,v in items
- ]) ++ "}"
- is Array(items)
- return "[" ++ ", ".join([item.encode() for item in items]) ++ "]"
- is Boolean(value)
- return (if value then "true" else "false")
- is String(text)
- return text.json_quoted()
- is Number(n)
- return "$n"
- is Null
- return "null"
-
- func pretty_print(j:JSON, max_line:Int=80, indent:Text=" ", current_indent:Text="" -> Text)
- inline := j.encode()
- if inline.length > max_line
- next_indent := current_indent ++ indent
- when j is Object(items)
- return "{\n$next_indent" ++ ",\n$next_indent".join([
- '$(k.json_quoted()): $(v.pretty_print(max_line, indent, next_indent))'
- for k,v in items
- ]) ++ "\n$current_indent}"
- is Array(items)
- return "[\n$next_indent" ++ ",\n$next_indent".join([item.pretty_print(max_line, indent, next_indent) for item in items]) ++ "\n$current_indent]"
- else pass
-
- return inline
-
- func parse_text(text:Text, remainder:&Text? = none -> JSONDecodeResult)
- if text.starts_with('"')
- string := ""
- pos := 2
- escapes := {"n"="\n", "t"="\t", "r"="\r", '"'='"', "\\"="\\", "/"="/", "b"="\b", "f"="\f"}
- while pos <= text.length
- c := text[pos]
- if c == '"'
- if remainder
- remainder[] = text.from(pos + 1)
- return Success(JSON.String(string))
-
- if c == "\\"
- stop if pos + 1 > text.length
-
- if esc := escapes[text[pos+1]]
- string ++= esc
- pos += 2
- else if m := text.matching_pattern($Pat"u{4 digit}")
- string ++= Text.from_codepoints([Int32.parse(m.captures[1])!])
- pos += 1 + m.text.length
- else
- if remainder
- remainder[] = text
- return JSONDecodeResult.invalid(text)
- else
- string ++= c
- pos += 1
-
- if remainder
- remainder[] = text
- return JSONDecodeResult.invalid(text)
-
- func parse(text:Text, remainder:&Text? = none, trailing_commas:Bool=no -> JSONDecodeResult)
- if text.starts_with("true", remainder)
- return Success(JSON.Boolean(yes))
- else if text.starts_with("false", remainder)
- return Success(JSON.Boolean(no))
- else if text.starts_with("null", remainder)
- return Success(JSON.Null)
- else if n := Num.parse(text, remainder)
- return Success(JSON.Number(n))
- else if text.starts_with('"')
- return JSON.parse_text(text, remainder)
- else if text.starts_with("[")
- elements : &[JSON]
- text = text.from(2).trim_pattern($Pat"{whitespace}", right=no)
- repeat
- when JSON.parse(text, &text) is Success(elem)
- elements.insert(elem)
- else stop
-
- if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}')
- text = text.from(delim.text.length + 1)
- else stop
-
- if trailing_commas
- if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}')
- text = text.from(delim.text.length + 1)
-
- if terminator := text.matching_pattern($Pat'{0+ ws}]')
- if remainder
- remainder[] = text.from(terminator.text.length + 1)
- return Success(JSON.Array(elements))
- else if text.starts_with("{")
- object : &{Text=JSON}
- text = text.from(2).trim_pattern($Pat"{whitespace}", right=no)
- repeat
- key_text := text
- when JSON.parse_text(text, &text) is Success(key)
- if separator := text.matching_pattern($Pat'{0+ ws}:{0+ ws}')
- text = text.from(separator.text.length + 1)
- else
- return JSONDecodeResult.invalid(text)
-
- when JSON.parse(text, &text) is Success(value)
- when key is String(str)
- object[str] = value
- else
- return JSONDecodeResult.invalid(key_text)
- else
- return JSONDecodeResult.invalid(text)
- else stop
-
- if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}')
- text = text.from(delim.text.length + 1)
- else stop
-
- if trailing_commas
- if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}')
- text = text.from(delim.text.length + 1)
-
- if terminator := text.matching_pattern($Pat'{0+ ws}{}}')
- if remainder
- remainder[] = text.from(terminator.text.length + 1)
- return Success(JSON.Object(object))
-
- return JSONDecodeResult.invalid(text)
-
-func main(input=(/dev/stdin), pretty_print:Bool = no, trailing_commas:Bool = yes)
- text := (input.read() or exit("Invalid file: $input")).trim_pattern($Pat"{whitespace}")
- while text.length > 0
- when JSON.parse(text, remainder=&text, trailing_commas=trailing_commas) is Success(json)
- if pretty_print
- say(json.pretty_print())
- else
- say(json.encode())
- is Failure(msg)
- exit("\033[31;1m$msg\033[m", code=1)
-
- text = text.trim_pattern($Pat"{whitespace}")
diff --git a/lib/patterns/CHANGES.md b/lib/patterns/CHANGES.md
deleted file mode 100644
index cf6254cb..00000000
--- a/lib/patterns/CHANGES.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Version History
-
-## v1.1
-- Added `Text.matching_pattern(text:Text, pattern:Pattern, pos:Int = 1 -> PatternMatch?)`
-
-## v1.0
-
-Initial version
diff --git a/lib/patterns/README.md b/lib/patterns/README.md
deleted file mode 100644
index faf2854e..00000000
--- a/lib/patterns/README.md
+++ /dev/null
@@ -1,444 +0,0 @@
-# Text Pattern Matching
-
-As an alternative to full regular expressions, Tomo provides a limited text
-matching pattern syntax that is intended to solve 80% of use cases in under 1%
-of the code size (PCRE's codebase is roughly 150k lines of code, and Tomo's
-pattern matching code is a bit under 1k lines of code). Tomo's pattern matching
-syntax is highly readable and works well for matching literal text without
-getting [leaning toothpick syndrome](https://en.wikipedia.org/wiki/Leaning_toothpick_syndrome).
-
-For more advanced use cases, consider linking against a C library for regular
-expressions or pattern matching.
-
-`Pat` is a [domain-specific language](docs/langs.md), in other words, it's
-like a `Text`, but it has a distinct type.
-
-Patterns are used in a small, but very powerful API that handles many text
-functions that would normally be handled by a more extensive API:
-
-- [`by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?))`](#by_pattern)
-- [`by_pattern_split(text:Text, pattern:Pat -> func(->Text?))`](#by_pattern_split)
-- [`each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes)`](#each_pattern)
-- [`find_patterns(text:Text, pattern:Pat -> [PatternMatch])`](#find_patterns)
-- [`has_pattern(text:Text, pattern:Pat -> Bool)`](#has_pattern)
-- [`map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text)`](#map_pattern)
-- [`matches_pattern(text:Text, pattern:Pat -> Bool)`](#matches_pattern)
-- [`pattern_captures(text:Text, pattern:Pat -> [Text]?)`](#pattern_captures)
-- [`replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text)`](#replace_pattern)
-- [`split_pattern(text:Text, pattern:Pat -> [Text])`](#split_pattern)
-- [`translate_patterns(text:Text, replacements:{Pat,Text}, backref="@", recursive=yes -> Text)`](#translate_patterns)
-- [`trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text)`](#trim_pattern)
-
-## Matches
-
-Pattern matching functions work with a type called `PatternMatch` that has three fields:
-
-- `text`: The full text of the match.
-- `index`: The index in the text where the match was found.
-- `captures`: A list containing the matching text of each non-literal pattern group.
-
-See [Text Functions](text.md#Text-Functions) for the full API documentation.
-
-## Syntax
-
-Patterns have three types of syntax:
-
-- `{` followed by an optional count (`n`, `n-m`, or `n+`), followed by an
- optional `!` to negate the pattern, followed by an optional pattern name or
- Unicode character name, followed by a required `}`.
-
-- Any matching pair of quotes or parentheses or braces with a `?` in the middle
- (e.g. `"?"` or `(?)`).
-
-- Any other character is treated as a literal to be matched exactly.
-
-## Named Patterns
-
-Named patterns match certain pre-defined patterns that are commonly useful. To
-use a named pattern, use the syntax `{name}`. Names are case-insensitive and
-mostly ignore spaces, underscores, and dashes.
-
-- `..` - Any character (note that a single `.` would mean the literal period
- character).
-- `digit` - A unicode digit
-- `email` - an email address
-- `emoji` - an emoji
-- `end` - the very end of the text
-- `id` - A unicode identifier
-- `int` - One or more digits with an optional `-` (minus sign) in front
-- `ip` - an IP address (IPv4 or IPv6)
-- `ipv4` - an IPv4 address
-- `ipv6` - an IPv6 address
-- `nl`/`newline`/`crlf` - A line break (either `\r\n` or `\n`)
-- `num` - One or more digits with an optional `-` (minus sign) in front and an optional `.` and more digits after
-- `start` - the very start of the text
-- `uri` - a URI
-- `url` - a URL (URI that specifically starts with `http://`, `https://`, `ws://`, `wss://`, or `ftp://`)
-- `word` - A unicode identifier (same as `id`)
-
-For non-alphabetic characters, any single character is treated as matching
-exactly that character. For example, `{1{}` matches exactly one `{`
-character. Or, `{1.}` matches exactly one `.` character.
-
-Patterns can also use any Unicode property name. Some helpful ones are:
-
-- `hex` - Hexidecimal digits
-- `lower` - Lowercase letters
-- `space` - The space character
-- `upper` - Uppercase letters
-- `whitespace` - Whitespace characters
-
-Patterns may also use exact Unicode codepoint names. For example: `{1 latin
-small letter A}` matches `a`.
-
-## Negating Patterns
-
-If an exclamation mark (`!`) is placed before a pattern's name, then characters
-are matched only when they _don't_ match the pattern. For example, `{!alpha}`
-will match all characters _except_ alphabetic ones.
-
-## Interpolating Text and Escaping
-
-To escape a character in a pattern (e.g. if you want to match the literal
-character `?`), you can use the syntax `{1 ?}`. This is almost never necessary
-unless you have text that looks like a Tomo text pattern and has something like
-`{` or `(?)` inside it.
-
-However, if you're trying to do an exact match of arbitrary text values, you'll
-want to have the text automatically escaped. Fortunately, Tomo's injection-safe
-DSL text interpolation supports automatic text escaping. This means that if you
-use text interpolation with the `$` sign to insert a text value, the value will
-be automatically escaped using the `{1 ?}` rule described above:
-
-```tomo
-# Risk of code injection (would cause an error because 'xxx' is not a valid
-# pattern name:
->> user_input := get_user_input()
-= "{xxx}"
-
-# Interpolation automatically escapes:
->> $/$user_input/
-= $/{1{}..xxx}/
-
-# This is: `{ 1{ }` (one open brace) followed by the literal text "..xxx}"
-
-# No error:
->> some_text.find($/$user_input/)
-= 0
-```
-
-If you prefer, you can also use this to insert literal characters:
-
-```tomo
->> $/literal $"{..}"/
-= $/literal {1{}..}/
-```
-
-## Repetitions
-
-By default, named patterns match 1 or more repetitions, but you can specify how
-many repetitions you want by putting a number or range of numbers first using
-`n` (exactly `n` repetitions), `n-m` (between `n` and `m` repetitions), or `n+`
-(`n` or more repetitions):
-
-```
-{4-5 alpha}
-0x{hex}
-{4 digit}-{2 digit}-{2 digit}
-{2+ space}
-{0-1 question mark}
-```
-
-
-# Methods
-
-### `by_pattern`
-Returns an iterator function that yields `PatternMatch` objects for each occurrence.
-
-```tomo
-func by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?))
-```
-
-- `text`: The text to search.
-- `pattern`: The pattern to match.
-
-**Returns:**
-An iterator function that yields `PatternMatch` objects one at a time.
-
-**Example:**
-```tomo
-text := "one, two, three"
-for word in text.by_pattern($Pat"{id}"):
- say(word.text)
-```
-
----
-
-### `by_pattern_split`
-Returns an iterator function that yields text segments split by a pattern.
-
-```tomo
-func by_pattern_split(text:Text, pattern:Pat -> func(->Text?))
-```
-
-- `text`: The text to split.
-- `pattern`: The pattern to use as a separator.
-
-**Returns:**
-An iterator function that yields text segments.
-
-**Example:**
-```tomo
-text := "one two three"
-for word in text.by_pattern_split($Pat"{whitespace}"):
- say(word.text)
-```
-
----
-
-### `each_pattern`
-Applies a function to each occurrence of a pattern in the text.
-
-```tomo
-func each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes)
-```
-
-- `text`: The text to search.
-- `pattern`: The pattern to match.
-- `fn`: The function to apply to each match.
-- `recursive`: If `yes`, applies the function recursively on modified text.
-
-**Example:**
-```tomo
-text := "one two three"
-text.each_pattern($Pat"{id}", func(m:PatternMatch):
- say(m.txt)
-)
-```
-
----
-
-### `find_patterns`
-Finds all occurrences of a pattern in a text and returns them as `PatternMatch` objects.
-
-```tomo
-func find_patterns(text:Text, pattern:Pat -> [PatternMatch])
-```
-
-- `text`: The text to search.
-- `pattern`: The pattern to match.
-
-**Returns:**
-A list of `PatternMatch` objects.
-
-**Example:**
-```tomo
-text := "one! two three!"
->> text.find_patterns($Pat"{id}!")
-= [PatternMatch(text="one!", index=1, captures=["one"]), PatternMatch(text="three!", index=10, captures=["three"])]
-```
-
----
-
-### `has_pattern`
-Checks whether a given pattern appears in the text.
-
-```tomo
-func has_pattern(text:Text, pattern:Pat -> Bool)
-```
-
-- `text`: The text to search.
-- `pattern`: The pattern to check for.
-
-**Returns:**
-`yes` if a match is found, otherwise `no`.
-
-**Example:**
-```tomo
-text := "...okay..."
->> text.has_pattern($Pat"{id}")
-= yes
-```
-
----
-
-### `map_pattern`
-Transforms matches of a pattern using a mapping function.
-
-```tomo
-func map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text)
-```
-
-- `text`: The text to modify.
-- `pattern`: The pattern to match.
-- `fn`: A function that transforms matches.
-- `recursive`: If `yes`, applies transformations recursively.
-
-**Returns:**
-A new text with the transformed matches.
-
-**Example:**
-```tomo
-text := "I have #apples and #oranges and #plums"
-fruits := {"apples"=4, "oranges"=5}
->> text.map_pattern($Pat'#{id}', func(match:PatternMatch):
- fruit := match.captures[1]
- "$(fruits[fruit] or 0) $fruit"
-)
-= "I have 4 apples and 5 oranges and 0 plums"
-```
-
----
-
-### `matches_pattern`
-Returns whether or not text matches a pattern completely.
-
-```tomo
-func matches_pattern(text:Text, pattern:Pat -> Bool)
-```
-
-- `text`: The text to match against.
-- `pattern`: The pattern to match.
-
-**Returns:**
-`yes` if the whole text matches the pattern, otherwise `no`.
-
-**Example:**
-```tomo
->> "Hello!!!".matches_pattern($Pat"{id}")
-= no
->> "Hello".matches_pattern($Pat"{id}")
-= yes
-```
-
----
-
-### `pattern_captures`
-Returns a list of pattern captures for the given pattern.
-
-```tomo
-func pattern_captures(text:Text, pattern:Pat -> [Text]?)
-```
-
-- `text`: The text to match against.
-- `pattern`: The pattern to match.
-
-**Returns:**
-An optional list of matched pattern captures. Returns `none` if the text does
-not match the pattern.
-
-**Example:**
-```tomo
->> "123 boxes".pattern_captures($Pat"{int} {id}")
-= ["123", "boxes"]?
->> "xxx".pattern_captures($Pat"{int} {id}")
-= none
-```
-
----
-
-### `replace_pattern`
-Replaces occurrences of a pattern with a replacement text, supporting backreferences.
-
-```tomo
-func replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text)
-```
-
-- `text`: The text to modify.
-- `pattern`: The pattern to match.
-- `replacement`: The text to replace matches with.
-- `backref`: The symbol for backreferences in the replacement.
-- `recursive`: If `yes`, applies replacements recursively.
-
-**Returns:**
-A new text with replacements applied.
-
-**Example:**
-```tomo
->> "I have 123 apples and 456 oranges".replace_pattern($Pat"{int}", "some")
-= "I have some apples and some oranges"
-
->> "I have 123 apples and 456 oranges".replace_pattern($Pat"{int}", "(@1)")
-= "I have (123) apples and (456) oranges"
-
->> "I have 123 apples and 456 oranges".replace_pattern($Pat"{int}", "(?1)", backref="?")
-= "I have (123) apples and (456) oranges"
-
->> "bad(fn(), bad(notbad))".replace_pattern($Pat"bad(?)", "good(@1)")
-= "good(fn(), good(notbad))"
-
->> "bad(fn(), bad(notbad))".replace_pattern($Pat"bad(?)", "good(@1)", recursive=no)
-= "good(fn(), bad(notbad))"
-```
-
----
-
-### `split_pattern`
-Splits a text into segments using a pattern as the delimiter.
-
-```tomo
-func split_pattern(text:Text, pattern:Pat -> [Text])
-```
-
-- `text`: The text to split.
-- `pattern`: The pattern to use as a separator.
-
-**Returns:**
-A list of text segments.
-
-**Example:**
-```tomo
->> "one two three".split_pattern($Pat"{whitespace}")
-= ["one", "two", "three"]
-```
-
----
-
-### `translate_patterns`
-Replaces multiple patterns using a mapping of patterns to replacement texts.
-
-```tomo
-func translate_patterns(text:Text, replacements:{Pat,Text}, backref="@", recursive=yes -> Text)
-```
-
-- `text`: The text to modify.
-- `replacements`: A table mapping patterns to their replacements.
-- `backref`: The symbol for backreferences in replacements.
-- `recursive`: If `yes`, applies replacements recursively.
-
-**Returns:**
-A new text with all specified replacements applied.
-
-**Example:**
-```tomo
->> text := "foo(x, baz(1))"
->> text.translate_patterns({
- $Pat"{id}(?)"="call(fn('@1'), @2)",
- $Pat"{id}"="var('@1')",
- $Pat"{int}"="int(@1)",
-})
-= "call(fn('foo'), var('x'), call(fn('baz'), int(1)))"
-```
-
----
-
-### `trim_pattern`
-Removes matching patterns from the beginning and/or end of a text.
-
-```tomo
-func trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text)
-```
-
-- `text`: The text to trim.
-- `pattern`: The pattern to trim (defaults to whitespace).
-- `left`: If `yes`, trims from the beginning.
-- `right`: If `yes`, trims from the end.
-
-**Returns:**
-The trimmed text.
-
-**Example:**
-```tomo
->> "123abc456".trim_pattern($Pat"{digit}")
-= "abc"
-```
diff --git a/lib/patterns/_test.tm b/lib/patterns/_test.tm
deleted file mode 100644
index 26c23628..00000000
--- a/lib/patterns/_test.tm
+++ /dev/null
@@ -1,256 +0,0 @@
-use patterns_v1.0
-
-func main()
- amelie := "Am\{UE9}lie"
- amelie2 := "Am\{U65}\{U301}lie"
- >> "Hello".replace_pattern($Pat/e/, "X")
- = "HXllo"
-
- >> "Hello".has_pattern($Pat/l/)
- = yes
- >> "Hello".has_pattern($Pat/l{end}/)
- = no
- >> "Hello".has_pattern($Pat/{start}l/)
- = no
-
- >> "Hello".has_pattern($Pat/o/)
- = yes
- >> "Hello".has_pattern($Pat/o{end}/)
- = yes
- >> "Hello".has_pattern($Pat/{start}o/)
- = no
-
- >> "Hello".has_pattern($Pat/H/)
- = yes
- >> "Hello".has_pattern($Pat/H{end}/)
- = no
- >> "Hello".has_pattern($Pat/{start}H/)
- = yes
-
- >> "Hello".replace_pattern($Pat/l/, "")
- = "Heo"
- >> "xxxx".replace_pattern($Pat/x/, "")
- = ""
- >> "xxxx".replace_pattern($Pat/y/, "")
- = "xxxx"
- >> "One two three four five six".replace_pattern($Pat/e /, "")
- = "Ontwo threfour fivsix"
-
- >> " one ".replace_pattern($Pat/{start}{space}/, "")
- = "one "
- >> " one ".replace_pattern($Pat/{space}{end}/, "")
- = " one"
-
- >> amelie.has_pattern($Pat/$amelie2/)
- = yes
-
- >> "one two three".replace_pattern($Pat/{alpha}/, "")
- = " "
- >> "one two three".replace_pattern($Pat/{alpha}/, "word")
- = "word word word"
-
- say("Test splitting and joining text:")
-
- >> "one two three".split_pattern($Pat/ /)
- = ["one", "two", "three"]
-
- >> "one,two,three,".split_pattern($Pat/,/)
- = ["one", "two", "three", ""]
-
- >> "one two three".split_pattern($Pat/{space}/)
- = ["one", "two", "three"]
-
- >> "abc".split_pattern($Pat//)
- = ["a", "b", "c"]
-
- >> ", ".join(["one", "two", "three"])
- = "one, two, three"
-
- >> "".join(["one", "two", "three"])
- = "onetwothree"
-
- >> "+".join(["one"])
- = "one"
-
- >> "+".join([])
- = ""
-
- say("Test text.find_patterns()")
- >> " #one #two #three ".find_patterns($Pat/#{alpha}/)
- = [PatternMatch(text="#one", index=2, captures=["one"]), PatternMatch(text="#two", index=8, captures=["two"]), PatternMatch(text="#three", index=13, captures=["three"])]
-
- >> " #one #two #three ".find_patterns($Pat/#{!space}/)
- = [PatternMatch(text="#one", index=2, captures=["one"]), PatternMatch(text="#two", index=8, captures=["two"]), PatternMatch(text="#three", index=13, captures=["three"])]
-
- >> " ".find_patterns($Pat/{alpha}/)
- = []
-
- >> " foo(baz(), 1) doop() ".find_patterns($Pat/{id}(?)/)
- = [PatternMatch(text="foo(baz(), 1)", index=2, captures=["foo", "baz(), 1"]), PatternMatch(text="doop()", index=17, captures=["doop", ""])]
-
- >> "".find_patterns($Pat'')
- = []
-
- >> "Hello".find_patterns($Pat'')
- = []
-
- say("Test text slicing:")
- >> "abcdef".slice()
- = "abcdef"
- >> "abcdef".slice(from=3)
- = "cdef"
- >> "abcdef".slice(to=-2)
- = "abcde"
- >> "abcdef".slice(from=2, to=4)
- = "bcd"
- >> "abcdef".slice(from=5, to=1)
- = ""
-
- >> house := "家"
- = "家"
- >> house.length
- = 1
- >> house.codepoint_names()
- = ["CJK Unified Ideographs-5BB6"]
- >> house.utf32_codepoints()
- = [23478]
-
- >> "🐧".codepoint_names()
- = ["PENGUIN"]
-
- >> Text.from_codepoint_names(["not a valid name here buddy"])
- = none
-
- >> "one two; three four".find_patterns($Pat/; {..}/)
- = [PatternMatch(text="; three four", index=8, captures=["three four"])]
-
- malicious := "{xxx}"
- >> $Pat/$malicious/
- = $Pat/{1{}xxx}/
-
- >> "Hello".replace_pattern($Pat/{lower}/, "(@0)")
- = "H(ello)"
-
- >> " foo(xyz) foo(yyy) foo(z()) ".replace_pattern($Pat/foo(?)/, "baz(@1)")
- = " baz(xyz) baz(yyy) baz(z()) "
-
- >> "<tag>".translate_patterns({$Pat/</="&lt;", $Pat/>/="&gt;"})
- = "&lt;tag&gt;"
-
- >> " BAD(x, fn(y), BAD(z), w) ".replace_pattern($Pat/BAD(?)/, "good(@1)", recursive=yes)
- = " good(x, fn(y), good(z), w) "
-
- >> " BAD(x, fn(y), BAD(z), w) ".replace_pattern($Pat/BAD(?)/, "good(@1)", recursive=no)
- = " good(x, fn(y), BAD(z), w) "
-
- >> "Hello".matches_pattern($Pat/{id}/)
- = yes
- >> "Hello".matches_pattern($Pat/{lower}/)
- = no
- >> "Hello".matches_pattern($Pat/{upper}/)
- = no
- >> "Hello...".matches_pattern($Pat/{id}/)
- = no
-
- >> "hello world".map_pattern($Pat/world/, func(m:PatternMatch) m.text.upper())
- = "hello WORLD"
-
- >> "Abc".repeat(3)
- = "AbcAbcAbc"
-
- >> " abc def ".trim_pattern()
- = "abc def"
- >> " abc123def ".trim_pattern($Pat/{!digit}/)
- = "123"
- >> " abc123def ".trim_pattern($Pat/{!digit}/, left=no)
- = " abc123"
- >> " abc123def ".trim_pattern($Pat/{!digit}/, right=no)
- = "123def "
- # Only trim single whole matches that bookend the text:
- >> "AbcAbcxxxxxxxxAbcAbc".trim_pattern($Pat/Abc/)
- = "AbcxxxxxxxxAbc"
-
- >> "A=B=C=D".replace_pattern($Pat/{..}={..}/, "1:(@1) 2:(@2)")
- = "1:(A) 2:(B=C=D)"
-
- >> "abcde".starts_with("ab")
- = yes
- >> "abcde".starts_with("bc")
- = no
-
- >> "abcde".ends_with("de")
- = yes
- >> "abcde".starts_with("cd")
- = no
-
- >> ("hello" ++ " " ++ "Amélie").reversed()
- = "eilémA olleh"
-
- do
- say("Testing concatenation-stability:")
- ab := Text.from_codepoint_names(["LATIN SMALL LETTER E", "COMBINING VERTICAL LINE BELOW"])!
- >> ab.codepoint_names()
- = ["LATIN SMALL LETTER E", "COMBINING VERTICAL LINE BELOW"]
- >> ab.length
- = 1
-
- a := Text.from_codepoint_names(["LATIN SMALL LETTER E"])!
- b := Text.from_codepoint_names(["COMBINING VERTICAL LINE BELOW"])!
- >> (a++b).codepoint_names()
- = ["LATIN SMALL LETTER E", "COMBINING VERTICAL LINE BELOW"]
- >> (a++b) == ab
- = yes
- >> (a++b).length
- = 1
-
-
- do
- concat := "e" ++ Text.from_codepoints([Int32(0x300)])
- >> concat.length
- = 1
-
- concat2 := concat ++ Text.from_codepoints([Int32(0x302)])
- >> concat2.length
- = 1
-
- concat3 := concat2 ++ Text.from_codepoints([Int32(0x303)])
- >> concat3.length
- = 1
-
- final := Text.from_codepoints([Int32(0x65), Int32(0x300), Int32(0x302), Int32(0x303)])
- >> final.length
- = 1
- >> concat3 == final
- = yes
-
- concat4 := Text.from_codepoints([Int32(0x65), Int32(0x300)]) ++ Text.from_codepoints([Int32(0x302), Int32(0x303)])
- >> concat4.length
- = 1
- >> concat4 == final
- = yes
-
- >> "x".left_pad(5)
- = " x"
- >> "x".right_pad(5)
- = "x "
- >> "x".middle_pad(5)
- = " x "
- >> "1234".left_pad(8, "XYZ")
- = "XYZX1234"
- >> "1234".right_pad(8, "XYZ")
- = "1234XYZX"
- >> "1234".middle_pad(9, "XYZ")
- = "XY1234XYZ"
-
- >> amelie.width()
- = 6
- cowboy := "🤠"
- >> cowboy.width()
- = 2
- >> cowboy.left_pad(4)
- = " 🤠"
- >> cowboy.right_pad(4)
- = "🤠 "
- >> cowboy.middle_pad(4)
- = " 🤠 "
-
diff --git a/lib/patterns/match_type.h b/lib/patterns/match_type.h
deleted file mode 100644
index f85f1862..00000000
--- a/lib/patterns/match_type.h
+++ /dev/null
@@ -1,9 +0,0 @@
-// A datatype used for pattern match results
-
-#pragma once
-
-typedef struct {
- Text_t text;
- Int_t index;
- List_t captures;
-} XMatch;
diff --git a/lib/patterns/patterns.c b/lib/patterns/patterns.c
deleted file mode 100644
index 27799c40..00000000
--- a/lib/patterns/patterns.c
+++ /dev/null
@@ -1,1212 +0,0 @@
-// Logic for text pattern matching
-
-#include <ctype.h>
-#include <string.h>
-#include <strings.h>
-#include <sys/param.h>
-#include <unictype.h>
-#include <uniname.h>
-#include <unistring/version.h>
-
-#define MAX_BACKREFS 100
-
-typedef struct {
- Text_t text;
- Int_t index;
- List_t captures;
-} PatternMatch;
-
-typedef struct {
- Text_t text;
- Int_t index;
- List_t captures;
- bool is_none : 1;
-} OptionalPatternMatch;
-
-#define NONE_MATCH ((OptionalPatternMatch){.is_none = true})
-
-typedef struct {
- int64_t index, length;
- bool occupied, recursive;
-} capture_t;
-
-typedef struct {
- enum { PAT_START, PAT_END, PAT_ANY, PAT_GRAPHEME, PAT_PROPERTY, PAT_QUOTE, PAT_PAIR, PAT_FUNCTION } tag;
- bool negated, non_capturing;
- int64_t min, max;
- union {
- int32_t grapheme;
- uc_property_t property;
- int64_t (*fn)(TextIter_t *, int64_t);
- int32_t quote_graphemes[2];
- int32_t pair_graphemes[2];
- };
-} pat_t;
-
-static Text_t replace_list(Text_t text, List_t replacements, Text_t backref_pat, bool recursive);
-
-static INLINE void skip_whitespace(TextIter_t *state, int64_t *i) {
- while (*i < state->stack[0].text.length) {
- int32_t grapheme = Text$get_grapheme_fast(state, *i);
- if (grapheme > 0 && !uc_is_property_white_space((ucs4_t)grapheme)) return;
- *i += 1;
- }
-}
-
-static INLINE bool match_grapheme(TextIter_t *state, int64_t *i, int32_t grapheme) {
- if (*i < state->stack[0].text.length && Text$get_grapheme_fast(state, *i) == grapheme) {
- *i += 1;
- return true;
- }
- return false;
-}
-
-static INLINE bool match_str(TextIter_t *state, int64_t *i, const char *str) {
- int64_t matched = 0;
- while (matched[str]) {
- if (*i + matched >= state->stack[0].text.length || Text$get_grapheme_fast(state, *i + matched) != str[matched])
- return false;
- matched += 1;
- }
- *i += matched;
- return true;
-}
-
-static int64_t parse_int(TextIter_t *state, int64_t *i) {
- int64_t value = 0;
- for (;; *i += 1) {
- uint32_t grapheme = Text$get_main_grapheme_fast(state, *i);
- int digit = uc_digit_value(grapheme);
- if (digit < 0) break;
- if (value >= INT64_MAX / 10) break;
- value = 10 * value + digit;
- }
- return value;
-}
-
-static const char *get_property_name(TextIter_t *state, int64_t *i) {
- skip_whitespace(state, i);
- char *name = GC_MALLOC_ATOMIC(UNINAME_MAX);
- char *dest = name;
- while (*i < state->stack[0].text.length) {
- int32_t grapheme = Text$get_grapheme_fast(state, *i);
- if (!(grapheme & ~0xFF) && (isalnum(grapheme) || grapheme == ' ' || grapheme == '_' || grapheme == '-')) {
- *dest = (char)grapheme;
- ++dest;
- if (dest >= name + UNINAME_MAX - 1) break;
- } else {
- break;
- }
- *i += 1;
- }
-
- while (dest > name && dest[-1] == ' ')
- *(dest--) = '\0';
-
- if (dest == name) return NULL;
- *dest = '\0';
- return name;
-}
-
-#define EAT1(state, index, cond) \
- ({ \
- int32_t grapheme = Text$get_grapheme_fast(state, index); \
- bool success = (cond); \
- if (success) index += 1; \
- success; \
- })
-
-#define EAT2(state, index, cond1, cond2) \
- ({ \
- int32_t grapheme = Text$get_grapheme_fast(state, index); \
- bool success = (cond1); \
- if (success) { \
- grapheme = Text$get_grapheme_fast(state, index + 1); \
- success = (cond2); \
- if (success) index += 2; \
- } \
- success; \
- })
-
-#define EAT_MANY(state, index, cond) \
- ({ \
- int64_t _n = 0; \
- while (EAT1(state, index, cond)) { \
- _n += 1; \
- } \
- _n; \
- })
-
-static int64_t match_email(TextIter_t *state, int64_t index) {
- // email = local "@" domain
- // local = 1-64 ([a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~] | non-ascii)
- // domain = dns-label ("." dns-label)*
- // dns-label = 1-63 ([a-zA-Z0-9-] | non-ascii)
-
- if (index > 0) {
- uint32_t prev_codepoint = Text$get_main_grapheme_fast(state, index - 1);
- if (uc_is_property_alphabetic(prev_codepoint)) return -1;
- }
-
- int64_t start_index = index;
-
- // Local part:
- int64_t local_len = 0;
- static const char *allowed_local = "!#$%&‘*+–/=?^_`.{|}~";
- while (EAT1(state, index, (grapheme & ~0x7F) || isalnum((char)grapheme) || strchr(allowed_local, (char)grapheme))) {
- local_len += 1;
- if (local_len > 64) return -1;
- }
-
- if (!EAT1(state, index, grapheme == '@')) return -1;
-
- // Host
- int64_t host_len = 0;
- do {
- int64_t label_len = 0;
- while (EAT1(state, index, (grapheme & ~0x7F) || isalnum((char)grapheme) || grapheme == '-')) {
- label_len += 1;
- if (label_len > 63) return -1;
- }
-
- if (label_len == 0) return -1;
-
- host_len += label_len;
- if (host_len > 255) return -1;
- host_len += 1;
- } while (EAT1(state, index, grapheme == '.'));
-
- return index - start_index;
-}
-
-static int64_t match_ipv6(TextIter_t *state, int64_t index) {
- if (index > 0) {
- int32_t prev_codepoint = Text$get_grapheme_fast(state, index - 1);
- if ((prev_codepoint & ~0x7F) && (isxdigit(prev_codepoint) || prev_codepoint == ':')) return -1;
- }
- int64_t start_index = index;
- const int NUM_CLUSTERS = 8;
- bool double_colon_used = false;
- for (int cluster = 0; cluster < NUM_CLUSTERS; cluster++) {
- for (int digits = 0; digits < 4; digits++) {
- if (!EAT1(state, index, ~(grapheme & ~0x7F) && isxdigit((char)grapheme))) break;
- }
- if (EAT1(state, index, ~(grapheme & ~0x7F) && isxdigit((char)grapheme))) return -1; // Too many digits
-
- if (cluster == NUM_CLUSTERS - 1) {
- break;
- } else if (!EAT1(state, index, grapheme == ':')) {
- if (double_colon_used) break;
- return -1;
- }
-
- if (EAT1(state, index, grapheme == ':')) {
- if (double_colon_used) return -1;
- double_colon_used = true;
- }
- }
- return index - start_index;
-}
-
-static int64_t match_ipv4(TextIter_t *state, int64_t index) {
- if (index > 0) {
- int32_t prev_codepoint = Text$get_grapheme_fast(state, index - 1);
- if ((prev_codepoint & ~0x7F) && (isdigit(prev_codepoint) || prev_codepoint == '.')) return -1;
- }
- int64_t start_index = index;
-
- const int NUM_CLUSTERS = 4;
- for (int cluster = 0; cluster < NUM_CLUSTERS; cluster++) {
- for (int digits = 0; digits < 3; digits++) {
- if (!EAT1(state, index, ~(grapheme & ~0x7F) && isdigit((char)grapheme))) {
- if (digits == 0) return -1;
- break;
- }
- }
-
- if (EAT1(state, index, ~(grapheme & ~0x7F) && isdigit((char)grapheme))) return -1; // Too many digits
-
- if (cluster == NUM_CLUSTERS - 1) break;
- else if (!EAT1(state, index, grapheme == '.')) return -1;
- }
- return (index - start_index);
-}
-
-static int64_t match_ip(TextIter_t *state, int64_t index) {
- int64_t len = match_ipv6(state, index);
- if (len >= 0) return len;
- len = match_ipv4(state, index);
- return (len >= 0) ? len : -1;
-}
-
-static int64_t match_host(TextIter_t *state, int64_t index) {
- int64_t ip_len = match_ip(state, index);
- if (ip_len > 0) return ip_len;
-
- int64_t start_index = index;
- if (match_grapheme(state, &index, '[')) {
- ip_len = match_ip(state, index);
- if (ip_len <= 0) return -1;
- index += ip_len;
- if (match_grapheme(state, &index, ']')) return (index - start_index);
- return -1;
- }
-
- if (!EAT1(state, index, isalpha(grapheme))) return -1;
-
- static const char *non_host_chars = "/#?:@ \t\r\n<>[]{}\\^|\"`";
- EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_host_chars, (char)grapheme));
- return (index - start_index);
-}
-
-static int64_t match_authority(TextIter_t *state, int64_t index) {
- int64_t authority_start = index;
- static const char *non_segment_chars = "/#?:@ \t\r\n<>[]{}\\^|\"`.";
-
- // Optional user@ prefix:
- int64_t username_len = EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_segment_chars, (char)grapheme));
- if (username_len < 1 || !EAT1(state, index, grapheme == '@')) index = authority_start; // No user@ part
-
- // Host:
- int64_t host_len = match_host(state, index);
- if (host_len <= 0) return -1;
- index += host_len;
-
- // Port:
- if (EAT1(state, index, grapheme == ':')) {
- if (EAT_MANY(state, index, !(grapheme & ~0x7F) && isdigit(grapheme)) == 0) return -1;
- }
- return (index - authority_start);
-}
-
-static int64_t match_uri(TextIter_t *state, int64_t index) {
- // URI = scheme ":" ["//" authority] path ["?" query] ["#" fragment]
- // scheme = [a-zA-Z] [a-zA-Z0-9+.-]
- // authority = [userinfo "@"] host [":" port]
-
- if (index > 0) {
- // Don't match if we're not at a word edge:
- uint32_t prev_codepoint = Text$get_main_grapheme_fast(state, index - 1);
- if (uc_is_property_alphabetic(prev_codepoint)) return -1;
- }
-
- int64_t start_index = index;
-
- // Scheme:
- if (!EAT1(state, index, isalpha(grapheme))) return -1;
- EAT_MANY(state, index,
- !(grapheme & ~0x7F) && (isalnum(grapheme) || grapheme == '+' || grapheme == '.' || grapheme == '-'));
- if (!match_grapheme(state, &index, ':')) return -1;
-
- // Authority:
- int64_t authority_len;
- if (match_str(state, &index, "//")) {
- authority_len = match_authority(state, index);
- if (authority_len > 0) index += authority_len;
- } else {
- authority_len = 0;
- }
-
- // Path:
- int64_t path_start = index;
- if (EAT1(state, index, grapheme == '/') || authority_len <= 0) {
- static const char *non_path = " \"#?<>[]{}\\^`|";
- EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_path, (char)grapheme));
-
- if (EAT1(state, index, grapheme == '?')) { // Query
- static const char *non_query = " \"#<>[]{}\\^`|";
- EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_query, (char)grapheme));
- }
-
- if (EAT1(state, index, grapheme == '#')) { // Fragment
- static const char *non_fragment = " \"#<>[]{}\\^`|";
- EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_fragment, (char)grapheme));
- }
- }
-
- if (authority_len <= 0 && index == path_start) return -1;
-
- return index - start_index;
-}
-
-static int64_t match_url(TextIter_t *state, int64_t index) {
- int64_t lookahead = index;
- if (!(match_str(state, &lookahead, "https:") || match_str(state, &lookahead, "http:")
- || match_str(state, &lookahead, "ftp:") || match_str(state, &lookahead, "wss:")
- || match_str(state, &lookahead, "ws:")))
- return -1;
-
- return match_uri(state, index);
-}
-
-static int64_t match_id(TextIter_t *state, int64_t index) {
- if (!EAT1(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_XID_START))) return -1;
- return 1 + EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_XID_CONTINUE));
-}
-
-static int64_t match_int(TextIter_t *state, int64_t index) {
- int64_t negative = EAT1(state, index, grapheme == '-') ? 1 : 0;
- int64_t len = EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_DECIMAL_DIGIT));
- return len > 0 ? negative + len : -1;
-}
-
-static int64_t match_alphanumeric(TextIter_t *state, int64_t index) {
- return EAT1(state, index, uc_is_property_alphabetic((ucs4_t)grapheme) || uc_is_property_numeric((ucs4_t)grapheme))
- ? 1
- : -1;
-}
-
-static int64_t match_num(TextIter_t *state, int64_t index) {
- bool negative = EAT1(state, index, grapheme == '-') ? 1 : 0;
- int64_t pre_decimal = EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_DECIMAL_DIGIT));
- bool decimal = (EAT1(state, index, grapheme == '.') == 1);
- int64_t post_decimal =
- decimal ? EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_DECIMAL_DIGIT)) : 0;
- if (pre_decimal == 0 && post_decimal == 0) return -1;
- return negative + pre_decimal + decimal + post_decimal;
-}
-
-static int64_t match_newline(TextIter_t *state, int64_t index) {
- if (index >= state->stack[0].text.length) return -1;
-
- uint32_t grapheme = index >= state->stack[0].text.length ? 0 : Text$get_main_grapheme_fast(state, index);
- if (grapheme == '\n') return 1;
- if (grapheme == '\r' && Text$get_grapheme_fast(state, index + 1) == '\n') return 2;
- return -1;
-}
-
-static int64_t match_pat(TextIter_t *state, int64_t index, pat_t pat) {
- Text_t text = state->stack[0].text;
- int32_t grapheme = index >= text.length ? 0 : Text$get_grapheme_fast(state, index);
-
- switch (pat.tag) {
- case PAT_START: {
- if (index == 0) return pat.negated ? -1 : 0;
- return pat.negated ? 0 : -1;
- }
- case PAT_END: {
- if (index >= text.length) return pat.negated ? -1 : 0;
- return pat.negated ? 0 : -1;
- }
- case PAT_ANY: {
- assert(!pat.negated);
- return (index < text.length) ? 1 : -1;
- }
- case PAT_GRAPHEME: {
- if (index >= text.length) return -1;
- else if (grapheme == pat.grapheme) return pat.negated ? -1 : 1;
- return pat.negated ? 1 : -1;
- }
- case PAT_PROPERTY: {
- if (index >= text.length) return -1;
- else if (uc_is_property((ucs4_t)grapheme, pat.property)) return pat.negated ? -1 : 1;
- return pat.negated ? 1 : -1;
- }
- case PAT_PAIR: {
- // Nested punctuation: (?), [?], etc
- if (index >= text.length) return -1;
-
- int32_t open = pat.pair_graphemes[0];
- if (grapheme != open) return pat.negated ? 1 : -1;
-
- int32_t close = pat.pair_graphemes[1];
- int64_t depth = 1;
- int64_t match_len = 1;
- for (; depth > 0; match_len++) {
- if (index + match_len >= text.length) return pat.negated ? 1 : -1;
-
- int32_t c = Text$get_grapheme_fast(state, index + match_len);
- if (c == open) depth += 1;
- else if (c == close) depth -= 1;
- }
- return pat.negated ? -1 : match_len;
- }
- case PAT_QUOTE: {
- // Nested quotes: "?", '?', etc
- if (index >= text.length) return -1;
-
- int32_t open = pat.quote_graphemes[0];
- if (grapheme != open) return pat.negated ? 1 : -1;
-
- int32_t close = pat.quote_graphemes[1];
- for (int64_t i = index + 1; i < text.length; i++) {
- int32_t c = Text$get_grapheme_fast(state, i);
- if (c == close) {
- return pat.negated ? -1 : (i - index) + 1;
- } else if (c == '\\' && index + 1 < text.length) {
- i += 1; // Skip ahead an extra step
- }
- }
- return pat.negated ? 1 : -1;
- }
- case PAT_FUNCTION: {
- int64_t match_len = pat.fn(state, index);
- if (match_len >= 0) return pat.negated ? -1 : match_len;
- return pat.negated ? 1 : -1;
- }
- default: errx(1, "Invalid pattern");
- }
- errx(1, "Unreachable");
- return 0;
-}
-
-static pat_t parse_next_pat(TextIter_t *state, int64_t *index) {
- if (EAT2(state, *index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_QUOTATION_MARK), grapheme == '?')) {
- // Quotations: "?", '?', etc
- int32_t open = Text$get_grapheme_fast(state, *index - 2);
- int32_t close = open;
- uc_mirror_char((ucs4_t)open, (ucs4_t *)&close);
- if (!match_grapheme(state, index, close)) fail("Pattern's closing quote is missing: ", state->stack[0].text);
-
- return (pat_t){
- .tag = PAT_QUOTE,
- .min = 1,
- .max = 1,
- .quote_graphemes = {open, close},
- };
- } else if (EAT2(state, *index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_PAIRED_PUNCTUATION), grapheme == '?')) {
- // Nested punctuation: (?), [?], etc
- int32_t open = Text$get_grapheme_fast(state, *index - 2);
- int32_t close = open;
- uc_mirror_char((ucs4_t)open, (ucs4_t *)&close);
- if (!match_grapheme(state, index, close)) fail("Pattern's closing brace is missing: ", state->stack[0].text);
-
- return (pat_t){
- .tag = PAT_PAIR,
- .min = 1,
- .max = 1,
- .pair_graphemes = {open, close},
- };
- } else if (EAT1(state, *index, grapheme == '{')) { // named patterns {id}, {2-3 hex}, etc.
- skip_whitespace(state, index);
- int64_t min, max;
- if (uc_is_digit((ucs4_t)Text$get_grapheme_fast(state, *index))) {
- min = parse_int(state, index);
- skip_whitespace(state, index);
- if (match_grapheme(state, index, '+')) {
- max = INT64_MAX;
- } else if (match_grapheme(state, index, '-')) {
- max = parse_int(state, index);
- } else {
- max = min;
- }
- if (min > max) fail("Minimum repetitions (", min, ") is less than the maximum (", max, ")");
- } else {
- min = -1, max = -1;
- }
-
- skip_whitespace(state, index);
-
- bool negated = match_grapheme(state, index, '!');
-#define PAT(_tag, ...) ((pat_t){.min = min, .max = max, .negated = negated, .tag = _tag, __VA_ARGS__})
- const char *prop_name;
- if (match_str(state, index, "..")) prop_name = "..";
- else prop_name = get_property_name(state, index);
-
- if (!prop_name) {
- // Literal character, e.g. {1?}
- skip_whitespace(state, index);
- int32_t grapheme = Text$get_grapheme_fast(state, (*index)++);
- if (!match_grapheme(state, index, '}')) fail("Missing closing '}' in pattern: ", state->stack[0].text);
- return PAT(PAT_GRAPHEME, .grapheme = grapheme);
- } else if (strlen(prop_name) == 1) {
- // Single letter names: {1+ A}
- skip_whitespace(state, index);
- if (!match_grapheme(state, index, '}')) fail("Missing closing '}' in pattern: ", state->stack[0].text);
- return PAT(PAT_GRAPHEME, .grapheme = prop_name[0]);
- }
-
- skip_whitespace(state, index);
- if (!match_grapheme(state, index, '}')) fail("Missing closing '}' in pattern: ", state->stack[0].text);
-
- switch (tolower(prop_name[0])) {
- case '.':
- if (prop_name[1] == '.') {
- if (negated) return ((pat_t){.tag = PAT_END, .min = min, .max = max, .non_capturing = true});
- else return PAT(PAT_ANY);
- }
- break;
- case 'a':
- if (strcasecmp(prop_name, "authority") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_authority);
- } else if (strcasecmp(prop_name, "alphanum") == 0 || strcasecmp(prop_name, "anum") == 0
- || strcasecmp(prop_name, "alphanumeric") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_alphanumeric);
- }
- break;
- case 'c':
- if (strcasecmp(prop_name, "crlf") == 0) return PAT(PAT_FUNCTION, .fn = match_newline);
- break;
- case 'd':
- if (strcasecmp(prop_name, "digit") == 0) {
- return PAT(PAT_PROPERTY, .property = UC_PROPERTY_DECIMAL_DIGIT);
- }
- break;
- case 'e':
- if (strcasecmp(prop_name, "end") == 0) {
- return PAT(PAT_END, .non_capturing = !negated);
- } else if (strcasecmp(prop_name, "email") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_email);
- }
-#if _LIBUNISTRING_VERSION >= 0x0100000
- else if (strcasecmp(prop_name, "emoji") == 0) {
- return PAT(PAT_PROPERTY, .property = UC_PROPERTY_EMOJI);
- }
-#endif
- break;
- case 'h':
- if (strcasecmp(prop_name, "host") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_host);
- }
- break;
- case 'i':
- if (strcasecmp(prop_name, "id") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_id);
- } else if (strcasecmp(prop_name, "int") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_int);
- } else if (strcasecmp(prop_name, "ipv4") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_ipv4);
- } else if (strcasecmp(prop_name, "ipv6") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_ipv6);
- } else if (strcasecmp(prop_name, "ip") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_ip);
- }
- break;
- case 'n':
- if (strcasecmp(prop_name, "nl") == 0 || strcasecmp(prop_name, "newline") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_newline);
- } else if (strcasecmp(prop_name, "num") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_num);
- }
- break;
- case 's':
- if (strcasecmp(prop_name, "start") == 0) {
- return PAT(PAT_START, .non_capturing = !negated);
- }
- break;
- case 'u':
- if (strcasecmp(prop_name, "uri") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_uri);
- } else if (strcasecmp(prop_name, "url") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_url);
- }
- break;
- case 'w':
- if (strcasecmp(prop_name, "word") == 0) {
- return PAT(PAT_FUNCTION, .fn = match_id);
- } else if (strcasecmp(prop_name, "ws") == 0 || strcasecmp(prop_name, "whitespace") == 0) {
- return PAT(PAT_PROPERTY, .property = UC_PROPERTY_WHITE_SPACE);
- }
- break;
- default: break;
- }
-
- uc_property_t prop = uc_property_byname(prop_name);
- if (uc_property_is_valid(prop)) return PAT(PAT_PROPERTY, .property = prop);
-
- ucs4_t grapheme = unicode_name_character(prop_name);
- if (grapheme == UNINAME_INVALID) fail("Not a valid property or character name: ", prop_name);
- return PAT(PAT_GRAPHEME, .grapheme = (int32_t)grapheme);
-#undef PAT
- } else {
- return (pat_t){.tag = PAT_GRAPHEME,
- .non_capturing = true,
- .min = 1,
- .max = 1,
- .grapheme = Text$get_grapheme_fast(state, (*index)++)};
- }
-}
-
-static int64_t match(Text_t text, int64_t text_index, Text_t pattern, int64_t pattern_index, capture_t *captures,
- int64_t capture_index) {
- if (pattern_index >= pattern.length) // End of the pattern
- return 0;
-
- int64_t start_index = text_index;
- TextIter_t pattern_state = NEW_TEXT_ITER_STATE(pattern), text_state = NEW_TEXT_ITER_STATE(text);
- pat_t pat = parse_next_pat(&pattern_state, &pattern_index);
-
- if (pat.min == -1 && pat.max == -1) {
- if (pat.tag == PAT_ANY && pattern_index >= pattern.length) {
- pat.min = pat.max = MAX(1, text.length - text_index);
- } else {
- pat.min = 1;
- pat.max = INT64_MAX;
- }
- }
-
- int64_t capture_start = text_index;
- int64_t count = 0, capture_len = 0, next_match_len = 0;
-
- if (pat.tag == PAT_ANY && pattern_index >= pattern.length) {
- int64_t remaining = text.length - text_index;
- capture_len = remaining >= pat.min ? MIN(remaining, pat.max) : -1;
- text_index += capture_len;
- goto success;
- }
-
- if (pat.min == 0 && pattern_index < pattern.length) {
- next_match_len =
- match(text, text_index, pattern, pattern_index, captures, capture_index + (pat.non_capturing ? 0 : 1));
- if (next_match_len >= 0) {
- capture_len = 0;
- goto success;
- }
- }
-
- while (count < pat.max) {
- int64_t match_len = match_pat(&text_state, text_index, pat);
- if (match_len < 0) break;
- capture_len += match_len;
- text_index += match_len;
- count += 1;
-
- if (pattern_index < pattern.length) { // More stuff after this
- if (count < pat.min) next_match_len = -1;
- else
- next_match_len = match(text, text_index, pattern, pattern_index, captures,
- capture_index + (pat.non_capturing ? 0 : 1));
- } else {
- next_match_len = 0;
- }
-
- if (match_len == 0) {
- if (next_match_len >= 0) {
- // If we're good to go, no need to keep re-matching zero-length
- // matches till we hit max:
- count = pat.max;
- break;
- } else {
- return -1;
- }
- }
-
- if (pattern_index < pattern.length && next_match_len >= 0) break; // Next guy exists and wants to stop here
-
- if (text_index >= text.length) break;
- }
-
- if (count < pat.min || next_match_len < 0) return -1;
-
-success:
- if (captures && capture_index < MAX_BACKREFS && !pat.non_capturing) {
- if (pat.tag == PAT_PAIR || pat.tag == PAT_QUOTE) {
- assert(capture_len > 0);
- captures[capture_index] = (capture_t){
- .index = capture_start + 1, // Skip leading quote/paren
- .length = capture_len - 2, // Skip open/close
- .occupied = true,
- .recursive = (pat.tag == PAT_PAIR),
- };
- } else {
- captures[capture_index] = (capture_t){
- .index = capture_start,
- .length = capture_len,
- .occupied = true,
- .recursive = false,
- };
- }
- }
- return (text_index - start_index) + next_match_len;
-}
-
-#undef EAT1
-#undef EAT2
-#undef EAT_MANY
-
-static int64_t _find(Text_t text, Text_t pattern, int64_t first, int64_t last, int64_t *match_length,
- capture_t *captures) {
- int32_t first_grapheme = Text$get_grapheme(pattern, 0);
- bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK)
- && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION));
-
- TextIter_t text_state = NEW_TEXT_ITER_STATE(text);
- for (int64_t i = first; i <= last; i++) {
- // Optimization: quickly skip ahead to first char in pattern:
- if (find_first) {
- while (i < text.length && Text$get_grapheme_fast(&text_state, i) != first_grapheme)
- ++i;
- }
-
- int64_t m = match(text, i, pattern, 0, captures, 0);
- if (m >= 0) {
- if (match_length) *match_length = m;
- return i;
- }
- }
- if (match_length) *match_length = -1;
- return -1;
-}
-
-static OptionalPatternMatch find(Text_t text, Text_t pattern, Int_t from_index) {
- int64_t first = Int64$from_int(from_index, false);
- if (first == 0) fail("Invalid index: 0");
- if (first < 0) first = text.length + first + 1;
- if (first > text.length || first < 1) return NONE_MATCH;
-
- capture_t captures[MAX_BACKREFS] = {};
- int64_t len = 0;
- int64_t found = _find(text, pattern, first - 1, text.length - 1, &len, captures);
- if (found == -1) return NONE_MATCH;
-
- List_t capture_list = {};
- for (int i = 0; captures[i].occupied; i++) {
- Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length));
- List$insert(&capture_list, &capture, I(0), sizeof(Text_t));
- }
- return (OptionalPatternMatch){
- .text = Text$slice(text, I(found + 1), I(found + len)),
- .index = I(found + 1),
- .captures = capture_list,
- };
-}
-
-PUREFUNC static bool Pattern$has(Text_t text, Text_t pattern) {
- if (Text$starts_with(pattern, Text("{start}"), &pattern)) {
- int64_t m = match(text, 0, pattern, 0, NULL, 0);
- return m >= 0;
- } else if (Text$ends_with(text, Text("{end}"), NULL)) {
- for (int64_t i = text.length - 1; i >= 0; i--) {
- int64_t match_len = match(text, i, pattern, 0, NULL, 0);
- if (match_len >= 0 && i + match_len == text.length) return true;
- }
- return false;
- } else {
- int64_t found = _find(text, pattern, 0, text.length - 1, NULL, NULL);
- return (found >= 0);
- }
-}
-
-static bool Pattern$matches(Text_t text, Text_t pattern) {
- capture_t captures[MAX_BACKREFS] = {};
- int64_t match_len = match(text, 0, pattern, 0, NULL, 0);
- return (match_len == text.length);
-}
-
-static bool Pattern$match_at(Text_t text, Text_t pattern, Int_t pos, PatternMatch *dest) {
- int64_t start = Int64$from_int(pos, false) - 1;
- capture_t captures[MAX_BACKREFS] = {};
- int64_t match_len = match(text, start, pattern, 0, captures, 0);
- if (match_len < 0) return false;
-
- List_t capture_list = {};
- for (int i = 0; captures[i].occupied; i++) {
- Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length));
- List$insert(&capture_list, &capture, I(0), sizeof(Text_t));
- }
- dest->text = Text$slice(text, I(start + 1), I(start + match_len));
- dest->index = I(start + 1);
- dest->captures = capture_list;
- return true;
-}
-
-static OptionalList_t Pattern$captures(Text_t text, Text_t pattern) {
- capture_t captures[MAX_BACKREFS] = {};
- int64_t match_len = match(text, 0, pattern, 0, captures, 0);
- if (match_len != text.length) return NONE_LIST;
-
- List_t capture_list = {};
- for (int i = 0; captures[i].occupied; i++) {
- Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length));
- List$insert(&capture_list, &capture, I(0), sizeof(Text_t));
- }
- return capture_list;
-}
-
-static List_t Pattern$find_all(Text_t text, Text_t pattern) {
- if (pattern.length == 0) // special case
- return (List_t){.length = 0};
-
- List_t matches = {};
- for (int64_t i = 1;;) {
- OptionalPatternMatch m = find(text, pattern, I(i));
- if (m.is_none) break;
- i = Int64$from_int(m.index, false) + m.text.length;
- List$insert(&matches, &m, I_small(0), sizeof(PatternMatch));
- }
- return matches;
-}
-
-typedef struct {
- TextIter_t state;
- Int_t i;
- Text_t pattern;
-} match_iter_state_t;
-
-static OptionalPatternMatch next_match(match_iter_state_t *state) {
- if (Int64$from_int(state->i, false) > state->state.stack[0].text.length) return NONE_MATCH;
-
- OptionalPatternMatch m = find(state->state.stack[0].text, state->pattern, state->i);
- if (m.is_none) // No match
- state->i = I(state->state.stack[0].text.length + 1);
- else state->i = Int$plus(m.index, I(MAX(1, m.text.length)));
- return m;
-}
-
-static Closure_t Pattern$by_match(Text_t text, Text_t pattern) {
- return (Closure_t){
- .fn = (void *)next_match,
- .userdata = new (match_iter_state_t, .state = NEW_TEXT_ITER_STATE(text), .i = I_small(1), .pattern = pattern),
- };
-}
-
-static Text_t apply_backrefs(Text_t text, List_t recursive_replacements, Text_t replacement, Text_t backref_pat,
- capture_t *captures) {
- if (backref_pat.length == 0) return replacement;
-
- int32_t first_grapheme = Text$get_grapheme(backref_pat, 0);
- bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK)
- && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION));
-
- Text_t ret = Text("");
- TextIter_t replacement_state = NEW_TEXT_ITER_STATE(replacement);
- int64_t nonmatching_pos = 0;
- for (int64_t pos = 0; pos < replacement.length;) {
- // Optimization: quickly skip ahead to first char in the backref pattern:
- if (find_first) {
- while (pos < replacement.length && Text$get_grapheme_fast(&replacement_state, pos) != first_grapheme)
- ++pos;
- }
-
- int64_t backref_len = match(replacement, pos, backref_pat, 0, NULL, 0);
- if (backref_len < 0) {
- pos += 1;
- continue;
- }
-
- int64_t after_backref = pos + backref_len;
- int64_t backref = parse_int(&replacement_state, &after_backref);
- if (after_backref == pos + backref_len) { // Not actually a backref if there's no number
- pos += 1;
- continue;
- }
- if (backref < 0 || backref > 9)
- fail("Invalid backref index: ", backref, " (only 0-", MAX_BACKREFS - 1, " are allowed)");
- backref_len = (after_backref - pos);
-
- if (Text$get_grapheme_fast(&replacement_state, pos + backref_len) == ';')
- backref_len += 1; // skip optional semicolon
-
- if (!captures[backref].occupied) fail("There is no capture number ", backref, "!");
-
- Text_t backref_text =
- Text$slice(text, I(captures[backref].index + 1), I(captures[backref].index + captures[backref].length));
-
- if (captures[backref].recursive && recursive_replacements.length > 0)
- backref_text = replace_list(backref_text, recursive_replacements, backref_pat, true);
-
- if (pos > nonmatching_pos) {
- Text_t before_slice = Text$slice(replacement, I(nonmatching_pos + 1), I(pos));
- ret = Text$concat(ret, before_slice, backref_text);
- } else {
- ret = Text$concat(ret, backref_text);
- }
-
- pos += backref_len;
- nonmatching_pos = pos;
- }
- if (nonmatching_pos < replacement.length) {
- Text_t last_slice = Text$slice(replacement, I(nonmatching_pos + 1), I(replacement.length));
- ret = Text$concat(ret, last_slice);
- }
- return ret;
-}
-
-static Text_t Pattern$replace(Text_t text, Text_t pattern, Text_t replacement, Text_t backref_pat, bool recursive) {
- Text_t ret = EMPTY_TEXT;
-
- int32_t first_grapheme = Text$get_grapheme(pattern, 0);
- bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK)
- && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION));
-
- Text_t entries[2] = {pattern, replacement};
- List_t replacements = {
- .data = entries,
- .length = 1,
- .stride = sizeof(entries),
- };
-
- TextIter_t text_state = NEW_TEXT_ITER_STATE(text);
- int64_t nonmatching_pos = 0;
- for (int64_t pos = 0; pos < text.length;) {
- // Optimization: quickly skip ahead to first char in pattern:
- if (find_first) {
- while (pos < text.length && Text$get_grapheme_fast(&text_state, pos) != first_grapheme)
- ++pos;
- }
-
- capture_t captures[MAX_BACKREFS] = {};
- int64_t match_len = match(text, pos, pattern, 0, captures, 1);
- if (match_len < 0) {
- pos += 1;
- continue;
- }
- captures[0] = (capture_t){
- .index = pos,
- .length = match_len,
- .occupied = true,
- .recursive = false,
- };
-
- Text_t replacement_text =
- apply_backrefs(text, recursive ? replacements : (List_t){}, replacement, backref_pat, captures);
- if (pos > nonmatching_pos) {
- Text_t before_slice = Text$slice(text, I(nonmatching_pos + 1), I(pos));
- ret = Text$concat(ret, before_slice, replacement_text);
- } else {
- ret = Text$concat(ret, replacement_text);
- }
- nonmatching_pos = pos + match_len;
- pos += MAX(match_len, 1);
- }
- if (nonmatching_pos < text.length) {
- Text_t last_slice = Text$slice(text, I(nonmatching_pos + 1), I(text.length));
- ret = Text$concat(ret, last_slice);
- }
- return ret;
-}
-
-static Text_t Pattern$trim(Text_t text, Text_t pattern, bool trim_left, bool trim_right) {
- int64_t first = 0, last = text.length - 1;
- if (trim_left) {
- int64_t match_len = match(text, 0, pattern, 0, NULL, 0);
- if (match_len > 0) first = match_len;
- }
-
- if (trim_right) {
- for (int64_t i = text.length - 1; i >= first; i--) {
- int64_t match_len = match(text, i, pattern, 0, NULL, 0);
- if (match_len > 0 && i + match_len == text.length) last = i - 1;
- }
- }
- return Text$slice(text, I(first + 1), I(last + 1));
-}
-
-static Text_t Pattern$map(Text_t text, Text_t pattern, Closure_t fn, bool recursive) {
- Text_t ret = EMPTY_TEXT;
-
- int32_t first_grapheme = Text$get_grapheme(pattern, 0);
- bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK)
- && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION));
-
- TextIter_t text_state = NEW_TEXT_ITER_STATE(text);
- int64_t nonmatching_pos = 0;
-
- Text_t (*text_mapper)(PatternMatch, void *) = fn.fn;
- for (int64_t pos = 0; pos < text.length; pos++) {
- // Optimization: quickly skip ahead to first char in pattern:
- if (find_first) {
- while (pos < text.length && Text$get_grapheme_fast(&text_state, pos) != first_grapheme)
- ++pos;
- }
-
- capture_t captures[MAX_BACKREFS] = {};
- int64_t match_len = match(text, pos, pattern, 0, captures, 0);
- if (match_len < 0) continue;
-
- PatternMatch m = {
- .text = Text$slice(text, I(pos + 1), I(pos + match_len)),
- .index = I(pos + 1),
- .captures = {},
- };
- for (int i = 0; captures[i].occupied; i++) {
- Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length));
- if (recursive) capture = Pattern$map(capture, pattern, fn, recursive);
- List$insert(&m.captures, &capture, I(0), sizeof(Text_t));
- }
-
- Text_t replacement = text_mapper(m, fn.userdata);
- if (pos > nonmatching_pos) {
- Text_t before_slice = Text$slice(text, I(nonmatching_pos + 1), I(pos));
- ret = Text$concat(ret, before_slice, replacement);
- } else {
- ret = Text$concat(ret, replacement);
- }
- nonmatching_pos = pos + match_len;
- pos += (match_len - 1);
- }
- if (nonmatching_pos < text.length) {
- Text_t last_slice = Text$slice(text, I(nonmatching_pos + 1), I(text.length));
- ret = Text$concat(ret, last_slice);
- }
- return ret;
-}
-
-static void Pattern$each(Text_t text, Text_t pattern, Closure_t fn, bool recursive) {
- int32_t first_grapheme = Text$get_grapheme(pattern, 0);
- bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK)
- && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION));
-
- TextIter_t text_state = NEW_TEXT_ITER_STATE(text);
- void (*action)(PatternMatch, void *) = fn.fn;
- for (int64_t pos = 0; pos < text.length; pos++) {
- // Optimization: quickly skip ahead to first char in pattern:
- if (find_first) {
- while (pos < text.length && Text$get_grapheme_fast(&text_state, pos) != first_grapheme)
- ++pos;
- }
-
- capture_t captures[MAX_BACKREFS] = {};
- int64_t match_len = match(text, pos, pattern, 0, captures, 0);
- if (match_len < 0) continue;
-
- PatternMatch m = {
- .text = Text$slice(text, I(pos + 1), I(pos + match_len)),
- .index = I(pos + 1),
- .captures = {},
- };
- for (int i = 0; captures[i].occupied; i++) {
- Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length));
- if (recursive) Pattern$each(capture, pattern, fn, recursive);
- List$insert(&m.captures, &capture, I(0), sizeof(Text_t));
- }
-
- action(m, fn.userdata);
- pos += (match_len - 1);
- }
-}
-
-Text_t replace_list(Text_t text, List_t replacements, Text_t backref_pat, bool recursive) {
- if (replacements.length == 0) return text;
-
- Text_t ret = EMPTY_TEXT;
-
- int64_t nonmatch_pos = 0;
- for (int64_t pos = 0; pos < text.length;) {
- // Find the first matching pattern at this position:
- for (int64_t i = 0; i < replacements.length; i++) {
- Text_t pattern = *(Text_t *)(replacements.data + i * replacements.stride);
- capture_t captures[MAX_BACKREFS] = {};
- int64_t len = match(text, pos, pattern, 0, captures, 1);
- if (len < 0) continue;
- captures[0].index = pos;
- captures[0].length = len;
-
- // If we skipped over some non-matching text before finding a match, insert it here:
- if (pos > nonmatch_pos) {
- Text_t before_slice = Text$slice(text, I(nonmatch_pos + 1), I(pos));
- ret = Text$concat(ret, before_slice);
- }
-
- // Concatenate the replacement:
- Text_t replacement = *(Text_t *)(replacements.data + i * replacements.stride + sizeof(Text_t));
- Text_t replacement_text =
- apply_backrefs(text, recursive ? replacements : (List_t){}, replacement, backref_pat, captures);
- ret = Text$concat(ret, replacement_text);
- pos += MAX(len, 1);
- nonmatch_pos = pos;
- goto next_pos;
- }
-
- pos += 1;
- next_pos:
- continue;
- }
-
- if (nonmatch_pos <= text.length) {
- Text_t last_slice = Text$slice(text, I(nonmatch_pos + 1), I(text.length));
- ret = Text$concat(ret, last_slice);
- }
- return ret;
-}
-
-static Text_t Pattern$replace_all(Text_t text, Table_t replacements, Text_t backref_pat, bool recursive) {
- return replace_list(text, replacements.entries, backref_pat, recursive);
-}
-
-static List_t Pattern$split(Text_t text, Text_t pattern) {
- if (text.length == 0) // special case
- return (List_t){.length = 0};
-
- if (pattern.length == 0) // special case
- return Text$clusters(text);
-
- List_t chunks = {};
-
- int64_t i = 0;
- for (;;) {
- int64_t len = 0;
- int64_t found = _find(text, pattern, i, text.length - 1, &len, NULL);
- if (found == i && len == 0) found = _find(text, pattern, i + 1, text.length - 1, &len, NULL);
- if (found < 0) break;
- Text_t chunk = Text$slice(text, I(i + 1), I(found));
- List$insert(&chunks, &chunk, I_small(0), sizeof(Text_t));
- i = MAX(found + len, i + 1);
- }
-
- Text_t last_chunk = Text$slice(text, I(i + 1), I(text.length));
- List$insert(&chunks, &last_chunk, I_small(0), sizeof(Text_t));
-
- return chunks;
-}
-
-typedef struct {
- TextIter_t state;
- int64_t i;
- Text_t pattern;
-} split_iter_state_t;
-
-static OptionalText_t next_split(split_iter_state_t *state) {
- Text_t text = state->state.stack[0].text;
- if (state->i >= text.length) {
- if (state->pattern.length > 0 && state->i == text.length) { // special case
- state->i = text.length + 1;
- return EMPTY_TEXT;
- }
- return NONE_TEXT;
- }
-
- if (state->pattern.length == 0) { // special case
- Text_t ret = Text$cluster(text, I(state->i + 1));
- state->i += 1;
- return ret;
- }
-
- int64_t start = state->i;
- int64_t len = 0;
- int64_t found = _find(text, state->pattern, start, text.length - 1, &len, NULL);
-
- if (found == start && len == 0) found = _find(text, state->pattern, start + 1, text.length - 1, &len, NULL);
-
- if (found >= 0) {
- state->i = MAX(found + len, state->i + 1);
- return Text$slice(text, I(start + 1), I(found));
- } else {
- state->i = state->state.stack[0].text.length + 1;
- return Text$slice(text, I(start + 1), I(text.length));
- }
-}
-
-static Closure_t Pattern$by_split(Text_t text, Text_t pattern) {
- return (Closure_t){
- .fn = (void *)next_split,
- .userdata = new (split_iter_state_t, .state = NEW_TEXT_ITER_STATE(text), .i = 0, .pattern = pattern),
- };
-}
-
-static Text_t Pattern$escape_text(Text_t text) {
- // TODO: optimize for spans of non-escaped text
- Text_t ret = EMPTY_TEXT;
- TextIter_t state = NEW_TEXT_ITER_STATE(text);
- for (int64_t i = 0; i < text.length; i++) {
- uint32_t g = Text$get_main_grapheme_fast(&state, i);
- if (g == '{') {
- ret = Text$concat(ret, Text("{1{}"));
- } else if (g == '?' || uc_is_property_quotation_mark(g)
- || (uc_is_property_paired_punctuation(g) && uc_is_property_left_of_pair(g))) {
- ret = Text$concat(ret, Text("{1"), Text$slice(text, I(i + 1), I(i + 1)), Text("}"));
- } else {
- ret = Text$concat(ret, Text$slice(text, I(i + 1), I(i + 1)));
- }
- }
- return ret;
-}
-
-static Text_t Pattern$as_text(const void *obj, bool colorize, const TypeInfo_t *info) {
- (void)info;
- if (!obj) return Text("Pattern");
-
- Text_t pat = *(Text_t *)obj;
- Text_t quote = Pattern$has(pat, Text("/")) && !Pattern$has(pat, Text("|")) ? Text("|") : Text("/");
- return Text$concat(colorize ? Text("\x1b[1m$\033[m") : Text("$"), Text$quoted(pat, colorize, quote));
-}
diff --git a/lib/patterns/patterns.tm b/lib/patterns/patterns.tm
deleted file mode 100644
index f62c6be0..00000000
--- a/lib/patterns/patterns.tm
+++ /dev/null
@@ -1,66 +0,0 @@
-use ./patterns.c
-
-struct PatternMatch(text:Text, index:Int, captures:[Text])
-
-lang Pat
- convert(text:Text -> Pat)
- return C_code:Pat`Pattern$escape_text(@text)`
-
- convert(n:Int -> Pat)
- return Pat.from_text("$n")
-
-extend Text
- func matching_pattern(text:Text, pattern:Pat, pos:Int = 1 -> PatternMatch?)
- result : PatternMatch
- if C_code:Bool`Pattern$match_at(@text, @pattern, @pos, (void*)&@result)`
- return result
- return none
-
- func matches_pattern(text:Text, pattern:Pat -> Bool)
- return C_code:Bool`Pattern$matches(@text, @pattern)`
-
- func pattern_captures(text:Text, pattern:Pat -> [Text]?)
- return C_code:[Text]?`Pattern$captures(@text, @pattern)`
-
- func replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text)
- return C_code:Text`Pattern$replace(@text, @pattern, @replacement, @backref, @recursive)`
-
- func translate_patterns(text:Text, replacements:{Pat=Text}, backref="@", recursive=yes -> Text)
- return C_code:Text`Pattern$replace_all(@text, @replacements, @backref, @recursive)`
-
- func has_pattern(text:Text, pattern:Pat -> Bool)
- return C_code:Bool`Pattern$has(@text, @pattern)`
-
- func find_patterns(text:Text, pattern:Pat -> [PatternMatch])
- return C_code:[PatternMatch]`Pattern$find_all(@text, @pattern)`
-
- func by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?))
- return C_code:func(->PatternMatch?)`Pattern$by_match(@text, @pattern)`
-
- func each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes)
- C_code `Pattern$each(@text, @pattern, @fn, @recursive);`
-
- func map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text)
- return C_code:Text`Pattern$map(@text, @pattern, @fn, @recursive)`
-
- func split_pattern(text:Text, pattern:Pat -> [Text])
- return C_code:[Text]`Pattern$split(@text, @pattern)`
-
- func by_pattern_split(text:Text, pattern:Pat -> func(->Text?))
- return C_code:func(->Text?)`Pattern$by_split(@text, @pattern)`
-
- func trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text)
- return C_code:Text`Pattern$trim(@text, @pattern, @left, @right)`
-
-func main()
- >> "Hello world".matching_pattern($Pat'{id}')
- >> "...Hello world".matching_pattern($Pat'{id}')
-# func main(pattern:Pat, input=(/dev/stdin))
-# for line in input.by_line()!
-# skip if not line.has_pattern(pattern)
-# pos := 1
-# for match in line.by_pattern(pattern)
-# say(line.slice(pos, match.index-1), newline=no)
-# say("\033[34;1m$(match.text)\033[m", newline=no)
-# pos = match.index + match.text.length
-# say(line.from(pos), newline=yes)
diff --git a/lib/pthreads/CHANGES.md b/lib/pthreads/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/pthreads/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/pthreads/pthreads.tm b/lib/pthreads/pthreads.tm
deleted file mode 100644
index c93df20a..00000000
--- a/lib/pthreads/pthreads.tm
+++ /dev/null
@@ -1,134 +0,0 @@
-# A Posix Threads (pthreads) wrapper
-use <pthread.h>
-
-struct pthread_mutex_t(; extern, opaque)
- func new(->@pthread_mutex_t)
- return C_code : @pthread_mutex_t `
- pthread_mutex_t *mutex = GC_MALLOC(sizeof(pthread_mutex_t));
- pthread_mutex_init(mutex, NULL);
- GC_register_finalizer(mutex, (void*)pthread_mutex_destroy, NULL, NULL, NULL);
- mutex
- `
-
- func lock(m:&pthread_mutex_t)
- fail("Failed to lock mutex") unless C_code:Int32 `pthread_mutex_lock(@m)` == 0
-
- func unlock(m:&pthread_mutex_t)
- fail("Failed to unlock mutex") unless C_code:Int32 `pthread_mutex_unlock(@m)` == 0
-
-struct pthread_cond_t(; extern, opaque)
- func new(->@pthread_cond_t)
- return C_code : @pthread_cond_t `
- pthread_cond_t *cond = GC_MALLOC(sizeof(pthread_cond_t));
- pthread_cond_init(cond, NULL);
- GC_register_finalizer(cond, (void*)pthread_cond_destroy, NULL, NULL, NULL);
- cond
- `
-
- func wait(cond:&pthread_cond_t, mutex:&pthread_mutex_t)
- fail("Failed to wait on condition") unless C_code:Int32 `pthread_cond_wait(@cond, @mutex)` == 0
-
- func signal(cond:&pthread_cond_t)
- fail("Failed to signal pthread_cond_t") unless C_code:Int32 `pthread_cond_signal(@cond)` == 0
-
- func broadcast(cond:&pthread_cond_t)
- fail("Failed to broadcast pthread_cond_t") unless C_code:Int32 `pthread_cond_broadcast(@cond)` == 0
-
-struct pthread_rwlock_t(; extern, opaque)
- func new(->@pthread_rwlock_t)
- return C_code : @pthread_rwlock_t `
- pthread_rwlock_t *lock = GC_MALLOC(sizeof(pthread_rwlock_t));
- pthread_rwlock_init(lock, NULL);
- GC_register_finalizer(lock, (void*)pthread_rwlock_destroy, NULL, NULL, NULL);
- lock
- `
-
- func read_lock(lock:&pthread_rwlock_t)
- C_code `
- pthread_rwlock_rdlock(@lock);
- `
-
- func write_lock(lock:&pthread_rwlock_t)
- C_code `
- pthread_rwlock_wrlock(@lock);
- `
-
- func unlock(lock:&pthread_rwlock_t)
- C_code `
- pthread_rwlock_unlock(@lock);
- `
-
-struct pthread_t(; extern, opaque)
- func new(fn:func() -> @pthread_t)
- return C_code:@pthread_t `
- pthread_t *thread = GC_MALLOC(sizeof(pthread_t));
- pthread_create(thread, NULL, @fn.fn, @fn.userdata);
- thread
- `
-
- func join(p:pthread_t)
- C_code `
- pthread_join(@p, NULL);
- `
-
- func cancel(p:pthread_t)
- C_code `
- pthread_cancel(@p);
- `
-
- func detatch(p:pthread_t)
- C_code `
- pthread_detach(@p);
- `
-
-struct IntQueue(_queue:@[Int], _mutex:@pthread_mutex_t, _cond:@pthread_cond_t)
- func new(initial:[Int]=[] -> IntQueue)
- return IntQueue(@initial, pthread_mutex_t.new(), pthread_cond_t.new())
-
- func give(q:IntQueue, n:Int)
- do q._mutex.lock()
- q._queue.insert(n)
- q._mutex.unlock()
- q._cond.signal()
-
- func take(q:IntQueue -> Int)
- do q._mutex.lock()
- n := q._queue.pop(1)
- while not n
- q._cond.wait(q._mutex)
- n = q._queue.pop(1)
- q._mutex.unlock()
- return n!
-
-func main()
- jobs := IntQueue.new()
- results := IntQueue.new()
-
- say_mutex := pthread_mutex_t.new()
- announce := func(speaker:Text, text:Text)
- do say_mutex.lock()
- say("\[2][$speaker]\[] $text")
- say_mutex.unlock()
-
- worker := pthread_t.new(func()
- say("I'm in the thread!")
- repeat
- announce("worker", "waiting for job")
- job := jobs.take()
- result := job * 10
- announce("worker", "Jobbing $job into $result")
- results.give(result)
- announce("worker", "Signaled $result")
- )
-
- for i in 10
- announce("boss", "Pushing job $i")
- jobs.give(i)
- announce("boss", "Gave job $i")
-
- for i in 10
- announce("boss", "Getting result...")
- result := results.take()
- announce("boss", "Got result $result")
-
- >> worker.cancel()
diff --git a/lib/random/CHANGES.md b/lib/random/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/random/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/random/README.md b/lib/random/README.md
deleted file mode 100644
index 183b9d0b..00000000
--- a/lib/random/README.md
+++ /dev/null
@@ -1,196 +0,0 @@
-# Random Number Generators (RNG)
-
-This library provides an `RNG` type (Random Number Generator). This type
-represents a self-contained piece of data that encapsulates the state of a
-relatively fast and relatively secure pseudo-random number generator. The
-current implementation is based on the [ChaCha20 stream
-cipher,](https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant) inspired by
-[`arc4random` in OpenBSD.](https://man.openbsd.org/arc4random.3)
-
-An `RNG` object can be used for deterministic, repeatable generation of
-pseudorandom numbers (for example, to be used in a video game for creating
-seeded levels). The default random number generator for Tomo is called `random`
-and is, by default, initialized with random data from the operating system when
-a Tomo program launches.
-
-## RNG Functions
-
-This documentation provides details on RNG functions available in the API.
-Lists also have some methods which use RNG values:
-`list.shuffle()`, `list.shuffled()`, `list.random()`, and `list.sample()`.
-
-- [`func bool(rng: RNG, p: Num = 0.5 -> Bool)`](#bool)
-- [`func byte(rng: RNG -> Byte)`](#byte)
-- [`func bytes(rng: RNG, count: Int -> [Byte])`](#bytes)
-- [`func copy(rng: RNG -> RNG)`](#copy)
-- [`func int(rng: RNG, min: Int, max: Int -> Int)`](#int`, `int64`, `int32`, `int16`, `int8)
-- [`func new(seed: [Byte] = (/dev/urandom).read_bytes(40)! -> RNG)`](#new)
-- [`func num(rng: RNG, min: Num = 0.0, max: Num = 1.0 -> Num)`](#num`, `num32)
-
--------------
-
-### `bool`
-Generate a random boolean value with a given probability.
-
-```tomo
-func bool(rng: RNG, p: Num = 0.5 -> Bool)
-```
-
-- `rng`: The random number generator to use.
-- `p`: The probability of returning a `yes` value. Values less than zero and
- `NaN` values are treated as equal to zero and values larger than zero are
- treated as equal to one.
-
-**Returns:**
-`yes` with probability `p` and `no` with probability `1-p`.
-
-**Example:**
-```tomo
->> random.bool()
-= no
->> random.bool(1.0)
-= yes
-```
-
----
-
-### `byte`
-Generate a random byte with uniform probability.
-
-```tomo
-func byte(rng: RNG -> Byte)
-```
-
-- `rng`: The random number generator to use.
-
-**Returns:**
-A random byte (0-255).
-
-**Example:**
-```tomo
->> random.byte()
-= 103[B]
-```
-
----
-
-### `bytes`
-Generate a list of uniformly random bytes with the given length.
-
-```tomo
-func bytes(rng: RNG, count: Int -> [Byte])
-```
-
-- `rng`: The random number generator to use.
-- `count`: The number of random bytes to return.
-
-**Returns:**
-A list of length `count` random bytes with uniform random distribution (0-255).
-
-**Example:**
-```tomo
->> random.bytes(4)
-= [135[B], 169[B], 103[B], 212[B]]
-```
-
----
-
-### `copy`
-Return a copy of a random number generator. This copy will be a parallel version of
-the given RNG with its own internal state.
-
-```tomo
-func copy(rng: RNG -> RNG)
-```
-
-- `rng`: The random number generator to copy.
-
-**Returns:**
-A copy of the given RNG.
-
-**Example:**
-```tomo
->> rng := RNG.new([])
->> copy := rng.copy()
-
->> rng.bytes(10)
-= [224[B], 102[B], 190[B], 59[B], 251[B], 50[B], 217[B], 170[B], 15[B], 221[B]]
-
-# The copy runs in parallel to the original RNG:
->> copy.bytes(10)
-= [224[B], 102[B], 190[B], 59[B], 251[B], 50[B], 217[B], 170[B], 15[B], 221[B]]
-```
-
----
-
-### `int`, `int64`, `int32`, `int16`, `int8`
-Generate a random integer value with the given range.
-
-```tomo
-func int(rng: RNG, min: Int, max: Int -> Int)
-func int64(rng: RNG, min: Int64 = Int64.min, max: Int64 = Int64.max -> Int)
-func int32(rng: RNG, min: Int32 = Int32.min, max: Int32 = Int32.max -> Int)
-func int16(rng: RNG, min: Int16 = Int16.min, max: Int16 = Int16.max -> Int)
-func int8(rng: RNG, min: Int8 = Int8.min, max: Int8 = Int8.max -> Int)
-```
-
-- `rng`: The random number generator to use.
-- `min`: The minimum value to be returned.
-- `max`: The maximum value to be returned.
-
-**Returns:**
-An integer uniformly chosen from the range `[min, max]` (inclusive). If `min`
-is greater than `max`, an error will be raised.
-
-**Example:**
-```tomo
->> random.int(1, 10)
-= 8
-```
-
----
-
-### `new`
-Return a new random number generator.
-
-```tomo
-func new(seed: [Byte] = (/dev/urandom).read_bytes(40)! -> RNG)
-```
-
-- `seed`: The seed use for the random number generator. A seed length of 40
- bytes is recommended. Seed lengths of less than 40 bytes are padded with
- zeroes.
-
-**Returns:**
-A new random number generator.
-
-**Example:**
-```tomo
->> my_rng := RNG.new([1[B], 2[B], 3[B], 4[B]])
->> my_rng.bool()
-= yes
-```
-
----
-
-### `num`, `num32`
-Generate a random floating point value with the given range.
-
-```tomo
-func num(rng: RNG, min: Num = 0.0, max: Num = 1.0 -> Int)
-func num32(rng: RNG, min: Num = 0.0_f32, max: Num = 1.0_f32 -> Int)
-```
-
-- `rng`: The random number generator to use.
-- `min`: The minimum value to be returned.
-- `max`: The maximum value to be returned.
-
-**Returns:**
-A floating point number uniformly chosen from the range `[min, max]`
-(inclusive). If `min` is greater than `max`, an error will be raised.
-
-**Example:**
-```tomo
->> random.num(1, 10)
-= 9.512830439975572
-```
diff --git a/lib/random/chacha.h b/lib/random/chacha.h
deleted file mode 100644
index 22015819..00000000
--- a/lib/random/chacha.h
+++ /dev/null
@@ -1,192 +0,0 @@
-// The ChaCha stream cipher used for pseudorandom number generation
-
-#pragma once
-/*
-chacha-merged.c version 20080118
-D. J. Bernstein
-Public domain.
-*/
-
-/* $OpenBSD: chacha_private.h,v 1.3 2022/02/28 21:56:29 dtucker Exp $ */
-/* Tomo: chacha.h,v 1.0 2024/11/03 Bruce Hill */
-
-typedef unsigned char u8;
-typedef unsigned int u32;
-
-typedef struct {
- u32 input[16]; /* could be compressed */
-} chacha_ctx;
-
-#define KEYSZ 32
-#define IVSZ 8
-
-#define U8C(v) (v##U)
-#define U32C(v) (v##U)
-
-#define U8V(v) ((u8)(v) & U8C(0xFF))
-#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
-
-#define ROTL32(v, n) (U32V((v) << (n)) | ((v) >> (32 - (n))))
-
-#define U8TO32_LITTLE(p) (((u32)((p)[0])) | ((u32)((p)[1]) << 8) | ((u32)((p)[2]) << 16) | ((u32)((p)[3]) << 24))
-
-#define U32TO8_LITTLE(p, v) \
- do { \
- (p)[0] = U8V((v)); \
- (p)[1] = U8V((v) >> 8); \
- (p)[2] = U8V((v) >> 16); \
- (p)[3] = U8V((v) >> 24); \
- } while (0)
-
-#define ROTATE(v, c) (ROTL32(v, c))
-#define XOR(v, w) ((v) ^ (w))
-#define PLUS(v, w) (U32V((v) + (w)))
-#define PLUSONE(v) (PLUS((v), 1))
-
-#define QUARTERROUND(a, b, c, d) \
- a = PLUS(a, b); \
- d = ROTATE(XOR(d, a), 16); \
- c = PLUS(c, d); \
- b = ROTATE(XOR(b, c), 12); \
- a = PLUS(a, b); \
- d = ROTATE(XOR(d, a), 8); \
- c = PLUS(c, d); \
- b = ROTATE(XOR(b, c), 7);
-
-static const char sigma[16] = "expand 32-byte k";
-
-static void chacha_keysetup(chacha_ctx *chacha, const u8 *k) {
- chacha->input[0] = U8TO32_LITTLE(sigma + 0);
- chacha->input[1] = U8TO32_LITTLE(sigma + 4);
- chacha->input[2] = U8TO32_LITTLE(sigma + 8);
- chacha->input[3] = U8TO32_LITTLE(sigma + 12);
- chacha->input[4] = U8TO32_LITTLE(k + 0);
- chacha->input[5] = U8TO32_LITTLE(k + 4);
- chacha->input[6] = U8TO32_LITTLE(k + 8);
- chacha->input[7] = U8TO32_LITTLE(k + 12);
- chacha->input[8] = U8TO32_LITTLE(k + 16);
- chacha->input[9] = U8TO32_LITTLE(k + 20);
- chacha->input[10] = U8TO32_LITTLE(k + 24);
- chacha->input[11] = U8TO32_LITTLE(k + 28);
-}
-
-static void chacha_ivsetup(chacha_ctx *chacha, const u8 *iv) {
- chacha->input[12] = 0;
- chacha->input[13] = 0;
- chacha->input[14] = U8TO32_LITTLE(iv + 0);
- chacha->input[15] = U8TO32_LITTLE(iv + 4);
-}
-
-static void chacha_encrypt_bytes(chacha_ctx *chacha, const u8 *m, u8 *c, u32 bytes) {
- u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
- u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
- u8 *ctarget = (u8 *)0;
- u8 tmp[64];
- unsigned int i;
-
- if (!bytes) return;
-
- j0 = chacha->input[0];
- j1 = chacha->input[1];
- j2 = chacha->input[2];
- j3 = chacha->input[3];
- j4 = chacha->input[4];
- j5 = chacha->input[5];
- j6 = chacha->input[6];
- j7 = chacha->input[7];
- j8 = chacha->input[8];
- j9 = chacha->input[9];
- j10 = chacha->input[10];
- j11 = chacha->input[11];
- j12 = chacha->input[12];
- j13 = chacha->input[13];
- j14 = chacha->input[14];
- j15 = chacha->input[15];
-
- for (;;) {
- if (bytes < 64) {
- for (i = 0; i < bytes; ++i)
- tmp[i] = m[i];
- m = tmp;
- ctarget = c;
- c = tmp;
- }
- x0 = j0;
- x1 = j1;
- x2 = j2;
- x3 = j3;
- x4 = j4;
- x5 = j5;
- x6 = j6;
- x7 = j7;
- x8 = j8;
- x9 = j9;
- x10 = j10;
- x11 = j11;
- x12 = j12;
- x13 = j13;
- x14 = j14;
- x15 = j15;
- for (i = 20; i > 0; i -= 2) {
- QUARTERROUND(x0, x4, x8, x12)
- QUARTERROUND(x1, x5, x9, x13)
- QUARTERROUND(x2, x6, x10, x14)
- QUARTERROUND(x3, x7, x11, x15)
- QUARTERROUND(x0, x5, x10, x15)
- QUARTERROUND(x1, x6, x11, x12)
- QUARTERROUND(x2, x7, x8, x13)
- QUARTERROUND(x3, x4, x9, x14)
- }
- x0 = PLUS(x0, j0);
- x1 = PLUS(x1, j1);
- x2 = PLUS(x2, j2);
- x3 = PLUS(x3, j3);
- x4 = PLUS(x4, j4);
- x5 = PLUS(x5, j5);
- x6 = PLUS(x6, j6);
- x7 = PLUS(x7, j7);
- x8 = PLUS(x8, j8);
- x9 = PLUS(x9, j9);
- x10 = PLUS(x10, j10);
- x11 = PLUS(x11, j11);
- x12 = PLUS(x12, j12);
- x13 = PLUS(x13, j13);
- x14 = PLUS(x14, j14);
- x15 = PLUS(x15, j15);
-
- j12 = PLUSONE(j12);
- if (!j12) {
- j13 = PLUSONE(j13);
- /* stopping at 2^70 bytes per nonce is user's responsibility */
- }
-
- U32TO8_LITTLE(c + 0, x0);
- U32TO8_LITTLE(c + 4, x1);
- U32TO8_LITTLE(c + 8, x2);
- U32TO8_LITTLE(c + 12, x3);
- U32TO8_LITTLE(c + 16, x4);
- U32TO8_LITTLE(c + 20, x5);
- U32TO8_LITTLE(c + 24, x6);
- U32TO8_LITTLE(c + 28, x7);
- U32TO8_LITTLE(c + 32, x8);
- U32TO8_LITTLE(c + 36, x9);
- U32TO8_LITTLE(c + 40, x10);
- U32TO8_LITTLE(c + 44, x11);
- U32TO8_LITTLE(c + 48, x12);
- U32TO8_LITTLE(c + 52, x13);
- U32TO8_LITTLE(c + 56, x14);
- U32TO8_LITTLE(c + 60, x15);
-
- if (bytes <= 64) {
- if (bytes < 64) {
- for (i = 0; i < bytes; ++i)
- ctarget[i] = c[i];
- }
- chacha->input[12] = j12;
- chacha->input[13] = j13;
- return;
- }
- bytes -= 64;
- c += 64;
- }
-}
diff --git a/lib/random/random.tm b/lib/random/random.tm
deleted file mode 100644
index 6d639c3a..00000000
--- a/lib/random/random.tm
+++ /dev/null
@@ -1,234 +0,0 @@
-# Random Number Generator (RNG) implementation based on ChaCha
-
-use <assert.h>
-use ./sysrandom.h
-use ./chacha.h
-
-struct chacha_ctx(j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15:Int32; extern, secret)
- func from_seed(seed:[Byte]=[] -> chacha_ctx)
- return C_code:chacha_ctx `
- chacha_ctx ctx;
- uint8_t seed_bytes[KEYSZ + IVSZ] = {};
- for (int64_t i = 0; i < (int64_t)sizeof(seed_bytes); i++)
- seed_bytes[i] = i < @seed.length ? *(uint8_t*)(@seed.data + i*@seed.stride) : 0;
- chacha_keysetup(&ctx, seed_bytes);
- chacha_ivsetup(&ctx, seed_bytes + KEYSZ);
- ctx
- `
-
-random := RandomNumberGenerator.new()
-
-func _os_random_bytes(count:Int64 -> [Byte])
- return C_code:[Byte] `
- uint8_t *random_bytes = GC_MALLOC_ATOMIC(@count);
- assert(getrandom(random_bytes, (size_t)@count, 0) == (size_t)@count);
- (List_t){.length=@count, .data=random_bytes, .stride=1, .atomic=1}
- `
-
-struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret)
- func new(seed:[Byte]?=none, -> @RandomNumberGenerator)
- ctx := chacha_ctx.from_seed(seed or _os_random_bytes(40))
- return @RandomNumberGenerator(ctx, [])
-
- func _rekey(rng:&RandomNumberGenerator)
- rng._random_bytes = C_code:[Byte] `
- Byte_t new_keystream[KEYSZ + IVSZ] = {};
- // Fill the buffer with the keystream
- chacha_encrypt_bytes(&@rng->_chacha, new_keystream, new_keystream, sizeof(new_keystream));
- // Immediately reinitialize for backtracking resistance
- chacha_keysetup(&@rng->_chacha, new_keystream);
- chacha_ivsetup(&@rng->_chacha, new_keystream + KEYSZ);
- List_t new_bytes = (List_t){.data=GC_MALLOC_ATOMIC(1024), .length=1024, .stride=1, .atomic=1};
- memset(new_bytes.data, 0, new_bytes.length);
- chacha_encrypt_bytes(&@rng->_chacha, new_bytes.data, new_bytes.data, new_bytes.length);
- new_bytes
- `
-
- func _fill_bytes(rng:&RandomNumberGenerator, dest:&Memory, needed:Int64)
- C_code `
- while (@needed > 0) {
- if (@rng->_random_bytes.length == 0)
- @(rng._rekey());
-
- assert(@rng->_random_bytes.stride == 1);
-
- int64_t batch_size = MIN(@needed, @rng->_random_bytes.length);
- uint8_t *batch_src = @rng->_random_bytes.data;
- memcpy(@dest, batch_src, batch_size);
- memset(batch_src, 0, batch_size);
- @rng->_random_bytes.data += batch_size;
- @rng->_random_bytes.length -= batch_size;
- @dest += batch_size;
- @needed -= batch_size;
- }
- `
-
- func bytes(rng:&RandomNumberGenerator, count:Int -> [Byte])
- count64 := Int64(count)
- buf := C_code:@Memory `GC_MALLOC_ATOMIC(@count64)`
- rng._fill_bytes(buf, count64)
- return C_code:[Byte] `(List_t){.data=@buf, .stride=1, .atomic=1, .length=@count64}`
-
- func byte(rng:&RandomNumberGenerator -> Byte)
- byte : &Byte
- rng._fill_bytes(byte, 1)
- return byte[]
-
- func bool(rng:&RandomNumberGenerator, probability=0.5 -> Bool)
- if probability == 0.5
- return rng.byte() < 0x80
- else
- return rng.num(0., 1.) < 0.5
-
- func int64(rng:&RandomNumberGenerator, min=Int64.min, max=Int64.max -> Int64)
- fail("Random minimum value $min is larger than the maximum value $max") if min > max
- return min if min == max
- random_int64 : &Int64
- rng._fill_bytes(random_int64, 8)
- if min == Int64.min and max == Int64.max
- return random_int64
-
- return C_code:Int64 `
- uint64_t range = (uint64_t)@max - (uint64_t)@min + 1;
- uint64_t min_r = -range % range;
- uint64_t r;
- @random_int64 = (int64_t*)&r;
- for (;;) {
- @(rng._fill_bytes(random_int64, 8));
- if (r >= min_r) break;
- }
- (int64_t)((uint64_t)@min + (r % range))
- `
-
- func int32(rng:&RandomNumberGenerator, min=Int32.min, max=Int32.max -> Int32)
- fail("Random minimum value $min is larger than the maximum value $max") if min > max
- return min if min == max
- random_int32 : &Int32
- rng._fill_bytes(random_int32, 8)
- if min == Int32.min and max == Int32.max
- return random_int32
-
- return C_code:Int32 `
- uint32_t range = (uint32_t)@max - (uint32_t)@min + 1;
- uint32_t min_r = -range % range;
- uint32_t r;
- @random_int32 = (int32_t*)&r;
- for (;;) {
- @(rng._fill_bytes(random_int32, 8));
- if (r >= min_r) break;
- }
- (int32_t)((uint32_t)@min + (r % range))
- `
-
- func int16(rng:&RandomNumberGenerator, min=Int16.min, max=Int16.max -> Int16)
- fail("Random minimum value $min is larger than the maximum value $max") if min > max
- return min if min == max
- random_int16 : &Int16
- rng._fill_bytes(random_int16, 8)
- if min == Int16.min and max == Int16.max
- return random_int16
-
- return C_code:Int16 `
- uint16_t range = (uint16_t)@max - (uint16_t)@min + 1;
- uint16_t min_r = -range % range;
- uint16_t r;
- @random_int16 = (int16_t*)&r;
- for (;;) {
- @(rng._fill_bytes(random_int16, 8));
- if (r >= min_r) break;
- }
- (int16_t)((uint16_t)@min + (r % range))
- `
-
- func int8(rng:&RandomNumberGenerator, min=Int8.min, max=Int8.max -> Int8)
- fail("Random minimum value $min is larger than the maximum value $max") if min > max
- return min if min == max
- random_int8 : &Int8
- rng._fill_bytes(random_int8, 8)
- if min == Int8.min and max == Int8.max
- return random_int8
-
- return C_code:Int8 `
- uint8_t range = (uint8_t)@max - (uint8_t)@min + 1;
- uint8_t min_r = -range % range;
- uint8_t r;
- @random_int8 = (int8_t*)&r;
- for (;;) {
- @(rng._fill_bytes(random_int8, 8));
- if (r >= min_r) break;
- }
- (int8_t)((uint8_t)@min + (r % range))
- `
-
- func num(rng:&RandomNumberGenerator, min=0., max=1. -> Num)
- num_buf : &Num
- return C_code:Num `
- if (@min > @max) fail("Random minimum value (", @min, ") is larger than the maximum value (", @max, ")");
- if (@min == @max) return @min;
-
- union {
- Num_t num;
- uint64_t bits;
- } r = {.bits=0}, one = {.num=1.0};
- @num_buf = &r.num;
- @(rng._fill_bytes(num_buf, 8));
-
- // Set r.num to 1.<random-bits>
- r.bits &= ~(0xFFFULL << 52);
- r.bits |= (one.bits & (0xFFFULL << 52));
-
- r.num -= 1.0;
-
- (@min == 0.0 && @max == 1.0) ? r.num : ((1.0-r.num)*@min + r.num*@max)
- `
-
- func num32(rng:&RandomNumberGenerator, min=Num32(0.), max=Num32(1.) -> Num32)
- return Num32(rng.num(Num(min), Num(max)))
-
- func int(rng:&RandomNumberGenerator, min:Int, max:Int -> Int)
- return C_code:Int `
- if (likely(((@min.small & @max.small) & 1) != 0)) {
- int32_t r = @(rng.int32(Int32(min), Int32(max)));
- return I_small(r);
- }
-
- int32_t cmp = @(min <> max);
- if (cmp > 0)
- fail("Random minimum value (", @min, ") is larger than the maximum value (", @max, ")");
- if (cmp == 0) return @min;
-
- mpz_t range_size;
- mpz_init_set_int(range_size, @max);
- if (@min.small & 1) {
- mpz_t min_mpz;
- mpz_init_set_si(min_mpz, @min.small >> 2);
- mpz_sub(range_size, range_size, min_mpz);
- } else {
- mpz_sub(range_size, range_size, *@min.big);
- }
-
- gmp_randstate_t gmp_rng;
- gmp_randinit_default(gmp_rng);
- int64_t seed = @(rng.int64());
- gmp_randseed_ui(gmp_rng, (unsigned long)seed);
-
- mpz_t r;
- mpz_init(r);
- mpz_urandomm(r, gmp_rng, range_size);
-
- gmp_randclear(gmp_rng);
- Int$plus(@min, Int$from_mpz(r))
- `
-
-func main()
- >> rng := RandomNumberGenerator.new()
- >> rng.num()
- >> rng.num()
- >> rng.num()
- >> rng.num(0, 100)
- >> rng.byte()
- >> rng.bytes(20)
- # >> rng.int(1, 100)
- # >> rng.int(1, 100)
- # >> rng.int(1, 100)
- # >> rng.int(1, 100)
diff --git a/lib/random/sysrandom.h b/lib/random/sysrandom.h
deleted file mode 100644
index 3d6b800f..00000000
--- a/lib/random/sysrandom.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Logic for using system random numbers
-
-#pragma once
-
-#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
-#include <stdlib.h>
-static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
- (void)flags;
- arc4random_buf(buf, buflen);
- return buflen;
-}
-#elif defined(__linux__)
-// Use getrandom()
-#include <sys/random.h>
-#else
-#error "Unsupported platform for secure random number generation"
-#endif
diff --git a/lib/shell/CHANGES.md b/lib/shell/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/shell/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/shell/README.md b/lib/shell/README.md
deleted file mode 100644
index d4bcd42e..00000000
--- a/lib/shell/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Shell
-
-This module defines a `lang` for running shell scripts:
-
-```tomo
-use shell_v1.0
-
->> $Shell"
- seq 5
- echo DONE
-":get_output()
-= "1$\n2$\n3$\n4$\n5$\nDONE"
-```
diff --git a/lib/shell/shell.tm b/lib/shell/shell.tm
deleted file mode 100644
index 08b36f1a..00000000
--- a/lib/shell/shell.tm
+++ /dev/null
@@ -1,44 +0,0 @@
-use commands_v1.0
-
-lang Shell
- convert(text:Text -> Shell)
- return Shell.from_text("'" ++ text.replace(`'`, `'"'"'`) ++ "'")
-
- convert(texts:[Text] -> Shell)
- return Shell.from_text(" ".join([Shell(t).text for t in texts]))
-
- convert(path:Path -> Shell)
- return Shell(Text(path.expand_home()))
-
- convert(paths:[Path] -> Shell)
- return Shell.from_text(" ".join([Shell(Text(p)).text for p in paths]))
-
- convert(n:Int -> Shell) return Shell.from_text(Text(n))
- convert(n:Int64 -> Shell) return Shell.from_text(Text(n))
- convert(n:Int32 -> Shell) return Shell.from_text(Text(n))
- convert(n:Int16 -> Shell) return Shell.from_text(Text(n))
- convert(n:Int8 -> Shell) return Shell.from_text(Text(n))
- convert(n:Num -> Shell) return Shell.from_text(Text(n))
- convert(n:Num32 -> Shell) return Shell.from_text(Text(n))
-
- func command(shell:Shell -> Command)
- return Command("sh", ["-c", shell.text])
-
- func result(shell:Shell, input="", input_bytes:[Byte]=[] -> ProgramResult)
- return shell.command().result(input=input, input_bytes=input_bytes)
-
- func run(shell:Shell -> ExitType)
- return shell.command().run()
-
- func get_output(shell:Shell, input="", trim_newline=yes -> Text?)
- return shell.command().get_output(input=input, trim_newline=trim_newline)
-
- func get_output_bytes(shell:Shell, input="", input_bytes:[Byte]=[] -> [Byte]?)
- return shell.command().get_output_bytes(input=input, input_bytes=input_bytes)
-
- func by_line(shell:Shell -> func(->Text?)?)
- return shell.command().by_line()
-
-func main(command:Shell)
- for line in command.by_line()!
- >> line
diff --git a/lib/time/CHANGES.md b/lib/time/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/time/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/time/README.md b/lib/time/README.md
deleted file mode 100644
index 55f725f1..00000000
--- a/lib/time/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Time
-
-This is a Tomo library for working with dates and times. The `Time` type that
-is provided is a date-and-time datatype that refers to a specific moment in
-time.
diff --git a/lib/time/time.tm b/lib/time/time.tm
deleted file mode 100644
index cb7005cf..00000000
--- a/lib/time/time.tm
+++ /dev/null
@@ -1,214 +0,0 @@
-# Time - a module for dealing with dates and times
-use <math.h>
-use ./time_defs.h
-
-enum Weekday(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
-
-struct TimeInfo(year,month,day,hour,minute,second,nanosecond:Int, weekday:Weekday, day_of_year:Int, timezone:Text)
-
-struct Time(tv_sec:Int64, tv_usec:Int64; extern)
- func now(->Time)
- return C_code : Time `
- struct timespec ts;
- if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
- fail("Couldn't get the time!");
- (Time){.tv_sec=ts.tv_sec, .tv_usec=ts.tv_nsec/1000}
- `
-
- func local_timezone(->Text)
- C_code `
- if (_local_timezone.length < 0) {
- static char buf[PATH_MAX];
- ssize_t len = readlink("/etc/localtime", buf, sizeof(buf));
- if (len < 0)
- fail("Could not get local tz!");
-
- char *zoneinfo = strstr(buf, "/zoneinfo/");
- if (zoneinfo)
- _local_timezone = Text$from_str(zoneinfo + strlen("/zoneinfo/"));
- else
- fail("Could not resolve local tz!");
- }
- `
- return C_code:Text`_local_timezone`
-
- func set_local_timezone(timezone:Text)
- C_code `
- setenv("TZ", @(CString(timezone)), 1);
- _local_timezone = @timezone;
- tzset();
- `
-
- func format(t:Time, format="%c", timezone=Time.local_timezone() -> Text)
- return C_code : Text `
- struct tm result;
- time_t time = @t.tv_sec;
- struct tm *final_info;
- WITH_TIMEZONE(@timezone, final_info = localtime_r(&time, &result));
- static char buf[256];
- size_t len = strftime(buf, sizeof(buf), String(@format), final_info);
- Text$from_strn(buf, len)
- `
-
- func new(year,month,day:Int, hour=0, minute=0, second=0.0, timezone=Time.local_timezone() -> Time)
- return C_code : Time`
- struct tm info = {
- .tm_min=Int32$from_int(@minute, false),
- .tm_hour=Int32$from_int(@hour, false),
- .tm_mday=Int32$from_int(@day, false),
- .tm_mon=Int32$from_int(@month, false) - 1,
- .tm_year=Int32$from_int(@year, false) - 1900,
- .tm_isdst=-1,
- };
-
- time_t t;
- WITH_TIMEZONE(@timezone, t = mktime(&info));
- (Time){.tv_sec=t + (time_t)@second, .tv_usec=(suseconds_t)(fmod(@second, 1.0) * 1e9)}
- `
-
- func unix_timestamp(t:Time -> Int64)
- return C_code:Int64`(int64_t)@t.tv_sec`
-
- func from_unix_timestamp(timestamp:Int64 -> Time)
- return C_code:Time`(Time){.tv_sec=@timestamp};`
-
- func seconds_till(t:Time, target:Time -> Num)
- seconds := Num(target.tv_sec - t.tv_sec)
- seconds += 1e-9*Num(target.tv_usec - t.tv_usec)
- return seconds
-
- func minutes_till(t:Time, target:Time -> Num)
- return t.seconds_till(target)/60.
-
- func hours_till(t:Time, target:Time -> Num)
- return t.seconds_till(target)/3600.
-
- func relative(t:Time, relative_to=Time.now(), timezone=Time.local_timezone() -> Text)
- C_code `
- struct tm info = {};
- struct tm relative_info = {};
- WITH_TIMEZONE(@timezone, {
- localtime_r(&@t.tv_sec, &info);
- localtime_r(&@relative_to.tv_sec, &relative_info);
- });
- double second_diff = @(relative_to.seconds_till(t));
- if (info.tm_year != relative_info.tm_year && fabs(second_diff) > 365.*24.*60.*60.)
- return num_format((long)info.tm_year - (long)relative_info.tm_year, "year");
- else if (info.tm_mon != relative_info.tm_mon && fabs(second_diff) > 31.*24.*60.*60.)
- return num_format(12*((long)info.tm_year - (long)relative_info.tm_year) + (long)info.tm_mon - (long)relative_info.tm_mon, "month");
- else if (info.tm_yday != relative_info.tm_yday && fabs(second_diff) > 24.*60.*60.)
- return num_format(round(second_diff/(24.*60.*60.)), "day");
- else if (info.tm_hour != relative_info.tm_hour && fabs(second_diff) > 60.*60.)
- return num_format(round(second_diff/(60.*60.)), "hour");
- else if (info.tm_min != relative_info.tm_min && fabs(second_diff) > 60.)
- return num_format(round(second_diff/(60.)), "minute");
- else {
- if (fabs(second_diff) < 1e-6)
- return num_format((long)(second_diff*1e9), "nanosecond");
- else if (fabs(second_diff) < 1e-3)
- return num_format((long)(second_diff*1e6), "microsecond");
- else if (fabs(second_diff) < 1.0)
- return num_format((long)(second_diff*1e3), "millisecond");
- else
- return num_format((long)(second_diff), "second");
- }
- `
- fail("Unreachable")
-
- func time(t:Time, seconds=no, am_pm=yes, timezone=Time.local_timezone() -> Text)
- time := if seconds and am_pm
- t.format("%l:%M:%S%P")
- else if seconds and not am_pm
- t.format("%T")
- else if not seconds and am_pm
- t.format("%l:%M%P")
- else
- t.format("%H:%M")
- return time.trim()
-
- func date(t:Time, timezone=Time.local_timezone() -> Text)
- return t.format("%F")
-
- func info(t:Time, timezone=Time.local_timezone() -> TimeInfo)
- ret : TimeInfo
- C_code `
- struct tm info = {};
- WITH_TIMEZONE(@timezone, localtime_r(&@t.tv_sec, &info));
- @ret.year = I(info.tm_year + 1900);
- @ret.month = I(info.tm_mon + 1);
- @ret.day = I(info.tm_mday);
- @ret.hour = I(info.tm_hour);
- @ret.minute = I(info.tm_min);
- @ret.second = I(info.tm_sec);
- @ret.nanosecond = I(@t.tv_usec);
- @ret.weekday = info.tm_wday + 1;
- @ret.day_of_year = I(info.tm_yday);
- @ret.timezone = @timezone;
- `
- return ret
-
- func after(t:Time, seconds=0.0, minutes=0.0, hours=0.0, days=0, weeks=0, months=0, years=0, timezone=Time.local_timezone() -> Time)
- return C_code : Time `
- double offset = @seconds + 60.*@minutes + 3600.*@hours ;
- @t.tv_sec += (time_t)offset;
-
- struct tm info = {};
- WITH_TIMEZONE(@timezone, localtime_r(&@t.tv_sec, &info));
-
- info.tm_mday += Int32$from_int(@days, false) + 7*Int32$from_int(@weeks, false);
- info.tm_mon += Int32$from_int(@months, false);
- info.tm_year += Int32$from_int(@years, false);
-
- time_t t = mktime(&info);
- (Time){
- .tv_sec=t,
- .tv_usec=@t.tv_usec + (suseconds_t)(fmod(offset, 1.0) * 1e9),
- }
- `
-
- func parse(text:Text, format="%Y-%m-%dT%H:%M:%S%z", timezone=Time.local_timezone() -> Time?)
- ret : Time?
- C_code `
- struct tm info = {.tm_isdst=-1};
- const char *str = Text$as_c_string(@text);
- const char *fmt = Text$as_c_string(@format);
- if (strstr(fmt, "%Z"))
- fail("The %Z specifier is not supported for time parsing!");
-
- char *invalid;
- WITH_TIMEZONE(@timezone, invalid = strptime(str, fmt, &info));
- if (!invalid || invalid[0] != '\0') {
- @ret.is_none = true;
- } else {
- long offset = info.tm_gmtoff; // Need to cache this because mktime() mutates it to local tz >:(
- time_t t;
- WITH_TIMEZONE(@timezone, t = mktime(&info));
- @ret.value.tv_sec = t + offset - info.tm_gmtoff;
- }
- `
- return ret
-
-func _run_tests()
- >> Time.now().format()
- >> Time.set_local_timezone("Europe/Paris")
- >> Time.now().format()
- >> Time.set_local_timezone("America/New_York")
- >> Time.now().format()
- # >> Time.now().format(timezone="Europe/Paris")
- # >> Time.now().format()
- # >> Time.now().format("%Y-%m-%d")
- # >> Time.new(2023, 11, 5).format()
- # >> Time.local_timezone()
-
- # >> Time.new(2023, 11, 5).seconds_till(Time.now())
- # >> Time.new(2023, 11, 5).relative()
-
- # >> Time.now().info()
- # >> Time.now().time()
- # >> Time.now().date()
-
- # >> Time.parse("2023-11-05 01:01", "%Y-%m-%d %H:%M")
- # >> Time.parse("2023-11-05 01:01", "%Y-%m-%d %H:%M", timezone="Europe/Paris")
-
-func main()
- _run_tests()
diff --git a/lib/time/time_defs.h b/lib/time/time_defs.h
deleted file mode 100644
index eac3f23a..00000000
--- a/lib/time/time_defs.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Some helper logic for working with times.
-
-#pragma once
-#include <stdlib.h>
-#include <sys/time.h>
-#include <time.h>
-
-typedef struct timeval Time;
-
-static OptionalText_t _local_timezone = NONE_TEXT;
-
-static INLINE Text_t num_format(long n, const char *unit) {
- if (n == 0) return Text("now");
- return Text$from_str(
- String((int64_t)labs(n), " ", unit, (n == -1 || n == 1) ? "" : "s", n <= 0 ? " ago" : " later"));
-}
-
-static void set_local_timezone(Text_t tz) {
- setenv("TZ", Text$as_c_string(tz), 1);
- _local_timezone = tz;
- tzset();
-}
-
-#define WITH_TIMEZONE(tz, body) \
- ({ \
- if (tz.length >= 0) { \
- OptionalText_t old_timezone = _local_timezone; \
- set_local_timezone(tz); \
- body; \
- set_local_timezone(old_timezone); \
- } else { \
- body; \
- } \
- })
diff --git a/lib/uuid/CHANGES.md b/lib/uuid/CHANGES.md
deleted file mode 100644
index 42ae752c..00000000
--- a/lib/uuid/CHANGES.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Version History
-
-## v1.0
-
-Initial version
diff --git a/lib/uuid/uuid.tm b/lib/uuid/uuid.tm
deleted file mode 100644
index f2be618e..00000000
--- a/lib/uuid/uuid.tm
+++ /dev/null
@@ -1,39 +0,0 @@
-use random_v1.0
-use time_v1.0
-
-lang UUID
- func v4(-> UUID) # Random UUID
- bytes := &random.bytes(16)
- bytes[7; unchecked] = 0x40 or (bytes[7; unchecked] and 0x0F)
- bytes[9; unchecked] = (Byte(random.int8(0x8, 0xB)) << 4) or (bytes[9; unchecked] and 0x0F)
- hex := "".join([b.hex() for b in bytes])
- uuid := "$(hex.slice(1, 8))-$(hex.slice(9, 12))-$(hex.slice(13, 16))-$(hex.slice(17, -1))"
- return UUID.from_text(uuid)
-
- func v7(-> UUID) # Timestamp + random UUID
- n := Time.now()
- timestamp := n.tv_sec*1000 + n.tv_usec/1_000
-
- bytes := [
- Byte((timestamp >> 40)),
- Byte((timestamp >> 32)),
- Byte((timestamp >> 24)),
- Byte((timestamp >> 16)),
- Byte((timestamp >> 8)),
- Byte(timestamp),
- (random.byte() and 0x0F) or 0x70,
- random.byte(),
- (random.byte() and 0x3F) or 0x80,
- random.byte() for _ in 7,
- ]
-
- hex := "".join([b.hex() for b in bytes])
- uuid := "$(hex.slice(1, 8))-$(hex.slice(9, 12))-$(hex.slice(13, 16))-$(hex.slice(17, -1))"
- return UUID.from_text(uuid)
-
-enum UUIDVersion(v4, v7)
-func main(version=UUIDVersion.v7)
- when version is v4
- say(UUID.v4().text)
- is v7
- say(UUID.v7().text)
diff --git a/local-tomo b/local-tomo
index c7eaeb21..bdf1e0da 100755
--- a/local-tomo
+++ b/local-tomo
@@ -7,8 +7,5 @@ if [ ! -e "$here/build/bin/tomo_$version" ]; then
fi
PATH="$here/build/bin${PATH:+:$PATH}" \
-LD_LIBRARY_PATH="$here/build/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
-LIBRARY_PATH="$here/build/lib${LIBRARY_PATH:+:$LIBRARY_PATH}" \
-C_INCLUDE_PATH="$here/build/include${C_INCLUDE_PATH:+:$C_INCLUDE_PATH}" \
-CPATH="$here/build/include${CPATH:+:$CPATH}" \
+TOMO_PATH="$here/build" \
tomo_"$version" "$@"
diff --git a/man/man1/tomo.1 b/man/man1/tomo.1
index c3330175..5134d963 100644
--- a/man/man1/tomo.1
+++ b/man/man1/tomo.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pandoc 3.1.12.1
+.\" Automatically generated by Pandoc 3.1.13
.\"
.TH "TOMO" "1" "June 11, 2024" "" ""
.SH NAME
@@ -29,45 +29,63 @@ It compiles by first outputting C code, which is then compiled using a C
compiler of your choice.
.SH OPTIONS
.TP
-\f[B]\-h\f[R], \f[B]\-\-help\f[R]
-Print the usage and exit.
+\f[B]\-\-changelog\f[R]
+Print the compiler change log and exit.
.TP
-\f[B]\-t\f[R], \f[B]\-\-transpile\f[R]
-Transpile the input files to C code without compiling them.
+\f[B]\-\-compile\-exe\f[R], \f[B]\-e\f[R]
+Compile the input file to an executable.
.TP
-\f[B]\-c\f[R], \f[B]\-\-compile\-obj\f[R]
+\f[B]\-\-compile\-obj\f[R], \f[B]\-c\f[R]
Compile the input files to static objects, rather than running them.
.TP
-\f[B]\-e\f[R], \f[B]\-\-compile\-exe\f[R]
-Compile the input file to an executable.
-.TP
-\f[B]\-L\f[R], \f[B]\-\-library\f[R]
-Compile the input files to a shared library file and header.
+\f[B]\-\-help\f[R], \f[B]\-h\f[R]
+Print the usage and exit.
.TP
-\f[B]\-I\f[R], \f[B]\-\-install\f[R]
+\f[B]\-\-install\f[R], \f[B]\-I\f[R]
Install the compiled executable or library.
.TP
-\f[B]\-C\f[R] \f[I]\f[R], \f[B]\-\-show\-codegen\f[R] \f[I]\f[R]
+\f[B]\-\-library\f[R], \f[B]\-L\f[R]
+Compile the input files to a shared library file and header.
+.TP
+\f[B]\-\-show\-codegen\f[R] \f[I]\f[R], \f[B]\-C\f[R] \f[I]\f[R]
Set a program (e.g.\ \f[B]cat\f[R] or \f[B]bat\f[R]) to display the
generated code
.TP
-\f[B]\-O\f[R] \f[B]level\f[R], \f[B]\-\-optimization\f[R] \f[B]level\f[R]
-Set the optimization level.
+\f[B]\-\-force\-rebuild\f[R], \f[B]\-f\f[R]
+Force rebuilding/recompiling.
.TP
-\f[B]\-v\f[R], \f[B]\-\-verbose\f[R]
-Print extra verbose output.
+\f[B]\-\-format\f[R]
+Autoformat a file and print it to standard output.
.TP
-\f[B]\-\-version\f[R]
-Print the compiler version and exit.
+\f[B]\-\-format\-inplace\f[R]
+Autoformat a file in\-place.
.TP
-\f[B]\-\-changelog\f[R]
-Print the compiler change log and exit.
+\f[B]\-\-optimization\f[R] \f[B]level\f[R], \f[B]\-O\f[R] \f[B]level\f[R]
+Set the optimization level.
.TP
\f[B]\-\-prefix\f[R]
Print the Tomo installation prefix and exit.
.TP
-\f[B]\-r\f[R], \f[B]\-\-run\f[R]
+\f[B]\-\-quiet\f[R], \f[B]\-q\f[R]
+Run in quiet mode.
+.TP
+\f[B]\-\-run\f[R], \f[B]\-r\f[R]
Run an installed tomo program from
-\f[B]\[ti]/.local/share/tomo_vX.Y/installed\f[R].
+\f[B]\[ti]/.local/lib/tomo_vX.Y/\f[R].
+.TP
+\f[B]\-\-source\-mapping=\f[R], \f[B]\-m=\f[R] \f[B]<yes|no>\f[R]
+Toggle whether source mapping should be enabled or disabled.
+.TP
+\f[B]\-\-transpile\f[R], \f[B]\-t\f[R]
+Transpile the input files to C code without compiling them.
+.TP
+\f[B]\-\-uninstall\f[R], \f[B]\-u\f[R]
+Uninstall a compiled executable or library.
+.TP
+\f[B]\-\-verbose\f[R], \f[B]\-v\f[R]
+Print extra verbose output.
+.TP
+\f[B]\-\-version\f[R]
+Print the compiler version and exit.
.SH AUTHORS
Bruce Hill (\f[I]bruce\[at]bruce\-hill.com\f[R]).
diff --git a/modules/core.ini b/modules/core.ini
new file mode 100644
index 00000000..ddad0a09
--- /dev/null
+++ b/modules/core.ini
@@ -0,0 +1,35 @@
+[patterns]
+version=v1.1
+git=https://github.com/bruce-hill/tomo-patterns
+
+[base64]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-base64
+
+[commands]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-commands
+
+[json]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-json
+
+[pthreads]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-pthreads
+
+[random]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-random
+
+[shell]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-shell
+
+[time]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-time
+
+[uuid]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-uuid
diff --git a/modules/examples.ini b/modules/examples.ini
new file mode 100644
index 00000000..20a04639
--- /dev/null
+++ b/modules/examples.ini
@@ -0,0 +1,27 @@
+[log]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-log
+
+[ini]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-ini
+
+[vectors]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-vectors
+
+[http]
+version=v1.1
+git=https://github.com/bruce-hill/tomo-http
+
+[http-server]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-http-server
+
+[wrap]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-wrap
+
+[colorful]
+version=v1.0
+git=https://github.com/bruce-hill/tomo-colorful
diff --git a/src/ast.c b/src/ast.c
index 53d80a81..37c5a514 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -5,77 +5,76 @@
#include "ast.h"
#include "stdlib/datatypes.h"
+#include "stdlib/optionals.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
-static Text_t quoted_text(const char *text) { return Text$quoted(Text$from_str(text), false, Text("\"")); }
-
-CONSTFUNC const char *binop_method_name(ast_e tag) {
- switch (tag) {
- case Power:
- case PowerUpdate: return "power";
- case Multiply:
- case MultiplyUpdate: return "times";
- case Divide:
- case DivideUpdate: return "divided_by";
- case Mod:
- case ModUpdate: return "modulo";
- case Mod1:
- case Mod1Update: return "modulo1";
- case Plus:
- case PlusUpdate: return "plus";
- case Minus:
- case MinusUpdate: return "minus";
- case Concat:
- case ConcatUpdate: return "concatenated_with";
- case LeftShift:
- case LeftShiftUpdate: return "left_shifted";
- case RightShift:
- case RightShiftUpdate: return "right_shifted";
- case UnsignedLeftShift:
- case UnsignedLeftShiftUpdate: return "unsigned_left_shifted";
- case UnsignedRightShift:
- case UnsignedRightShiftUpdate: return "unsigned_right_shifted";
- case And:
- case AndUpdate: return "bit_and";
- case Or:
- case OrUpdate: return "bit_or";
- case Xor:
- case XorUpdate: return "bit_xor";
- default: return NULL;
- }
+const int op_tightness[NUM_AST_TAGS] = {
+ [Power] = 9,
+ [Multiply] = 8,
+ [Divide] = 8,
+ [Mod] = 8,
+ [Mod1] = 8,
+ [Plus] = 7,
+ [Minus] = 7,
+ [Concat] = 6,
+ [LeftShift] = 5,
+ [RightShift] = 5,
+ [UnsignedLeftShift] = 5,
+ [UnsignedRightShift] = 5,
+ [Min] = 4,
+ [Max] = 4,
+ [Equals] = 3,
+ [NotEquals] = 3,
+ [LessThan] = 2,
+ [LessThanOrEquals] = 2,
+ [GreaterThan] = 2,
+ [GreaterThanOrEquals] = 2,
+ [Compare] = 2,
+ [And] = 1,
+ [Or] = 1,
+ [Xor] = 1,
};
-CONSTFUNC const char *binop_operator(ast_e tag) {
- switch (tag) {
- case Multiply:
- case MultiplyUpdate: return "*";
- case Divide:
- case DivideUpdate: return "/";
- case Mod:
- case ModUpdate: return "%";
- case Plus:
- case PlusUpdate: return "+";
- case Minus:
- case MinusUpdate: return "-";
- case LeftShift:
- case LeftShiftUpdate: return "<<";
- case RightShift:
- case RightShiftUpdate: return ">>";
- case And:
- case AndUpdate: return "&";
- case Or:
- case OrUpdate: return "|";
- case Xor:
- case XorUpdate: return "^";
- case Equals: return "==";
- case NotEquals: return "!=";
- case LessThan: return "<";
- case LessThanOrEquals: return "<=";
- case GreaterThan: return ">";
- case GreaterThanOrEquals: return ">=";
- default: return NULL;
- }
+const binop_info_t binop_info[NUM_AST_TAGS] = {
+ [Power] = {"power", "^"},
+ [PowerUpdate] = {"power", "^="},
+ [Multiply] = {"times", "*"},
+ [MultiplyUpdate] = {"times", "*="},
+ [Divide] = {"divided_by", "/"},
+ [DivideUpdate] = {"divided_by", "/="},
+ [Mod] = {"modulo", "mod"},
+ [ModUpdate] = {"modulo", "mod="},
+ [Mod1] = {"modulo1", "mod1"},
+ [Mod1Update] = {"modulo1", "mod1="},
+ [Plus] = {"plus", "+"},
+ [PlusUpdate] = {"plus", "+="},
+ [Minus] = {"minus", "-"},
+ [MinusUpdate] = {"minus", "-="},
+ [Concat] = {"concatenated_with", "++"},
+ [ConcatUpdate] = {"concatenated_with", "++="},
+ [LeftShift] = {"left_shifted", "<<"},
+ [LeftShiftUpdate] = {"left_shifted", "<<="},
+ [RightShift] = {"right_shifted", ">>"},
+ [RightShiftUpdate] = {"right_shifted", ">>="},
+ [UnsignedLeftShift] = {"unsigned_left_shifted", NULL},
+ [UnsignedLeftShiftUpdate] = {"unsigned_left_shifted", NULL},
+ [UnsignedRightShift] = {"unsigned_right_shifted", NULL},
+ [UnsignedRightShiftUpdate] = {"unsigned_right_shifted", NULL},
+ [And] = {"bit_and", "and"},
+ [AndUpdate] = {"bit_and", "and="},
+ [Or] = {"bit_or", "or"},
+ [OrUpdate] = {"bit_or", "or="},
+ [Xor] = {"bit_xor", "xor"},
+ [XorUpdate] = {"bit_xor", "xor="},
+ [Equals] = {NULL, "=="},
+ [NotEquals] = {NULL, "!="},
+ [LessThan] = {NULL, "<"},
+ [LessThanOrEquals] = {NULL, "<="},
+ [GreaterThan] = {NULL, ">"},
+ [GreaterThanOrEquals] = {NULL, ">="},
+ [Min] = {NULL, "_min_"},
+ [Max] = {NULL, "_max_"},
};
static Text_t ast_list_to_sexp(ast_list_t *asts);
@@ -86,6 +85,8 @@ static Text_t tags_to_sexp(tag_ast_t *tags);
static Text_t optional_sexp(const char *tag, ast_t *ast);
static Text_t optional_type_sexp(const char *tag, type_ast_t *ast);
+static Text_t quoted_text(const char *text) { return Text$quoted(Text$from_str(text), false, Text("\"")); }
+
Text_t ast_list_to_sexp(ast_list_t *asts) {
Text_t c = EMPTY_TEXT;
for (; asts; asts = asts->next) {
@@ -175,8 +176,8 @@ Text_t ast_to_sexp(ast_t *ast) {
T(None, "(None)");
T(Bool, "(Bool ", data.b ? "yes" : "no", ")");
T(Var, "(Var ", quoted_text(data.name), ")");
- T(Int, "(Int ", quoted_text(ast_source(ast)), ")");
- T(Num, "(Num ", quoted_text(ast_source(ast)), ")");
+ T(Int, "(Int ", Text$quoted(ast_source(ast), false, Text("\"")), ")");
+ T(Num, "(Num ", Text$quoted(ast_source(ast), false, Text("\"")), ")");
T(TextLiteral, Text$quoted(data.text, false, Text("\"")));
T(TextJoin, "(Text", data.lang ? Texts(" :lang ", quoted_text(data.lang)) : EMPTY_TEXT,
ast_list_to_sexp(data.children), ")");
@@ -255,7 +256,7 @@ Text_t ast_to_sexp(ast_t *ast) {
")");
T(When, "(When ", ast_to_sexp(data.subject), when_clauses_to_sexp(data.clauses),
optional_sexp("else", data.else_body), ")");
- T(Reduction, "(Reduction ", quoted_text(binop_method_name(data.op)), " ", ast_to_sexp(data.key), " ",
+ T(Reduction, "(Reduction ", quoted_text(binop_info[data.op].operator), " ", ast_to_sexp(data.key), " ",
ast_to_sexp(data.iter), ")");
T(Skip, "(Skip ", quoted_text(data.target), ")");
T(Stop, "(Stop ", quoted_text(data.target), ")");
@@ -285,13 +286,9 @@ Text_t ast_to_sexp(ast_t *ast) {
const char *ast_to_sexp_str(ast_t *ast) { return Text$as_c_string(ast_to_sexp(ast)); }
-const char *ast_source(ast_t *ast) {
- if (!ast) return NULL;
- size_t len = (size_t)(ast->end - ast->start);
- char *source = GC_MALLOC_ATOMIC(len + 1);
- memcpy(source, ast->start, len);
- source[len] = '\0';
- return source;
+OptionalText_t ast_source(ast_t *ast) {
+ if (ast == NULL || ast->start == NULL || ast->end == NULL) return NONE_TEXT;
+ return Text$from_strn(ast->start, (size_t)(ast->end - ast->start));
}
PUREFUNC bool is_idempotent(ast_t *ast) {
@@ -400,6 +397,8 @@ void visit_topologically(ast_list_t *asts, Closure_t fn) {
CONSTFUNC bool is_binary_operation(ast_t *ast) {
switch (ast->tag) {
+ case Min:
+ case Max:
case BINOP_CASES: return true;
default: return false;
}
diff --git a/src/ast.h b/src/ast.h
index bf22c9f6..7396555e 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -65,6 +65,8 @@ typedef struct ast_list_s {
} ast_list_t;
typedef struct arg_ast_s {
+ file_t *file;
+ const char *start, *end;
const char *name, *alias;
type_ast_t *type;
ast_t *value;
@@ -88,6 +90,7 @@ typedef enum {
} type_ast_e;
typedef struct tag_ast_s {
+ const char *start, *end;
const char *name;
arg_ast_t *fields;
struct tag_ast_s *next;
@@ -160,7 +163,12 @@ struct type_ast_s {
case MinusUpdate: \
case ConcatUpdate: \
case LeftShiftUpdate: \
- case UnsignedLeftShiftUpdate
+ case UnsignedLeftShiftUpdate: \
+ case RightShiftUpdate: \
+ case UnsignedRightShiftUpdate: \
+ case AndUpdate: \
+ case OrUpdate: \
+ case XorUpdate
#define UPDATE_CASES \
PowerUpdate: \
case MultiplyUpdate: \
@@ -271,6 +279,7 @@ typedef enum {
Extend,
ExplicitlyTyped,
} ast_e;
+#define NUM_AST_TAGS (ExplicitlyTyped + 1)
struct ast_s {
ast_e tag;
@@ -470,7 +479,16 @@ struct ast_s {
} __data;
};
-const char *ast_source(ast_t *ast);
+extern const int op_tightness[NUM_AST_TAGS];
+
+typedef struct {
+ const char *method_name;
+ const char *operator;
+} binop_info_t;
+
+extern const binop_info_t binop_info[NUM_AST_TAGS];
+
+OptionalText_t ast_source(ast_t *ast);
Text_t ast_to_sexp(ast_t *ast);
const char *ast_to_sexp_str(ast_t *ast);
@@ -479,7 +497,5 @@ Text_t type_ast_to_sexp(type_ast_t *ast);
PUREFUNC bool is_idempotent(ast_t *ast);
void visit_topologically(ast_list_t *ast, Closure_t fn);
CONSTFUNC bool is_update_assignment(ast_t *ast);
-CONSTFUNC const char *binop_method_name(ast_e tag);
-CONSTFUNC const char *binop_operator(ast_e tag);
CONSTFUNC ast_e binop_tag(ast_e tag);
CONSTFUNC bool is_binary_operation(ast_t *ast);
diff --git a/src/compile/assertions.c b/src/compile/assertions.c
index ce9abcbc..0f1d27b6 100644
--- a/src/compile/assertions.c
+++ b/src/compile/assertions.c
@@ -53,27 +53,26 @@ Text_t compile_assertion(env_t *env, ast_t *ast) {
ast_t *var_comparison = new (ast_t, .file = expr->file, .start = expr->start, .end = expr->end,
.tag = expr->tag, .__data.Equals = {.lhs = lhs_var, .rhs = rhs_var});
int64_t line = get_line_number(ast->file, ast->start);
- return Texts("{ // assertion\n", compile_declaration(operand_t, Text("_lhs")), " = ",
- compile_to_type(env, cmp.lhs, operand_t), ";\n", "\n#line ", String(line), "\n",
- compile_declaration(operand_t, Text("_rhs")), " = ", compile_to_type(env, cmp.rhs, operand_t),
- ";\n", "\n#line ", String(line), "\n", "if (!(", compile_condition(env, var_comparison), "))\n",
- "#line ", String(line), "\n",
- Texts("fail_source(", quoted_str(ast->file->filename), ", ",
- String((int64_t)(expr->start - expr->file->text)), ", ",
- String((int64_t)(expr->end - expr->file->text)), ", ",
- message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")")
- : Text("\"This assertion failed!\""),
- ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")),
- ", "
- "\" ",
- failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"),
- "}\n");
+ return Texts(
+ "{ // assertion\n", compile_declaration(operand_t, Text("_lhs")), " = ",
+ compile_to_type(env, cmp.lhs, operand_t), ";\n", "\n#line ", line, "\n",
+ compile_declaration(operand_t, Text("_rhs")), " = ", compile_to_type(env, cmp.rhs, operand_t), ";\n",
+ "\n#line ", line, "\n", "if (!(", compile_condition(env, var_comparison), "))\n", "#line ", line, "\n",
+ Texts("fail_source(", quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text),
+ ", ", (int64_t)(expr->end - expr->file->text), ", ",
+ message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")")
+ : Text("\"This assertion failed!\""),
+ ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")),
+ ", "
+ "\" ",
+ failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"),
+ "}\n");
}
default: {
int64_t line = get_line_number(ast->file, ast->start);
- return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", String(line), "\n", "fail_source(",
- quoted_str(ast->file->filename), ", ", String((int64_t)(expr->start - expr->file->text)), ", ",
- String((int64_t)(expr->end - expr->file->text)), ", ",
+ return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", line, "\n", "fail_source(",
+ quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text), ", ",
+ (int64_t)(expr->end - expr->file->text), ", ",
message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")")
: Text("\"This assertion failed!\""),
");\n");
diff --git a/src/compile/assignments.c b/src/compile/assignments.c
index ab28b972..3cb60fd5 100644
--- a/src/compile/assignments.c
+++ b/src/compile/assignments.c
@@ -119,12 +119,12 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) {
"stack memory.");
env_t *val_env = with_enum_scope(env, lhs_t);
Text_t val = compile_to_type(val_env, value->ast, lhs_t);
- code = Texts(code, compile_type(lhs_t), " $", String(i), " = ", val, ";\n");
+ code = Texts(code, compile_type(lhs_t), " $", i, " = ", val, ";\n");
i += 1;
}
i = 1;
for (ast_list_t *target = assign->targets; target; target = target->next) {
- code = Texts(code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n");
+ code = Texts(code, compile_assignment(env, target->ast, Texts("$", i)), ";\n");
i += 1;
}
return Texts(code, "\n}");
@@ -171,8 +171,7 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) {
")");
} else {
return Texts("List_lvalue(", compile_type(item_type), ", ", target_code, ", ", index_code, ", ",
- String((int)(ast->start - ast->file->text)), ", ",
- String((int)(ast->end - ast->file->text)), ")");
+ (int64_t)(ast->start - ast->file->text), ", ", (int64_t)(ast->end - ast->file->text), ")");
}
} else if (container_t->tag == TableType) {
DeclareMatch(table_type, container_t, TableType);
diff --git a/src/compile/binops.c b/src/compile/binops.c
index 87fd2c7a..ed4aaeba 100644
--- a/src/compile/binops.c
+++ b/src/compile/binops.c
@@ -67,7 +67,7 @@ Text_t compile_binary_op(env_t *env, ast_t *ast) {
}
}
} else if ((ast->tag == Divide || ast->tag == Mod || ast->tag == Mod1) && is_numeric_type(rhs_t)) {
- b = get_namespace_binding(env, binop.lhs, binop_method_name(ast->tag));
+ b = get_namespace_binding(env, binop.lhs, binop_info[ast->tag].method_name);
if (b && b->type->tag == FunctionType) {
DeclareMatch(fn, b->type, FunctionType);
if (type_eq(fn->ret, lhs_t)) {
diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c
index d73664de..d7531261 100644
--- a/src/compile/comparisons.c
+++ b/src/compile/comparisons.c
@@ -7,6 +7,18 @@
#include "../typecheck.h"
#include "compilation.h"
+static CONSTFUNC const char *comparison_operator(ast_e tag) {
+ switch (tag) {
+ case Equals: return "==";
+ case NotEquals: return "!=";
+ case LessThan: return "<";
+ case LessThanOrEquals: return "<=";
+ case GreaterThan: return ">";
+ case GreaterThanOrEquals: return ">=";
+ default: return NULL;
+ }
+}
+
Text_t compile_comparison(env_t *env, ast_t *ast) {
switch (ast->tag) {
@@ -75,7 +87,7 @@ Text_t compile_comparison(env_t *env, ast_t *ast) {
if (ast->tag == Compare)
return Texts("generic_compare(stack(", lhs, "), stack(", rhs, "), ", compile_type_info(operand_t), ")");
- const char *op = binop_operator(ast->tag);
+ const char *op = comparison_operator(ast->tag);
switch (operand_t->tag) {
case BigIntType: return Texts("(Int$compare_value(", lhs, ", ", rhs, ") ", op, " 0)");
case BoolType:
diff --git a/src/compile/conditionals.c b/src/compile/conditionals.c
index 23d2a177..f9dfa751 100644
--- a/src/compile/conditionals.c
+++ b/src/compile/conditionals.c
@@ -59,7 +59,7 @@ Text_t compile_if_statement(env_t *env, ast_t *ast) {
code = Texts(code, compile_block(nonnull_scope, if_->body));
if (if_->else_body) {
- Text_t label = Texts("_falsey_", String((int64_t)(ast->start - ast->file->text)));
+ Text_t label = Texts("_falsey_", (int64_t)(ast->start - ast->file->text));
code = Texts(code, "else goto ", label,
";\n"
"} else {\n",
diff --git a/src/compile/doctests.c b/src/compile/doctests.c
index 872684ed..20081ae7 100644
--- a/src/compile/doctests.c
+++ b/src/compile/doctests.c
@@ -4,7 +4,6 @@
#include "../config.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
-#include "../stdlib/print.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "../typecheck.h"
@@ -69,12 +68,12 @@ Text_t compile_doctest(env_t *env, ast_t *ast) {
if (target == assign->targets) expr_t = lhs_t;
env_t *val_scope = with_enum_scope(env, lhs_t);
Text_t val_code = compile_to_type(val_scope, value->ast, lhs_t);
- test_code = Texts(test_code, compile_type(lhs_t), " $", String(i), " = ", val_code, ";\n");
+ test_code = Texts(test_code, compile_type(lhs_t), " $", i, " = ", val_code, ";\n");
i += 1;
}
i = 1;
for (ast_list_t *target = assign->targets; target; target = target->next) {
- test_code = Texts(test_code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n");
+ test_code = Texts(test_code, compile_assignment(env, target->ast, Texts("$", i)), ";\n");
i += 1;
}
@@ -104,16 +103,16 @@ Text_t compile_doctest(env_t *env, ast_t *ast) {
if (test->expected) {
return Texts(setup, "test(", compile_type(expr_t), ", ", test_code, ", ",
compile_to_type(env, test->expected, expr_t), ", ", compile_type_info(expr_t), ", ",
- String((int64_t)(test->expr->start - test->expr->file->text)), ", ",
- String((int64_t)(test->expr->end - test->expr->file->text)), ");");
+ (int64_t)(test->expr->start - test->expr->file->text), ", ",
+ (int64_t)(test->expr->end - test->expr->file->text), ");");
} else {
if (expr_t->tag == VoidType || expr_t->tag == AbortType) {
return Texts(setup, "inspect_void(", test_code, ", ", compile_type_info(expr_t), ", ",
- String((int64_t)(test->expr->start - test->expr->file->text)), ", ",
- String((int64_t)(test->expr->end - test->expr->file->text)), ");");
+ (int64_t)(test->expr->start - test->expr->file->text), ", ",
+ (int64_t)(test->expr->end - test->expr->file->text), ");");
}
return Texts(setup, "inspect(", compile_type(expr_t), ", ", test_code, ", ", compile_type_info(expr_t), ", ",
- String((int64_t)(test->expr->start - test->expr->file->text)), ", ",
- String((int64_t)(test->expr->end - test->expr->file->text)), ");");
+ (int64_t)(test->expr->start - test->expr->file->text), ", ",
+ (int64_t)(test->expr->end - test->expr->file->text), ");");
}
}
diff --git a/src/compile/enums.c b/src/compile/enums.c
index d9c29f26..f5500831 100644
--- a/src/compile/enums.c
+++ b/src/compile/enums.c
@@ -30,12 +30,12 @@ Text_t compile_enum_typeinfo(env_t *env, ast_t *ast) {
type_t *t = Table$str_get(*env->types, def->name);
const char *metamethods = is_packed_data(t) ? "PackedDataEnum$metamethods" : "Enum$metamethods";
Text_t info = namespace_name(env, env->namespace, Texts(def->name, "$$info"));
- Text_t typeinfo = Texts("public const TypeInfo_t ", info, " = {", String((int64_t)type_size(t)), "u, ",
- String((int64_t)type_align(t)), "u, .metamethods=", metamethods,
- ", {.tag=EnumInfo, .EnumInfo={.name=\"", def->name,
- "\", "
- ".num_tags=",
- String((int64_t)num_tags), ", .tags=(NamedType_t[]){");
+ Text_t typeinfo =
+ Texts("public const TypeInfo_t ", info, " = {", (int64_t)type_size(t), "u, ", (int64_t)type_align(t),
+ "u, .metamethods=", metamethods, ", {.tag=EnumInfo, .EnumInfo={.name=\"", def->name,
+ "\", "
+ ".num_tags=",
+ (int64_t)num_tags, ", .tags=(NamedType_t[]){");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
const char *tag_type_name = String(def->name, "$", tag->name);
@@ -144,10 +144,10 @@ Text_t compile_empty_enum(type_t *t) {
assert(tag);
assert(tag->type);
if (Match(tag->type, StructType)->fields)
- return Texts("((", compile_type(t), "){.$tag=", String(tag->tag_value), ", .", tag->name, "=",
- compile_empty(tag->type), "})");
- else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", String(tag->tag_value), "})");
- else return Texts("((", compile_type(t), ")", String(tag->tag_value), ")");
+ 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, ")");
}
public
diff --git a/src/compile/expressions.c b/src/compile/expressions.c
index 2320474a..544b7723 100644
--- a/src/compile/expressions.c
+++ b/src/compile/expressions.c
@@ -152,7 +152,7 @@ Text_t compile(env_t *env, ast_t *ast) {
ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key;
ast_t *lhs = ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs;
ast_t *rhs = ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs;
- const char *key_name = "$";
+ const char *key_name = ast->tag == Min ? "_min_" : "_max_";
if (key == NULL) key = FakeAST(Var, key_name);
env_t *expr_env = fresh_scope(env);
@@ -237,7 +237,8 @@ Text_t compile(env_t *env, ast_t *ast) {
case Index: return compile_indexing(env, ast);
case InlineCCode: {
type_t *t = get_type(env, ast);
- if (t->tag == VoidType) return Texts("{\n", compile_statement(env, ast), "\n}");
+ if (Match(ast, InlineCCode)->type_ast != NULL) return Texts("({", compile_statement(env, ast), "; })");
+ else if (t->tag == VoidType) return Texts("{\n", compile_statement(env, ast), "\n}");
else return compile_statement(env, ast);
}
case Use: code_err(ast, "Compiling 'use' as expression!");
diff --git a/src/compile/files.c b/src/compile/files.c
index 2b0ac4bf..4d6fb1a8 100644
--- a/src/compile/files.c
+++ b/src/compile/files.c
@@ -12,7 +12,16 @@
#include "../types.h"
#include "compilation.h"
-static void initialize_vars_and_statics(env_t *env, ast_t *ast) {
+static void initialize_vars_and_statics(env_t *env, ast_t *ast);
+static void initialize_namespace(env_t *env, const char *name, ast_t *namespace);
+static Text_t compile_top_level_code(env_t *env, ast_t *ast);
+static Text_t compile_namespace(env_t *env, const char *name, ast_t *namespace);
+
+void initialize_namespace(env_t *env, const char *name, ast_t *namespace) {
+ initialize_vars_and_statics(namespace_env(env, name), namespace);
+}
+
+void initialize_vars_and_statics(env_t *env, ast_t *ast) {
if (!ast) return;
for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
@@ -34,17 +43,13 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast) {
Texts(full_name, " = ", val_code, ",\n", initialized_name, " = true;\n")));
}
} else if (stmt->ast->tag == StructDef) {
- initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, StructDef)->name),
- Match(stmt->ast, StructDef)->namespace);
+ initialize_namespace(env, Match(stmt->ast, StructDef)->name, Match(stmt->ast, StructDef)->namespace);
} else if (stmt->ast->tag == EnumDef) {
- initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, EnumDef)->name),
- Match(stmt->ast, EnumDef)->namespace);
+ initialize_namespace(env, Match(stmt->ast, EnumDef)->name, Match(stmt->ast, EnumDef)->namespace);
} else if (stmt->ast->tag == LangDef) {
- initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, LangDef)->name),
- Match(stmt->ast, LangDef)->namespace);
+ initialize_namespace(env, Match(stmt->ast, LangDef)->name, Match(stmt->ast, LangDef)->namespace);
} else if (stmt->ast->tag == Extend) {
- initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, Extend)->name),
- Match(stmt->ast, Extend)->body);
+ initialize_namespace(env, Match(stmt->ast, Extend)->name, Match(stmt->ast, Extend)->body);
} else if (stmt->ast->tag == Use) {
continue;
} else {
@@ -54,7 +59,12 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast) {
}
}
-static Text_t compile_top_level_code(env_t *env, ast_t *ast) {
+Text_t compile_namespace(env_t *env, const char *name, ast_t *namespace) {
+ env_t *ns_env = namespace_env(env, name);
+ return namespace ? compile_top_level_code(ns_env, namespace) : EMPTY_TEXT;
+}
+
+Text_t compile_top_level_code(env_t *env, ast_t *ast) {
if (!ast) return EMPTY_TEXT;
switch (ast->tag) {
@@ -95,15 +105,15 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) {
return compile_function(env, name_code, ast, &env->code->staticdefs);
}
case ConvertDef: {
- type_t *type = get_function_def_type(env, ast);
- const char *name = get_type_name(Match(type, FunctionType)->ret);
+ type_t *type = get_function_return_type(env, ast);
+ const char *name = get_type_name(type);
if (!name)
code_err(ast,
"Conversions are only supported for text, struct, and enum "
"types, not ",
- type_to_str(Match(type, FunctionType)->ret));
+ type_to_str(type));
Text_t name_code =
- namespace_name(env, env->namespace, Texts(name, "$", String(get_line_number(ast->file, ast->start))));
+ namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start)));
return compile_function(env, name_code, ast, &env->code->staticdefs);
}
case StructDef: {
@@ -111,24 +121,21 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) {
type_t *t = Table$str_get(*env->types, def->name);
assert(t && t->tag == StructType);
Text_t code = compile_struct_typeinfo(env, t, def->name, def->fields, def->secret, def->opaque);
- env_t *ns_env = namespace_env(env, def->name);
- return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT);
+ return Texts(code, compile_namespace(env, def->name, def->namespace));
}
case EnumDef: {
DeclareMatch(def, ast, EnumDef);
Text_t code = compile_enum_typeinfo(env, ast);
code = Texts(code, compile_enum_constructors(env, ast));
- env_t *ns_env = namespace_env(env, def->name);
- return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT);
+ return Texts(code, compile_namespace(env, def->name, def->namespace));
}
case LangDef: {
DeclareMatch(def, ast, LangDef);
Text_t code =
Texts("public const TypeInfo_t ", namespace_name(env, env->namespace, Texts(def->name, "$$info")), " = {",
- String((int64_t)sizeof(Text_t)), ", ", String((int64_t)__alignof__(Text_t)),
+ (int64_t)sizeof(Text_t), ", ", (int64_t)__alignof__(Text_t),
", .metamethods=Text$metamethods, .tag=TextInfo, .TextInfo={", quoted_str(def->name), "}};\n");
- env_t *ns_env = namespace_env(env, def->name);
- return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT);
+ return Texts(code, compile_namespace(env, def->name, def->namespace));
}
case Extend: {
DeclareMatch(extend, ast, Extend);
diff --git a/src/compile/functions.c b/src/compile/functions.c
index 01de26e3..f04a3b59 100644
--- a/src/compile/functions.c
+++ b/src/compile/functions.c
@@ -32,7 +32,7 @@ Text_t compile_function_declaration(env_t *env, ast_t *ast) {
if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code);
Text_t name = namespace_name(env, env->namespace, Text$from_str(decl_name));
if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name))
- name = namespace_name(env, env->namespace, Text$from_str(String(get_line_number(ast->file, ast->start))));
+ name = namespace_name(env, env->namespace, Texts(get_line_number(ast->file, ast->start)));
return Texts(ret_type_code, " ", name, arg_signature, ";\n");
}
@@ -56,8 +56,7 @@ Text_t compile_convert_declaration(env_t *env, ast_t *ast) {
"Conversions are only supported for text, struct, and enum "
"types, not ",
type_to_str(ret_t));
- Text_t name_code =
- namespace_name(env, env->namespace, Texts(name, "$", String(get_line_number(ast->file, ast->start))));
+ Text_t name_code = namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start)));
return Texts(ret_type_code, " ", name_code, arg_signature, ";\n");
}
@@ -248,7 +247,7 @@ 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$", String(lambda->id)));
+ Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id));
env_t *body_scope = fresh_scope(env);
body_scope->deferred = NULL;
@@ -257,18 +256,7 @@ Text_t compile_lambda(env_t *env, ast_t *ast) {
set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name));
}
- type_t *ret_t = get_type(body_scope, lambda->body);
- if (ret_t->tag == ReturnType) ret_t = Match(ret_t, ReturnType)->ret;
-
- if (lambda->ret_type) {
- type_t *declared = parse_type_ast(env, lambda->ret_type);
- if (can_promote(ret_t, declared)) ret_t = declared;
- else
- code_err(ast, "This function was declared to return a value of type ", type_to_str(declared),
- ", but actually returns a value of type ", type_to_str(ret_t));
- }
-
- body_scope->fn_ret = ret_t;
+ 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
@@ -290,6 +278,7 @@ Text_t compile_lambda(env_t *env, ast_t *ast) {
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);
@@ -624,7 +613,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
bool is_private = false;
const char *function_name;
arg_ast_t *args;
- type_t *ret_t;
+ type_t *ret_t = get_function_return_type(env, ast);
ast_t *body;
ast_t *cache;
bool is_inline;
@@ -633,14 +622,12 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
function_name = Match(fndef->name, Var)->name;
is_private = function_name[0] == '_';
args = fndef->args;
- ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType);
body = fndef->body;
cache = fndef->cache;
is_inline = fndef->is_inline;
} else {
DeclareMatch(convertdef, ast, ConvertDef);
args = convertdef->args;
- ret_t = convertdef->ret_type ? parse_type_ast(env, convertdef->ret_type) : Type(VoidType);
function_name = get_type_name(ret_t);
if (!function_name)
code_err(ast,
@@ -690,7 +677,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name));
}
- body_scope->fn_ret = ret_t;
+ body_scope->fn = ast;
type_t *body_type = get_type(body_scope, body);
if (ret_t->tag == AbortType) {
@@ -736,7 +723,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
// FIXME: this currently just deletes the first entry, but this
// should be more like a least-recently-used cache eviction policy
// or least-frequently-used
- pop_code = Texts("if (cache.entries.length > ", String(cache_size.value),
+ pop_code = Texts("if (cache.entries.length > ", cache_size.value,
") Table$remove(&cache, cache.entries.data + "
"cache.entries.stride*0, table_type);\n");
}
@@ -772,14 +759,13 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
int64_t num_fields = used_names.entries.length;
const char *metamethods = is_packed_data(t) ? "PackedData$metamethods" : "Struct$metamethods";
- Text_t args_typeinfo =
- Texts("((TypeInfo_t[1]){{.size=sizeof(args), "
- ".align=__alignof__(args), .metamethods=",
- metamethods,
- ", .tag=StructInfo, "
- ".StructInfo.name=\"FunctionArguments\", "
- ".StructInfo.num_fields=",
- String(num_fields), ", .StructInfo.fields=(NamedType_t[", String(num_fields), "]){");
+ Text_t args_typeinfo = Texts("((TypeInfo_t[1]){{.size=sizeof(args), "
+ ".align=__alignof__(args), .metamethods=",
+ metamethods,
+ ", .tag=StructInfo, "
+ ".StructInfo.name=\"FunctionArguments\", "
+ ".StructInfo.num_fields=",
+ num_fields, ", .StructInfo.fields=(NamedType_t[", num_fields, "]){");
Text_t args_type = Text("struct { ");
for (arg_t *f = fields; f; f = f->next) {
args_typeinfo = Texts(args_typeinfo, "{\"", f->name, "\", ", compile_type_info(f->type), "}");
diff --git a/src/compile/headers.c b/src/compile/headers.c
index 8c0863ee..6dc69f03 100644
--- a/src/compile/headers.c
+++ b/src/compile/headers.c
@@ -174,10 +174,10 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast)
module_info_t mod = get_module_info(ast);
glob_t tm_files;
const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name;
- if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE,
- NULL, &tm_files)
+ if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
+ &tm_files)
!= 0) {
- if (!try_install_module(mod)) code_err(ast, "Could not find library");
+ if (!try_install_module(mod, true)) code_err(ast, "Could not find library");
}
Text_t includes = EMPTY_TEXT;
diff --git a/src/compile/indexing.c b/src/compile/indexing.c
index e99feeb2..39af1160 100644
--- a/src/compile/indexing.c
+++ b/src/compile/indexing.c
@@ -44,8 +44,8 @@ Text_t compile_indexing(env_t *env, ast_t *ast) {
return Texts("List_get_unchecked(", compile_type(item_type), ", ", list, ", ", index_code, ")");
else
return Texts("List_get(", compile_type(item_type), ", ", list, ", ", index_code, ", ",
- String((int64_t)(indexing->index->start - f->text)), ", ",
- String((int64_t)(indexing->index->end - f->text)), ")");
+ (int64_t)(indexing->index->start - f->text), ", ", (int64_t)(indexing->index->end - f->text),
+ ")");
} else if (container_t->tag == TableType) {
DeclareMatch(table_type, container_t, TableType);
if (indexing->unchecked) code_err(ast, "Table indexes cannot be unchecked");
diff --git a/src/compile/lists.c b/src/compile/lists.c
index 5df39863..d9d71278 100644
--- a/src/compile/lists.c
+++ b/src/compile/lists.c
@@ -33,7 +33,7 @@ Text_t compile_typed_list(env_t *env, ast_t *ast, type_t *list_type) {
{
env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env;
if (is_incomplete_type(item_type)) code_err(ast, "This list's type can't be inferred!");
- Text_t code = Texts("TypedListN(", compile_type(item_type), ", ", String(n));
+ Text_t code = Texts("TypedListN(", compile_type(item_type), ", ", n);
for (ast_list_t *item = list->items; item; item = item->next) {
code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type));
}
diff --git a/src/compile/loops.c b/src/compile/loops.c
index c742589a..332024f4 100644
--- a/src/compile/loops.c
+++ b/src/compile/loops.c
@@ -405,7 +405,7 @@ Text_t compile_skip(env_t *env, ast_t *ast) {
if (matched) {
if (ctx->skip_label.length == 0) {
static int64_t skip_label_count = 1;
- ctx->skip_label = Texts("skip_", String(skip_label_count));
+ ctx->skip_label = Texts("skip_", skip_label_count);
++skip_label_count;
}
Text_t code = EMPTY_TEXT;
@@ -431,7 +431,7 @@ Text_t compile_stop(env_t *env, ast_t *ast) {
if (matched) {
if (ctx->stop_label.length == 0) {
static int64_t stop_label_count = 1;
- ctx->stop_label = Texts("stop_", String(stop_label_count));
+ ctx->stop_label = Texts("stop_", stop_label_count);
++stop_label_count;
}
Text_t code = EMPTY_TEXT;
diff --git a/src/compile/optionals.c b/src/compile/optionals.c
index b3d94005..cd50b1bf 100644
--- a/src/compile/optionals.c
+++ b/src/compile/optionals.c
@@ -125,10 +125,9 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) {
type_t *t = get_type(env, value);
Text_t value_code = compile(env, 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 ", String(line), "\n", "fail_source(",
- quoted_str(ast->file->filename), ", ", String((int64_t)(value->start - value->file->text)), ", ",
- String((int64_t)(value->end - value->file->text)), ", ",
- "\"This was expected to be a value, but it's none\");\n", optional_into_nonnone(t, Text("opt")),
- "; })");
+ 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", optional_into_nonnone(t, Text("opt")), "; })");
}
diff --git a/src/compile/promotions.c b/src/compile/promotions.c
index fcedce3f..6595e5aa 100644
--- a/src/compile/promotions.c
+++ b/src/compile/promotions.c
@@ -43,12 +43,12 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede
// Automatic optional checking for nums:
if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) {
int64_t line = get_line_number(ast->file, ast->start);
- *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (",
- check_none(actual, Text("opt")), ")\n", "#line ", String(line), "\n", "fail_source(",
- quoted_str(ast->file->filename), ", ", String((int64_t)(ast->start - ast->file->text)), ", ",
- String((int64_t)(ast->end - ast->file->text)), ", ",
- "\"This was expected to be a value, but it's none\");\n",
- optional_into_nonnone(actual, Text("opt")), "; })");
+ *code =
+ Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (",
+ check_none(actual, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
+ quoted_str(ast->file->filename), ", ", (int64_t)(ast->start - ast->file->text), ", ",
+ (int64_t)(ast->end - ast->file->text), ", ", "\"This was expected to be a value, but it's none\");\n",
+ optional_into_nonnone(actual, Text("opt")), "; })");
return true;
}
diff --git a/src/compile/reductions.c b/src/compile/reductions.c
index e0477a9c..438e072b 100644
--- a/src/compile/reductions.c
+++ b/src/compile/reductions.c
@@ -12,6 +12,7 @@ public
Text_t compile_reduction(env_t *env, ast_t *ast) {
DeclareMatch(reduction, ast, Reduction);
ast_e op = reduction->op;
+ const char *op_str = binop_info[op].operator;
type_t *iter_t = get_type(env, reduction->iter);
type_t *item_t = get_iterated_type(iter_t);
@@ -29,7 +30,7 @@ Text_t compile_reduction(env_t *env, ast_t *ast) {
type_t *item_value_type = item_t;
ast_t *item_value = item;
if (reduction->key) {
- set_binding(body_scope, "$", item_t, compile(body_scope, item));
+ set_binding(body_scope, op_str, item_t, compile(body_scope, item));
item_value = reduction->key;
item_value_type = get_type(body_scope, reduction->key);
}
@@ -67,7 +68,7 @@ Text_t compile_reduction(env_t *env, ast_t *ast) {
ast_e cmp_op = op == Min ? LessThan : GreaterThan;
if (reduction->key) {
env_t *key_scope = fresh_scope(env);
- set_binding(key_scope, "$", item_t, item_code);
+ set_binding(key_scope, op_str, item_t, item_code);
type_t *key_type = get_type(key_scope, reduction->key);
Text_t superlative_key = op == Min ? Text("min_key") : Text("max_key");
code = Texts(code, compile_declaration(key_type, superlative_key), ";\n");
@@ -111,7 +112,7 @@ Text_t compile_reduction(env_t *env, ast_t *ast) {
type_t *reduction_type = Match(get_type(env, ast), OptionalType)->type;
ast_t *item_value = item;
if (reduction->key) {
- set_binding(body_scope, "$", item_t, compile(body_scope, item));
+ set_binding(body_scope, op_str, item_t, compile(body_scope, item));
item_value = reduction->key;
}
diff --git a/src/compile/sets.c b/src/compile/sets.c
index d33677cc..3346a9aa 100644
--- a/src/compile/sets.c
+++ b/src/compile/sets.c
@@ -25,7 +25,7 @@ Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type) {
}
{ // No comprehension:
- Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", String(n));
+ Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", n);
env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env;
for (ast_list_t *item = set->items; item; item = item->next) {
code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type));
diff --git a/src/compile/statements.c b/src/compile/statements.c
index 7c58559d..bde9ae36 100644
--- a/src/compile/statements.c
+++ b/src/compile/statements.c
@@ -22,7 +22,15 @@ public
Text_t with_source_info(env_t *env, ast_t *ast, Text_t code) {
if (code.length == 0 || !ast || !ast->file || !env->do_source_mapping) return code;
int64_t line = get_line_number(ast->file, ast->start);
- return Texts("\n#line ", String(line), "\n", code);
+ return Texts("\n#line ", line, "\n", code);
+}
+
+static Text_t compile_simple_update_assignment(env_t *env, ast_t *ast, const char *op) {
+ binary_operands_t update = BINARY_OPERANDS(ast);
+ type_t *lhs_t = get_type(env, update.lhs);
+ if (is_idempotent(update.lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType))
+ return Texts(compile_lvalue(env, update.lhs), " ", op, "= ", compile_to_type(env, update.rhs, lhs_t), ";");
+ return compile_update_assignment(env, ast);
}
static Text_t _compile_statement(env_t *env, ast_t *ast) {
@@ -47,41 +55,12 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
}
}
case Assign: return compile_assignment_statement(env, ast);
- case PlusUpdate: {
- DeclareMatch(update, ast, PlusUpdate);
- type_t *lhs_t = get_type(env, update->lhs);
- if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType))
- return Texts(compile_lvalue(env, update->lhs), " += ", compile_to_type(env, update->rhs, lhs_t), ";");
- return compile_update_assignment(env, ast);
- }
- case MinusUpdate: {
- DeclareMatch(update, ast, MinusUpdate);
- type_t *lhs_t = get_type(env, update->lhs);
- if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType))
- return Texts(compile_lvalue(env, update->lhs), " -= ", compile_to_type(env, update->rhs, lhs_t), ";");
- return compile_update_assignment(env, ast);
- }
- case MultiplyUpdate: {
- DeclareMatch(update, ast, MultiplyUpdate);
- type_t *lhs_t = get_type(env, update->lhs);
- if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType))
- return Texts(compile_lvalue(env, update->lhs), " *= ", compile_to_type(env, update->rhs, lhs_t), ";");
- return compile_update_assignment(env, ast);
- }
- case DivideUpdate: {
- DeclareMatch(update, ast, DivideUpdate);
- type_t *lhs_t = get_type(env, update->lhs);
- if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType))
- return Texts(compile_lvalue(env, update->lhs), " /= ", compile_to_type(env, update->rhs, lhs_t), ";");
- return compile_update_assignment(env, ast);
- }
- case ModUpdate: {
- DeclareMatch(update, ast, ModUpdate);
- type_t *lhs_t = get_type(env, update->lhs);
- if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType))
- return Texts(compile_lvalue(env, update->lhs), " %= ", compile_to_type(env, update->rhs, lhs_t), ";");
- return compile_update_assignment(env, ast);
- }
+ case PlusUpdate: return compile_simple_update_assignment(env, ast, "+");
+ case MinusUpdate: return compile_simple_update_assignment(env, ast, "-");
+ case MultiplyUpdate: return compile_simple_update_assignment(env, ast, "*");
+ case DivideUpdate: return compile_simple_update_assignment(env, ast, "/");
+ case ModUpdate: return compile_simple_update_assignment(env, ast, "%");
+
case PowerUpdate:
case Mod1Update:
case ConcatUpdate:
@@ -121,7 +100,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
if (Text$starts_with(entry->b->code, Text("userdata->"), NULL)) {
Table$str_set(defer_env->locals, entry->name, entry->b);
} else {
- Text_t defer_name = Texts("defer$", String(++defer_id), "$", entry->name);
+ Text_t defer_name = Texts("defer$", ++defer_id, "$", entry->name);
defer_id += 1;
code = Texts(code, compile_declaration(entry->b->type, defer_name), " = ", entry->b->code, ";\n");
set_binding(defer_env, entry->name, entry->b->type, defer_name);
@@ -131,7 +110,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
return code;
}
case Return: {
- if (!env->fn_ret) code_err(ast, "This return statement is not inside any function");
+ if (!env->fn) code_err(ast, "This return statement is not inside any function");
ast_t *ret = Match(ast, Return)->value;
Text_t code = EMPTY_TEXT;
@@ -139,22 +118,30 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
code = Texts(code, compile_statement(deferred->defer_env, deferred->block));
}
+ type_t *ret_type = get_function_return_type(env, env->fn);
if (ret) {
- if (env->fn_ret->tag == VoidType || env->fn_ret->tag == AbortType)
+ if (ret_type->tag == VoidType || ret_type->tag == AbortType)
code_err(ast, "This function is not supposed to return any values, "
"according to its type signature");
- env = with_enum_scope(env, env->fn_ret);
- Text_t value = compile_to_type(env, ret, env->fn_ret);
+ env = with_enum_scope(env, ret_type);
+ if (env->fn->tag == ConvertDef) {
+ type_t *value_type = get_type(env, ret);
+ if (!type_eq(value_type, ret_type)) {
+ code_err(ret, "This value is a ", type_to_text(value_type),
+ " but this conversion needs an explicit ", type_to_text(ret_type));
+ }
+ }
+ Text_t value = compile_to_type(env, ret, ret_type);
if (env->deferred) {
- code = Texts(compile_declaration(env->fn_ret, Text("ret")), " = ", value, ";\n", code);
+ code = Texts(compile_declaration(ret_type, Text("ret")), " = ", value, ";\n", code);
value = Text("ret");
}
return Texts(code, "return ", value, ";");
} else {
- if (env->fn_ret->tag != VoidType)
- code_err(ast, "This function expects you to return a ", type_to_str(env->fn_ret), " value");
+ if (ret_type->tag != VoidType)
+ code_err(ast, "This function expects you to return a ", type_to_text(ret_type), " value");
return Texts(code, "return;");
}
}
@@ -204,10 +191,10 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
module_info_t mod = get_module_info(ast);
glob_t tm_files;
const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name;
- if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE,
- NULL, &tm_files)
+ if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
+ &tm_files)
!= 0) {
- if (!try_install_module(mod)) code_err(ast, "Could not find library");
+ if (!try_install_module(mod, true)) code_err(ast, "Could not find library");
}
Text_t initialization = EMPTY_TEXT;
diff --git a/src/compile/structs.c b/src/compile/structs.c
index 526c11c7..8560a790 100644
--- a/src/compile/structs.c
+++ b/src/compile/structs.c
@@ -18,7 +18,7 @@ Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_
? Text$from_str(name)
: Texts("struct ", namespace_name(env, env->namespace, Texts(name, "$$struct")));
- int num_fields = 0;
+ int64_t num_fields = 0;
for (arg_ast_t *f = fields; f; f = f->next)
num_fields += 1;
const char *short_name = name;
@@ -33,9 +33,9 @@ Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_
", "
".tag=StructInfo, .StructInfo.name=\"",
short_name, "\"", is_secret ? Text(", .StructInfo.is_secret=true") : EMPTY_TEXT,
- is_opaque ? Text(", .StructInfo.is_opaque=true") : EMPTY_TEXT, ", .StructInfo.num_fields=", String(num_fields));
+ is_opaque ? Text(", .StructInfo.is_opaque=true") : EMPTY_TEXT, ", .StructInfo.num_fields=", num_fields);
if (fields) {
- typeinfo = Texts(typeinfo, ", .StructInfo.fields=(NamedType_t[", String(num_fields), "]){");
+ typeinfo = Texts(typeinfo, ", .StructInfo.fields=(NamedType_t[", num_fields, "]){");
for (arg_ast_t *f = fields; f; f = f->next) {
type_t *field_type = get_arg_ast_type(env, f);
typeinfo = Texts(typeinfo, "{\"", f->name, "\", ", compile_type_info(field_type), "}");
@@ -70,8 +70,7 @@ Text_t compile_struct_header(env_t *env, ast_t *ast) {
Text_t struct_code = def->external ? EMPTY_TEXT : Texts(type_code, " {\n", fields, "};\n");
type_t *t = Table$str_get(*env->types, def->name);
- Text_t unpadded_size =
- def->opaque ? Texts("sizeof(", type_code, ")") : Text$from_str(String((int64_t)unpadded_struct_size(t)));
+ Text_t unpadded_size = def->opaque ? Texts("sizeof(", type_code, ")") : Texts((int64_t)unpadded_struct_size(t));
Text_t typeinfo_code = Texts("extern const TypeInfo_t ", typeinfo_name, ";\n");
Text_t optional_code = EMPTY_TEXT;
if (!def->opaque) {
diff --git a/src/compile/tables.c b/src/compile/tables.c
index b955178e..dde8669a 100644
--- a/src/compile/tables.c
+++ b/src/compile/tables.c
@@ -43,7 +43,7 @@ Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) {
size_t n = 0;
for (ast_list_t *entry = table->entries; entry; entry = entry->next)
++n;
- code = Texts(code, ", ", String((int64_t)n));
+ code = Texts(code, ", ", (int64_t)n);
for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
DeclareMatch(e, entry->ast, TableEntry);
diff --git a/src/compile/types.c b/src/compile/types.c
index aa06e2fd..f6f276a5 100644
--- a/src/compile/types.c
+++ b/src/compile/types.c
@@ -23,10 +23,10 @@ Text_t compile_type(type_t *t) {
case ByteType: return Text("Byte_t");
case CStringType: return Text("const char*");
case BigIntType: return Text("Int_t");
- case IntType: return Texts("Int", String(Match(t, IntType)->bits), "_t");
+ case IntType: return Texts("Int", (int32_t)Match(t, IntType)->bits, "_t");
case NumType:
return Match(t, NumType)->bits == TYPE_NBITS64 ? Text("Num_t")
- : Texts("Num", String(Match(t, NumType)->bits), "_t");
+ : Texts("Num", (int32_t)Match(t, NumType)->bits, "_t");
case TextType: {
DeclareMatch(text, t, TextType);
if (!text->lang || streq(text->lang, "Text")) return Text("Text_t");
diff --git a/src/config.h b/src/config.h
index 1afbb3e5..8ee44200 100644
--- a/src/config.h
+++ b/src/config.h
@@ -8,10 +8,12 @@
#define GIT_VERSION "???"
#endif
-#ifndef TOMO_PREFIX
-#define TOMO_PREFIX "/usr/local"
+#ifndef TOMO_INSTALL
+#define TOMO_INSTALL "/usr/local"
#endif
+extern const char *TOMO_PATH;
+
#ifndef DEFAULT_C_COMPILER
#define DEFAULT_C_COMPILER "cc"
#endif
diff --git a/src/environment.c b/src/environment.c
index 5efedfbe..7ac54a7a 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -73,7 +73,9 @@ env_t *global_env(bool source_mapping) {
} ns_entry_t;
#define MAKE_TYPE(name, type, type_name, type_info, ...) \
- {name, type, type_name, type_info, TypedList(ns_entry_t, __VA_ARGS__)}
+ { \
+ name, type, type_name, type_info, TypedList(ns_entry_t, __VA_ARGS__) \
+ }
struct {
const char *name;
type_t *type;
@@ -736,7 +738,7 @@ PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args, bool
}
PUREFUNC binding_t *get_metamethod_binding(env_t *env, ast_e tag, ast_t *lhs, ast_t *rhs, type_t *ret) {
- const char *method_name = binop_method_name(tag);
+ const char *method_name = binop_info[tag].method_name;
if (!method_name) return NULL;
binding_t *b = get_namespace_binding(env, lhs, method_name);
if (!b || b->type->tag != FunctionType) return NULL;
diff --git a/src/environment.h b/src/environment.h
index 1ef9c1f9..c726508d 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -43,7 +43,7 @@ typedef struct env_s {
Text_t id_suffix;
Table_t *imports;
compilation_unit_t *code;
- type_t *fn_ret;
+ ast_t *fn;
loop_ctx_t *loop_ctx;
deferral_t *deferred;
Closure_t *comprehension_action;
diff --git a/src/formatter/args.c b/src/formatter/args.c
new file mode 100644
index 00000000..997a1e39
--- /dev/null
+++ b/src/formatter/args.c
@@ -0,0 +1,57 @@
+// Logic for formatting arguments and argument lists
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.h"
+#include "../stdlib/text.h"
+#include "formatter.h"
+#include "types.h"
+#include "utils.h"
+
+OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) {
+ if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT;
+ if (arg->name == NULL && arg->value) return must(format_inline_code(arg->value, comments));
+ Text_t code = Text$from_str(arg->name);
+ if (arg->type) code = Texts(code, ":", must(format_type(arg->type)));
+ if (arg->value) code = Texts(code, "=", must(format_inline_code(arg->value, comments)));
+ return code;
+}
+
+Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) {
+ OptionalText_t inline_arg = format_inline_arg(arg, comments);
+ if (inline_arg.length >= 0 && inline_arg.length <= MAX_WIDTH) return inline_arg;
+ if (arg->name == NULL && arg->value) return format_code(arg->value, comments, indent);
+ Text_t code = Text$from_str(arg->name);
+ if (arg->type) code = Texts(code, ":", format_type(arg->type));
+ if (arg->value) code = Texts(code, "=", format_code(arg->value, comments, indent));
+ return code;
+}
+
+OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) {
+ Text_t code = EMPTY_TEXT;
+ for (arg_ast_t *arg = args; arg; arg = arg->next) {
+ if (arg->name && arg->next && arg->type == arg->next->type && arg->value == arg->next->value) {
+ code = Texts(code, Text$from_str(arg->name), ",");
+ } else {
+ code = Texts(code, must(format_inline_arg(arg, comments)));
+ if (arg->next) code = Texts(code, ", ");
+ }
+ if (arg->next && range_has_comment(arg->end, arg->next->start, comments)) return NONE_TEXT;
+ }
+ return code;
+}
+
+Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) {
+ OptionalText_t inline_args = format_inline_args(args, comments);
+ if (inline_args.length >= 0 && inline_args.length <= MAX_WIDTH) return inline_args;
+ Text_t code = EMPTY_TEXT;
+ for (arg_ast_t *arg = args; arg; arg = arg->next) {
+ if (arg->name && arg->next && arg->type == arg->next->type && arg->value == arg->next->value) {
+ code = Texts(code, Text$from_str(arg->name), ",");
+ } else {
+ code = Texts(code, "\n", indent, single_indent, format_arg(arg, comments, Texts(indent, single_indent)));
+ if (args->next) code = Texts(code, ",");
+ }
+ }
+ return code;
+}
diff --git a/src/formatter/args.h b/src/formatter/args.h
new file mode 100644
index 00000000..c902684b
--- /dev/null
+++ b/src/formatter/args.h
@@ -0,0 +1,11 @@
+// Logic for formatting arguments and argument lists
+
+#pragma once
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+
+OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments);
+Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent);
+OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments);
+Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent);
diff --git a/src/formatter/enums.c b/src/formatter/enums.c
new file mode 100644
index 00000000..893f055b
--- /dev/null
+++ b/src/formatter/enums.c
@@ -0,0 +1,51 @@
+// Logic for formatting enums and enum tags
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.h"
+#include "../stdlib/text.h"
+#include "args.h"
+#include "utils.h"
+
+OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments) {
+ if (range_has_comment(tag->start, tag->end, comments)) return NONE_TEXT;
+ Text_t code = Text$from_str(tag->name);
+ if (tag->fields || tag->secret) {
+ code = Texts(code, "(", must(format_inline_args(tag->fields, comments)));
+ if (tag->secret) code = Texts(code, "; secret");
+ code = Texts(code, ")");
+ }
+ return code;
+}
+
+Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent) {
+ OptionalText_t inline_tag = format_inline_tag(tag, comments);
+ if (inline_tag.length >= 0) return inline_tag;
+ Text_t code = Text$from_str(tag->name);
+ if (tag->fields || tag->secret) {
+ code = Texts(code, "(", format_args(tag->fields, comments, Texts(indent, single_indent)));
+ if (tag->secret) code = Texts(code, "; secret");
+ code = Texts(code, ")");
+ }
+ return code;
+}
+
+OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments) {
+ Text_t code = EMPTY_TEXT;
+ for (; tags; tags = tags->next) {
+ code = Texts(code, must(format_inline_tag(tags, comments)));
+ if (tags->next) code = Texts(code, ", ");
+ if (tags->next && range_has_comment(tags->end, tags->next->start, comments)) return NONE_TEXT;
+ }
+ return code;
+}
+
+Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent) {
+ OptionalText_t inline_tags = format_inline_tags(tags, comments);
+ if (inline_tags.length >= 0) return inline_tags;
+ Text_t code = EMPTY_TEXT;
+ for (; tags; tags = tags->next) {
+ add_line(&code, Texts(format_tag(tags, comments, indent), ","), indent);
+ }
+ return code;
+}
diff --git a/src/formatter/enums.h b/src/formatter/enums.h
new file mode 100644
index 00000000..e7233df4
--- /dev/null
+++ b/src/formatter/enums.h
@@ -0,0 +1,11 @@
+// Logic for formatting enums and enum tags
+
+#pragma once
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+
+OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments);
+Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent);
+OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments);
+Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent);
diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c
new file mode 100644
index 00000000..b68b3874
--- /dev/null
+++ b/src/formatter/formatter.c
@@ -0,0 +1,884 @@
+// This code defines functions for transforming ASTs back into Tomo source text
+
+#include <assert.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unictype.h>
+
+#include "../ast.h"
+#include "../parse/context.h"
+#include "../parse/files.h"
+#include "../parse/utils.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/integers.h"
+#include "../stdlib/optionals.h"
+#include "../stdlib/stdlib.h"
+#include "../stdlib/text.h"
+#include "args.h"
+#include "enums.h"
+#include "formatter.h"
+#include "types.h"
+#include "utils.h"
+
+#define fmt_inline(...) must(format_inline_code(__VA_ARGS__))
+#define fmt(...) format_code(__VA_ARGS__)
+
+Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) {
+ if (unwrap_block(namespace) == NULL) return EMPTY_TEXT;
+ return Texts("\n", indent, single_indent, fmt(namespace, comments, Texts(indent, single_indent)));
+}
+
+typedef struct {
+ Text_t quote, unquote, interp;
+} text_opts_t;
+
+PUREFUNC text_opts_t choose_text_options(ast_list_t *chunks) {
+ int double_quotes = 0, single_quotes = 0, backticks = 0;
+ for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
+ if (chunk->ast->tag == TextLiteral) {
+ Text_t literal = Match(chunk->ast, TextLiteral)->text;
+ if (Text$has(literal, Text("\""))) double_quotes += 1;
+ if (Text$has(literal, Text("'"))) single_quotes += 1;
+ if (Text$has(literal, Text("`"))) backticks += 1;
+ }
+ }
+ Text_t quote;
+ if (double_quotes == 0) quote = Text("\"");
+ else if (single_quotes == 0) quote = Text("'");
+ else if (backticks == 0) quote = Text("`");
+ else quote = Text("\"");
+
+ text_opts_t opts = {.quote = quote, .unquote = quote, .interp = Text("$")};
+ return opts;
+}
+
+static bool starts_with_id(Text_t text) {
+ List_t codepoints = Text$utf32_codepoints(Text$slice(text, I_small(1), I_small(1)));
+ return uc_is_property_xid_continue(*(ucs4_t *)codepoints.data);
+}
+
+static OptionalText_t format_inline_text(text_opts_t opts, ast_list_t *chunks, Table_t comments) {
+ Text_t code = opts.quote;
+ for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
+ if (chunk->ast->tag == TextLiteral) {
+ Text_t literal = Match(chunk->ast, TextLiteral)->text;
+ Text_t segment = Text$escaped(literal, false, Texts(opts.unquote, opts.interp));
+ code = Texts(code, segment);
+ } else {
+ if (chunk->ast->tag == Var
+ && (!chunk->next || chunk->next->ast->tag != TextLiteral
+ || !starts_with_id(Match(chunk->next->ast, TextLiteral)->text))) {
+ code = Texts(code, opts.interp, fmt_inline(chunk->ast, comments));
+ } else {
+ code = Texts(code, opts.interp, "(", fmt_inline(chunk->ast, comments), ")");
+ }
+ }
+ }
+ return Texts(code, opts.unquote);
+}
+
+static Text_t format_text(text_opts_t opts, ast_list_t *chunks, Table_t comments, Text_t indent) {
+ Text_t code = EMPTY_TEXT;
+ Text_t current_line = EMPTY_TEXT;
+ for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
+ if (chunk->ast->tag == TextLiteral) {
+ Text_t literal = Match(chunk->ast, TextLiteral)->text;
+ List_t lines = Text$lines(literal);
+ if (lines.length == 0) continue;
+ current_line = Texts(current_line, Text$escaped(*(Text_t *)lines.data, false, opts.interp));
+ for (int64_t i = 1; i < lines.length; i += 1) {
+ add_line(&code, current_line, Texts(indent, single_indent));
+ current_line = Text$escaped(*(Text_t *)(lines.data + i * lines.stride), false, opts.interp);
+ }
+ } else {
+ current_line = Texts(current_line, opts.interp, "(", fmt(chunk->ast, comments, indent), ")");
+ }
+ }
+ add_line(&code, current_line, Texts(indent, single_indent));
+ code = Texts(opts.quote, "\n", indent, single_indent, code, "\n", indent, opts.unquote);
+ return code;
+}
+
+OptionalText_t format_inline_code(ast_t *ast, Table_t comments) {
+ if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT;
+ switch (ast->tag) {
+ /*inline*/ case Unknown:
+ fail("Invalid AST");
+ /*inline*/ case Block: {
+ ast_list_t *statements = Match(ast, Block)->statements;
+ if (statements == NULL) return Text("pass");
+ else if (statements->next == NULL) return fmt_inline(statements->ast, comments);
+ else return NONE_TEXT;
+ }
+ /*inline*/ case StructDef:
+ /*inline*/ case EnumDef:
+ /*inline*/ case LangDef:
+ /*inline*/ case Extend:
+ /*inline*/ case FunctionDef:
+ /*inline*/ case ConvertDef:
+ /*inline*/ case DocTest:
+ /*inline*/ case Extern:
+ return NONE_TEXT;
+ /*inline*/ case Assert: {
+ DeclareMatch(assert, ast, Assert);
+ Text_t expr = fmt_inline(assert->expr, comments);
+ if (!assert->message) return Texts("assert ", expr);
+ Text_t message = fmt_inline(assert->message, comments);
+ return Texts("assert ", expr, ", ", message);
+ }
+ /*inline*/ case Defer:
+ return Texts("defer ", fmt_inline(Match(ast, Defer)->body, comments));
+ /*inline*/ case Lambda: {
+ DeclareMatch(lambda, ast, Lambda);
+ Text_t code = Texts("func(", format_inline_args(lambda->args, comments));
+ if (lambda->ret_type)
+ code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type));
+ code = Texts(code, ") ", fmt_inline(lambda->body, comments));
+ return Texts(code);
+ }
+ /*inline*/ case If: {
+ DeclareMatch(if_, ast, If);
+
+ Text_t if_condition = if_->condition->tag == Not
+ ? Texts("unless ", fmt_inline(Match(if_->condition, Not)->value, comments))
+ : Texts("if ", fmt_inline(if_->condition, comments));
+
+ if (if_->else_body == NULL && if_->condition->tag != Declare) {
+ ast_t *stmt = unwrap_block(if_->body);
+ if (!stmt) return Texts("pass ", if_condition);
+ switch (stmt->tag) {
+ case Return:
+ case Skip:
+ case Stop: return Texts(fmt_inline(stmt, comments), " ", if_condition);
+ default: break;
+ }
+ }
+
+ Text_t code = Texts(if_condition, " then ", fmt_inline(if_->body, comments));
+ if (if_->else_body) code = Texts(code, " else ", fmt_inline(if_->else_body, comments));
+ return code;
+ }
+ /*inline*/ case When: {
+ DeclareMatch(when, ast, When);
+ Text_t code = Texts("when ", fmt_inline(when->subject, comments));
+ for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
+ code = Texts(code, " is ", fmt_inline(clause->pattern, comments));
+ while (clause->next && clause->next->body == clause->body) {
+ clause = clause->next;
+ code = Texts(code, ", ", fmt_inline(clause->pattern, comments));
+ }
+ code = Texts(code, " then ", fmt_inline(clause->body, comments));
+ }
+ if (when->else_body) code = Texts(code, " else ", fmt_inline(when->else_body, comments));
+ return code;
+ }
+ /*inline*/ case Repeat:
+ return Texts("repeat ", fmt_inline(Match(ast, Repeat)->body, comments));
+ /*inline*/ case While: {
+ DeclareMatch(loop, ast, While);
+ return Texts("while ", fmt_inline(loop->condition, comments), " do ", fmt_inline(loop->body, comments));
+ }
+ /*inline*/ case For: {
+ DeclareMatch(loop, ast, For);
+ Text_t code = Text("for ");
+ for (ast_list_t *var = loop->vars; var; var = var->next) {
+ code = Texts(code, fmt_inline(var->ast, comments));
+ if (var->next) code = Texts(code, ", ");
+ }
+ code = Texts(code, " in ", fmt_inline(loop->iter, comments), " do ", fmt_inline(loop->body, comments));
+ if (loop->empty) code = Texts(code, " else ", fmt_inline(loop->empty, comments));
+ return code;
+ }
+ /*inline*/ case Comprehension: {
+ DeclareMatch(comp, ast, Comprehension);
+ Text_t code = Texts(fmt_inline(comp->expr, comments), " for ");
+ for (ast_list_t *var = comp->vars; var; var = var->next) {
+ code = Texts(code, fmt_inline(var->ast, comments));
+ if (var->next) code = Texts(code, ", ");
+ }
+ code = Texts(code, " in ", fmt_inline(comp->iter, comments));
+ if (comp->filter) code = Texts(code, " if ", fmt_inline(comp->filter, comments));
+ return code;
+ }
+ /*inline*/ case List:
+ /*inline*/ case Set: {
+ ast_list_t *items = ast->tag == List ? Match(ast, List)->items : Match(ast, Set)->items;
+ Text_t code = EMPTY_TEXT;
+ for (ast_list_t *item = items; item; item = item->next) {
+ code = Texts(code, fmt_inline(item->ast, comments));
+ if (item->next) code = Texts(code, ", ");
+ }
+ return ast->tag == List ? Texts("[", code, "]") : Texts("|", code, "|");
+ }
+ /*inline*/ case Table: {
+ DeclareMatch(table, ast, Table);
+ Text_t code = EMPTY_TEXT;
+ for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
+ code = Texts(code, fmt_inline(entry->ast, comments));
+ if (entry->next) code = Texts(code, ", ");
+ }
+ if (table->fallback) code = Texts(code, "; fallback=", fmt_inline(table->fallback, comments));
+ if (table->default_value) code = Texts(code, "; default=", fmt_inline(table->default_value, comments));
+ return Texts("{", code, "}");
+ }
+ /*inline*/ case TableEntry: {
+ DeclareMatch(entry, ast, TableEntry);
+ return Texts(fmt_inline(entry->key, comments), "=", fmt_inline(entry->value, comments));
+ }
+ /*inline*/ case Declare: {
+ DeclareMatch(decl, ast, Declare);
+ Text_t code = fmt_inline(decl->var, comments);
+ if (decl->type) code = Texts(code, " : ", format_type(decl->type));
+ if (decl->value) code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt_inline(decl->value, comments));
+ return code;
+ }
+ /*inline*/ case Assign: {
+ DeclareMatch(assign, ast, Assign);
+ Text_t code = EMPTY_TEXT;
+ for (ast_list_t *target = assign->targets; target; target = target->next) {
+ code = Texts(code, fmt_inline(target->ast, comments));
+ if (target->next) code = Texts(code, ", ");
+ }
+ code = Texts(code, " = ");
+ for (ast_list_t *value = assign->values; value; value = value->next) {
+ code = Texts(code, fmt_inline(value->ast, comments));
+ if (value->next) code = Texts(code, ", ");
+ }
+ return code;
+ }
+ /*inline*/ case Pass:
+ return Text("pass");
+ /*inline*/ case Return: {
+ ast_t *value = Match(ast, Return)->value;
+ return value ? Texts("return ", fmt_inline(value, comments)) : Text("return");
+ }
+ /*inline*/ case Not: {
+ ast_t *val = Match(ast, Not)->value;
+ return Texts("not ", must(termify_inline(val, comments)));
+ }
+ /*inline*/ case Negative: {
+ ast_t *val = Match(ast, Negative)->value;
+ return Texts("-", must(termify_inline(val, comments)));
+ }
+ /*inline*/ case HeapAllocate: {
+ ast_t *val = Match(ast, HeapAllocate)->value;
+ return Texts("@", must(termify_inline(val, comments)));
+ }
+ /*inline*/ case StackReference: {
+ ast_t *val = Match(ast, StackReference)->value;
+ return Texts("&", must(termify_inline(val, comments)));
+ }
+ /*inline*/ case Optional: {
+ ast_t *val = Match(ast, Optional)->value;
+ return Texts(must(termify_inline(val, comments)), "?");
+ }
+ /*inline*/ case NonOptional: {
+ ast_t *val = Match(ast, NonOptional)->value;
+ return Texts(must(termify_inline(val, comments)), "!");
+ }
+ /*inline*/ case FieldAccess: {
+ DeclareMatch(access, ast, FieldAccess);
+ return Texts(must(termify_inline(access->fielded, comments)), ".", Text$from_str(access->field));
+ }
+ /*inline*/ case Index: {
+ DeclareMatch(index, ast, Index);
+ Text_t indexed = must(termify_inline(index->indexed, comments));
+ if (index->index) return Texts(indexed, "[", fmt_inline(index->index, comments), "]");
+ else return Texts(indexed, "[]");
+ }
+ /*inline*/ case TextJoin: {
+ text_opts_t opts = choose_text_options(Match(ast, TextJoin)->children);
+ Text_t ret = must(format_inline_text(opts, Match(ast, TextJoin)->children, comments));
+ const char *lang = Match(ast, TextJoin)->lang;
+ return lang ? Texts("$", Text$from_str(lang), ret) : ret;
+ }
+ /*inline*/ case InlineCCode: {
+ DeclareMatch(c_code, ast, InlineCCode);
+ Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast)) : Text("C_code");
+ text_opts_t opts = {.quote = Text("`"), .unquote = Text("`"), .interp = Text("@")};
+ return Texts(code, must(format_inline_text(opts, Match(ast, InlineCCode)->chunks, comments)));
+ }
+ /*inline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); }
+ /*inline*/ case Path: {
+ return Texts("(", Text$escaped(Text$from_str(Match(ast, Path)->path), false, Text("()")), ")");
+ }
+ /*inline*/ case Stop: {
+ const char *target = Match(ast, Stop)->target;
+ return target ? Texts("stop ", Text$from_str(target)) : Text("stop");
+ }
+ /*inline*/ case Skip: {
+ const char *target = Match(ast, Skip)->target;
+ return target ? Texts("skip ", Text$from_str(target)) : Text("skip");
+ }
+ /*inline*/ case Min:
+ /*inline*/ case Max: {
+ Text_t lhs = fmt_inline(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments);
+ Text_t rhs = fmt_inline(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments);
+ ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key;
+ return Texts(lhs, key ? fmt_inline(key, comments) : (ast->tag == Min ? Text(" _min_ ") : Text(" _max_ ")), rhs);
+ }
+ /*inline*/ case Reduction: {
+ DeclareMatch(reduction, ast, Reduction);
+ if (reduction->key) {
+ return Texts("(", fmt_inline(reduction->key, comments), ": ", fmt_inline(reduction->iter, comments));
+ } else {
+ return Texts("(", Text$from_str(binop_info[reduction->op].operator), ": ",
+ fmt_inline(reduction->iter, comments));
+ }
+ }
+ /*inline*/ case None:
+ return Text("none");
+ /*inline*/ case Bool:
+ return Match(ast, Bool)->b ? Text("yes") : Text("no");
+ /*inline*/ case Int: {
+ OptionalText_t source = ast_source(ast);
+ return source.length > 0 ? source : Text$from_str(Match(ast, Int)->str);
+ }
+ /*inline*/ case Num: {
+ OptionalText_t source = ast_source(ast);
+ return source.length > 0 ? source : Text$from_str(String(Match(ast, Num)->n));
+ }
+ /*inline*/ case Var:
+ return Text$from_str(Match(ast, Var)->name);
+ /*inline*/ case FunctionCall: {
+ DeclareMatch(call, ast, FunctionCall);
+ return Texts(fmt_inline(call->fn, comments), "(", must(format_inline_args(call->args, comments)), ")");
+ }
+ /*inline*/ case MethodCall: {
+ DeclareMatch(call, ast, MethodCall);
+ Text_t self = fmt_inline(call->self, comments);
+ if (is_binary_operation(call->self) || call->self->tag == Negative || call->self->tag == Not)
+ self = parenthesize(self, EMPTY_TEXT);
+ return Texts(self, ".", Text$from_str(call->name), "(", must(format_inline_args(call->args, comments)), ")");
+ }
+ /*inline*/ case BINOP_CASES: {
+ binary_operands_t operands = BINARY_OPERANDS(ast);
+ const char *op = binop_info[ast->tag].operator;
+
+ Text_t lhs = fmt_inline(operands.lhs, comments);
+ Text_t rhs = fmt_inline(operands.rhs, comments);
+
+ if (is_update_assignment(ast)) {
+ return Texts(lhs, " ", Text$from_str(op), " ", rhs);
+ }
+
+ if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])
+ lhs = parenthesize(lhs, EMPTY_TEXT);
+ if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])
+ rhs = parenthesize(rhs, EMPTY_TEXT);
+
+ Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" ");
+ return Texts(lhs, space, Text$from_str(binop_info[ast->tag].operator), space, rhs);
+ }
+ /*inline*/ case Deserialize: {
+ DeclareMatch(deserialize, ast, Deserialize);
+ return Texts("deserialize(", fmt_inline(deserialize->value, comments), " -> ", format_type(deserialize->type),
+ ")");
+ }
+ /*inline*/ case Use: {
+ DeclareMatch(use, ast, Use);
+ // struct {
+ // ast_t *var;
+ // const char *path;
+ // enum { USE_LOCAL, USE_MODULE, USE_SHARED_OBJECT, USE_HEADER, USE_C_CODE, USE_ASM } what;
+ // } Use;
+ return Texts("use ", use->path);
+ }
+ /*inline*/ case ExplicitlyTyped:
+ fail("Explicitly typed AST nodes are only meant to be used internally.");
+ default: {
+ fail("Formatting not implemented for: ", ast_to_sexp(ast));
+ }
+ }
+}
+
+PUREFUNC static int64_t trailing_line_len(Text_t text) {
+ TextIter_t state = NEW_TEXT_ITER_STATE(text);
+ int64_t len = 0;
+ for (int64_t i = text.length - 1; i >= 0; i--) {
+ int32_t g = Text$get_grapheme_fast(&state, i);
+ if (g == '\n' || g == '\r') break;
+ len += 1;
+ }
+ return len;
+}
+
+Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) {
+ OptionalText_t inlined = format_inline_code(ast, comments);
+ bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH);
+
+ switch (ast->tag) {
+ /*multiline*/ case Unknown:
+ fail("Invalid AST");
+ /*multiline*/ case Block: {
+ Text_t code = EMPTY_TEXT;
+ bool gap_before_comment = false;
+ const char *comment_pos = ast->start;
+ for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
+ for (OptionalText_t comment;
+ (comment = next_comment(comments, &comment_pos, stmt->ast->start)).length > 0;) {
+ if (gap_before_comment) {
+ add_line(&code, Text(""), indent);
+ gap_before_comment = false;
+ }
+ add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent);
+ }
+
+ if (stmt->ast->tag == Block) {
+ add_line(&code,
+ Texts("do\n", indent, single_indent, fmt(stmt->ast, comments, Texts(indent, single_indent))),
+ indent);
+ } else {
+ add_line(&code, fmt(stmt->ast, comments, indent), indent);
+ }
+ comment_pos = stmt->ast->end;
+
+ if (stmt->next) {
+ int suggested_blanks = suggested_blank_lines(stmt->ast, stmt->next->ast);
+ for (int blanks = suggested_blanks; blanks > 0; blanks--)
+ add_line(&code, Text(""), indent);
+ gap_before_comment = (suggested_blanks == 0);
+ } else gap_before_comment = true;
+ }
+
+ for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) {
+ if (gap_before_comment) {
+ add_line(&code, Text(""), indent);
+ gap_before_comment = false;
+ }
+ add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent);
+ }
+ return code;
+ }
+ /*multiline*/ case If: {
+ DeclareMatch(if_, ast, If);
+ Text_t code = if_->condition->tag == Not
+ ? Texts("unless ", fmt(Match(if_->condition, Not)->value, comments, indent))
+ : Texts("if ", fmt(if_->condition, comments, indent));
+
+ code = Texts(code, "\n", indent, single_indent, fmt(if_->body, comments, Texts(indent, single_indent)));
+ if (if_->else_body) {
+ if (if_->else_body->tag != If) {
+ code = Texts(code, "\n", indent, "else\n", indent, single_indent,
+ fmt(if_->else_body, comments, Texts(indent, single_indent)));
+ } else {
+ code = Texts(code, "\n", indent, "else ", fmt(if_->else_body, comments, indent));
+ }
+ }
+ return code;
+ }
+ /*multiline*/ case When: {
+ DeclareMatch(when, ast, When);
+ Text_t code = Texts("when ", fmt(when->subject, comments, indent));
+ for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
+ code = Texts(code, "\n", indent, "is ", fmt(clause->pattern, comments, indent));
+ while (clause->next && clause->next->body == clause->body) {
+ clause = clause->next;
+ code = Texts(code, ", ", fmt(clause->pattern, comments, indent));
+ }
+ code = Texts(code, format_namespace(clause->body, comments, indent));
+ }
+ if (when->else_body)
+ code = Texts(code, "\n", indent, "else", format_namespace(when->else_body, comments, indent));
+ return code;
+ }
+ /*multiline*/ case Repeat: {
+ return Texts("repeat\n", indent, single_indent,
+ fmt(Match(ast, Repeat)->body, comments, Texts(indent, single_indent)));
+ }
+ /*multiline*/ case While: {
+ DeclareMatch(loop, ast, While);
+ return Texts("while ", fmt(loop->condition, comments, indent), "\n", indent, single_indent,
+ fmt(loop->body, comments, Texts(indent, single_indent)));
+ }
+ /*multiline*/ case For: {
+ DeclareMatch(loop, ast, For);
+ Text_t code = Text("for ");
+ for (ast_list_t *var = loop->vars; var; var = var->next) {
+ code = Texts(code, fmt(var->ast, comments, indent));
+ if (var->next) code = Texts(code, ", ");
+ }
+ code = Texts(code, " in ", fmt(loop->iter, comments, indent), format_namespace(loop->body, comments, indent));
+ if (loop->empty) code = Texts(code, "\n", indent, "else", format_namespace(loop->empty, comments, indent));
+ return code;
+ }
+ /*multiline*/ case Comprehension: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(comp, ast, Comprehension);
+ Text_t code = Texts("(", fmt(comp->expr, comments, indent));
+ if (code.length >= MAX_WIDTH) code = Texts(code, "\n", indent, "for ");
+ else code = Texts(code, " for ");
+
+ for (ast_list_t *var = comp->vars; var; var = var->next) {
+ code = Texts(code, fmt(var->ast, comments, indent));
+ if (var->next) code = Texts(code, ", ");
+ }
+
+ code = Texts(code, " in ", fmt(comp->iter, comments, indent));
+
+ if (comp->filter) {
+ if (code.length >= MAX_WIDTH) code = Texts(code, "\n", indent, "if ");
+ else code = Texts(code, " if ");
+ code = Texts(code, fmt(comp->filter, comments, indent));
+ }
+ return code;
+ }
+ /*multiline*/ case FunctionDef: {
+ DeclareMatch(func, ast, FunctionDef);
+ Text_t code = Texts("func ", fmt(func->name, comments, indent), "(", format_args(func->args, comments, indent));
+ if (func->ret_type) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type));
+ if (func->cache) code = Texts(code, "; cache=", fmt(func->cache, comments, indent));
+ if (func->is_inline) code = Texts(code, "; inline");
+ code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent,
+ single_indent, fmt(func->body, comments, Texts(indent, single_indent)));
+ return Texts(code);
+ }
+ /*multiline*/ case Lambda: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(lambda, ast, Lambda);
+ Text_t code = Texts("func(", format_args(lambda->args, comments, indent));
+ if (lambda->ret_type)
+ code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type));
+ code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent,
+ single_indent, fmt(lambda->body, comments, Texts(indent, single_indent)));
+ return Texts(code);
+ }
+ /*multiline*/ case ConvertDef: {
+ DeclareMatch(convert, ast, ConvertDef);
+ Text_t code = Texts("convert (", format_args(convert->args, comments, indent));
+ if (convert->ret_type)
+ code = Texts(code, convert->args ? Text(" -> ") : Text("-> "), format_type(convert->ret_type));
+ if (convert->cache) code = Texts(code, "; cache=", fmt(convert->cache, comments, indent));
+ if (convert->is_inline) code = Texts(code, "; inline");
+ code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent,
+ single_indent, fmt(convert->body, comments, Texts(indent, single_indent)));
+ return Texts(code);
+ }
+ /*multiline*/ case StructDef: {
+ DeclareMatch(def, ast, StructDef);
+ Text_t args = format_args(def->fields, comments, indent);
+ Text_t code = Texts("struct ", Text$from_str(def->name), "(", args);
+ if (def->secret) code = Texts(code, "; secret");
+ if (def->external) code = Texts(code, "; external");
+ if (def->opaque) code = Texts(code, "; opaque");
+ code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"));
+ return Texts(code, format_namespace(def->namespace, comments, indent));
+ }
+ /*multiline*/ case EnumDef: {
+ DeclareMatch(def, ast, EnumDef);
+ Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent));
+ return Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"),
+ format_namespace(def->namespace, comments, indent));
+ }
+ /*multiline*/ case LangDef: {
+ DeclareMatch(def, ast, LangDef);
+ return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent));
+ }
+ /*multiline*/ case Extend: {
+ DeclareMatch(extend, ast, Extend);
+ return Texts("lang ", Text$from_str(extend->name), format_namespace(extend->body, comments, indent));
+ }
+ /*multiline*/ case Extern: {
+ DeclareMatch(ext, ast, Extern);
+ return Texts("extern ", Text$from_str(ext->name), " : ", format_type(ext->type));
+ }
+ /*multiline*/ case Defer:
+ return Texts("defer ", format_namespace(Match(ast, Defer)->body, comments, indent));
+ /*multiline*/ case List:
+ /*multiline*/ case Set: {
+ if (inlined_fits) return inlined;
+ ast_list_t *items = ast->tag == List ? Match(ast, List)->items : Match(ast, Set)->items;
+ Text_t code = ast->tag == List ? Text("[") : Text("|");
+ const char *comment_pos = ast->start;
+ for (ast_list_t *item = items; item; item = item->next) {
+ for (OptionalText_t comment;
+ (comment = next_comment(comments, &comment_pos, item->ast->start)).length > 0;) {
+ add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
+ }
+ Text_t item_text = fmt(item->ast, comments, Texts(indent, single_indent));
+ if (Text$ends_with(code, Text(","), NULL)) {
+ if (!Text$has(item_text, Text("\n")) && trailing_line_len(code) + 1 + item_text.length + 1 <= MAX_WIDTH)
+ code = Texts(code, " ", item_text, ",");
+ else code = Texts(code, "\n", indent, single_indent, item_text, ",");
+ } else {
+ add_line(&code, Texts(item_text, ","), Texts(indent, single_indent));
+ }
+ }
+ for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) {
+ add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
+ }
+ return ast->tag == List ? Texts(code, "\n", indent, "]") : Texts(code, "\n", indent, "|");
+ }
+ /*multiline*/ case Table: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(table, ast, Table);
+ Text_t code = Texts("{");
+ const char *comment_pos = ast->start;
+ for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
+ for (OptionalText_t comment;
+ (comment = next_comment(comments, &comment_pos, entry->ast->start)).length > 0;) {
+ add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
+ }
+
+ Text_t entry_text = fmt(entry->ast, comments, Texts(indent, single_indent));
+ if (Text$ends_with(code, Text(","), NULL)) {
+ if (!Text$has(entry_text, Text("\n"))
+ && trailing_line_len(code) + 1 + entry_text.length + 1 <= MAX_WIDTH)
+ code = Texts(code, " ", entry_text, ",");
+ else code = Texts(code, "\n", indent, single_indent, entry_text, ",");
+ } else {
+ add_line(&code, Texts(entry_text, ","), Texts(indent, single_indent));
+ }
+
+ add_line(&code, Texts(entry_text, ","), Texts(indent, single_indent));
+ }
+ for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) {
+ add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
+ }
+
+ if (table->fallback)
+ code = Texts(code, ";\n", indent, single_indent, "fallback=", fmt(table->fallback, comments, indent));
+
+ if (table->default_value)
+ code = Texts(code, ";\n", indent, single_indent, "default=", fmt(table->default_value, comments, indent));
+
+ return Texts(code, "\n", indent, "}");
+ }
+ /*multiline*/ case TableEntry: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(entry, ast, TableEntry);
+ return Texts(fmt(entry->key, comments, indent), "=", fmt(entry->value, comments, indent));
+ }
+ /*multiline*/ case Declare: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(decl, ast, Declare);
+ Text_t code = fmt(decl->var, comments, indent);
+ if (decl->type) code = Texts(code, " : ", format_type(decl->type));
+ if (decl->value)
+ code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt(decl->value, comments, indent));
+ return code;
+ }
+ /*multiline*/ case Assign: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(assign, ast, Assign);
+ Text_t code = EMPTY_TEXT;
+ for (ast_list_t *target = assign->targets; target; target = target->next) {
+ code = Texts(code, fmt(target->ast, comments, indent));
+ if (target->next) code = Texts(code, ", ");
+ }
+ code = Texts(code, " = ");
+ for (ast_list_t *value = assign->values; value; value = value->next) {
+ code = Texts(code, fmt(value->ast, comments, indent));
+ if (value->next) code = Texts(code, ", ");
+ }
+ return code;
+ }
+ /*multiline*/ case Pass:
+ return Text("pass");
+ /*multiline*/ case Return: {
+ if (inlined_fits) return inlined;
+ ast_t *value = Match(ast, Return)->value;
+ return value ? Texts("return ", fmt(value, comments, indent)) : Text("return");
+ }
+ /*inline*/ case Not: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, Not)->value;
+ if (is_binary_operation(val)) return Texts("not ", termify(val, comments, indent));
+ else return Texts("not ", fmt(val, comments, indent));
+ }
+ /*inline*/ case Negative: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, Negative)->value;
+ if (is_binary_operation(val)) return Texts("-", termify(val, comments, indent));
+ else return Texts("-", fmt(val, comments, indent));
+ }
+ /*multiline*/ case HeapAllocate: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, HeapAllocate)->value;
+ return Texts("@", termify(val, comments, indent), "");
+ }
+ /*multiline*/ case StackReference: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, StackReference)->value;
+ return Texts("&(", termify(val, comments, indent), ")");
+ }
+ /*multiline*/ case Optional: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, Optional)->value;
+ return Texts(termify(val, comments, indent), "?");
+ }
+ /*multiline*/ case NonOptional: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, NonOptional)->value;
+ return Texts(termify(val, comments, indent), "!");
+ }
+ /*multiline*/ case FieldAccess: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(access, ast, FieldAccess);
+ return Texts(termify(access->fielded, comments, indent), ".", Text$from_str(access->field));
+ }
+ /*multiline*/ case Index: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(index, ast, Index);
+ if (index->index)
+ return Texts(termify(index->indexed, comments, indent), "[", fmt(index->index, comments, indent), "]");
+ else return Texts(termify(index->indexed, comments, indent), "[]");
+ }
+ /*multiline*/ case TextJoin: {
+ if (inlined_fits) return inlined;
+
+ text_opts_t opts = choose_text_options(Match(ast, TextJoin)->children);
+ Text_t ret = format_text(opts, Match(ast, TextJoin)->children, comments, indent);
+ const char *lang = Match(ast, TextJoin)->lang;
+ return lang ? Texts("$", Text$from_str(lang), ret) : ret;
+ }
+ /*multiline*/ case InlineCCode: {
+ DeclareMatch(c_code, ast, InlineCCode);
+ if (inlined_fits && c_code->type != NULL) return inlined;
+ Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast)) : Text("C_code");
+ text_opts_t opts = {.quote = Text("`"), .unquote = Text("`"), .interp = Text("@")};
+ return Texts(code, format_text(opts, Match(ast, InlineCCode)->chunks, comments, indent));
+ }
+ /*multiline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); }
+ /*multiline*/ case Path: {
+ assert(inlined.length > 0);
+ return inlined;
+ }
+ /*multiline*/ case Min:
+ /*multiline*/ case Max: {
+ if (inlined_fits) return inlined;
+ Text_t lhs = termify(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments, indent);
+ Text_t rhs = termify(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments, indent);
+ ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key;
+ Text_t op = key ? fmt(key, comments, indent) : (ast->tag == Min ? Text("_min_") : Text("_max_"));
+ return Texts(lhs, " ", op, " ", rhs);
+ }
+ /*multiline*/ case Reduction: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(reduction, ast, Reduction);
+ if (reduction->key) {
+ return Texts("(", fmt(reduction->key, comments, Texts(indent, single_indent)), ": ",
+ fmt(reduction->iter, comments, Texts(indent, single_indent)));
+ } else {
+ return Texts("(", binop_info[reduction->op].operator, ": ",
+ fmt(reduction->iter, comments, Texts(indent, single_indent)));
+ }
+ }
+ /*multiline*/ case Stop:
+ /*multiline*/ case Skip:
+ /*multiline*/ case None:
+ /*multiline*/ case Bool:
+ /*multiline*/ case Int:
+ /*multiline*/ case Num:
+ /*multiline*/ case Var: {
+ assert(inlined.length >= 0);
+ return inlined;
+ }
+ /*multiline*/ case FunctionCall: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(call, ast, FunctionCall);
+ Text_t args = format_args(call->args, comments, indent);
+ return Texts(fmt(call->fn, comments, indent), "(", args,
+ Text$has(args, Text("\n")) ? Texts("\n", indent) : EMPTY_TEXT, ")");
+ }
+ /*multiline*/ case MethodCall: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(call, ast, MethodCall);
+ Text_t args = format_args(call->args, comments, indent);
+ return Texts(termify(call->self, comments, indent), ".", Text$from_str(call->name), "(", args,
+ Text$has(args, Text("\n")) ? Texts("\n", indent) : EMPTY_TEXT, ")");
+ }
+ /*multiline*/ case DocTest: {
+ DeclareMatch(test, ast, DocTest);
+ Text_t expr = fmt(test->expr, comments, indent);
+ Text_t code = Texts(">> ", expr);
+ if (test->expected) {
+ Text_t expected = fmt(test->expected, comments, indent);
+ code = Texts(code, "\n", indent, "= ", expected);
+ }
+ return code;
+ }
+ /*multiline*/ case Assert: {
+ DeclareMatch(assert, ast, Assert);
+ Text_t expr = fmt(assert->expr, comments, indent);
+ if (!assert->message) return Texts("assert ", expr);
+ Text_t message = fmt(assert->message, comments, indent);
+ return Texts("assert ", expr, ", ", message);
+ }
+ /*multiline*/ case BINOP_CASES: {
+ if (inlined_fits) return inlined;
+ binary_operands_t operands = BINARY_OPERANDS(ast);
+ const char *op = binop_info[ast->tag].operator;
+ Text_t lhs = fmt(operands.lhs, comments, indent);
+ Text_t rhs = fmt(operands.rhs, comments, indent);
+
+ if (is_update_assignment(ast)) {
+ return Texts(lhs, " ", Text$from_str(op), " ", rhs);
+ }
+
+ if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])
+ lhs = parenthesize(lhs, indent);
+ if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])
+ rhs = parenthesize(rhs, indent);
+
+ Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" ");
+ return Texts(lhs, space, Text$from_str(binop_info[ast->tag].operator), space, rhs);
+ }
+ /*multiline*/ case Deserialize: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(deserialize, ast, Deserialize);
+ return Texts("deserialize(", fmt(deserialize->value, comments, indent), " -> ", format_type(deserialize->type),
+ ")");
+ }
+ /*multiline*/ case Use: {
+ assert(inlined.length > 0);
+ return inlined;
+ }
+ /*multiline*/ case ExplicitlyTyped:
+ fail("Explicitly typed AST nodes are only meant to be used internally.");
+ default: {
+ if (inlined_fits) return inlined;
+ fail("Formatting not implemented for: ", ast_to_sexp(ast));
+ }
+ }
+}
+
+Text_t format_file(const char *path) {
+ file_t *file = load_file(path);
+ if (!file) return EMPTY_TEXT;
+
+ jmp_buf on_err;
+ if (setjmp(on_err) != 0) {
+ return Text$from_str(file->text);
+ }
+ parse_ctx_t ctx = {
+ .file = file,
+ .on_err = &on_err,
+ .comments = {},
+ };
+
+ const char *pos = file->text;
+ if (match(&pos, "#!")) // shebang
+ some_not(&pos, "\r\n");
+
+ whitespace(&ctx, &pos);
+ ast_t *ast = parse_file_body(&ctx, pos);
+ if (!ast) return Text$from_str(file->text);
+ pos = ast->end;
+ whitespace(&ctx, &pos);
+ if (pos < file->text + file->len && *pos != '\0') {
+ return Text$from_str(file->text);
+ }
+
+ const char *fmt_pos = file->text;
+ Text_t code = EMPTY_TEXT;
+ for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) {
+ code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n");
+ }
+ code = Texts(code, fmt(ast, ctx.comments, EMPTY_TEXT));
+ for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) {
+ code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n");
+ }
+ return code;
+}
diff --git a/src/formatter/formatter.h b/src/formatter/formatter.h
new file mode 100644
index 00000000..a8f9013a
--- /dev/null
+++ b/src/formatter/formatter.h
@@ -0,0 +1,13 @@
+// This code defines functions for transforming ASTs back into Tomo source text
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+
+Text_t format_file(const char *path);
+Text_t format_code(ast_t *ast, Table_t comments, Text_t indentation);
+Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent);
+OptionalText_t format_inline_code(ast_t *ast, Table_t comments);
diff --git a/src/formatter/types.c b/src/formatter/types.c
new file mode 100644
index 00000000..e52faf70
--- /dev/null
+++ b/src/formatter/types.c
@@ -0,0 +1,45 @@
+// Logic for formatting types
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/stdlib.h"
+#include "../stdlib/text.h"
+#include "args.h"
+#include "formatter.h"
+
+Text_t format_type(type_ast_t *type) {
+ switch (type->tag) {
+ case VarTypeAST: return Text$from_str(Match(type, VarTypeAST)->name);
+ case PointerTypeAST: {
+ DeclareMatch(ptr, type, PointerTypeAST);
+ return Texts(ptr->is_stack ? Text("&") : Text("@"), format_type(ptr->pointed));
+ }
+ case ListTypeAST: {
+ return Texts("[", format_type(Match(type, ListTypeAST)->item), "]");
+ }
+ case SetTypeAST: {
+ return Texts("|", format_type(Match(type, SetTypeAST)->item), "|");
+ }
+ case TableTypeAST: {
+ DeclareMatch(table, type, TableTypeAST);
+ Text_t code = Texts("{", format_type(table->key), "=", format_type(table->value));
+ if (table->default_value) {
+ OptionalText_t val = format_inline_code(table->default_value, (Table_t){});
+ assert(val.length >= 0);
+ code = Texts(code, "; default=", val);
+ }
+ return Texts(code, "}");
+ }
+ case FunctionTypeAST: {
+ DeclareMatch(func, type, FunctionTypeAST);
+ Text_t code = Texts("func(", format_inline_args(func->args, (Table_t){}));
+ if (func->ret) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret));
+ return Texts(code, ")");
+ }
+ case OptionalTypeAST: {
+ return Texts(format_type(Match(type, OptionalTypeAST)->type), "?");
+ }
+ case UnknownTypeAST:
+ default: fail("Invalid Type AST");
+ }
+}
diff --git a/src/formatter/types.h b/src/formatter/types.h
new file mode 100644
index 00000000..2571f880
--- /dev/null
+++ b/src/formatter/types.h
@@ -0,0 +1,8 @@
+// Logic for formatting types
+
+#pragma once
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+
+Text_t format_type(type_ast_t *type);
diff --git a/src/formatter/utils.c b/src/formatter/utils.c
new file mode 100644
index 00000000..bbe74d7f
--- /dev/null
+++ b/src/formatter/utils.c
@@ -0,0 +1,154 @@
+// This file defines utility functions for autoformatting code
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "../ast.h"
+#include "../parse/context.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.h"
+#include "../stdlib/tables.h"
+#include "../stdlib/text.h"
+#include "formatter.h"
+
+const Text_t single_indent = Text(" ");
+
+void add_line(Text_t *code, Text_t line, Text_t indent) {
+ if (code->length == 0) {
+ *code = line;
+ } else {
+ if (line.length > 0) *code = Texts(*code, "\n", indent, line);
+ else *code = Texts(*code, "\n");
+ }
+}
+
+OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) {
+ for (const char *p = *pos; p < end; p++) {
+ const char **comment_end = Table$get(comments, &p, parse_comments_info);
+ if (comment_end) {
+ *pos = *comment_end;
+ return Text$from_strn(p, (size_t)(*comment_end - p));
+ }
+ }
+ return NONE_TEXT;
+}
+
+bool range_has_comment(const char *start, const char *end, Table_t comments) {
+ OptionalText_t comment = next_comment(comments, &start, end);
+ return (comment.length >= 0);
+}
+
+CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second) {
+ if (second == NULL) return 0;
+
+ for (;;) {
+ if (first->tag == Declare && Match(first, Declare)->value) {
+ first = Match(first, Declare)->value;
+ } else if (first->tag == DocTest && Match(first, DocTest)->expr && Match(first, DocTest)->expected == NULL) {
+ first = Match(first, DocTest)->expr;
+ } else break;
+ }
+
+ for (;;) {
+ if (second->tag == Declare && Match(second, Declare)->value) {
+ second = Match(second, Declare)->value;
+ } else if (second->tag == DocTest && Match(second, DocTest)->expr && Match(second, DocTest)->expected == NULL) {
+ second = Match(second, DocTest)->expr;
+ } else break;
+ }
+
+ switch (first->tag) {
+ case If:
+ case When:
+ case Repeat:
+ case While:
+ case For:
+ case Block:
+ case Defer:
+ case ConvertDef:
+ case FunctionDef:
+ case Lambda:
+ case StructDef:
+ case EnumDef:
+ case LangDef:
+ case Extend: return 1;
+ case Use: {
+ if (second->tag != Use) return 1;
+ break;
+ }
+ case Declare: {
+ DeclareMatch(decl, first, Declare);
+ if (decl->value) return suggested_blank_lines(decl->value, second);
+ break;
+ }
+ case Assign: {
+ DeclareMatch(assign, first, Assign);
+ for (ast_list_t *val = assign->values; val; val = val->next) {
+ if (suggested_blank_lines(val->ast, second) > 0) return 1;
+ }
+ break;
+ }
+ default: break;
+ }
+
+ switch (second->tag) {
+ case If:
+ case When:
+ case Repeat:
+ case While:
+ case For:
+ case Block:
+ case Defer:
+ case ConvertDef:
+ case FunctionDef:
+ case Lambda:
+ case StructDef:
+ case EnumDef:
+ case LangDef:
+ case Extend: return 1;
+ default: break;
+ }
+ return 0;
+}
+
+Text_t indent_code(Text_t code) {
+ if (code.length <= 0) return code;
+ return Texts(single_indent, Text$replace(code, Text("\n"), Texts("\n", single_indent)));
+}
+
+Text_t parenthesize(Text_t code, Text_t indent) {
+ if (Text$has(code, Text("\n"))) return Texts("(\n", indent, indent_code(code), "\n", indent, ")");
+ else return Texts("(", code, ")");
+}
+
+CONSTFUNC ast_t *unwrap_block(ast_t *ast) {
+ if (ast == NULL) return NULL;
+ while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) {
+ ast = Match(ast, Block)->statements->ast;
+ }
+ if (ast->tag == Block && Match(ast, Block)->statements == NULL) return NULL;
+ return ast;
+}
+
+OptionalText_t termify_inline(ast_t *ast, Table_t comments) {
+ if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT;
+ switch (ast->tag) {
+ case BINOP_CASES:
+ case Not:
+ case Negative:
+ case HeapAllocate:
+ case StackReference: return parenthesize(format_inline_code(ast, comments), EMPTY_TEXT);
+ default: return format_inline_code(ast, comments);
+ }
+}
+
+Text_t termify(ast_t *ast, Table_t comments, Text_t indent) {
+ switch (ast->tag) {
+ case BINOP_CASES:
+ case Not:
+ case Negative:
+ case HeapAllocate:
+ case StackReference: return parenthesize(format_code(ast, comments, indent), indent);
+ default: return format_inline_code(ast, comments);
+ }
+}
diff --git a/src/formatter/utils.h b/src/formatter/utils.h
new file mode 100644
index 00000000..880da0a9
--- /dev/null
+++ b/src/formatter/utils.h
@@ -0,0 +1,30 @@
+// This file defines utility functions for autoformatting code
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.h"
+
+#define MAX_WIDTH 100
+
+#define must(expr) \
+ ({ \
+ OptionalText_t _expr = expr; \
+ if (_expr.length < 0) return NONE_TEXT; \
+ (Text_t) _expr; \
+ })
+
+extern const Text_t single_indent;
+
+void add_line(Text_t *code, Text_t line, Text_t indent);
+OptionalText_t next_comment(Table_t comments, const char **pos, const char *end);
+bool range_has_comment(const char *start, const char *end, Table_t comments);
+CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second);
+Text_t indent_code(Text_t code);
+Text_t parenthesize(Text_t code, Text_t indent);
+CONSTFUNC ast_t *unwrap_block(ast_t *ast);
+OptionalText_t termify_inline(ast_t *ast, Table_t comments);
+Text_t termify(ast_t *ast, Table_t comments, Text_t indent);
diff --git a/src/modules.c b/src/modules.c
index 40b3daf3..fafbbf86 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -5,6 +5,7 @@
#include <string.h>
#include <sys/wait.h>
+#include "config.h"
#include "modules.h"
#include "stdlib/memory.h"
#include "stdlib/paths.h"
@@ -14,6 +15,7 @@
#include "stdlib/tables.h"
#include "stdlib/text.h"
#include "stdlib/types.h"
+#include "stdlib/util.h"
#define xsystem(...) \
({ \
@@ -22,6 +24,33 @@
errx(1, "Failed to run command: %s", String(__VA_ARGS__)); \
})
+bool install_from_modules_ini(Path_t ini_file, bool ask_confirmation) {
+ OptionalClosure_t by_line = Path$by_line(ini_file);
+ if (by_line.fn == NULL) return false;
+ OptionalText_t (*next_line)(void *) = by_line.fn;
+ module_info_t info = {};
+ for (Text_t line; (line = next_line(by_line.userdata)).length >= 0;) {
+ char *line_str = Text$as_c_string(line);
+ const char *next_section = NULL;
+ if (!strparse(line_str, "[", &next_section, "]")) {
+ if (info.name) {
+ if (!try_install_module(info, ask_confirmation)) return false;
+ }
+ print("Checking module ", next_section, "...");
+ info = (module_info_t){.name = next_section};
+ continue;
+ }
+ if (!strparse(line_str, "version=", &info.version) || !strparse(line_str, "url=", &info.url)
+ || !strparse(line_str, "git=", &info.git) || !strparse(line_str, "path=", &info.path)
+ || !strparse(line_str, "revision=", &info.revision))
+ continue;
+ }
+ if (info.name) {
+ if (!try_install_module(info, ask_confirmation)) return false;
+ }
+ return true;
+}
+
static void read_modules_ini(Path_t ini_file, module_info_t *info) {
OptionalClosure_t by_line = Path$by_line(ini_file);
if (by_line.fn == NULL) return;
@@ -50,47 +79,41 @@ module_info_t get_module_info(ast_t *use) {
if (cached) return **cached;
const char *name = Match(use, Use)->path;
module_info_t *info = new (module_info_t, .name = name);
- if (streq(name, "commands")) info->version = "v1.0";
- else if (streq(name, "random")) info->version = "v1.0";
- else if (streq(name, "base64")) info->version = "v1.0";
- else if (streq(name, "core")) info->version = "v1.0";
- else if (streq(name, "patterns")) info->version = "v1.1";
- else if (streq(name, "json")) info->version = "v1.0";
- else if (streq(name, "pthreads")) info->version = "v1.0";
- else if (streq(name, "shell")) info->version = "v1.0";
- else if (streq(name, "time")) info->version = "v1.0";
- else if (streq(name, "uuid")) info->version = "v1.0";
- else {
- 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);
- }
+ 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);
Table$set(&cache, &use, &info, cache_type);
return *info;
}
-bool try_install_module(module_info_t mod) {
+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)));
+ if (Path$exists(dest)) return true;
+
if (mod.git) {
- OptionalText_t answer = ask(Texts(Text("The module \""), Text$from_str(mod.name),
- Text("\" is not installed.\nDo you want to install it from git URL "),
- Text$from_str(mod.git), Text("? [Y/n] ")),
- true, true);
- if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
- return false;
+ if (ask_confirmation) {
+ OptionalText_t answer =
+ ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version),
+ " is not installed.\nDo you want to install it from git URL ", Text$from_str(mod.git),
+ "? [Y/n] "),
+ true, true);
+ if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
+ return false;
+ }
print("Installing ", mod.name, " from git...");
- Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX"));
- if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", tmpdir);
- else xsystem("git clone --depth=1 ", mod.git, " ", tmpdir);
- if (mod.path) xsystem("tomo -IL ", tmpdir, "/", mod.path);
- else xsystem("tomo -IL ", tmpdir);
- Path$remove(tmpdir, true);
+ if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest);
+ else xsystem("git clone --depth=1 ", mod.git, " ", dest);
+ xsystem("tomo -L ", dest);
return true;
} else if (mod.url) {
- OptionalText_t answer = ask(Texts(Text("The module "), Text$from_str(mod.name),
- Text(" is not installed.\nDo you want to install it from URL "),
- Text$from_str(mod.url), Text("? [Y/n] ")),
- true, true);
- if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
- return false;
+ if (ask_confirmation) {
+ OptionalText_t answer = ask(
+ Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version),
+ " is not installed.\nDo you want to install it from URL ", Text$from_str(mod.url), "? [Y/n] "),
+ true, true);
+ if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
+ return false;
+ }
print("Installing ", mod.name, " from URL...");
@@ -101,25 +124,31 @@ bool try_install_module(module_info_t mod) {
if (!p) return false;
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);
+
xsystem("curl ", mod.url, " -o ", tmpdir);
- if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename);
- else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) xsystem("tar xf ", tmpdir, "/", filename);
+ Path$create_directory(dest, 0755);
+ 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);
else return false;
- const char *basename = String(string_slice(filename, strcspn(filename, ".")));
- if (mod.path) xsystem("tomo -IL ", tmpdir, "/", basename, "/", mod.path);
- else xsystem("tomo -IL ", tmpdir, "/", basename);
+ xsystem("tomo -L ", dest);
Path$remove(tmpdir, true);
return true;
} else if (mod.path) {
- OptionalText_t answer = ask(Texts(Text("The module "), Text$from_str(mod.name),
- Text(" is not installed.\nDo you want to install it from path "),
- Text$from_str(mod.path), Text("? [Y/n] ")),
- true, true);
- if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
- return false;
+ if (ask_confirmation) {
+ OptionalText_t answer = ask(
+ Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version),
+ " is not installed.\nDo you want to install it from path ", Text$from_str(mod.path), "? [Y/n] "),
+ true, true);
+ if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
+ return false;
+ }
print("Installing ", mod.name, " from path...");
- xsystem("tomo -IL ", mod.path);
+ xsystem("ln -s ", mod.path, " ", dest);
+ xsystem("tomo -L ", dest);
return true;
}
diff --git a/src/modules.h b/src/modules.h
index 1c3b2d8e..c36d96dd 100644
--- a/src/modules.h
+++ b/src/modules.h
@@ -11,4 +11,5 @@ typedef struct {
} module_info_t;
module_info_t get_module_info(ast_t *use);
-bool try_install_module(module_info_t mod);
+bool install_from_modules_ini(Path_t ini_file, bool ask_confirmation);
+bool try_install_module(module_info_t mod, bool ask_confirmation);
diff --git a/src/parse/binops.c b/src/parse/binops.c
index 7ccf1379..4676b249 100644
--- a/src/parse/binops.c
+++ b/src/parse/binops.c
@@ -9,33 +9,6 @@
#include "suffixes.h"
#include "utils.h"
-int op_tightness[] = {
- [Power] = 9,
- [Multiply] = 8,
- [Divide] = 8,
- [Mod] = 8,
- [Mod1] = 8,
- [Plus] = 7,
- [Minus] = 7,
- [Concat] = 6,
- [LeftShift] = 5,
- [RightShift] = 5,
- [UnsignedLeftShift] = 5,
- [UnsignedRightShift] = 5,
- [Min] = 4,
- [Max] = 4,
- [Equals] = 3,
- [NotEquals] = 3,
- [LessThan] = 2,
- [LessThanOrEquals] = 2,
- [GreaterThan] = 2,
- [GreaterThanOrEquals] = 2,
- [Compare] = 2,
- [And] = 1,
- [Or] = 1,
- [Xor] = 1,
-};
-
ast_e match_binary_operator(const char **pos) {
switch (**pos) {
case '+': {
@@ -94,7 +67,7 @@ ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightness) {
for (ast_e op; (op = match_binary_operator(&pos)) != Unknown && op_tightness[op] >= min_tightness; spaces(&pos)) {
ast_t *key = NULL;
if (op == Min || op == Max) {
- key = NewAST(ctx->file, pos, pos, Var, .name = "$");
+ key = NewAST(ctx->file, pos, pos, Var, .name = (op == Min ? "_min_" : "_max_"));
for (bool progress = true; progress;) {
ast_t *new_term;
progress =
@@ -108,7 +81,7 @@ ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightness) {
else if (key) pos = key->end;
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (get_line_number(ctx->file, pos) != starting_line && get_indent(ctx, pos) < starting_indent)
parser_err(ctx, pos, eol(pos), "I expected this line to be at least as indented than the line above it");
diff --git a/src/parse/containers.c b/src/parse/containers.c
index 821cbdd4..73d30ecd 100644
--- a/src/parse/containers.c
+++ b/src/parse/containers.c
@@ -16,7 +16,7 @@ ast_t *parse_list(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "[")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_list_t *items = NULL;
for (;;) {
@@ -29,9 +29,9 @@ ast_t *parse_list(parse_ctx_t *ctx, const char *pos) {
suffixed = parse_comprehension_suffix(ctx, item);
}
items = new (ast_list_t, .ast = item, .next = items);
- if (!match_separator(&pos)) break;
+ if (!match_separator(ctx, &pos)) break;
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this list");
REVERSE_LIST(items);
@@ -42,14 +42,14 @@ ast_t *parse_table(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "{")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_list_t *entries = NULL;
for (;;) {
const char *entry_start = pos;
ast_t *key = optional(ctx, &pos, parse_extended_expr);
if (!key) break;
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, "=")) return NULL;
ast_t *value = expect(ctx, pos - 1, &pos, parse_expr, "I couldn't parse the value for this table entry");
ast_t *entry = NewAST(ctx->file, entry_start, pos, TableEntry, .key = key, .value = value);
@@ -60,37 +60,37 @@ ast_t *parse_table(parse_ctx_t *ctx, const char *pos) {
suffixed = parse_comprehension_suffix(ctx, entry);
}
entries = new (ast_list_t, .ast = entry, .next = entries);
- if (!match_separator(&pos)) break;
+ if (!match_separator(ctx, &pos)) break;
}
REVERSE_LIST(entries);
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_t *fallback = NULL, *default_value = NULL;
if (match(&pos, ";")) {
for (;;) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
const char *attr_start = pos;
if (match_word(&pos, "fallback")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, "=")) parser_err(ctx, attr_start, pos, "I expected an '=' after 'fallback'");
if (fallback) parser_err(ctx, attr_start, pos, "This table already has a fallback");
fallback = expect(ctx, attr_start, &pos, parse_expr, "I expected a fallback table");
} else if (match_word(&pos, "default")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, "=")) parser_err(ctx, attr_start, pos, "I expected an '=' after 'default'");
if (default_value) parser_err(ctx, attr_start, pos, "This table already has a default");
default_value = expect(ctx, attr_start, &pos, parse_expr, "I expected a default value");
} else {
break;
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, ",")) break;
}
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this table");
return NewAST(ctx->file, start, pos, Table, .default_value = default_value, .entries = entries,
@@ -102,13 +102,13 @@ ast_t *parse_set(parse_ctx_t *ctx, const char *pos) {
if (match(&pos, "||")) return NewAST(ctx->file, start, pos, Set);
if (!match(&pos, "|")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_list_t *items = NULL;
for (;;) {
ast_t *item = optional(ctx, &pos, parse_extended_expr);
if (!item) break;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_t *suffixed = parse_comprehension_suffix(ctx, item);
while (suffixed) {
item = suffixed;
@@ -116,12 +116,12 @@ ast_t *parse_set(parse_ctx_t *ctx, const char *pos) {
suffixed = parse_comprehension_suffix(ctx, item);
}
items = new (ast_list_t, .ast = item, .next = items);
- if (!match_separator(&pos)) break;
+ if (!match_separator(ctx, &pos)) break;
}
REVERSE_LIST(items);
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this set");
return NewAST(ctx->file, start, pos, Set, .items = items);
diff --git a/src/parse/context.c b/src/parse/context.c
new file mode 100644
index 00000000..cd8d16bc
--- /dev/null
+++ b/src/parse/context.c
@@ -0,0 +1,8 @@
+// A context parameter that gets passed around during parsing.
+
+#include "../stdlib/memory.h"
+#include "../stdlib/pointers.h"
+#include "../stdlib/tables.h"
+#include "../stdlib/types.h"
+
+const TypeInfo_t *parse_comments_info = Table$info(Pointer$info("@", &Memory$info), Pointer$info("@", &Memory$info));
diff --git a/src/parse/context.h b/src/parse/context.h
index 6008060e..f1e3be2f 100644
--- a/src/parse/context.h
+++ b/src/parse/context.h
@@ -4,10 +4,15 @@
#include <setjmp.h>
#include <stdint.h>
+#include "../stdlib/datatypes.h"
#include "../stdlib/files.h"
+#include "../stdlib/types.h"
+
+extern const TypeInfo_t *parse_comments_info;
typedef struct {
file_t *file;
jmp_buf *on_err;
int64_t next_lambda_id;
+ Table_t comments; // Map of <start pos> -> <end pos>
} parse_ctx_t;
diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c
index 6f6292af..1087e20e 100644
--- a/src/parse/controlflow.c
+++ b/src/parse/controlflow.c
@@ -36,7 +36,7 @@ ast_t *parse_block(parse_ctx_t *ctx, const char *pos) {
if (indent(ctx, &pos)) {
indented:;
int64_t block_indent = get_indent(ctx, pos);
- whitespace(&pos);
+ whitespace(ctx, &pos);
while (*pos) {
ast_t *stmt = optional(ctx, &pos, parse_statement);
if (!stmt) {
@@ -55,7 +55,7 @@ ast_t *parse_block(parse_ctx_t *ctx, const char *pos) {
break;
}
statements = new (ast_list_t, .ast = stmt, .next = statements);
- whitespace(&pos);
+ whitespace(ctx, &pos);
// Guard against having two valid statements on the same line, separated by spaces (but no newlines):
if (!memchr(stmt->end, '\n', (size_t)(pos - stmt->end))) {
@@ -131,18 +131,8 @@ ast_t *parse_while(parse_ctx_t *ctx, const char *pos) {
// while condition ["do"] [<indent>] body
const char *start = pos;
if (!match_word(&pos, "while")) return NULL;
-
- const char *tmp = pos;
- // Shorthand form: `while when ...`
- if (match_word(&tmp, "when")) {
- ast_t *when = expect(ctx, start, &pos, parse_when, "I expected a 'when' block after this");
- if (!when->__data.When.else_body) when->__data.When.else_body = NewAST(ctx->file, pos, pos, Stop);
- return NewAST(ctx->file, start, pos, While, .body = when);
- }
-
- (void)match_word(&pos, "do"); // Optional 'do'
-
ast_t *condition = expect(ctx, start, &pos, parse_expr, "I don't see a viable condition for this 'while'");
+ (void)match_word(&pos, "do"); // Optional 'do'
ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'while'");
return NewAST(ctx->file, start, pos, While, .condition = condition, .body = body);
}
@@ -174,7 +164,7 @@ ast_t *parse_if(parse_ctx_t *ctx, const char *pos) {
ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'if' statement");
const char *tmp = pos;
- whitespace(&tmp);
+ whitespace(ctx, &tmp);
ast_t *else_body = NULL;
const char *else_start = pos;
if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) {
@@ -198,7 +188,7 @@ ast_t *parse_when(parse_ctx_t *ctx, const char *pos) {
when_clause_t *clauses = NULL;
const char *tmp = pos;
- whitespace(&tmp);
+ whitespace(ctx, &tmp);
while (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "is")) {
pos = tmp;
spaces(&pos);
@@ -217,7 +207,7 @@ ast_t *parse_when(parse_ctx_t *ctx, const char *pos) {
}
clauses = new_clauses;
tmp = pos;
- whitespace(&tmp);
+ whitespace(ctx, &tmp);
}
REVERSE_LIST(clauses);
@@ -255,7 +245,7 @@ ast_t *parse_for(parse_ctx_t *ctx, const char *pos) {
ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'for'");
const char *else_start = pos;
- whitespace(&else_start);
+ whitespace(ctx, &else_start);
ast_t *empty = NULL;
if (match_word(&else_start, "else") && get_indent(ctx, else_start) == starting_indent) {
pos = else_start;
diff --git a/src/parse/expressions.c b/src/parse/expressions.c
index d643d4e7..df0a10a7 100644
--- a/src/parse/expressions.c
+++ b/src/parse/expressions.c
@@ -10,10 +10,10 @@
#include "context.h"
#include "controlflow.h"
#include "errors.h"
+#include "expressions.h"
#include "files.h"
#include "functions.h"
#include "numbers.h"
-#include "expressions.h"
#include "suffixes.h"
#include "text.h"
#include "types.h"
@@ -23,7 +23,7 @@ ast_t *parse_parens(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
spaces(&pos);
if (!match(&pos, "(")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_t *expr = optional(ctx, &pos, parse_extended_expr);
if (!expr) return NULL;
@@ -34,7 +34,7 @@ ast_t *parse_parens(parse_ctx_t *ctx, const char *pos) {
comprehension = parse_comprehension_suffix(ctx, expr);
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this expression");
// Update the span to include the parens:
@@ -45,11 +45,13 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "(")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_e op = match_binary_operator(&pos);
if (op == Unknown) return NULL;
- ast_t *key = NewAST(ctx->file, pos, pos, Var, .name = "$");
+ const char *op_str = binop_info[op].operator;
+ assert(op_str);
+ ast_t *key = NewAST(ctx->file, pos, pos, Var, .name = op_str);
for (bool progress = true; progress;) {
ast_t *new_term;
progress =
@@ -61,7 +63,7 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) {
if (key && key->tag == Var) key = NULL;
else if (key) pos = key->end;
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, ":")) return NULL;
ast_t *iter = optional(ctx, &pos, parse_extended_expr);
@@ -73,7 +75,7 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) {
suffixed = parse_comprehension_suffix(ctx, iter);
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this reduction");
return NewAST(ctx->file, start, pos, Reduction, .iter = iter, .op = op, .key = key);
@@ -164,14 +166,14 @@ ast_t *parse_deserialize(parse_ctx_t *ctx, const char *pos) {
spaces(&pos);
expect_str(ctx, start, &pos, "(", "I expected arguments for this `deserialize` call");
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_t *value = expect(ctx, start, &pos, parse_extended_expr, "I expected an expression here");
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_str(ctx, start, &pos, "->",
"I expected a `-> Type` for this `deserialize` call so I know what it deserializes to");
- whitespace(&pos);
+ whitespace(ctx, &pos);
type_ast_t *type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this deserialization");
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, ")", "I expected a closing ')' for this `deserialize` call");
return NewAST(ctx->file, start, pos, Deserialize, .value = value, .type = type);
}
@@ -238,10 +240,10 @@ ast_t *parse_expr_str(const char *str) {
};
const char *pos = file->text;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
ast_t *ast = parse_extended_expr(&ctx, pos);
pos = ast->end;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
if (pos < file->text + file->len && *pos != '\0')
parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the string");
return ast;
diff --git a/src/parse/files.c b/src/parse/files.c
index 8078d544..5ff41c68 100644
--- a/src/parse/files.c
+++ b/src/parse/files.c
@@ -11,9 +11,9 @@
#include "../stdlib/util.h"
#include "context.h"
#include "errors.h"
+#include "expressions.h"
#include "files.h"
#include "functions.h"
-#include "expressions.h"
#include "statements.h"
#include "text.h"
#include "typedefs.h"
@@ -33,11 +33,11 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) {
ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_list_t *statements = NULL;
for (;;) {
const char *next = pos;
- whitespace(&next);
+ 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))
@@ -47,12 +47,12 @@ ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) {
|| (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(&pos); // TODO: check for newline
+ whitespace(ctx, &pos); // TODO: check for newline
} else {
break;
}
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (pos < ctx->file->text + ctx->file->len && *pos != '\0') {
parser_err(ctx, pos, eol(pos), "I expect all top-level statements to be declarations of some kind");
}
@@ -90,10 +90,10 @@ ast_t *parse_file(const char *path, jmp_buf *on_err) {
if (match(&pos, "#!")) // shebang
some_not(&pos, "\r\n");
- whitespace(&pos);
+ whitespace(&ctx, &pos);
ast = parse_file_body(&ctx, pos);
pos = ast->end;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
if (pos < file->text + file->len && *pos != '\0') {
parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the file");
}
@@ -171,10 +171,10 @@ ast_t *parse_file_str(const char *str) {
};
const char *pos = file->text;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
ast_t *ast = parse_file_body(&ctx, pos);
pos = ast->end;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
if (pos < file->text + file->len && *pos != '\0')
parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the string");
return ast;
diff --git a/src/parse/functions.c b/src/parse/functions.c
index 37505ac5..ceb0a8bc 100644
--- a/src/parse/functions.c
+++ b/src/parse/functions.c
@@ -26,37 +26,42 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) {
type_ast_t *type = NULL;
typedef struct name_list_s {
+ const char *start, *end;
const char *name, *alias;
struct name_list_s *next;
} name_list_t;
name_list_t *names = NULL;
for (;;) {
- whitespace(pos);
+ whitespace(ctx, pos);
const char *name = get_id(pos);
if (!name) break;
- whitespace(pos);
+ const char *name_start = *pos;
+ whitespace(ctx, pos);
const char *alias = NULL;
if (match(pos, "|")) {
- whitespace(pos);
+ whitespace(ctx, pos);
alias = get_id(pos);
if (!alias) parser_err(ctx, *pos, *pos, "I expected an argument alias after `|`");
}
if (match(pos, ":")) {
type = expect(ctx, *pos - 1, pos, parse_type, "I expected a type here");
- names = new (name_list_t, .name = name, .alias = alias, .next = names);
- whitespace(pos);
+ whitespace(ctx, pos);
if (match(pos, "="))
default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='");
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
break;
} else if (strncmp(*pos, "==", 2) != 0 && match(pos, "=")) {
default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='");
- names = new (name_list_t, .name = name, .alias = alias, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
break;
} else if (name) {
- names = new (name_list_t, .name = name, .alias = alias, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
spaces(pos);
if (!match(pos, ",")) break;
} else {
@@ -71,10 +76,10 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) {
REVERSE_LIST(names);
for (; names; names = names->next)
- args = new (arg_ast_t, .name = names->name, .alias = names->alias, .type = type, .value = default_val,
- .next = args);
+ args = new (arg_ast_t, .start = names->start, .end = names->end, .name = names->name, .alias = names->alias,
+ .type = type, .value = default_val, .next = args);
- if (!match_separator(pos)) break;
+ if (!match_separator(ctx, pos)) break;
}
REVERSE_LIST(args);
@@ -95,19 +100,19 @@ ast_t *parse_func_def(parse_ctx_t *ctx, const char *pos) {
arg_ast_t *args = parse_args(ctx, &pos);
spaces(&pos);
type_ast_t *ret_type = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
bool is_inline = false;
ast_t *cache_ast = NULL;
- for (bool specials = match(&pos, ";"); specials; specials = match_separator(&pos)) {
+ for (bool specials = match(&pos, ";"); specials; specials = match_separator(ctx, &pos)) {
const char *flag_start = pos;
if (match_word(&pos, "inline")) {
is_inline = true;
} else if (match_word(&pos, "cached")) {
if (!cache_ast) cache_ast = NewAST(ctx->file, pos, pos, Int, .str = "-1");
} else if (match_word(&pos, "cache_size")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, "=")) parser_err(ctx, flag_start, pos, "I expected a value for 'cache_size'");
- whitespace(&pos);
+ whitespace(ctx, &pos);
cache_ast = expect(ctx, start, &pos, parse_expr, "I expected a maximum size for the cache");
}
}
@@ -129,19 +134,19 @@ ast_t *parse_convert_def(parse_ctx_t *ctx, const char *pos) {
arg_ast_t *args = parse_args(ctx, &pos);
spaces(&pos);
type_ast_t *ret_type = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
bool is_inline = false;
ast_t *cache_ast = NULL;
- for (bool specials = match(&pos, ";"); specials; specials = match_separator(&pos)) {
+ for (bool specials = match(&pos, ";"); specials; specials = match_separator(ctx, &pos)) {
const char *flag_start = pos;
if (match_word(&pos, "inline")) {
is_inline = true;
} else if (match_word(&pos, "cached")) {
if (!cache_ast) cache_ast = NewAST(ctx->file, pos, pos, Int, .str = "-1");
} else if (match_word(&pos, "cache_size")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, "=")) parser_err(ctx, flag_start, pos, "I expected a value for 'cache_size'");
- whitespace(&pos);
+ whitespace(ctx, &pos);
cache_ast = expect(ctx, start, &pos, parse_expr, "I expected a maximum size for the cache");
}
}
diff --git a/src/parse/statements.c b/src/parse/statements.c
index a30231f0..9606acdc 100644
--- a/src/parse/statements.c
+++ b/src/parse/statements.c
@@ -8,8 +8,8 @@
#include "../stdlib/util.h"
#include "context.h"
#include "errors.h"
-#include "files.h"
#include "expressions.h"
+#include "files.h"
#include "statements.h"
#include "suffixes.h"
#include "types.h"
@@ -46,7 +46,7 @@ ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos) {
targets = new (ast_list_t, .ast = lhs, .next = targets);
spaces(&pos);
if (!match(&pos, ",")) break;
- whitespace(&pos);
+ whitespace(ctx, &pos);
}
if (!targets) return NULL;
@@ -62,7 +62,7 @@ ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos) {
values = new (ast_list_t, .ast = rhs, .next = values);
spaces(&pos);
if (!match(&pos, ",")) break;
- whitespace(&pos);
+ whitespace(ctx, &pos);
}
REVERSE_LIST(targets);
@@ -101,7 +101,7 @@ ast_t *parse_doctest(parse_ctx_t *ctx, const char *pos) {
if (!match(&pos, ">>")) return NULL;
spaces(&pos);
ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest");
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_t *expected = NULL;
if (match(&pos, "=")) {
spaces(&pos);
@@ -120,7 +120,7 @@ ast_t *parse_assert(parse_ctx_t *ctx, const char *pos) {
spaces(&pos);
ast_t *message = NULL;
if (match(&pos, ",")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
message = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the error message for this assert");
} else {
pos = expr->end;
diff --git a/src/parse/suffixes.c b/src/parse/suffixes.c
index 7e748caf..cb54b2f6 100644
--- a/src/parse/suffixes.c
+++ b/src/parse/suffixes.c
@@ -14,10 +14,10 @@
ast_t *parse_field_suffix(parse_ctx_t *ctx, ast_t *lhs) {
if (!lhs) return NULL;
const char *pos = lhs->end;
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, ".")) return NULL;
if (*pos == '.') return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
bool dollar = match(&pos, "$");
const char *field = get_id(&pos);
if (!field) return NULL;
@@ -44,9 +44,9 @@ ast_t *parse_index_suffix(parse_ctx_t *ctx, ast_t *lhs) {
const char *start = lhs->start;
const char *pos = lhs->end;
if (!match(&pos, "[")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
ast_t *index = optional(ctx, &pos, parse_extended_expr);
- whitespace(&pos);
+ whitespace(ctx, &pos);
bool unchecked = match(&pos, ";") && (spaces(&pos), match_word(&pos, "unchecked") != 0);
expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this index");
return NewAST(ctx->file, start, pos, Index, .indexed = lhs, .index = index, .unchecked = unchecked);
@@ -57,7 +57,7 @@ ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *expr) {
if (!expr) return NULL;
const char *start = expr->start;
const char *pos = expr->end;
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match_word(&pos, "for")) return NULL;
ast_list_t *vars = NULL;
@@ -73,7 +73,7 @@ ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *expr) {
expect_str(ctx, start, &pos, "in", "I expected an 'in' for this 'for'");
ast_t *iter = expect(ctx, start, &pos, parse_expr, "I expected an iterable value for this 'for'");
const char *next_pos = pos;
- whitespace(&next_pos);
+ whitespace(ctx, &next_pos);
ast_t *filter = NULL;
if (match_word(&next_pos, "if")) {
pos = next_pos;
@@ -115,13 +115,13 @@ ast_t *parse_method_call_suffix(parse_ctx_t *ctx, ast_t *self) {
if (!fn) return NULL;
spaces(&pos);
if (!match(&pos, "(")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
arg_ast_t *args = NULL;
for (;;) {
const char *arg_start = pos;
const char *name = get_id(&pos);
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!name || !match(&pos, "=")) {
name = NULL;
pos = arg_start;
@@ -132,12 +132,12 @@ ast_t *parse_method_call_suffix(parse_ctx_t *ctx, ast_t *self) {
if (name) parser_err(ctx, arg_start, pos, "I expected an argument here");
break;
}
- args = new (arg_ast_t, .name = name, .value = arg, .next = args);
- if (!match_separator(&pos)) break;
+ args = new (arg_ast_t, .start = arg_start, .end = arg->end, .name = name, .value = arg, .next = args);
+ if (!match_separator(ctx, &pos)) break;
}
REVERSE_LIST(args);
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, ")")) parser_err(ctx, start, pos, "This parenthesis is unclosed");
@@ -152,13 +152,13 @@ ast_t *parse_fncall_suffix(parse_ctx_t *ctx, ast_t *fn) {
if (!match(&pos, "(")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
arg_ast_t *args = NULL;
for (;;) {
const char *arg_start = pos;
const char *name = get_id(&pos);
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!name || !match(&pos, "=")) {
name = NULL;
pos = arg_start;
@@ -170,10 +170,10 @@ ast_t *parse_fncall_suffix(parse_ctx_t *ctx, ast_t *fn) {
break;
}
args = new (arg_ast_t, .name = name, .value = arg, .next = args);
- if (!match_separator(&pos)) break;
+ if (!match_separator(ctx, &pos)) break;
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (!match(&pos, ")")) parser_err(ctx, start, pos, "This parenthesis is unclosed");
diff --git a/src/parse/text.c b/src/parse/text.c
index 8897fd34..30ff8656 100644
--- a/src/parse/text.c
+++ b/src/parse/text.c
@@ -148,21 +148,19 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) {
spaces(&pos);
type_ast_t *type = NULL;
- ast_list_t *chunks;
if (match(&pos, ":")) {
type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this C_code code");
spaces(&pos);
- chunks = _parse_text_helper(ctx, &pos);
- if (type) {
- chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("({")), .next = chunks);
- REVERSE_LIST(chunks);
- chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("; })")), .next = chunks);
- REVERSE_LIST(chunks);
- }
- } else {
- chunks = _parse_text_helper(ctx, &pos);
}
+ static const char *quote_chars = "\"'`|/;([{<";
+ if (!strchr(quote_chars, *pos))
+ parser_err(ctx, pos, pos + 1,
+ "This is not a valid string quotation character. Valid characters are: \"'`|/;([{<");
+
+ char quote = *(pos++);
+ char unquote = closing[(int)quote] ? closing[(int)quote] : quote;
+ ast_list_t *chunks = _parse_text_helper(ctx, &pos, quote, unquote, '@', false);
return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type);
}
diff --git a/src/parse/typedefs.c b/src/parse/typedefs.c
index 73fe9d7c..6e5e40d0 100644
--- a/src/parse/typedefs.c
+++ b/src/parse/typedefs.c
@@ -16,12 +16,12 @@
ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
- whitespace(&pos);
+ whitespace(ctx, &pos);
int64_t indent = get_indent(ctx, pos);
ast_list_t *statements = NULL;
for (;;) {
const char *next = pos;
- whitespace(&next);
+ whitespace(ctx, &next);
if (get_indent(ctx, next) != indent) break;
ast_t *stmt;
if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def))
@@ -31,7 +31,7 @@ ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos) {
|| (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_declaration))) {
statements = new (ast_list_t, .ast = stmt, .next = statements);
pos = stmt->end;
- whitespace(&pos); // TODO: check for newline
+ whitespace(ctx, &pos); // TODO: check for newline
// if (!(space_types & WHITESPACE_NEWLINES)) {
// pos = stmt->end;
// break;
@@ -62,10 +62,10 @@ ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) {
arg_ast_t *fields = parse_args(ctx, &pos);
- whitespace(&pos);
+ whitespace(ctx, &pos);
bool secret = false, external = false, opaque = false;
if (match(&pos, ";")) { // Extra flags
- whitespace(&pos);
+ whitespace(ctx, &pos);
for (;;) {
if (match_word(&pos, "secret")) {
secret = true;
@@ -79,7 +79,7 @@ ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) {
break;
}
- if (!match_separator(&pos)) break;
+ if (!match_separator(ctx, &pos)) break;
}
}
@@ -87,7 +87,7 @@ ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) {
ast_t *namespace = NULL;
const char *ns_pos = pos;
- whitespace(&ns_pos);
+ whitespace(ctx, &ns_pos);
int64_t ns_indent = get_indent(ctx, ns_pos);
if (ns_indent > starting_indent) {
pos = ns_pos;
@@ -110,9 +110,10 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) {
if (!match(&pos, "(")) return NULL;
tag_ast_t *tags = NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
for (;;) {
spaces(&pos);
+ const char *tag_start = pos;
const char *tag_name = get_id(&pos);
if (!tag_name) break;
@@ -120,25 +121,26 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) {
arg_ast_t *fields;
bool secret = false;
if (match(&pos, "(")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
fields = parse_args(ctx, &pos);
- whitespace(&pos);
+ whitespace(ctx, &pos);
if (match(&pos, ";")) { // Extra flags
- whitespace(&pos);
+ whitespace(ctx, &pos);
secret = match_word(&pos, "secret");
- whitespace(&pos);
+ whitespace(ctx, &pos);
}
expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this tagged union member");
} else {
fields = NULL;
}
- tags = new (tag_ast_t, .name = tag_name, .fields = fields, .secret = secret, .next = tags);
+ tags = new (tag_ast_t, .start = tag_start, .end = pos, .name = tag_name, .fields = fields, .secret = secret,
+ .next = tags);
- if (!match_separator(&pos)) break;
+ if (!match_separator(ctx, &pos)) break;
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this enum definition");
REVERSE_LIST(tags);
@@ -147,7 +149,7 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) {
ast_t *namespace = NULL;
const char *ns_pos = pos;
- whitespace(&ns_pos);
+ whitespace(ctx, &ns_pos);
int64_t ns_indent = get_indent(ctx, ns_pos);
if (ns_indent > starting_indent) {
pos = ns_pos;
@@ -170,7 +172,7 @@ ast_t *parse_lang_def(parse_ctx_t *ctx, const char *pos) {
ast_t *namespace = NULL;
const char *ns_pos = pos;
- whitespace(&ns_pos);
+ whitespace(ctx, &ns_pos);
int64_t ns_indent = get_indent(ctx, ns_pos);
if (ns_indent > starting_indent) {
pos = ns_pos;
@@ -192,7 +194,7 @@ ast_t *parse_extend(parse_ctx_t *ctx, const char *pos) {
ast_t *body = NULL;
const char *ns_pos = pos;
- whitespace(&ns_pos);
+ whitespace(ctx, &ns_pos);
int64_t ns_indent = get_indent(ctx, ns_pos);
if (ns_indent > starting_indent) {
pos = ns_pos;
diff --git a/src/parse/types.c b/src/parse/types.c
index 54bc0c03..ffb7d869 100644
--- a/src/parse/types.c
+++ b/src/parse/types.c
@@ -9,19 +9,19 @@
#include "../stdlib/print.h"
#include "context.h"
#include "errors.h"
-#include "functions.h"
#include "expressions.h"
+#include "functions.h"
#include "types.h"
#include "utils.h"
type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "{")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
type_ast_t *key_type = parse_type(ctx, pos);
if (!key_type) return NULL;
pos = key_type->end;
- whitespace(&pos);
+ whitespace(ctx, &pos);
type_ast_t *value_type = NULL;
if (match(&pos, "=")) {
value_type = expect(ctx, start, &pos, parse_type, "I couldn't parse the rest of this table type");
@@ -35,7 +35,7 @@ type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) {
default_value =
expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the default value for this table");
}
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this table type");
return NewTypeAST(ctx->file, start, pos, TableTypeAST, .key = key_type, .value = value_type,
.default_value = default_value);
@@ -44,11 +44,11 @@ type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) {
type_ast_t *parse_set_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "|")) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
type_ast_t *item_type = parse_type(ctx, pos);
if (!item_type) return NULL;
pos = item_type->end;
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this set type");
return NewTypeAST(ctx->file, start, pos, SetTypeAST, .item = item_type);
}
@@ -113,10 +113,10 @@ type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) {
|| (type = parse_table_type(ctx, pos)) || (type = parse_set_type(ctx, pos))
|| (type = parse_type_name(ctx, pos)) || (type = parse_func_type(ctx, pos)));
if (!success && match(&pos, "(")) {
- whitespace(&pos);
+ whitespace(ctx, &pos);
type = optional(ctx, &pos, parse_type);
if (!type) return NULL;
- whitespace(&pos);
+ whitespace(ctx, &pos);
expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this type");
type->start = start;
type->end = pos;
@@ -144,11 +144,11 @@ type_ast_t *parse_type_str(const char *str) {
};
const char *pos = file->text;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
type_ast_t *ast = parse_type(&ctx, pos);
if (!ast) return ast;
pos = ast->end;
- whitespace(&pos);
+ whitespace(&ctx, &pos);
if (strlen(pos) > 0) {
parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the type");
}
diff --git a/src/parse/utils.c b/src/parse/utils.c
index 7e827ac6..0644bfa0 100644
--- a/src/parse/utils.c
+++ b/src/parse/utils.c
@@ -6,6 +6,7 @@
#include <unictype.h>
#include <uniname.h>
+#include "../stdlib/tables.h"
#include "../stdlib/util.h"
#include "errors.h"
#include "utils.h"
@@ -43,8 +44,8 @@ size_t some_not(const char **pos, const char *forbid) {
size_t spaces(const char **pos) { return some_of(pos, " \t"); }
-void whitespace(const char **pos) {
- while (some_of(pos, " \t\r\n") || comment(pos))
+void whitespace(parse_ctx_t *ctx, const char **pos) {
+ while (some_of(pos, " \t\r\n") || comment(ctx, pos))
continue;
}
@@ -95,9 +96,12 @@ const char *get_id(const char **inout) {
PUREFUNC const char *eol(const char *str) { return str + strcspn(str, "\r\n"); }
-bool comment(const char **pos) {
+bool comment(parse_ctx_t *ctx, const char **pos) {
if ((*pos)[0] == '#') {
+ const char *start = *pos;
*pos += strcspn(*pos, "\r\n");
+ const char *end = *pos;
+ Table$set(&ctx->comments, &start, &end, parse_comments_info);
return true;
} else {
return false;
@@ -129,7 +133,7 @@ PUREFUNC int64_t get_indent(parse_ctx_t *ctx, const char *pos) {
bool indent(parse_ctx_t *ctx, const char **out) {
const char *pos = *out;
int64_t starting_indent = get_indent(ctx, pos);
- whitespace(&pos);
+ whitespace(ctx, &pos);
const char *next_line = get_line(ctx->file, get_line_number(ctx->file, pos));
if (next_line <= *out) return false;
@@ -239,12 +243,12 @@ const char *unescape(parse_ctx_t *ctx, const char **out) {
#pragma GCC diagnostic pop
#endif
-bool match_separator(const char **pos) { // Either comma or newline
+bool match_separator(parse_ctx_t *ctx, const char **pos) { // Either comma or newline
const char *p = *pos;
int separators = 0;
for (;;) {
if (some_of(&p, "\r\n,")) ++separators;
- else if (!comment(&p) && !some_of(&p, " \t")) break;
+ else if (!comment(ctx, &p) && !some_of(&p, " \t")) break;
}
if (separators > 0) {
*pos = p;
diff --git a/src/parse/utils.h b/src/parse/utils.h
index ba54120a..b8fb0756 100644
--- a/src/parse/utils.h
+++ b/src/parse/utils.h
@@ -12,16 +12,16 @@ CONSTFUNC bool is_keyword(const char *word);
size_t some_of(const char **pos, const char *allow);
size_t some_not(const char **pos, const char *forbid);
size_t spaces(const char **pos);
-void whitespace(const char **pos);
+void whitespace(parse_ctx_t *ctx, const char **pos);
size_t match(const char **pos, const char *target);
size_t match_word(const char **pos, const char *word);
const char *get_word(const char **pos);
const char *get_id(const char **pos);
-bool comment(const char **pos);
+bool comment(parse_ctx_t *ctx, const char **pos);
bool indent(parse_ctx_t *ctx, const char **pos);
const char *eol(const char *str);
PUREFUNC int64_t get_indent(parse_ctx_t *ctx, const char *pos);
const char *unescape(parse_ctx_t *ctx, const char **out);
bool is_xid_continue_next(const char *pos);
bool newline_with_indentation(const char **out, int64_t target);
-bool match_separator(const char **pos);
+bool match_separator(parse_ctx_t *ctx, const char **pos);
diff --git a/src/stdlib/integers.c b/src/stdlib/integers.c
index 7dda77bd..863bb42d 100644
--- a/src/stdlib/integers.c
+++ b/src/stdlib/integers.c
@@ -430,7 +430,7 @@ OptionalInt_t Int$parse(Text_t text, Text_t *remainder) {
else if (*end != '\0') return NONE_INT;
result = mpz_init_set_str(i, str + 2, 2);
} else {
- const char *end = str + 2 + strspn(str + 2, "0123456789");
+ const char *end = str + strspn(str, "0123456789");
if (remainder) *remainder = Text$from_str(end);
else if (*end != '\0') return NONE_INT;
result = mpz_init_set_str(i, str, 10);
@@ -618,6 +618,8 @@ void Int32$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_
return colorize ? Texts(Text("\033[35m"), text, Text("\033[m")) : text; \
} \
public \
+ Text_t KindOfInt##$value_as_text(c_type i) { return _int64_to_text((int64_t)i); } \
+ public \
PUREFUNC int32_t KindOfInt##$compare(const void *x, const void *y, const TypeInfo_t *info) { \
(void)info; \
return (*(c_type *)x > *(c_type *)y) - (*(c_type *)x < *(c_type *)y); \
diff --git a/src/stdlib/integers.h b/src/stdlib/integers.h
index 40c40754..34195d23 100644
--- a/src/stdlib/integers.h
+++ b/src/stdlib/integers.h
@@ -22,6 +22,7 @@
bool is_none : 1; \
} Optional##type_name##_t; \
Text_t type_name##$as_text(const void *i, bool colorize, const TypeInfo_t *type); \
+ Text_t type_name##$value_as_text(c_type i); \
PUREFUNC int32_t type_name##$compare(const void *x, const void *y, const TypeInfo_t *type); \
PUREFUNC bool type_name##$equal(const void *x, const void *y, const TypeInfo_t *type); \
Text_t type_name##$hex(c_type i, Int_t digits, bool uppercase, bool prefix); \
diff --git a/src/stdlib/nums.c b/src/stdlib/nums.c
index 55131cfd..4bbb1f6a 100644
--- a/src/stdlib/nums.c
+++ b/src/stdlib/nums.c
@@ -14,13 +14,18 @@
#include "types.h"
public
-PUREFUNC Text_t Num$as_text(const void *f, bool colorize, const TypeInfo_t *info) {
- (void)info;
- if (!f) return Text("Num");
+PUREFUNC Text_t Num$value_as_text(double x) {
char *str = GC_MALLOC_ATOMIC(24);
- int len = fpconv_dtoa(*(double *)f, str);
+ int len = fpconv_dtoa(x, str);
+ return Text$from_strn(str, (size_t)len);
+}
+
+public
+PUREFUNC Text_t Num$as_text(const void *x, bool colorize, const TypeInfo_t *info) {
+ (void)info;
+ if (!x) return Text("Num");
static const Text_t color_prefix = Text("\x1b[35m"), color_suffix = Text("\x1b[m");
- Text_t text = Text$from_strn(str, (size_t)len);
+ Text_t text = Num$value_as_text(*(double *)x);
return colorize ? Texts(color_prefix, text, color_suffix) : text;
}
@@ -60,10 +65,10 @@ CONSTFUNC bool Num$near(double a, double b, double ratio, double absolute) {
}
public
-Text_t Num$percent(double f, double precision) {
- double d = 100. * f;
+Text_t Num$percent(double x, double precision) {
+ double d = 100. * x;
d = Num$with_precision(d, precision);
- return Texts(Num$as_text(&d, false, &Num$info), Text("%"));
+ return Texts(Num$value_as_text(d), Text("%"));
}
public
@@ -142,10 +147,13 @@ const TypeInfo_t Num$info = {
};
public
-PUREFUNC Text_t Num32$as_text(const void *f, bool colorize, const TypeInfo_t *info) {
+PUREFUNC Text_t Num32$value_as_text(float x) { return Num$value_as_text((double)x); }
+
+public
+PUREFUNC Text_t Num32$as_text(const void *x, bool colorize, const TypeInfo_t *info) {
(void)info;
- if (!f) return Text("Num32");
- double d = (double)(*(float *)f);
+ if (!x) return Text("Num32");
+ double d = (double)(*(float *)x);
return Num$as_text(&d, colorize, &Num$info);
}
@@ -178,10 +186,10 @@ CONSTFUNC bool Num32$near(float a, float b, float ratio, float absolute) {
}
public
-Text_t Num32$percent(float f, float precision) {
- double d = 100. * (double)f;
+Text_t Num32$percent(float x, float precision) {
+ double d = 100. * (double)x;
d = Num$with_precision(d, (double)precision);
- return Texts(Num$as_text(&d, false, &Num$info), Text("%"));
+ return Texts(Num$value_as_text(d), Text("%"));
}
public
diff --git a/src/stdlib/nums.h b/src/stdlib/nums.h
index db051ed2..303aa362 100644
--- a/src/stdlib/nums.h
+++ b/src/stdlib/nums.h
@@ -15,11 +15,12 @@
#define N32(n) ((float)(n))
#define N64(n) ((double)(n))
-Text_t Num$as_text(const void *f, bool colorize, const TypeInfo_t *type);
+Text_t Num$as_text(const void *x, bool colorize, const TypeInfo_t *type);
+Text_t Num$value_as_text(double x);
PUREFUNC int32_t Num$compare(const void *x, const void *y, const TypeInfo_t *type);
PUREFUNC bool Num$equal(const void *x, const void *y, const TypeInfo_t *type);
CONSTFUNC bool Num$near(double a, double b, double ratio, double absolute);
-Text_t Num$percent(double f, double precision);
+Text_t Num$percent(double x, double precision);
double CONSTFUNC Num$with_precision(double num, double precision);
double Num$mod(double num, double modulus);
double Num$mod1(double num, double modulus);
@@ -70,11 +71,12 @@ MACROLIKE CONSTFUNC double Num$from_byte(Byte_t i) { return (double)i; }
extern const TypeInfo_t Num$info;
-Text_t Num32$as_text(const void *f, bool colorize, const TypeInfo_t *type);
+Text_t Num32$as_text(const void *x, bool colorize, const TypeInfo_t *type);
+Text_t Num32$value_as_text(float x);
PUREFUNC int32_t Num32$compare(const void *x, const void *y, const TypeInfo_t *type);
PUREFUNC bool Num32$equal(const void *x, const void *y, const TypeInfo_t *type);
CONSTFUNC bool Num32$near(float a, float b, float ratio, float absolute);
-Text_t Num32$percent(float f, float precision);
+Text_t Num32$percent(float x, float precision);
float CONSTFUNC Num32$with_precision(float num, float precision);
float Num32$mod(float num, float modulus);
float Num32$mod1(float num, float modulus);
diff --git a/src/stdlib/pointers.c b/src/stdlib/pointers.c
index b5e6400f..0a1623a0 100644
--- a/src/stdlib/pointers.c
+++ b/src/stdlib/pointers.c
@@ -44,7 +44,7 @@ Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *type) {
TypeInfo_t rec_table = *Table$info(type, &Int64$info);
int64_t *id = Table$get(pending, x, &rec_table);
if (id) {
- Text_t text = Texts(Text$from_str(ptr_info.sigil), Int64$as_text(id, false, &Int64$info));
+ Text_t text = Texts(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id));
return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text;
}
int64_t next_id = pending.entries.length + 2;
diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c
index 266dc4ef..0953e660 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 = TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/";
+ 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 bf36ca0d..5ea8cb79 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -41,6 +41,9 @@ bool USE_COLOR;
public
Text_t TOMO_VERSION_TEXT = Text(TOMO_VERSION);
+public
+const char *TOMO_PATH = TOMO_INSTALL;
+
static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) {
(void)info, (void)userdata;
assert(sig == SIGILL);
diff --git a/src/stdlib/text.c b/src/stdlib/text.c
index ed4023a4..d3757a0d 100644
--- a/src/stdlib/text.c
+++ b/src/stdlib/text.c
@@ -1390,17 +1390,8 @@ Text_t Text$title(Text_t text, Text_t language) {
}
public
-Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) {
- if (quotation_mark.length != 1) fail("Invalid quote text: ", quotation_mark, " (must have length == 1)");
-
+Text_t Text$escaped(Text_t text, bool colorize, Text_t extra_escapes) {
Text_t ret = colorize ? Text("\x1b[35m") : EMPTY_TEXT;
- if (!Text$equal_values(quotation_mark, Text("\"")) && !Text$equal_values(quotation_mark, Text("'"))
- && !Text$equal_values(quotation_mark, Text("`")))
- ret = concat2_assuming_safe(ret, Text("$"));
-
- ret = concat2_assuming_safe(ret, quotation_mark);
- int32_t quote_char = Text$get_grapheme(quotation_mark, 0);
-
#define flush_unquoted() \
({ \
if (unquoted_span > 0) { \
@@ -1454,15 +1445,18 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) {
break;
}
default: {
- if (g == quote_char) {
- flush_unquoted();
- if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[34;1m"));
- ret = concat2_assuming_safe(ret, Text("\\"));
- ret = concat2_assuming_safe(ret, quotation_mark);
- if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[0;35m"));
- } else {
- unquoted_span += 1;
+ TextIter_t esc_state = NEW_TEXT_ITER_STATE(extra_escapes);
+ for (int64_t j = 0; j < extra_escapes.length; j++) {
+ int32_t esc = Text$get_grapheme_fast(&esc_state, j);
+ if (g == esc) {
+ flush_unquoted();
+ if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[34;1m"));
+ ret = concat2_assuming_safe(ret, Text("\\"));
+ if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[0;35m"));
+ break;
+ }
}
+ unquoted_span += 1;
break;
}
}
@@ -1470,10 +1464,19 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) {
flush_unquoted();
#undef add_escaped
#undef flush_unquoted
-
- ret = concat2_assuming_safe(ret, quotation_mark);
if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[m"));
+ return ret;
+}
+
+public
+Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) {
+ if (quotation_mark.length != 1) fail("Invalid quote text: ", quotation_mark, " (must have length == 1)");
+ Text_t ret = Text$escaped(text, colorize, quotation_mark);
+ if (!(Text$equal_values(quotation_mark, Text("\"")) || Text$equal_values(quotation_mark, Text("'"))
+ || Text$equal_values(quotation_mark, Text("`"))))
+ ret = Texts("$", quotation_mark, ret, quotation_mark);
+ else ret = Texts(quotation_mark, ret, quotation_mark);
return ret;
}
@@ -1726,10 +1729,9 @@ Int_t Text$memory_size(Text_t text) {
public
Text_t Text$layout(Text_t text) {
switch (text.tag) {
- case TEXT_ASCII: return Texts(Text("ASCII("), Int64$as_text((int64_t[1]){text.length}, false, NULL), Text(")"));
- case TEXT_GRAPHEMES:
- return Texts(Text("Graphemes("), Int64$as_text((int64_t[1]){text.length}, false, NULL), Text(")"));
- case TEXT_BLOB: return Texts(Text("Blob("), Int64$as_text((int64_t[1]){text.length}, false, NULL), Text(")"));
+ case TEXT_ASCII: return Texts(Text("ASCII("), Int64$value_as_text(text.length), Text(")"));
+ case TEXT_GRAPHEMES: return Texts(Text("Graphemes("), Int64$value_as_text(text.length), Text(")"));
+ case TEXT_BLOB: return Texts(Text("Blob("), Int64$value_as_text(text.length), Text(")"));
case TEXT_CONCAT:
return Texts(Text("Concat("), Text$layout(*text.left), Text(", "), Text$layout(*text.right), Text(")"));
default: errx(1, "Invalid text tag: %d", text.tag);
diff --git a/src/stdlib/text.h b/src/stdlib/text.h
index 5fa95675..7f7fc2c6 100644
--- a/src/stdlib/text.h
+++ b/src/stdlib/text.h
@@ -7,7 +7,9 @@
#include <stdint.h>
#include "datatypes.h"
+#include "integers.h" // IWYU pragma: export
#include "mapmacro.h"
+#include "nums.h" // IWYU pragma: export
#include "types.h"
#include "util.h"
@@ -31,7 +33,17 @@ static inline Text_t Text_from_str_literal(const char *str) {
static inline Text_t Text_from_text(Text_t t) { return t; }
-#define convert_to_text(x) _Generic(x, Text_t: Text_from_text, char *: Text$from_str, const char *: Text$from_str)(x)
+#define convert_to_text(x) \
+ _Generic(x, \
+ Text_t: Text_from_text, \
+ char *: Text$from_str, \
+ const char *: Text$from_str, \
+ int8_t: Int8$value_as_text, \
+ int16_t: Int16$value_as_text, \
+ int32_t: Int32$value_as_text, \
+ int64_t: Int64$value_as_text, \
+ double: Num$value_as_text, \
+ float: Num32$value_as_text)(x)
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__})
@@ -54,6 +66,7 @@ Text_t Text$upper(Text_t text, Text_t language);
Text_t Text$lower(Text_t text, Text_t language);
Text_t Text$title(Text_t text, Text_t language);
Text_t Text$as_text(const void *text, bool colorize, const TypeInfo_t *info);
+Text_t Text$escaped(Text_t text, bool colorize, Text_t extra_escapes);
Text_t Text$quoted(Text_t str, bool colorize, Text_t quotation_mark);
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);
diff --git a/src/tomo.c b/src/tomo.c
index da10df87..e6636cfc 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -19,6 +19,7 @@
#include "compile/files.h"
#include "compile/headers.h"
#include "config.h"
+#include "formatter/formatter.h"
#include "modules.h"
#include "naming.h"
#include "parse/files.h"
@@ -34,6 +35,7 @@
#include "stdlib/siphash.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
+#include "stdlib/util.h"
#include "types.h"
#define run_cmd(...) \
@@ -74,9 +76,9 @@ static const char *paths_str(List_t paths) {
static OptionalList_t files = NONE_LIST, args = NONE_LIST, uninstall = NONE_LIST, libraries = NONE_LIST;
static OptionalBool_t verbose = false, quiet = false, show_version = false, show_parse_tree = false,
- show_prefix = false, stop_at_transpile = false, stop_at_obj_compilation = false,
- compile_exe = false, should_install = false, clean_build = false, source_mapping = true,
- show_changelog = false;
+ do_format_code = false, format_inplace = false, show_prefix = false, stop_at_transpile = false,
+ stop_at_obj_compilation = false, compile_exe = false, should_install = false, clean_build = false,
+ source_mapping = true, show_changelog = false;
static OptionalText_t show_codegen = NONE_TEXT,
cflags = Text("-Werror -fdollars-in-identifiers -std=c2x -Wno-trigraphs "
@@ -87,16 +89,13 @@ static OptionalText_t show_codegen = NONE_TEXT,
" -D_BSD_SOURCE"
#endif
" -DGC_THREADS"
- " -I'" TOMO_PREFIX "/include' -I'" TOMO_PREFIX "/share/tomo_" TOMO_VERSION
- "/installed' -I/usr/local/include"),
+ " -I/usr/local/include"),
ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo_" TOMO_VERSION),
- ldflags = Text("-Wl,-rpath,'" TOMO_PREFIX "/lib',-rpath,/usr/local/lib"
- " -L/usr/local/lib"),
- optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER);
+ ldflags = Text(" -L/usr/local/lib"), optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER);
static Text_t config_summary,
// This will be either "" or "sudo -u <user>" or "doas -u <user>"
- // to allow a command to put stuff into TOMO_PREFIX as the owner
+ // to allow a command to put stuff into TOMO_PATH as the owner
// of that directory.
as_owner = Text("");
@@ -149,67 +148,103 @@ int main(int argc, char *argv[]) {
#error "Unsupported platform for secure random number generation"
#endif
+ if (getenv("TOMO_PATH")) TOMO_PATH = getenv("TOMO_PATH");
+
+ cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "' ", cflags);
+
// Set up environment variables:
const char *PATH = getenv("PATH");
- setenv("PATH", PATH ? String(TOMO_PREFIX "/bin:", PATH) : TOMO_PREFIX "/bin", 1);
+ setenv("PATH", PATH ? String(TOMO_PATH, "/bin:", PATH) : String(TOMO_PATH, "/bin"), 1);
const char *LD_LIBRARY_PATH = getenv("LD_LIBRARY_PATH");
- setenv("LD_LIBRARY_PATH", LD_LIBRARY_PATH ? String(TOMO_PREFIX "/lib:", LD_LIBRARY_PATH) : TOMO_PREFIX "/lib", 1);
+ setenv("LD_LIBRARY_PATH", LD_LIBRARY_PATH ? String(TOMO_PATH, "/lib:", LD_LIBRARY_PATH) : String(TOMO_PATH, "/lib"),
+ 1);
const char *LIBRARY_PATH = getenv("LIBRARY_PATH");
- setenv("LIBRARY_PATH", LIBRARY_PATH ? String(TOMO_PREFIX "/lib:", LIBRARY_PATH) : TOMO_PREFIX "/lib", 1);
+ setenv("LIBRARY_PATH", LIBRARY_PATH ? String(TOMO_PATH, "/lib:", LIBRARY_PATH) : String(TOMO_PATH, "/lib"), 1);
const char *C_INCLUDE_PATH = getenv("C_INCLUDE_PATH");
- setenv("C_INCLUDE_PATH", C_INCLUDE_PATH ? String(TOMO_PREFIX "/include:", C_INCLUDE_PATH) : TOMO_PREFIX "/include",
- 1);
+ setenv("C_INCLUDE_PATH",
+ C_INCLUDE_PATH ? String(TOMO_PATH, "/include:", C_INCLUDE_PATH) : String(TOMO_PATH, "/include"), 1);
+ const char *CPATH = getenv("CPATH");
+ setenv("CPATH", CPATH ? String(TOMO_PATH, "/include:", CPATH) : String(TOMO_PATH, "/include"), 1);
// 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_PREFIX "'/share/tomo_" TOMO_VERSION "/installed/", 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]);
}
- Text_t usage = Text("\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 files:\x1b[m tomo -c file.tm...\n"
- "\x1b[1mCompile executables:\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"
- " --verbose|-v: verbose output\n"
- " --quiet|-q: quiet output\n"
- " --parse|-p: show parse tree\n"
- " --install|-I: install the executable or library\n"
- " --optimization|-O <level>: set optimization level\n"
- " --run|-r: run a program from " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed\n");
+ 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 files:\x1b[m tomo -c file.tm...\n"
+ "\x1b[1mCompile executables:\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"
+ " --verbose|-v: verbose output\n"
+ " --prefix: print the Tomo prefix directory\n"
+ " --quiet|-q: quiet output\n"
+ " --parse|-p: show parse tree\n"
+ " --transpile|-t: transpile C code without compiling\n"
+ " --show-codegen|-c <pager>: show generated code\n"
+ " --compile-obj|-c: compile C code for object file\n"
+ " --compile-exe|-e: compile to standalone executable without running\n"
+ " --format: print formatted code\n"
+ " --format-inplace: format the code in a file (in place)\n"
+ " --library|-L: build a folder as a library\n"
+ " --install|-I: install the executable or library\n"
+ " --uninstall|-u: uninstall an executable or library\n"
+ " --optimization|-O <level>: set optimization level\n"
+ " --force-rebuild|-f: force rebuilding\n"
+ " --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");
Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage);
- tomo_parse_args(
- argc, argv, usage, help, TOMO_VERSION, {"files", true, List$info(&Path$info), &files},
- {"args", true, List$info(&Text$info), &args}, {"verbose", false, &Bool$info, &verbose},
- {"v", false, &Bool$info, &verbose}, {"version", false, &Bool$info, &show_version},
- {"parse", false, &Bool$info, &show_parse_tree}, {"p", false, &Bool$info, &show_parse_tree},
- {"prefix", false, &Bool$info, &show_prefix}, {"quiet", false, &Bool$info, &quiet},
- {"q", false, &Bool$info, &quiet}, {"transpile", false, &Bool$info, &stop_at_transpile},
- {"t", false, &Bool$info, &stop_at_transpile}, {"compile-obj", false, &Bool$info, &stop_at_obj_compilation},
- {"c", false, &Bool$info, &stop_at_obj_compilation}, {"compile-exe", false, &Bool$info, &compile_exe},
- {"e", false, &Bool$info, &compile_exe}, {"uninstall", false, List$info(&Text$info), &uninstall},
- {"u", false, List$info(&Text$info), &uninstall}, {"library", false, List$info(&Path$info), &libraries},
- {"L", false, List$info(&Path$info), &libraries}, {"show-codegen", false, &Text$info, &show_codegen},
- {"C", false, &Text$info, &show_codegen}, {"install", false, &Bool$info, &should_install},
- {"I", false, &Bool$info, &should_install}, {"optimization", false, &Text$info, &optimization},
- {"O", false, &Text$info, &optimization}, {"force-rebuild", false, &Bool$info, &clean_build},
- {"f", false, &Bool$info, &clean_build}, {"source-mapping", false, &Bool$info, &source_mapping},
- {"m", false, &Bool$info, &source_mapping}, {"changelog", false, &Bool$info, &show_changelog}, );
+ tomo_parse_args(argc, argv, usage, help, TOMO_VERSION, //
+ {"files", true, List$info(&Path$info), &files}, //
+ {"args", true, List$info(&Text$info), &args}, //
+ {"verbose", false, &Bool$info, &verbose}, //
+ {"v", false, &Bool$info, &verbose}, //
+ {"version", false, &Bool$info, &show_version}, //
+ {"parse", false, &Bool$info, &show_parse_tree}, //
+ {"p", false, &Bool$info, &show_parse_tree}, //
+ {"format", false, &Bool$info, &do_format_code}, //
+ {"format-inplace", false, &Bool$info, &format_inplace}, //
+ {"prefix", false, &Bool$info, &show_prefix}, //
+ {"quiet", false, &Bool$info, &quiet}, //
+ {"q", false, &Bool$info, &quiet}, //
+ {"transpile", false, &Bool$info, &stop_at_transpile}, //
+ {"t", false, &Bool$info, &stop_at_transpile}, //
+ {"compile-obj", false, &Bool$info, &stop_at_obj_compilation}, //
+ {"c", false, &Bool$info, &stop_at_obj_compilation}, //
+ {"compile-exe", false, &Bool$info, &compile_exe}, //
+ {"e", false, &Bool$info, &compile_exe}, //
+ {"uninstall", false, List$info(&Text$info), &uninstall}, //
+ {"u", false, List$info(&Text$info), &uninstall}, //
+ {"library", false, List$info(&Path$info), &libraries}, //
+ {"L", false, List$info(&Path$info), &libraries}, //
+ {"show-codegen", false, &Text$info, &show_codegen}, //
+ {"C", false, &Text$info, &show_codegen}, //
+ {"install", false, &Bool$info, &should_install}, //
+ {"I", false, &Bool$info, &should_install}, //
+ {"optimization", false, &Text$info, &optimization}, //
+ {"O", false, &Text$info, &optimization}, //
+ {"force-rebuild", false, &Bool$info, &clean_build}, //
+ {"f", false, &Bool$info, &clean_build}, //
+ {"source-mapping", false, &Bool$info, &source_mapping},
+ {"m", false, &Bool$info, &source_mapping}, //
+ {"changelog", false, &Bool$info, &show_changelog}, );
if (show_prefix) {
- print(TOMO_PREFIX);
+ print(TOMO_PATH);
return 0;
}
if (show_changelog) {
- print_inline(string_slice((const char *)CHANGES_md, CHANGES_md_len));
+ print_inline(string_slice((const char *)CHANGES_md, (size_t)CHANGES_md_len));
return 0;
}
@@ -230,6 +265,8 @@ int main(int argc, char *argv[]) {
cflags = Texts(cflags, Text(" -Wno-parentheses-equality"));
}
+ ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib',-rpath,/usr/local/lib ", ldflags);
+
#ifdef __APPLE__
cflags = Texts(cflags, Text(" -I/opt/homebrew/include"));
ldflags = Texts(ldflags, Text(" -L/opt/homebrew/lib -Wl,-rpath,/opt/homebrew/lib"));
@@ -240,7 +277,7 @@ int main(int argc, char *argv[]) {
config_summary = Text$from_str(String(cc, " ", cflags, " -O", optimization));
- Text_t owner = Path$owner(Path$from_str(TOMO_PREFIX), true);
+ Text_t owner = Path$owner(Path$from_str(TOMO_PATH), true);
Text_t user = Text$from_str(getenv("USER"));
if (!Text$equal_values(user, owner)) {
as_owner = Texts(Text(SUDO " -u "), owner, Text(" "));
@@ -248,7 +285,7 @@ int main(int argc, char *argv[]) {
for (int64_t i = 0; i < uninstall.length; i++) {
Text_t *u = (Text_t *)(uninstall.data + i * uninstall.stride);
- xsystem(as_owner, "rm -rvf '" TOMO_PREFIX "'/share/tomo_" TOMO_VERSION "/installed/", *u);
+ xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", *u);
print("Uninstalled ", *u);
}
@@ -261,8 +298,15 @@ int main(int argc, char *argv[]) {
// This *could* be done in parallel, but there may be some dependency issues.
pid_t child = fork();
if (child == 0) {
- build_library(*lib);
- if (should_install) install_library(*lib);
+ if (Text$equal_values(Path$extension(*lib, false), Text("ini"))) {
+ if (!install_from_modules_ini(*lib, false)) {
+ print("Failed to install modules from file: ", *lib);
+ _exit(1);
+ }
+ } else {
+ build_library(*lib);
+ if (should_install) install_library(*lib);
+ }
_exit(0);
}
wait_for_child_success(child);
@@ -299,6 +343,17 @@ int main(int argc, char *argv[]) {
continue;
}
+ if (do_format_code || format_inplace) {
+ Text_t formatted = format_file(Path$as_c_string(path));
+ if (format_inplace) {
+ print("Formatted ", path);
+ Path$write(path, formatted, 0644);
+ } else {
+ print(formatted);
+ }
+ continue;
+ }
+
Path_t exe_path = compile_exe ? Path$with_extension(path, Text(""), true)
: build_file(Path$with_extension(path, Text(""), true), "");
@@ -327,7 +382,7 @@ int main(int argc, char *argv[]) {
for (int64_t i = 0; i < files.length; i++) {
Path_t path = *(Path_t *)(files.data + i * files.stride);
Path_t exe = Path$with_extension(path, Text(""), true);
- xsystem(as_owner, "cp -v '", exe, "' '" TOMO_PREFIX "'/bin/");
+ xsystem(as_owner, "cp -v '", exe, "' '", TOMO_PATH, "'/bin/");
}
}
return 0;
@@ -369,21 +424,25 @@ static const char *get_version(Path_t lib_dir) {
return String(string_slice(version_line + 4, strcspn(version_line + 4, "\r\n")));
}
-static Text_t get_version_suffix(Path_t lib_dir) { return Texts(Text("_"), Text$from_str(get_version(lib_dir))); }
+static Path_t with_version_suffix(Path_t lib_dir) {
+ Text_t suffix = Texts(Text("_"), Text$from_str(get_version(lib_dir)));
+ return Text$ends_with(Path$base_name(lib_dir), suffix, NULL)
+ ? lib_dir
+ : Path$sibling(lib_dir, Texts(Path$base_name(lib_dir), suffix));
+}
void build_library(Path_t lib_dir) {
lib_dir = Path$resolved(lib_dir, Path$current_dir());
if (!Path$is_directory(lib_dir, true)) print_err("Not a valid directory: ", lib_dir);
- Text_t lib_dir_name = Path$base_name(lib_dir);
List_t tm_files = Path$glob(Path$child(lib_dir, Text("[!._0-9]*.tm")));
env_t *env = fresh_scope(global_env(source_mapping));
List_t object_files = {}, extra_ldlibs = {};
compile_files(env, tm_files, &object_files, &extra_ldlibs);
- Text_t version_suffix = get_version_suffix(lib_dir);
- Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_dir_name, version_suffix, Text(SHARED_SUFFIX)));
+ Text_t versioned_dir = Path$base_name(with_version_suffix(lib_dir));
+ Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), versioned_dir, Text(SHARED_SUFFIX)));
if (!is_stale_for_any(shared_lib, object_files, false)) {
if (verbose) whisper("Unchanged: ", shared_lib);
return;
@@ -391,10 +450,10 @@ 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", lib_dir_name, version_suffix, SHARED_SUFFIX,
+ " -Wl,-install_name,@rpath/'lib", Path$base_name(lib_dir), version_suffix, SHARED_SUFFIX,
"'"
#else
- " -Wl,-soname,'lib", lib_dir_name, version_suffix, SHARED_SUFFIX,
+ " -Wl,-soname,'lib", versioned_dir, SHARED_SUFFIX,
"'"
#endif
" -shared ",
@@ -409,9 +468,9 @@ void build_library(Path_t lib_dir) {
void install_library(Path_t lib_dir) {
Text_t lib_dir_name = Path$base_name(lib_dir);
- Text_t version_suffix = get_version_suffix(lib_dir);
- Path_t dest = Path$child(Path$from_str(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed"),
- Texts(lib_dir_name, version_suffix));
+ Text_t versioned_dir = Path$base_name(with_version_suffix(lib_dir));
+ Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION)), versioned_dir);
+ print("Installing ", lib_dir, " into ", dest);
if (!Path$equal_values(lib_dir, dest)) {
if (verbose) whisper("Clearing out any pre-existing version of ", lib_dir_name);
xsystem(as_owner, "rm -rf '", dest, "'");
@@ -426,12 +485,11 @@ void install_library(Path_t lib_dir) {
int result = system(String(as_owner, "debugedit -b ", lib_dir, " -d '", dest,
"'"
" '",
- dest, "/lib", lib_dir_name, version_suffix, SHARED_SUFFIX,
+ dest, "/lib", versioned_dir, SHARED_SUFFIX,
"' "
">/dev/null 2>/dev/null"));
(void)result;
- print("Installed \033[1m", lib_dir_name, "\033[m to " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/",
- lib_dir_name, version_suffix);
+ print("Installed \033[1m", lib_dir_name, "\033[m to ", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", versioned_dir);
}
void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs) {
@@ -591,14 +649,13 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l
case USE_MODULE: {
module_info_t mod = get_module_info(stmt_ast);
const char *full_name = mod.version ? String(mod.name, "_", mod.version) : mod.name;
- Text_t lib =
- Texts(Text("-Wl,-rpath,'"), Text(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/"),
- Text$from_str(full_name), Text("' '" TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/"),
- Text$from_str(full_name), Text("/lib"), Text$from_str(full_name), Text(SHARED_SUFFIX "'"));
+ 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_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", full_name, "/[!._0-9]*.tm")));
+ List_t children =
+ Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm")));
for (int64_t i = 0; i < children.length; i++) {
Path_t *child = (Path_t *)(children.data + i * children.stride);
Table_t discarded = {.fallback = to_compile};
diff --git a/src/typecheck.c b/src/typecheck.c
index 50df9327..07f8aac4 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -186,10 +186,9 @@ static env_t *load_module(env_t *env, ast_t *module_ast) {
module_info_t mod = get_module_info(module_ast);
glob_t tm_files;
const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name;
- if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE,
- NULL, &tm_files)
+ if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files)
!= 0) {
- if (!try_install_module(mod)) code_err(module_ast, "Couldn't find or install library: ", folder);
+ if (!try_install_module(mod, true)) code_err(module_ast, "Couldn't find or install library: ", folder);
}
env_t *module_env = fresh_scope(env);
@@ -337,20 +336,20 @@ void bind_statement(env_t *env, ast_t *statement) {
case FunctionDef: {
DeclareMatch(def, statement, FunctionDef);
const char *name = Match(def->name, Var)->name;
- type_t *type = get_function_def_type(env, statement);
+ type_t *type = get_function_type(env, statement);
set_binding(env, name, type, namespace_name(env, env->namespace, Text$from_str(name)));
break;
}
case ConvertDef: {
- type_t *type = get_function_def_type(env, statement);
+ type_t *type = get_function_type(env, statement);
type_t *ret_t = Match(type, FunctionType)->ret;
const char *name = get_type_name(ret_t);
if (!name)
code_err(statement, "Conversions are only supported for text, struct, and enum types, not ",
type_to_str(ret_t));
- Text_t code = namespace_name(env, env->namespace,
- Texts(name, "$", String(get_line_number(statement->file, statement->start))));
+ Text_t code =
+ namespace_name(env, env->namespace, Texts(name, "$", get_line_number(statement->file, statement->start)));
binding_t binding = {.type = type, .code = code};
env_t *type_ns = get_namespace_by_type(env, ret_t);
List$insert(&type_ns->namespace->constructors, &binding, I(0), sizeof(binding));
@@ -578,10 +577,24 @@ void bind_statement(env_t *env, ast_t *statement) {
}
}
-type_t *get_function_def_type(env_t *env, ast_t *ast) {
- arg_ast_t *arg_asts = ast->tag == FunctionDef ? Match(ast, FunctionDef)->args : Match(ast, ConvertDef)->args;
- type_ast_t *ret_type =
- ast->tag == FunctionDef ? Match(ast, FunctionDef)->ret_type : Match(ast, ConvertDef)->ret_type;
+type_t *get_function_type(env_t *env, ast_t *ast) {
+ arg_ast_t *arg_asts;
+ type_ast_t *ret_ast;
+ switch (ast->tag) {
+ case FunctionDef:
+ arg_asts = Match(ast, FunctionDef)->args;
+ ret_ast = Match(ast, FunctionDef)->ret_type;
+ break;
+ case ConvertDef:
+ arg_asts = Match(ast, ConvertDef)->args;
+ ret_ast = Match(ast, ConvertDef)->ret_type;
+ break;
+ case Lambda:
+ arg_asts = Match(ast, Lambda)->args;
+ ret_ast = Match(ast, Lambda)->ret_type;
+ break;
+ default: code_err(ast, "This was expected to be a function definition of some sort");
+ }
arg_t *args = NULL;
env_t *scope = fresh_scope(env);
for (arg_ast_t *arg = arg_asts; arg; arg = arg->next) {
@@ -591,10 +604,40 @@ type_t *get_function_def_type(env_t *env, ast_t *ast) {
}
REVERSE_LIST(args);
- type_t *ret = ret_type ? parse_type_ast(scope, ret_type) : Type(VoidType);
- if (has_stack_memory(ret))
- code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame.");
- return Type(FunctionType, .args = args, .ret = ret);
+ if (ast->tag == Lambda) {
+ ast_t *body = Match(ast, Lambda)->body;
+
+ scope->fn = NULL;
+ type_t *ret_t = get_type(scope, body);
+ if (ret_t->tag == ReturnType) ret_t = Match(ret_t, ReturnType)->ret;
+ if (ret_t->tag == AbortType) ret_t = Type(VoidType);
+
+ if (ret_t->tag == OptionalType && !Match(ret_t, OptionalType)->type)
+ code_err(body, "This function doesn't return a specific optional type");
+
+ if (ret_ast) {
+ type_t *declared = parse_type_ast(env, ret_ast);
+ if (can_promote(ret_t, declared)) ret_t = declared;
+ else
+ code_err(ast, "This function was declared to return a value of type ", type_to_str(declared),
+ ", but actually returns a value of type ", type_to_str(ret_t));
+ }
+
+ if (has_stack_memory(ret_t))
+ code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame.");
+ return Type(ClosureType, Type(FunctionType, .args = args, .ret = ret_t));
+ } else {
+ type_t *ret_t = ret_ast ? parse_type_ast(scope, ret_ast) : Type(VoidType);
+ if (has_stack_memory(ret_t))
+ code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame.");
+ return Type(FunctionType, .args = args, .ret = ret_t);
+ }
+}
+
+type_t *get_function_return_type(env_t *env, ast_t *ast) {
+ type_t *fn_t = get_function_type(env, ast);
+ if (fn_t->tag == ClosureType) fn_t = Match(fn_t, ClosureType)->fn;
+ return Match(fn_t, FunctionType)->ret;
}
type_t *get_method_type(env_t *env, ast_t *self, const char *name) {
@@ -1086,7 +1129,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
case Return: {
ast_t *val = Match(ast, Return)->value;
- if (env->fn_ret) env = with_enum_scope(env, env->fn_ret);
+ if (env->fn) env = with_enum_scope(env, get_function_return_type(env, env->fn));
return Type(ReturnType, .ret = (val ? get_type(env, val) : Type(VoidType)));
}
case Stop:
@@ -1315,7 +1358,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
}
} else if ((ast->tag == Divide || ast->tag == Mod || ast->tag == Mod1) && is_numeric_type(rhs_t)) {
- binding_t *b = get_namespace_binding(env, binop.lhs, binop_method_name(ast->tag));
+ binding_t *b = get_namespace_binding(env, binop.lhs, binop_info[ast->tag].method_name);
if (b && b->type->tag == FunctionType) {
DeclareMatch(fn, b->type, FunctionType);
if (type_eq(fn->ret, lhs_t)) {
@@ -1374,7 +1417,8 @@ type_t *get_type(env_t *env, ast_t *ast) {
code_err(reduction->iter, "I don't know how to do a reduction over ", type_to_str(iter_t), " values");
if (reduction->key && !(reduction->op == Min || reduction->op == Max)) {
env_t *item_scope = fresh_scope(env);
- set_binding(item_scope, "$", iterated, EMPTY_TEXT);
+ const char *op_str = binop_info[reduction->op].operator;
+ set_binding(item_scope, op_str, iterated, EMPTY_TEXT);
iterated = get_type(item_scope, reduction->key);
}
return iterated->tag == OptionalType ? iterated : Type(OptionalType, .type = iterated);
@@ -1394,36 +1438,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
return t;
}
- case Lambda: {
- DeclareMatch(lambda, ast, Lambda);
- arg_t *args = NULL;
- env_t *scope = fresh_scope(env); // For now, just use closed variables in scope normally
- for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
- type_t *t = get_arg_ast_type(env, arg);
- args = new (arg_t, .name = arg->name, .alias = arg->alias, .type = t, .next = args);
- set_binding(scope, arg->name, t, EMPTY_TEXT);
- }
- REVERSE_LIST(args);
-
- type_t *ret = get_type(scope, lambda->body);
- if (ret->tag == ReturnType) ret = Match(ret, ReturnType)->ret;
- if (ret->tag == AbortType) ret = Type(VoidType);
-
- if (ret->tag == OptionalType && !Match(ret, OptionalType)->type)
- code_err(lambda->body, "This function doesn't return a specific optional type");
-
- if (lambda->ret_type) {
- type_t *declared = parse_type_ast(env, lambda->ret_type);
- if (can_promote(ret, declared)) ret = declared;
- else
- code_err(ast, "This function was declared to return a value of type ", type_to_str(declared),
- ", but actually returns a value of type ", type_to_str(ret));
- }
-
- if (has_stack_memory(ret))
- code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame.");
- return Type(ClosureType, Type(FunctionType, .args = args, .ret = ret));
- }
+ case Lambda: return get_function_type(env, ast);
case FunctionDef:
case ConvertDef:
diff --git a/src/typecheck.h b/src/typecheck.h
index 8fc30333..d64bb316 100644
--- a/src/typecheck.h
+++ b/src/typecheck.h
@@ -16,7 +16,8 @@ void prebind_statement(env_t *env, ast_t *statement);
void bind_statement(env_t *env, ast_t *statement);
PUREFUNC type_t *get_math_type(env_t *env, ast_t *ast, type_t *lhs_t, type_t *rhs_t);
PUREFUNC bool is_discardable(env_t *env, ast_t *ast);
-type_t *get_function_def_type(env_t *env, ast_t *ast);
+type_t *get_function_type(env_t *env, ast_t *ast);
+type_t *get_function_return_type(env_t *env, ast_t *ast);
type_t *get_arg_type(env_t *env, arg_t *arg);
type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg);
env_t *when_clause_scope(env_t *env, type_t *subject_t, when_clause_t *clause);
diff --git a/src/types.c b/src/types.c
index a4dcc5e5..b0caca1a 100644
--- a/src/types.c
+++ b/src/types.c
@@ -8,7 +8,6 @@
#include "environment.h"
#include "stdlib/integers.h"
-#include "stdlib/print.h"
#include "stdlib/text.h"
#include "stdlib/util.h"
#include "types.h"
@@ -30,7 +29,7 @@ Text_t type_to_text(type_t *t) {
case CStringType: return Text("CString");
case TextType: return Match(t, TextType)->lang ? Text$from_str(Match(t, TextType)->lang) : Text("Text");
case BigIntType: return Text("Int");
- case IntType: return Texts("Int", String(Match(t, IntType)->bits));
+ case IntType: return Texts("Int", (int32_t)Match(t, IntType)->bits);
case NumType: return Match(t, NumType)->bits == TYPE_NBITS32 ? Text("Num32") : Text("Num");
case ListType: {
DeclareMatch(list, t, ListType);
@@ -84,7 +83,7 @@ Text_t type_to_text(type_t *t) {
}
default: {
raise(SIGABRT);
- return Texts("Unknown type: ", String(t->tag));
+ return Texts("Unknown type: ", (int32_t)t->tag);
}
}
}
diff --git a/test/_vectors.tm b/test/_vectors.tm
new file mode 100644
index 00000000..84646cc9
--- /dev/null
+++ b/test/_vectors.tm
@@ -0,0 +1 @@
+struct Vec2(x,y:Num)
diff --git a/test/enums.tm b/test/enums.tm
index 080e5d97..1d00cd1a 100644
--- a/test/enums.tm
+++ b/test/enums.tm
@@ -61,9 +61,10 @@ func main()
i := 1
cases := [Foo.One(1), Foo.One(2), Foo.Zero]
- while when cases[i] is One(x)
+ repeat when cases[i] is One(x)
>> x
i += 1
+ else stop
>> [
(
diff --git a/test/import.tm b/test/import.tm
index 0686190b..edae08f6 100644
--- a/test/import.tm
+++ b/test/import.tm
@@ -1,4 +1,4 @@
-vectors := use ../examples/vectors/vectors.tm
+vectors := use ./_vectors.tm
use ./use_import.tm
func returns_vec(->vectors.Vec2)