diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2026-01-02 15:10:48 -0500 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2026-01-02 15:10:48 -0500 |
| commit | 9653a7c2e53e2bc5e8f146a7d9ea1e71eed19e08 (patch) | |
| tree | 7f026a142b4f8efcdbf517cc58adc97eb3b37cd5 | |
| parent | e4d5bf73e4ad9dc51f923a32903011edfeae2908 (diff) | |
| parent | ce49f93da58d007c0a52ee82e2421adfe06012f9 (diff) | |
Merge branch 'dev' into constructive-reals
80 files changed, 1479 insertions, 450 deletions
diff --git a/.clang-format b/.clang-format index 82e3ce3b..1fabd157 100644 --- a/.clang-format +++ b/.clang-format @@ -71,7 +71,7 @@ AllowShortCaseExpressionOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortCompoundRequirementOnASingleLine: true AllowShortEnumsOnASingleLine: true -AllowShortFunctionsOnASingleLine: All +AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: AllIfsAndElse AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..0e4f08d0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,148 @@ +name: Release binaries + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +env: + PKGNAME: tomo + +jobs: + build-linux: + strategy: + matrix: + arch: + - x86_64 + - aarch64 + + include: + - arch: x86_64 + runner: ubuntu-latest + - arch: aarch64 + runner: ubuntu-24.04-arm64 + + runs-on: ${{ matrix.runner }} + + steps: + - uses: actions/checkout@v4 + + - name: Install deps + run: | + sudo apt-get update + sudo apt-get install -y build-essential libgmp-dev libunistring-dev libgc-dev binutils + + - name: Build + run: | + make -j + + - name: Package + run: | + TAG=${GITHUB_REF_NAME} + FILE=${PKGNAME}-linux-${{ matrix.arch }}.tar.gz + tar -C build -czf "$FILE" ${PKGNAME}@${TAG} + sha256sum "$FILE" > "$FILE.sha256" + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: linux-${{ matrix.arch }} + path: | + ${PKGNAME}-linux-${{ matrix.arch }}.tar.gz + ${PKGNAME}-linux-${{ matrix.arch }}.tar.gz.sha256 + + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Install deps + run: | + brew update + brew install gmp libunistring bdw-gc llvm binutils + + - name: Build arm64 + run: | + make -j + + - name: Package + run: | + TAG=${GITHUB_REF_NAME} + tar -C build -czf ${PKGNAME}-macos-arm64.tar.gz ${PKGNAME}@${TAG} + shasum -a 256 ${PKGNAME}-macos-arm64.tar.gz > ${PKGNAME}-macos-arm64.tar.gz.sha256 + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: macos-arm64 + path: | + ${PKGNAME}-macos-arm64.tar.gz + ${PKGNAME}-macos-arm64.tar.gz.sha256 + + upload-release: + needs: [build-linux, build-macos] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v3 + with: + path: release/ + + - name: List artifacts + run: ls -l release/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: release/* + + aur: + needs: upload-release + runs-on: ubuntu-latest + env: + AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} + steps: + - uses: actions/checkout@v4 + + - name: Install deps + run: | + sudo apt-get install -y pacman-contrib jq gh + + - name: Set up SSH + run: | + mkdir -p ~/.ssh + echo "$AUR_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + + - name: Wait for release assets + run: | + TAG=${GITHUB_REF_NAME} + for i in $(seq 60); do + if gh release view "$TAG" --json assets \ + | jq -e '[ .assets[].name ] | index("tomo-linux-x86_64.tar.gz") and index("tomo-linux-aarch64.tar.gz")' >/dev/null + then exit 0; fi + sleep 10 + done + echo "Timed out waiting for release assets" + exit 1 + + - name: Update PKGBUILD + run: | + TAG=${GITHUB_REF_NAME#v} + sed -i "s/^_tomo_version=.*/_tomo_version=${TAG}/" PKGBUILD + updpkgsums + makepkg --printsrcinfo > .SRCINFO + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git diff --quiet || git commit -am "Release v${TAG}" + + - name: Push to AUR + run: | + git push aur HEAD:master + @@ -11,8 +11,6 @@ /lib/*/*.so /lib/*/*.a -/src/changes.md.h - .build /build *.o @@ -1,6 +1,45 @@ # Version History -## v2025-12-06 +## v2025-12-31 + +- Changed `is_between()` to be bidirectional so `(5).is_between(10, 1) == yes` + +## v2025-12-23.2 + +- Fixes for OpenBSD and Mac. + +## v2025-12-23 + +- Improved C preprocessing performance by eliminating expensive macro calls. + +## v2025-12-22 + +- Use static linking instead of dynamic linking for the Tomo standard library + as well as for user libraries. This produces binaries that do not depend on + having Tomo and the library installed at runtime. +- Added `Path.writer()` and `Path.byte_writer()` for multiple successive writes + +## v2025-12-21.6 + +- Add smarter default behavior if run without any args (REPL-like script runner) + +## v2025-12-21.5 + +- Various fixes for versioning and builds. + +## v2025-12-21.4 + +- Version bump and deprecated `--changelog` flag + +## v2025-12-21.3 + +- Version bump + +## v2025-12-21.2 + +- Update build process + +## v2025-12-21 - You can now discard empty struct values. - For an enum `Foo(A,B,C)`, the syntax `f!` now desugars to `f.A!` using the @@ -82,13 +82,12 @@ G=-ggdb O=-O3 # Note: older versions of Make have buggy behavior with hash marks inside strings, so this ugly code is necessary: 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_INSTALL='"$(PREFIX)"' -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ - -DTOMO_VERSION='"$(TOMO_VERSION)"' -DGIT_VERSION='"$(GIT_VERSION)"' +GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h" 2>/dev/null || echo "unknown") +CFLAGS+=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ + -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ + -DGIT_VERSION='"$(GIT_VERSION)"' -ffunction-sections -fdata-sections CFLAGS_PLACEHOLDER="$$(printf '\033[2m<flags...>\033[m\n')" LDLIBS=-lgc -lm -lunistring -lgmp -LIBTOMO_FLAGS=-shared ifeq ($(OS),OpenBSD) LDLIBS += -lexecinfo @@ -100,27 +99,64 @@ AR_FILE=libtomo@$(TOMO_VERSION).a ifeq ($(OS),Darwin) INCLUDE_DIRS += -I/opt/homebrew/include LDFLAGS += -L/opt/homebrew/lib - LIB_FILE=libtomo@$(TOMO_VERSION).dylib - LIBTOMO_FLAGS += -Wl,-install_name,@rpath/libtomo@$(TOMO_VERSION).dylib -else - LIB_FILE=libtomo@$(TOMO_VERSION).so - LIBTOMO_FLAGS += -Wl,-soname,libtomo@$(TOMO_VERSION).so endif EXE_FILE=tomo@$(TOMO_VERSION) -MODULES_FILE=build/lib/tomo@$(TOMO_VERSION)/modules.ini COMPILER_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/compile/*.c src/parse/*.c src/formatter/*.c)) STDLIB_OBJS=$(patsubst %.c,%.o,$(wildcard src/stdlib/*.c)) TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/[!_]*.tm)) API_YAML=$(wildcard api/*.yaml) API_MD=$(patsubst %.yaml,%.md,$(API_YAML)) -INCLUDE_SYMLINK=build/include/tomo@$(TOMO_VERSION) -all: config.mk check-c-compiler check-libs $(INCLUDE_SYMLINK) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) build/bin/$(EXE_FILE) +all: config.mk check-c-compiler check-libs build @$(ECHO) "All done!" -$(INCLUDE_SYMLINK): - ln -s ../../src/stdlib $@ +BUILD_DIR=build/tomo@$(TOMO_VERSION) +headers := $(wildcard src/stdlib/*.h) +build_headers := $(patsubst src/stdlib/%.h, $(BUILD_DIR)/include/tomo@$(TOMO_VERSION)/%.h, $(headers)) + +# generate corresponding build paths with .gz +build_manpages := $(patsubst %,$(BUILD_DIR)/%.gz,$(wildcard man/man*/*)) + +# Ensure directories exist +dirs := $(BUILD_DIR)/include/tomo@$(TOMO_VERSION) \ + $(BUILD_DIR)/lib \ + $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION) \ + $(BUILD_DIR)/bin \ + $(BUILD_DIR)/man/man1 \ + $(BUILD_DIR)/man/man3 \ + $(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION) + +$(dirs): + mkdir -p $@ + +# Rule for copying headers +$(BUILD_DIR)/include/tomo@$(TOMO_VERSION)%.h: src/stdlib/%.h | $(BUILD_DIR)/include/tomo@$(TOMO_VERSION) + cp $< $@ + +# Rule for gzipping man pages +$(BUILD_DIR)/man/%.gz: man/% | $(BUILD_DIR)/man/man1 $(BUILD_DIR)/man/man3 + gzip -c $< > $@ + +$(BUILD_DIR)/bin/tomo: $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) | $(BUILD_DIR)/bin + ln -sf tomo@$(TOMO_VERSION) $@ + +$(BUILD_DIR)/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS) | $(BUILD_DIR)/bin + @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@ + @$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ + +$(BUILD_DIR)/lib/$(AR_FILE): $(STDLIB_OBJS) | $(BUILD_DIR)/lib + ar -rcs $@ $^ + +$(BUILD_DIR)/lib/tomo@$(TOMO_VERSION)/modules.ini: modules/core.ini modules/examples.ini | $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION) + @cat $^ > $@ + +$(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION)/LICENSE.md: LICENSE.md | $(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION) + cp $< $@ + +build: $(BUILD_DIR)/bin/tomo $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) \ + $(BUILD_DIR)/lib/$(AR_FILE) $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION)/modules.ini \ + $(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION)/LICENSE.md $(build_headers) $(build_manpages) version: @echo $(TOMO_VERSION) @@ -133,24 +169,6 @@ check-libs: check-c-compiler @echo 'int main() { return 0; }' | $(DEFAULT_C_COMPILER) $(LDFLAGS) $(LDLIBS) -x c - -o /dev/null 2>/dev/null >/dev/null \ || { printf '\033[31;1m%s\033[m\n' "I expected to find the following libraries on your system, but I can't find them: $(LDLIBS)"; exit 1; } -build/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS) - @mkdir -p build/bin - @$(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 $@ - @$(CC) $^ $(CFLAGS) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ - -$(MODULES_FILE): modules/core.ini modules/examples.ini - @mkdir -p build/lib/tomo@$(TOMO_VERSION) - @cat $^ > $@ - -build/lib/$(AR_FILE): $(STDLIB_OBJS) - @mkdir -p build/lib - ar -rcs $@ $^ - tags: ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} src/formatter/*.{c,h} @@ -167,21 +185,10 @@ src/stdlib/int64.o src/stdlib/int32.o src/stdlib/int16.o src/stdlib/int8.o: src/ # Float implementations depend on the shared header: src/stdlib/float32.o src/stdlib/float64.o: src/stdlib/floatX.c.h src/stdlib/floatX.h -# These files all depend on the current tomo version: -src/compile/files.o src/compile/headers.o src/compile/statements.o src/config.o src/environment.o \ - src/modules.o src/stdlib/stacktrace.o src/stdlib/stdlib.o src/tomo.o src/typecheck.o: CHANGES.md - -src/changes.md.h: CHANGES.md - @$(ECHO) "Embedding changes.md" - xxd -i $< > $@ - -# The main Tomo executable embeds the changelog: -src/tomo.o: src/changes.md.h - %: %.tm ./local-tomo -e $< -test/results/%.tm.testresult: test/%.tm build/bin/$(EXE_FILE) +test/results/%.tm.testresult: test/%.tm build @mkdir -p test/results @printf '\033[33;1;4m%s\033[m\n' $< @if ! COLOR=1 LC_ALL=C ./local-tomo -O 1 $< 2>&1 | tee $@; then \ @@ -193,7 +200,7 @@ test: $(TESTS) @printf '\033[32;7m ALL TESTS PASSED! \033[m\n' clean: - rm -rf build/{lib,bin}/* $(COMPILER_OBJS) $(STDLIB_OBJS) test/*.tm.testresult test/.build lib/*/.build examples/.build examples/*/.build + rm -rf build/* $(COMPILER_OBJS) $(STDLIB_OBJS) test/*.tm.testresult test/.build lib/*/.build examples/.build examples/*/.build %: %.md pandoc --lua-filter=docs/.pandoc/bold-code.lua -s $< -t man -o $@ @@ -228,7 +235,7 @@ check-utilities: check-c-compiler @which debugedit 2>/dev/null >/dev/null \ || printf '\033[33;1m%s\033[m\n' "I couldn't find 'debugedit' on your system! Try installing the package 'debugedit' with your package manager. (It's not required though)" -install-files: $(INCLUDE_SYMLINK) build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) check-utilities +install-files: build check-utilities @if ! echo "$$PATH" | tr ':' '\n' | grep -qx "$(PREFIX)/bin"; then \ echo $$PATH; \ printf "\033[31;1mError: '$(PREFIX)/bin' is not in your \$$PATH variable!\033[m\n" >&2; \ @@ -241,17 +248,7 @@ install-files: $(INCLUDE_SYMLINK) build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) bu $(SUDO) -u $(OWNER) $(MAKE) install-files; \ exit 0; \ fi; \ - mkdir -p -m 755 "$(PREFIX)/man/man1" "$(PREFIX)/man/man3" "$(PREFIX)/bin" \ - "$(PREFIX)/include/tomo@$(TOMO_VERSION)" "$(PREFIX)/lib" "$(PREFIX)/lib/tomo@$(TOMO_VERSION)" "$(PREFIX)/share/licenses/tomo@$(TOMO_VERSION)"; \ - cp src/stdlib/*.h "$(PREFIX)/include/tomo@$(TOMO_VERSION)/"; \ - cp build/lib/$(LIB_FILE) build/lib/$(AR_FILE) "$(PREFIX)/lib/"; \ - cp $(MODULES_FILE) "$(PREFIX)/lib/tomo@$(TOMO_VERSION)"; \ - cp LICENSE.md "$(PREFIX)/share/licenses/tomo@$(TOMO_VERSION)"; \ - rm -f "$(PREFIX)/bin/$(EXE_FILE)"; \ - cp build/bin/$(EXE_FILE) "$(PREFIX)/bin/"; \ - cp man/man1/* "$(PREFIX)/man/man1/"; \ - cp man/man3/* "$(PREFIX)/man/man3/"; \ - sh link_versions.sh + cp -R $(BUILD_DIR)/* $(PREFIX)/ install: install-files @@ -260,11 +257,10 @@ uninstall: $(SUDO) -u $(OWNER) $(MAKE) uninstall; \ exit 0; \ fi; \ - rm -rvf "$(PREFIX)/bin/tomo" "$(PREFIX)/bin/tomo"[0-9]* "$(PREFIX)/bin/tomo_v"* "$(PREFIX)/include/tomo_v"* \ - "$(PREFIX)/lib/libtomo_v*" "$(PREFIX)/lib/tomo@$(TOMO_VERSION)" "$(PREFIX)/share/licenses/tomo@$(TOMO_VERSION)"; \ - sh link_versions.sh + rm -rvf "$(PREFIX)/bin/tomo" "$(PREFIX)/bin/tomo"* "$(PREFIX)/include/tomo"* \ + "$(PREFIX)/lib/libtomo@"* "$(PREFIX)/lib/tomo@"* "$(PREFIX)/share/licenses/tomo@"*; \ endif .SUFFIXES: -.PHONY: all clean install install-files uninstall test tags core-libs examples deps check-utilities check-c-compiler check-libs version +.PHONY: all build clean install install-files uninstall test tags core-libs examples deps check-utilities check-c-compiler check-libs version @@ -1,3 +1,5 @@ +[](https://github.com/bruce-hill/tomo/releases) + # Tomo - Tomorrow's Language Tomo is a statically typed, safe, simple, lightweight, efficient programming @@ -35,6 +37,24 @@ of many language features or the other example programs/modules in [examples/](examples/). You can also look at the [core libraries](lib/) which are implemented in Tomo. +## Quick Installation + +### Arch User Repository (AUR) + +``` +yay -Sy tomo-bin +``` + +### Install Script + +If you don't want to build from source but just want to install, run this script: + +``` +curl -o /tmp/install_tomo.sh https://raw.githubusercontent.com/bruce-hill/tomo/refs/heads/main/install_script.sh \ + && bash /tmp/install_tomo.sh +rm -f /tmp/install_tomo.sh +``` + ## Features ### Performance @@ -295,15 +295,16 @@ Determines if an integer is between two numbers (inclusive). Argument | Type | Description | Default ---------|------|-------------|--------- x | `Byte` | The integer to be checked. | - -low | `Byte` | The lower bound to check (inclusive). | - -high | `Byte` | The upper bound to check (inclusive). | - +low | `Byte` | One end of the range to check (inclusive); | - +high | `Byte` | The other end of the range to check (inclusive); | - -**Return:** `yes` if `low <= x and x <= high`, otherwise `no` +**Return:** `yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` **Example:** ```tomo assert Byte(7).is_between(1, 10) == yes +assert Byte(7).is_between(10, 1) == yes assert Byte(7).is_between(100, 200) == no assert Byte(7).is_between(1, 7) == yes @@ -1698,7 +1699,7 @@ assert (255).hex(digits=4, uppercase=yes, prefix=yes) == "0x00FF" ## Int.is_between ```tomo -Int.is_between : func(x: Int, low: Int, high: Int -> Bool) +Int.is_between : func(x: Int, a: Int, b: Int -> Bool) ``` Determines if an integer is between two numbers (inclusive). @@ -1706,15 +1707,16 @@ Determines if an integer is between two numbers (inclusive). Argument | Type | Description | Default ---------|------|-------------|--------- x | `Int` | The integer to be checked. | - -low | `Int` | The lower bound to check (inclusive). | - -high | `Int` | The upper bound to check (inclusive). | - +a | `Int` | One end of the range to check (inclusive). | - +b | `Int` | The other end of the range to check (inclusive). | - -**Return:** `yes` if `low <= x and x <= high`, otherwise `no` +**Return:** `yes` if `a <= x and x <= b` or `a >= x and x >= b`, otherwise `no` **Example:** ```tomo assert (7).is_between(1, 10) == yes +assert (7).is_between(10, 1) == yes assert (7).is_between(100, 200) == no assert (7).is_between(1, 7) == yes @@ -2646,6 +2648,32 @@ for line in (/dev/stdin).by_line()! say(line.upper()) ``` +## Path.byte_writer + +```tomo +Path.byte_writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(bytes:[Byte], close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write bytes to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write bytes to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).byte_writer() +write("Hello\n".utf8())! +write("world\n".utf8(), close=yes)! + +``` ## Path.can_execute ```tomo @@ -3464,6 +3492,32 @@ assert created.read() == [1, 2, 3] created.remove() ``` +## Path.writer + +```tomo +Path.writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(text:Text, close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).writer() +write("Hello\n")! +write("world\n", close=yes)! + +``` # Table ## Table.clear diff --git a/api/bytes.md b/api/bytes.md index bb54d92c..99055523 100644 --- a/api/bytes.md +++ b/api/bytes.md @@ -62,15 +62,16 @@ Determines if an integer is between two numbers (inclusive). Argument | Type | Description | Default ---------|------|-------------|--------- x | `Byte` | The integer to be checked. | - -low | `Byte` | The lower bound to check (inclusive). | - -high | `Byte` | The upper bound to check (inclusive). | - +low | `Byte` | One end of the range to check (inclusive); | - +high | `Byte` | The other end of the range to check (inclusive); | - -**Return:** `yes` if `low <= x and x <= high`, otherwise `no` +**Return:** `yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` **Example:** ```tomo assert Byte(7).is_between(1, 10) == yes +assert Byte(7).is_between(10, 1) == yes assert Byte(7).is_between(100, 200) == no assert Byte(7).is_between(1, 7) == yes diff --git a/api/bytes.yaml b/api/bytes.yaml index dea650e2..adf7103b 100644 --- a/api/bytes.yaml +++ b/api/bytes.yaml @@ -57,7 +57,7 @@ Byte.is_between: return: type: 'Bool' description: > - `yes` if `low <= x and x <= high`, otherwise `no` + `yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` args: x: type: 'Byte' @@ -66,13 +66,14 @@ Byte.is_between: low: type: 'Byte' description: > - The lower bound to check (inclusive). + One end of the range to check (inclusive); high: type: 'Byte' description: > - The upper bound to check (inclusive). + The other end of the range to check (inclusive); example: | assert Byte(7).is_between(1, 10) == yes + assert Byte(7).is_between(10, 1) == yes assert Byte(7).is_between(100, 200) == no assert Byte(7).is_between(1, 7) == yes diff --git a/api/floats.md b/api/floats.md index 69097bfd..a6d78b78 100644 --- a/api/floats.md +++ b/api/floats.md @@ -574,15 +574,16 @@ Determines if a number is between two numbers (inclusive). Argument | Type | Description | Default ---------|------|-------------|--------- x | `Float` | The integer to be checked. | - -low | `Float` | The lower bound to check (inclusive). | - -high | `Float` | The upper bound to check (inclusive). | - +low | `Num` | One end of the range to check (inclusive). | - +high | `Num` | The other end of the range to check (inclusive). | - -**Return:** `yes` if `low <= x and x <= high`, otherwise `no` +**Return:** `yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` **Example:** ```tomo assert (7.5).is_between(1, 10) == yes +assert (7.5).is_between(10, 1) == yes assert (7.5).is_between(100, 200) == no assert (7.5).is_between(1, 7.5) == yes diff --git a/api/floats.yaml b/api/floats.yaml index 3720b160..da053d2c 100644 --- a/api/floats.yaml +++ b/api/floats.yaml @@ -401,7 +401,7 @@ Float.is_between: return: type: 'Bool' description: > - `yes` if `low <= x and x <= high`, otherwise `no` + `yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` args: x: type: 'Float' @@ -410,13 +410,14 @@ Float.is_between: low: type: 'Float' description: > - The lower bound to check (inclusive). + One end of the range to check (inclusive). high: type: 'Float' description: > - The upper bound to check (inclusive). + The other end of the range to check (inclusive). example: | assert (7.5).is_between(1, 10) == yes + assert (7.5).is_between(10, 1) == yes assert (7.5).is_between(100, 200) == no assert (7.5).is_between(1, 7.5) == yes diff --git a/api/integers.md b/api/integers.md index ef3a6a60..73779021 100644 --- a/api/integers.md +++ b/api/integers.md @@ -138,7 +138,7 @@ assert (255).hex(digits=4, uppercase=yes, prefix=yes) == "0x00FF" ## Int.is_between ```tomo -Int.is_between : func(x: Int, low: Int, high: Int -> Bool) +Int.is_between : func(x: Int, a: Int, b: Int -> Bool) ``` Determines if an integer is between two numbers (inclusive). @@ -146,15 +146,16 @@ Determines if an integer is between two numbers (inclusive). Argument | Type | Description | Default ---------|------|-------------|--------- x | `Int` | The integer to be checked. | - -low | `Int` | The lower bound to check (inclusive). | - -high | `Int` | The upper bound to check (inclusive). | - +a | `Int` | One end of the range to check (inclusive). | - +b | `Int` | The other end of the range to check (inclusive). | - -**Return:** `yes` if `low <= x and x <= high`, otherwise `no` +**Return:** `yes` if `a <= x and x <= b` or `a >= x and x >= b`, otherwise `no` **Example:** ```tomo assert (7).is_between(1, 10) == yes +assert (7).is_between(10, 1) == yes assert (7).is_between(100, 200) == no assert (7).is_between(1, 7) == yes diff --git a/api/integers.yaml b/api/integers.yaml index b3c6b579..bbbd395d 100644 --- a/api/integers.yaml +++ b/api/integers.yaml @@ -146,22 +146,23 @@ Int.is_between: return: type: 'Bool' description: > - `yes` if `low <= x and x <= high`, otherwise `no` + `yes` if `a <= x and x <= b` or `a >= x and x >= b`, otherwise `no` args: x: type: 'Int' description: > The integer to be checked. - low: + a: type: 'Int' description: > - The lower bound to check (inclusive). - high: + One end of the range to check (inclusive). + b: type: 'Int' description: > - The upper bound to check (inclusive). + The other end of the range to check (inclusive). example: | assert (7).is_between(1, 10) == yes + assert (7).is_between(10, 1) == yes assert (7).is_between(100, 200) == no assert (7).is_between(1, 7) == yes diff --git a/api/paths.md b/api/paths.md index 435932e3..8c08b45b 100644 --- a/api/paths.md +++ b/api/paths.md @@ -118,6 +118,32 @@ for line in (/dev/stdin).by_line()! say(line.upper()) ``` +## Path.byte_writer + +```tomo +Path.byte_writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(bytes:[Byte], close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write bytes to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write bytes to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).byte_writer() +write("Hello\n".utf8())! +write("world\n".utf8(), close=yes)! + +``` ## Path.can_execute ```tomo @@ -936,3 +962,29 @@ assert created.read() == [1, 2, 3] created.remove() ``` +## Path.writer + +```tomo +Path.writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(text:Text, close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).writer() +write("Hello\n")! +write("world\n", close=yes)! + +``` diff --git a/api/paths.yaml b/api/paths.yaml index 02b8fbe8..a659ffbc 100644 --- a/api/paths.yaml +++ b/api/paths.yaml @@ -838,6 +838,82 @@ Path.write: example: | (./file.txt).write("Hello, world!") +Path.writer: + short: create a file writer + description: > + Returns a function that can be used to repeatedly write to the same file. + note: > + The file writer will keep its file descriptor open after each write (unless + the `close` argument is set to `yes`). If the file writer is never closed, + it will be automatically closed when the file writer is garbage collected. + return: + type: 'func(text:Text, close:Bool=no -> Result)' + description: > + Returns a function that can repeatedly write to the same file. If `close` + is set to `yes`, then the file will be closed after writing. If this + function is called again after closing, the file will be reopened for + appending. + args: + path: + type: 'Path' + description: > + The path of the file to write to. + append: + type: 'Bool' + default: 'no' + description: > + If set to `yes`, writes to the file will append. If set to `no`, then + the first write to the file will overwrite its contents and subsequent + calls will append. + permissions: + type: 'Int32' + default: 'Int32(0o644)' + description: > + The permissions to set on the file if it is created. + example: | + write := (./file.txt).writer() + write("Hello\n")! + write("world\n", close=yes)! + +Path.byte_writer: + short: create a byte-based file writer + description: > + Returns a function that can be used to repeatedly write bytes to the same + file. + note: > + The file writer will keep its file descriptor open after each write (unless + the `close` argument is set to `yes`). If the file writer is never closed, + it will be automatically closed when the file writer is garbage collected. + return: + type: 'func(bytes:[Byte], close:Bool=no -> Result)' + description: > + Returns a function that can repeatedly write bytes to the same file. If + `close` is set to `yes`, then the file will be closed after writing. If + this function is called again after closing, the file will be reopened + for appending. + args: + path: + type: 'Path' + description: > + The path of the file to write to. + append: + type: 'Bool' + default: 'no' + description: > + If set to `yes`, writes to the file will append. If set to `no`, then + the first write to the file will overwrite its contents and subsequent + calls will append. + permissions: + type: 'Int32' + default: 'Int32(0o644)' + description: > + The permissions to set on the file if it is created. + example: | + write := (./file.txt).byte_writer() + write("Hello\n".utf8())! + write("world\n".utf8(), close=yes)! + + Path.write_bytes: short: write bytes to a file description: > diff --git a/build/include/tomo b/build/include/tomo deleted file mode 120000 index 82be04c8..00000000 --- a/build/include/tomo +++ /dev/null @@ -1 +0,0 @@ -../../src/stdlib
\ No newline at end of file diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index 7af75880..07b9e051 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -19,7 +19,7 @@ func main() my_num := 2.0 # Strings can use interpolation with the dollar sign $: - say("My variable is $my_variable and this is a sum: $(1 + 2)") + say("My variable is $my_variable, my num is $my_num, and this is a sum: $(1 + 2)") say(" Multiline strings begin with a " at the end of a line and continue in @@ -115,6 +115,7 @@ func main() # Empty tables require specifying the key and value types: empty_table : {Text:Int} + assert empty_table == {} # Tables can be iterated over either by key or key,value: for key in table diff --git a/install_dependencies.sh b/install_dependencies.sh index 5ce6bd74..9b9026e9 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 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 ;; + 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 ;; *) echo "Unknown package manager: $PKG_MGR" >&2 exit 1 diff --git a/install_script.sh b/install_script.sh new file mode 100755 index 00000000..21535f3d --- /dev/null +++ b/install_script.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +set -euo pipefail + +OWNER="bruce-hill" +REPO="tomo" + +# Fetch latest release tag +TAG=$(curl -s "https://api.github.com/repos/$OWNER/$REPO/releases/latest" \ + | grep -Po '"tag_name": "\K.*?(?=")') + +if [[ -z "$TAG" ]]; then + echo "Failed to get latest release tag" + exit 1 +fi + +# Detect platform +OS="$(uname -s)" +ARCH="$(uname -m)" +case "$OS" in + Linux) + case "$ARCH" in + x86_64) FILE="tomo-linux-x86_64.tar.gz" ;; + aarch64|arm64) FILE="tomo-linux-aarch64.tar.gz" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; + esac + ;; + Darwin) + FILE="tomo-macos-universal.tar.gz" + ;; + *) + echo "Unsupported OS: $OS" + exit 1 + ;; +esac + +# Download the artifact (if not present) +if ! [ -e "$FILE" ]; then + URL="https://github.com/$OWNER/$REPO/releases/download/$TAG/$FILE" + echo "Downloading $URL ..." + curl -L -o "$FILE" "$URL" + echo "Downloaded $FILE" +fi + +# Download checksum (if not present) +if ! [ -e "$FILE.sha256" ]; then + CHECKSUM_URL="$URL.sha256" + echo "Downloading checksum $CHECKSUM_URL ..." + curl -L -o "$FILE.sha256" "$CHECKSUM_URL" +fi + +# Verify checksum +shasum --check "$FILE.sha256" +echo "Verified checksum" + +# Configure `doas` vs `sudo` +if command -v doas >/dev/null 2>&1; then + SUDO="doas" +elif command -v sudo >/dev/null 2>&1; then + SUDO="sudo" +else + echo "Neither doas nor sudo found." >&2 + exit 1 +fi + +# Autodetect package manager: +if [ -z "${PACKAGE_MANAGER:-}" ]; then + if command -v dnf >/dev/null 2>&1; then + PACKAGE_MANAGER="dnf" + elif command -v yay >/dev/null 2>&1; then + PACKAGE_MANAGER="yay" + elif command -v paru >/dev/null 2>&1; then + PACKAGE_MANAGER="paru" + elif command -v pacman >/dev/null 2>&1; then + PACKAGE_MANAGER="pacman" + elif command -v xbps-install >/dev/null 2>&1; then + PACKAGE_MANAGER="xbps" + elif command -v pkg_add >/dev/null 2>&1; then + PACKAGE_MANAGER="pkg_add" + elif command -v pkg >/dev/null 2>&1; then + PACKAGE_MANAGER="freebsd-pkg" + elif command -v brew >/dev/null 2>&1; then + PACKAGE_MANAGER="brew" + elif command -v port >/dev/null 2>&1; then + PACKAGE_MANAGER="macports" + elif command -v zypper >/dev/null 2>&1; then + PACKAGE_MANAGER="zypper" + elif command -v nix-env >/dev/null 2>&1; then + PACKAGE_MANAGER="nix" + elif command -v spack >/dev/null 2>&1; then + PACKAGE_MANAGER="spack" + elif command -v conda >/dev/null 2>&1; then + PACKAGE_MANAGER="conda" + elif command -v apt >/dev/null 2>&1; then + PACKAGE_MANAGER="apt" + elif command -v apt-get >/dev/null 2>&1; then + PACKAGE_MANAGER="apt-get" + else + echo "Unsupported package manager" >&2 + exit 1 + fi +fi + +# Install packages +echo 'Installing dependencies...' +case "$PACKAGE_MANAGER" in + apt) $SUDO apt install libgc-dev libunistring-dev binutils libgmp-dev ;; + apt-get) $SUDO apt-get 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) $PACKAGE_MANAGER -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 ;; + *) + echo "Unknown package manager: $PACKAGE_MANAGER" >&2 + exit 1 + ;; +esac + +# Choose installation location +default_prefix='/usr/local' +if echo "$PATH" | tr ':' '\n' | grep -qx "$HOME/.local/bin"; then + default_prefix="~/.local" +fi +printf '\033[1mChoose where to install Tomo (default: %s):\033[m ' "$default_prefix" +read DEST </dev/tty +if [ -z "$DEST" ]; then DEST="$default_prefix"; fi +DEST="${DEST/#\~/$HOME}" + +# Install +if ! [ -w "$DEST" ]; then + USER="$(ls -ld "$DEST" | awk '{print $$3}')" + $(SUDO) -u "$USER" tar -xzf "$FILE" -C "$DEST" --strip-components=1 "tomo@$TAG" +else + tar -xzf "$FILE" -C "$DEST" --strip-components=1 "tomo@$TAG" +fi +echo "Installed to $DEST" + +rm -f "$FILE" "$FILE.sha256" diff --git a/link_versions.sh b/link_versions.sh deleted file mode 100644 index 8dadbb52..00000000 --- a/link_versions.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -TOMO_PREFIX="$(awk -F= '/PREFIX/{print $2}' config.mk)" -cd "$TOMO_PREFIX/bin" -top_version="$(printf '%s\n' 'tomo@'* | sort -r | head -1)" -ln -fs "$top_version" tomo @@ -1,11 +1,6 @@ #!/bin/sh version=$(awk '/^## / {print $2; exit}' CHANGES.md) -here="$(realpath "$(dirname "$0")")" -if [ ! -e "$here/build/bin/tomo@$version" ]; then - echo "Tomo hasn't been compiled yet! Run \`make\` to compile it!" - exit 1; +if ! [ -e ./build/tomo@$version/bin/tomo ]; then + make -j fi - -PATH="$here/build/bin${PATH:+:$PATH}" \ -TOMO_PATH="$here/build" \ -tomo@"$version" "$@" +exec ./build/tomo@$version/bin/tomo "$@" diff --git a/man/man3/tomo-Byte.is_between.3 b/man/man3/tomo-Byte.is_between.3 index 06e53fb0..3b539ea1 100644 --- a/man/man3/tomo-Byte.is_between.3 +++ b/man/man3/tomo-Byte.is_between.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Byte.is_between 3 2025-11-29 "Tomo man-pages" +.TH Byte.is_between 3 2025-12-31 "Tomo man-pages" .SH NAME Byte.is_between \- test if inside a range .SH LIBRARY @@ -23,15 +23,16 @@ lb lb lbx l l l. Name Type Description x Byte The integer to be checked. -low Byte The lower bound to check (inclusive). -high Byte The upper bound to check (inclusive). +low Byte One end of the range to check (inclusive); +high Byte The other end of the range to check (inclusive); .TE .SH RETURN -`yes` if `low <= x and x <= high`, otherwise `no` +`yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` .SH EXAMPLES .EX assert Byte(7).is_between(1, 10) == yes +assert Byte(7).is_between(10, 1) == yes assert Byte(7).is_between(100, 200) == no assert Byte(7).is_between(1, 7) == yes .EE diff --git a/man/man3/tomo-Float.is_between.3 b/man/man3/tomo-Float.is_between.3 index 34eb9c98..a1ad3135 100644 --- a/man/man3/tomo-Float.is_between.3 +++ b/man/man3/tomo-Float.is_between.3 @@ -1,8 +1,8 @@ '\" t -.\" Copyright (c) 2025 Bruce Hill +.\" Copyright (c) 2026 Bruce Hill .\" All rights reserved. .\" -.TH Float.is_between 3 2025-12-11 "Tomo man-pages" +.TH Float.is_between 3 2026-01-02 "Tomo man-pages" .SH NAME Float.is_between \- check if a number is in a range .SH LIBRARY @@ -23,15 +23,16 @@ lb lb lbx l l l. Name Type Description x Float The integer to be checked. -low Float The lower bound to check (inclusive). -high Float The upper bound to check (inclusive). +low Float One end of the range to check (inclusive). +high Float The other end of the range to check (inclusive). .TE .SH RETURN -`yes` if `low <= x and x <= high`, otherwise `no` +`yes` if `a <= x and x <= b` or `b <= x and x <= a`, otherwise `no` .SH EXAMPLES .EX assert (7.5).is_between(1, 10) == yes +assert (7.5).is_between(10, 1) == yes assert (7.5).is_between(100, 200) == no assert (7.5).is_between(1, 7.5) == yes .EE diff --git a/man/man3/tomo-Int.3 b/man/man3/tomo-Int.3 index 186c0aae..64ae7bf6 100644 --- a/man/man3/tomo-Int.3 +++ b/man/man3/tomo-Int.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int 3 2025-11-30 "Tomo man-pages" +.TH Int 3 2025-12-31 "Tomo man-pages" .SH NAME Int \- a Tomo type .SH LIBRARY @@ -59,7 +59,7 @@ For more, see: .TP -.BI Int.is_between\ :\ func(x:\ Int,\ low:\ Int,\ high:\ Int\ ->\ Bool) +.BI Int.is_between\ :\ func(x:\ Int,\ a:\ Int,\ b:\ Int\ ->\ Bool) Determines if an integer is between two numbers (inclusive). For more, see: diff --git a/man/man3/tomo-Int.is_between.3 b/man/man3/tomo-Int.is_between.3 index 8087e0d0..fd54eb41 100644 --- a/man/man3/tomo-Int.is_between.3 +++ b/man/man3/tomo-Int.is_between.3 @@ -2,14 +2,14 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Int.is_between 3 2025-11-29 "Tomo man-pages" +.TH Int.is_between 3 2025-12-31 "Tomo man-pages" .SH NAME Int.is_between \- test if an int is in a range .SH LIBRARY Tomo Standard Library .SH SYNOPSIS .nf -.BI Int.is_between\ :\ func(x:\ Int,\ low:\ Int,\ high:\ Int\ ->\ Bool) +.BI Int.is_between\ :\ func(x:\ Int,\ a:\ Int,\ b:\ Int\ ->\ Bool) .fi .SH DESCRIPTION Determines if an integer is between two numbers (inclusive). @@ -23,15 +23,16 @@ lb lb lbx l l l. Name Type Description x Int The integer to be checked. -low Int The lower bound to check (inclusive). -high Int The upper bound to check (inclusive). +a Int One end of the range to check (inclusive). +b Int The other end of the range to check (inclusive). .TE .SH RETURN -`yes` if `low <= x and x <= high`, otherwise `no` +`yes` if `a <= x and x <= b` or `a >= x and x >= b`, otherwise `no` .SH EXAMPLES .EX assert (7).is_between(1, 10) == yes +assert (7).is_between(10, 1) == yes assert (7).is_between(100, 200) == no assert (7).is_between(1, 7) == yes .EE diff --git a/man/man3/tomo-Path.3 b/man/man3/tomo-Path.3 index ae9b6d51..b916005a 100644 --- a/man/man3/tomo-Path.3 +++ b/man/man3/tomo-Path.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path 3 2025-12-07 "Tomo man-pages" +.TH Path 3 2025-12-22 "Tomo man-pages" .SH NAME Path \- a Tomo type .SH LIBRARY @@ -51,6 +51,14 @@ For more, see: .TP +.BI Path.byte_writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(bytes:[Byte],\ close:Bool=no\ ->\ Result)) +Returns a function that can be used to repeatedly write bytes to the same file. + +For more, see: +.BR Tomo-Path.byte_writer (3) + + +.TP .BI Path.can_execute\ :\ func(path:\ Path\ ->\ Bool) Returns whether or not a file can be executed by the current user/group. @@ -345,3 +353,11 @@ Writes the given bytes to a unique file path based on the specified path. The fi For more, see: .BR Tomo-Path.write_unique_bytes (3) + +.TP +.BI Path.writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(text:Text,\ close:Bool=no\ ->\ Result)) +Returns a function that can be used to repeatedly write to the same file. + +For more, see: +.BR Tomo-Path.writer (3) + diff --git a/man/man3/tomo-Path.byte_writer.3 b/man/man3/tomo-Path.byte_writer.3 new file mode 100644 index 00000000..595f0156 --- /dev/null +++ b/man/man3/tomo-Path.byte_writer.3 @@ -0,0 +1,42 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Path.byte_writer 3 2025-12-22 "Tomo man-pages" +.SH NAME +Path.byte_writer \- create a byte-based file writer +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI Path.byte_writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(bytes:[Byte],\ close:Bool=no\ ->\ Result)) +.fi +.SH DESCRIPTION +Returns a function that can be used to repeatedly write bytes to the same file. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +path Path The path of the file to write to. - +append Bool If set to \fByes\fR, writes to the file will append. If set to \fBno\fR, then the first write to the file will overwrite its contents and subsequent calls will append. no +permissions Int32 The permissions to set on the file if it is created. Int32(0o644) +.TE +.SH RETURN +Returns a function that can repeatedly write bytes to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + +.SH NOTES +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +.SH EXAMPLES +.EX +write := (./file.txt).byte_writer() +write("Hello\[rs]n".utf8())! +write("world\[rs]n".utf8(), close=yes)! +.EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.writer.3 b/man/man3/tomo-Path.writer.3 new file mode 100644 index 00000000..8b3d53d8 --- /dev/null +++ b/man/man3/tomo-Path.writer.3 @@ -0,0 +1,42 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Path.writer 3 2025-12-22 "Tomo man-pages" +.SH NAME +Path.writer \- create a file writer +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI Path.writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(text:Text,\ close:Bool=no\ ->\ Result)) +.fi +.SH DESCRIPTION +Returns a function that can be used to repeatedly write to the same file. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +path Path The path of the file to write to. - +append Bool If set to \fByes\fR, writes to the file will append. If set to \fBno\fR, then the first write to the file will overwrite its contents and subsequent calls will append. no +permissions Int32 The permissions to set on the file if it is created. Int32(0o644) +.TE +.SH RETURN +Returns a function that can repeatedly write to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + +.SH NOTES +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +.SH EXAMPLES +.EX +write := (./file.txt).writer() +write("Hello\[rs]n")! +write("world\[rs]n", close=yes)! +.EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/modules/examples.ini b/modules/examples.ini index 20a04639..8e53d431 100644 --- a/modules/examples.ini +++ b/modules/examples.ini @@ -1,27 +1,27 @@ [log] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-log [ini] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-ini [vectors] -version=v1.0 +version=v1.1 git=https://github.com/bruce-hill/tomo-vectors [http] -version=v1.1 +version=v1.2 git=https://github.com/bruce-hill/tomo-http [http-server] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-http-server [wrap] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-wrap [colorful] -version=v1.0 +version=v2025-11-29 git=https://github.com/bruce-hill/tomo-colorful @@ -85,7 +85,9 @@ 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("\"")); } +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; @@ -281,7 +283,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_to_sexp_str(ast_t *ast) { + return Text$as_c_string(ast_to_sexp(ast)); +} OptionalText_t ast_source(ast_t *ast) { if (ast == NULL || ast->start == NULL || ast->end == NULL) return NONE_TEXT; diff --git a/src/compile/assertions.c b/src/compile/assertions.c index 34055998..18531fd9 100644 --- a/src/compile/assertions.c +++ b/src/compile/assertions.c @@ -60,13 +60,10 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { 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"), + ", ", (int64_t)(expr->end - expr->file->text), ", Text$concat(", + message ? compile_to_type(env, message, Type(TextType)) : Text("Text(\"This assertion failed!\")"), + ", Text(\" (\"), ", expr_as_text(Text("_lhs"), operand_t, Text("no")), ", Text(\" ", failure, + " \"), ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", Text(\")\")));\n"), "}\n"); } default: { @@ -74,8 +71,7 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { 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!\""), + message ? compile_to_type(env, message, Type(TextType)) : Text("Text(\"This assertion failed!\")"), ");\n"); } } diff --git a/src/compile/blocks.c b/src/compile/blocks.c index 1059fd34..66869ecc 100644 --- a/src/compile/blocks.c +++ b/src/compile/blocks.c @@ -9,7 +9,9 @@ #include "compilation.h" public -Text_t compile_block(env_t *env, ast_t *ast) { return Texts("{\n", compile_inline_block(env, ast), "}\n"); } +Text_t compile_block(env_t *env, ast_t *ast) { + return Texts("{\n", compile_inline_block(env, ast), "}\n"); +} Text_t compile_block_expression(env_t *env, ast_t *ast) { ast_list_t *stmts = Match(ast, Block)->statements; diff --git a/src/compile/cli.c b/src/compile/cli.c index 63a467ca..ade6caa7 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -83,10 +83,8 @@ static Text_t generate_usage(env_t *env, type_t *fn_type) { else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); else if (t->tag == EnumType) usage = Texts(usage, "[", flags, " val]"); else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); - } else if (t->tag == EnumType) { - usage = Texts(usage, "\x1b[1m", flag, "\x1b[m"); } else { - usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); + usage = Texts(usage, "\x1b[1m", flag, "\x1b[m"); } } return usage; diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c index 5e95459c..afb5dc15 100644 --- a/src/compile/comparisons.c +++ b/src/compile/comparisons.c @@ -41,6 +41,7 @@ Text_t compile_comparison(env_t *env, ast_t *ast) { } else { code_err(ast, "I can't do comparisons between ", type_to_text(lhs_t), " and ", type_to_text(rhs_t)); } + assert(operand_t); Text_t lhs, rhs; lhs = compile_to_type(env, binop.lhs, operand_t); diff --git a/src/compile/files.c b/src/compile/files.c index 27c2e041..555f848c 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -194,8 +194,8 @@ Text_t compile_file(env_t *env, ast_t *ast) { const char *name = file_base_name(ast->file->filename); return Texts(env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, - "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", - "#include <tomo@" TOMO_VERSION "/tomo.h>\n" + "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", "#include <tomo@", TOMO_VERSION, + "/tomo.h>\n" "#include \"", name, ".tm.h\"\n\n", includes, env->code->local_typedefs, "\n", env->code->lambdas, "\n", env->code->staticdefs, "\n", top_level_code, "public void ", diff --git a/src/compile/functions.c b/src/compile/functions.c index 77393ab2..cadd0453 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -630,7 +630,10 @@ static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { const char *name; } *entry = unused.entries.data + i * unused.entries.stride; if (streq(entry->name, "_")) continue; + // Global/file scoped vars are okay to mutate without reading + if (get_binding(env, entry->name) != NULL) continue; ast_t *var = Table$str_get(assigned_vars, entry->name); + assert(var); code_err(var, "This variable was assigned to, but never read from."); } } diff --git a/src/compile/headers.c b/src/compile/headers.c index e90556a1..df7142d1 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -135,7 +135,7 @@ Text_t compile_file_header(env_t *env, Path_t header_path, ast_t *ast) { Text_t header = Texts("#pragma once\n", env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT, - "#include <tomo@" TOMO_VERSION "/tomo.h>\n"); + "#include <tomo@", TOMO_VERSION, "/tomo.h>\n"); compile_typedef_info_t info = {.env = env, .header = &header, .header_path = header_path}; visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn = (void *)_make_typedefs, &info}); @@ -161,7 +161,7 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) module_info_t mod = get_used_module_info(ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + if (glob(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/compile/indexing.c b/src/compile/indexing.c index 13062641..031ef9a0 100644 --- a/src/compile/indexing.c +++ b/src/compile/indexing.c @@ -50,7 +50,7 @@ Text_t compile_indexing(env_t *env, ast_t *ast, bool checked) { return Texts("({ ", compile_declaration(item_type, Text("opt")), " = ", code, "; ", "if unlikely (", check_none(item_type, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ", start, ", ", end, ", ", - "\"This was expected to be a value, but it's `none`\\n\");\n", + "Text(\"This was expected to be a value, but it's `none`\\n\"));\n", optional_into_nonnone(item_type, Text("opt")), "; })"); } return code; diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 9aca84d0..f54d8931 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -134,9 +134,9 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { compile_to_pointer_depth(env, f->fielded, 0, true), ";", "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(", quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text), - ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", - tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), - ", \"\\n\");\n}\n", + ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", + "Text$concat(Text(\"This was expected to be ", tag->name, ", but it was: \"), ", + expr_as_text(Text("_test_enum"), enum_t, Text("false")), ", Text(\"\\n\")));\n}\n", compile_maybe_incref( env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type), "; })"); @@ -149,7 +149,7 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ", - "\"This was expected to be a value, but it's `none`\\n\");\n", + "Text(\"This was expected to be a value, but it's `none`\\n\"));\n", optional_into_nonnone(value_t, Text("opt")), "; })"); } } diff --git a/src/compile/promotions.c b/src/compile/promotions.c index 4b5458c9..151850f0 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -8,7 +8,9 @@ #include "../types.h" #include "compilation.h" -static Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); } +static Text_t quoted_str(const char *str) { + return Text$quoted(Text$from_str(str), false, Text("\"")); +} public bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed) { @@ -62,12 +64,12 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede if (needed->tag == FloatType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == FloatType) { 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 ", 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")), "; })"); + *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), ", ", + "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/statements.c b/src/compile/statements.c index 81a10ddd..c9a0fd0e 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -197,7 +197,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { module_info_t mod = get_used_module_info(ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + if (glob(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/compile/text.c b/src/compile/text.c index 25a6e9a7..7f11169b 100644 --- a/src/compile/text.c +++ b/src/compile/text.c @@ -4,7 +4,6 @@ #include "../ast.h" #include "../environment.h" -#include "../naming.h" #include "../stdlib/datatypes.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" @@ -102,19 +101,12 @@ Text_t compile_text_ast(env_t *env, ast_t *ast) { type_t *text_t = lang ? Table$str_get(*env->types, lang) : TEXT_TYPE; if (!text_t || text_t->tag != TextType) code_err(ast, quoted(lang), " is not a valid text language name"); - Text_t lang_constructor; - if (!lang || streq(lang, "Text")) lang_constructor = Text("Text"); - else - lang_constructor = namespace_name(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent, - Text$from_str(lang)); - ast_list_t *chunks = Match(ast, TextJoin)->children; if (!chunks) { - return Texts(lang_constructor, "(\"\")"); + return Text("EMPTY_TEXT"); } else if (!chunks->next && chunks->ast->tag == TextLiteral) { Text_t literal = Match(chunks->ast, TextLiteral)->text; - if (string_literal_is_all_ascii(literal)) - return Texts(lang_constructor, "(", compile_text_literal(literal), ")"); + if (string_literal_is_all_ascii(literal)) return Texts("Text(", compile_text_literal(literal), ")"); return Texts("((", compile_type(text_t), ")", compile(env, chunks->ast), ")"); } else { Text_t code = EMPTY_TEXT; @@ -142,7 +134,6 @@ Text_t compile_text_ast(env_t *env, ast_t *ast) { code = Texts(code, chunk_code); if (chunk->next) code = Texts(code, ", "); } - if (chunks->next) return Texts(lang_constructor, "s(", code, ")"); - else return code; + return Texts("Text$concat(", code, ")"); } } diff --git a/src/compile/text.h b/src/compile/text.h index c160c7a9..ae3cc5c3 100644 --- a/src/compile/text.h +++ b/src/compile/text.h @@ -14,5 +14,9 @@ Text_t compile_text(env_t *env, ast_t *ast, Text_t color); Text_t compile_text_literal(Text_t literal); Text_t expr_as_text(Text_t expr, type_t *t, Text_t color); -MACROLIKE Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); } -MACROLIKE Text_t quoted_text(Text_t text) { return Text$quoted(text, false, Text("\"")); } +MACROLIKE Text_t quoted_str(const char *str) { + return Text$quoted(Text$from_str(str), false, Text("\"")); +} +MACROLIKE Text_t quoted_text(Text_t text) { + return Text$quoted(text, false, Text("\"")); +} diff --git a/src/config.h b/src/config.h index 8ee44200..e703f95d 100644 --- a/src/config.h +++ b/src/config.h @@ -1,18 +1,11 @@ // Configuration of values that will be baked into the executable: -#ifndef TOMO_VERSION -#define TOMO_VERSION "v???" -#endif - #ifndef GIT_VERSION #define GIT_VERSION "???" #endif -#ifndef TOMO_INSTALL -#define TOMO_INSTALL "/usr/local" -#endif - extern const char *TOMO_PATH; +extern const char *TOMO_VERSION; #ifndef DEFAULT_C_COMPILER #define DEFAULT_C_COMPILER "cc" diff --git a/src/environment.c b/src/environment.c index f59194d4..27a57d7a 100644 --- a/src/environment.c +++ b/src/environment.c @@ -101,7 +101,7 @@ env_t *global_env(bool source_mapping) { "Byte", Type(ByteType), Text("Byte_t"), Text("Byte$info"), {"get_bit", "Byte$get_bit", "func(x:Byte, bit_index:Int -> Bool)"}, // {"hex", "Byte$hex", "func(byte:Byte, uppercase=yes, prefix=no -> Text)"}, // - {"is_between", "Byte$is_between", "func(x:Byte, low:Byte, high:Byte -> Bool)"}, // + {"is_between", "Byte$is_between", "func(x:Byte, a:Byte, b:Byte -> Bool)"}, // {"max", "Byte$max", "Byte"}, // {"min", "Byte$min", "Byte"}, // {"parse", "Byte$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Byte?)"}, // @@ -118,7 +118,7 @@ env_t *global_env(bool source_mapping) { {"gcd", "Int$gcd", "func(x,y:Int -> Int)"}, // {"get_bit", "Int$get_bit", "func(x,bit_index:Int -> Bool)"}, // {"hex", "Int$hex", "func(i:Int, digits=0, uppercase=yes, prefix=yes -> Text)"}, // - {"is_between", "Int$is_between", "func(x:Int,low:Int,high:Int -> Bool)"}, // + {"is_between", "Int$is_between", "func(x:Int, a:Int, b:Int -> Bool)"}, // {"is_prime", "Int$is_prime", "func(x:Int,reps=50 -> Bool)"}, // {"left_shifted", "Int$left_shifted", "func(x,y:Int -> Int)"}, // {"minus", "Int$minus", "func(x,y:Int -> Int)"}, // @@ -151,7 +151,7 @@ env_t *global_env(bool source_mapping) { {"parse", "Int64$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int64?)"}, // {"get_bit", "Int64$get_bit", "func(x:Int64, bit_index:Int -> Bool)"}, // {"hex", "Int64$hex", "func(i:Int64, digits=0, uppercase=yes, prefix=yes -> Text)"}, // - {"is_between", "Int64$is_between", "func(x:Int64,low:Int64,high:Int64 -> Bool)"}, // + {"is_between", "Int64$is_between", "func(x:Int64, a:Int64, b:Int64 -> Bool)"}, // {"max", "Int64$max", "Int64"}, // {"min", "Int64$min", "Int64"}, // {"modulo", "Int64$modulo", "func(x,y:Int64 -> Int64)"}, // @@ -173,7 +173,7 @@ env_t *global_env(bool source_mapping) { {"parse", "Int32$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int32?)"}, // {"get_bit", "Int32$get_bit", "func(x:Int32, bit_index:Int -> Bool)"}, // {"hex", "Int32$hex", "func(i:Int32, digits=0, uppercase=yes, prefix=yes -> Text)"}, // - {"is_between", "Int32$is_between", "func(x:Int32,low:Int32,high:Int32 -> Bool)"}, // + {"is_between", "Int32$is_between", "func(x:Int32, a:Int32, b:Int32 -> Bool)"}, // {"max", "Int32$max", "Int32"}, // {"min", "Int32$min", "Int32"}, // {"modulo", "Int32$modulo", "func(x,y:Int32 -> Int32)"}, // @@ -195,7 +195,7 @@ env_t *global_env(bool source_mapping) { {"parse", "Int16$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int16?)"}, // {"get_bit", "Int16$get_bit", "func(x:Int16, bit_index:Int -> Bool)"}, // {"hex", "Int16$hex", "func(i:Int16, digits=0, uppercase=yes, prefix=yes -> Text)"}, // - {"is_between", "Int16$is_between", "func(x:Int16,low:Int16,high:Int16 -> Bool)"}, // + {"is_between", "Int16$is_between", "func(x:Int16, a:Int16, b:Int16 -> Bool)"}, // {"max", "Int16$max", "Int16"}, // {"min", "Int16$min", "Int16"}, // {"modulo", "Int16$modulo", "func(x,y:Int16 -> Int16)"}, // @@ -217,7 +217,7 @@ env_t *global_env(bool source_mapping) { {"parse", "Int8$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int8?)"}, // {"get_bit", "Int8$get_bit", "func(x:Int8, bit_index:Int -> Bool)"}, // {"hex", "Int8$hex", "func(i:Int8, digits=0, uppercase=yes, prefix=yes -> Text)"}, // - {"is_between", "Int8$is_between", "func(x:Int8,low:Int8,high:Int8 -> Bool)"}, // + {"is_between", "Int8$is_between", "func(x:Int8, a:Int8, b:Int8 -> Bool)"}, // {"max", "Int8$max", "Int8"}, // {"min", "Int8$min", "Int8"}, // {"modulo", "Int8$modulo", "func(x,y:Int8 -> Int8)"}, // @@ -239,7 +239,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Float64$clamped", "func(x,low,high:Float64 -> Float64)"}, // {"percent", "Float64$percent", "func(n:Float64,precision=0.01 -> Text)"}, // {"with_precision", "Float64$with_precision", "func(n:Float64,precision:Float64 -> Float64)"}, // - {"is_between", "Float64$is_between", "func(x:Float64,low:Float64,high:Float64 -> Bool)"}, // + {"is_between", "Float64$is_between", "func(x:Float64, a:Float64, b:Float64 -> Bool)"}, // {"isinf", "Float64$isinf", "func(n:Float64 -> Bool)"}, // {"isfinite", "Float64$isfinite", "func(n:Float64 -> Bool)"}, // {"modulo", "Float64$mod", "func(x,y:Float64 -> Float64)"}, // @@ -269,7 +269,7 @@ env_t *global_env(bool source_mapping) { {"clamped", "Float32$clamped", "func(x,low,high:Float32 -> Float32)"}, // {"percent", "Float32$percent", "func(n:Float32,precision=Float32(.01) -> Text)"}, // {"with_precision", "Float32$with_precision", "func(n:Float32,precision:Float32 -> Float32)"}, // - {"is_between", "Float32$is_between", "func(x:Float32,low:Float32,high:Float32 -> Bool)"}, // + {"is_between", "Float32$is_between", "func(x:Float32, a:Float32, b:Float32 -> Bool)"}, // {"isinf", "Float32$isinf", "func(n:Float32 -> Bool)"}, // {"isfinite", "Float32$isfinite", "func(n:Float32 -> Bool)"}, // C(2_SQRTPI), C(E), C(PI_2), C(2_PI), C(1_PI), C(LN10), C(LN2), C(LOG2E), C(PI), C(PI_4), C(SQRT2), @@ -352,6 +352,10 @@ env_t *global_env(bool source_mapping) { {"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"}, // {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, // + {"writer", "Path$writer", + "func(path:Path, append=no, permissions=Int32(0o644) -> func(text:Text, close=no -> Result))"}, // + {"byte_writer", "Path$byte_writer", + "func(path:Path, append=no, permissions=Int32(0o644) -> func(bytes:[Byte], close=no -> Result))"}, // {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, // {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path?)"}, // {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path?)"}), @@ -723,7 +727,9 @@ env_t *namespace_env(env_t *env, const char *namespace_name) { return ns_env; } -PUREFUNC binding_t *get_binding(env_t *env, const char *name) { return Table$str_get(*env->locals, name); } +PUREFUNC binding_t *get_binding(env_t *env, const char *name) { + return Table$str_get(*env->locals, name); +} binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name) { type_t *self_type = get_type(env, self); diff --git a/src/modules.c b/src/modules.c index 36952ec8..c7c29d24 100644 --- a/src/modules.c +++ b/src/modules.c @@ -102,7 +102,7 @@ module_info_t get_used_module_info(ast_t *use) { const char *name = Match(use, Use)->path; module_info_t *info = new (module_info_t, .name = name); Path_t tomo_default_modules = - Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/modules.ini")); + Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/modules.ini")); read_modules_ini(tomo_default_modules, info); read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info); read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info); @@ -111,8 +111,8 @@ module_info_t get_used_module_info(ast_t *use) { } bool try_install_module(module_info_t mod, bool ask_confirmation) { - Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/", Text$from_str(mod.name), - "@", Text$from_str(mod.version))); + Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/", + Text$from_str(mod.name), "@", Text$from_str(mod.version))); if (Path$exists(dest)) return true; print("No such path: ", dest); @@ -131,6 +131,9 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) { if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest); else if (mod.version) xsystem("git clone --depth=1 --branch ", mod.version, " ", mod.git, " ", dest); else xsystem("git clone --depth=1 ", mod.git, " ", dest); + // Clean up .git/ folder after cloning: + xsystem("rm -rf ", dest, "/.git"); + // Build library: xsystem("tomo -L ", dest); return true; } else if (mod.url) { diff --git a/src/parse/expressions.c b/src/parse/expressions.c index d031c49f..27e44129 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -195,7 +195,9 @@ ast_t *parse_term(parse_ctx_t *ctx, const char *pos) { return term; } -ast_t *parse_expr(parse_ctx_t *ctx, const char *pos) { return parse_infix_expr(ctx, pos, 0); } +ast_t *parse_expr(parse_ctx_t *ctx, const char *pos) { + return parse_infix_expr(ctx, pos, 0); +} ast_t *parse_extended_expr(parse_ctx_t *ctx, const char *pos) { ast_t *expr = NULL; diff --git a/src/parse/utils.c b/src/parse/utils.c index f1b518ca..03e0ebcd 100644 --- a/src/parse/utils.c +++ b/src/parse/utils.c @@ -41,7 +41,9 @@ size_t some_not(const char **pos, const char *forbid) { return len; } -size_t spaces(const char **pos) { return some_of(pos, " \t"); } +size_t spaces(const char **pos) { + return some_of(pos, " \t"); +} void whitespace(parse_ctx_t *ctx, const char **pos) { while (some_of(pos, " \t\r\n") || comment(ctx, pos)) @@ -93,7 +95,9 @@ const char *get_id(const char **inout) { return word; } -PUREFUNC const char *eol(const char *str) { return str + strcspn(str, "\r\n"); } +PUREFUNC const char *eol(const char *str) { + return str + strcspn(str, "\r\n"); +} bool comment(parse_ctx_t *ctx, const char **pos) { if ((*pos)[0] == '#') { diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c index 7b81e319..a1ffc12e 100644 --- a/src/stdlib/bigint.c +++ b/src/stdlib/bigint.c @@ -72,8 +72,8 @@ static bool Int$is_none(const void *i, const TypeInfo_t *info) { public PUREFUNC int32_t Int$compare_value(const Int_t x, const Int_t y) { if (likely(x.small & y.small & 1L)) return (x.small > y.small) - (x.small < y.small); - else if (x.small & 1) return -mpz_cmp_si(y.big, x.small); - else if (y.small & 1) return mpz_cmp_si(x.big, y.small); + else if (x.small & 1) return -mpz_cmp_si(y.big, (x.small >> 2)); + else if (y.small & 1) return mpz_cmp_si(x.big, (y.small >> 2)); else return x.big == y.big ? 0 : mpz_cmp(x.big, y.big); } @@ -102,7 +102,9 @@ CONSTFUNC Int_t Int$clamped(Int_t x, Int_t low, Int_t high) { public CONSTFUNC bool Int$is_between(const Int_t x, const Int_t low, const Int_t high) { - return Int$compare_value(low, x) <= 0 && Int$compare_value(x, high) <= 0; + int32_t low_cmp = Int$compare_value(x, low); + int32_t high_cmp = Int$compare_value(x, high); + return (low_cmp >= 0 && high_cmp <= 0) || (low_cmp <= 0 && high_cmp >= 0); } public @@ -395,7 +397,9 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) { } public -Int_t Int$from_str(const char *str) { return Int$parse(Text$from_str(str), NONE_INT, NULL); } +Int_t Int$from_str(const char *str) { + return Int$parse(Text$from_str(str), NONE_INT, NULL); +} public OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) { diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h index 2936f2cd..a948e1e7 100644 --- a/src/stdlib/bigint.h +++ b/src/stdlib/bigint.h @@ -198,18 +198,30 @@ MACROLIKE PUREFUNC Int_t Int$from_float64(double n, bool truncate) { if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n); return Int$from_mpz(result); } -MACROLIKE PUREFUNC Int_t Int$from_float32(float n, bool truncate) { return Int$from_float64((double)n, truncate); } +MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) { + return Int$from_float64((double)n, truncate); +} MACROLIKE Int_t Int$from_int64(int64_t i) { if likely (i >= SMALLEST_SMALL_INT && i <= BIGGEST_SMALL_INT) return (Int_t){.small = (i << 2L) | 1L}; mpz_t result; mpz_init_set_si(result, i); return Int$from_mpz(result); } -MACROLIKE CONSTFUNC Int_t Int$from_int32(Int32_t i) { return Int$from_int64((Int32_t)i); } -MACROLIKE CONSTFUNC Int_t Int$from_int16(Int16_t i) { return I_small(i); } -MACROLIKE CONSTFUNC Int_t Int$from_int8(Int8_t i) { return I_small(i); } -MACROLIKE CONSTFUNC Int_t Int$from_byte(Byte_t b) { return I_small(b); } -MACROLIKE CONSTFUNC Int_t Int$from_bool(Bool_t b) { return I_small(b); } +MACROLIKE CONSTFUNC Int_t Int$from_int32(Int32_t i) { + return Int$from_int64((Int32_t)i); +} +MACROLIKE CONSTFUNC Int_t Int$from_int16(Int16_t i) { + return I_small(i); +} +MACROLIKE CONSTFUNC Int_t Int$from_int8(Int8_t i) { + return I_small(i); +} +MACROLIKE CONSTFUNC Int_t Int$from_byte(Byte_t b) { + return I_small(b); +} +MACROLIKE CONSTFUNC Int_t Int$from_bool(Bool_t b) { + return I_small(b); +} #ifdef __GNUC__ #pragma GCC diagnostic pop diff --git a/src/stdlib/bools.h b/src/stdlib/bools.h index 52bd45a8..17ff3681 100644 --- a/src/stdlib/bools.h +++ b/src/stdlib/bools.h @@ -13,11 +13,23 @@ PUREFUNC Text_t Bool$as_text(const void *b, bool colorize, const TypeInfo_t *type); OptionalBool_t Bool$parse(Text_t text, Text_t *remainder); -MACROLIKE Bool_t Bool$from_int(Int_t i) { return (i.small != 0); } -MACROLIKE Bool_t Bool$from_int64(Int64_t i) { return (i != 0); } -MACROLIKE Bool_t Bool$from_int32(Int32_t i) { return (i != 0); } -MACROLIKE Bool_t Bool$from_int16(Int16_t i) { return (i != 0); } -MACROLIKE Bool_t Bool$from_int8(Int8_t i) { return (i != 0); } -MACROLIKE Bool_t Bool$from_byte(uint8_t b) { return (b != 0); } +MACROLIKE Bool_t Bool$from_int(Int_t i) { + return (i.small != 0); +} +MACROLIKE Bool_t Bool$from_int64(Int64_t i) { + return (i != 0); +} +MACROLIKE Bool_t Bool$from_int32(Int32_t i) { + return (i != 0); +} +MACROLIKE Bool_t Bool$from_int16(Int16_t i) { + return (i != 0); +} +MACROLIKE Bool_t Bool$from_int8(Int8_t i) { + return (i != 0); +} +MACROLIKE Bool_t Bool$from_byte(uint8_t b) { + return (b != 0); +} extern const TypeInfo_t Bool$info; diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c index 4416d804..9874f222 100644 --- a/src/stdlib/bytes.c +++ b/src/stdlib/bytes.c @@ -25,12 +25,14 @@ PUREFUNC public Text_t Byte$as_text(const void *b, bool colorize, const TypeInfo '\0', }; Text_t text = Text$from_str(digits); - if (colorize) text = Texts(Text("\x1b[35m"), text, Text("\x1b[m")); + if (colorize) text = Text$concat(Text("\x1b[35m"), text, Text("\x1b[m")); return text; } public -CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { return low <= x && x <= high; } +CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { + return (low <= x && x <= high) || (high <= x && x <= low); +} public OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder) { @@ -67,7 +69,9 @@ public bool Byte$get_bit(Byte_t x, Int_t bit_index) { if (Int$compare_value(bit_index, I(1)) < 0) fail("Invalid bit index (expected 1 or higher): ", bit_index); if (Int$compare_value(bit_index, I(8)) > 0) - fail("Bit index is too large! There are only 8 bits in a byte, but index is: ", bit_index); + fail("Bit index is too large! There are only 8 bits in a byte, but index " + "is: ", + bit_index); return ((x & (Byte_t)(1L << (Int64$from_int(bit_index, true) - 1L))) != 0); } diff --git a/src/stdlib/bytes.h b/src/stdlib/bytes.h index 6581f300..b0a1c213 100644 --- a/src/stdlib/bytes.h +++ b/src/stdlib/bytes.h @@ -21,8 +21,12 @@ Byte_t Byte$from_int16(int16_t i, bool truncate); OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder); Closure_t Byte$to(Byte_t first, Byte_t last, OptionalInt8_t step); -MACROLIKE Byte_t Byte$from_int8(int8_t i) { return (Byte_t)i; } -MACROLIKE Byte_t Byte$from_bool(bool b) { return (Byte_t)b; } +MACROLIKE Byte_t Byte$from_int8(int8_t i) { + return (Byte_t)i; +} +MACROLIKE Byte_t Byte$from_bool(bool b) { + return (Byte_t)b; +} CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high); extern const Byte_t Byte$min; diff --git a/src/stdlib/c_strings.c b/src/stdlib/c_strings.c index 57960577..cbe46b68 100644 --- a/src/stdlib/c_strings.c +++ b/src/stdlib/c_strings.c @@ -70,8 +70,8 @@ const char *CString$join(const char *glue, List_t strings) { Text_t ret = EMPTY_TEXT; Text_t glue_text = Text$from_str(glue); for (int64_t i = 0; i < (int64_t)strings.length; i++) { - if (i > 0) ret = Texts(ret, glue_text); - ret = Texts(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride))); + if (i > 0) ret = Text$concat(ret, glue_text); + ret = Text$concat(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride))); } return Text$as_c_string(ret); } diff --git a/src/stdlib/enums.c b/src/stdlib/enums.c index b9b970fa..9cc16c5d 100644 --- a/src/stdlib/enums.c +++ b/src/stdlib/enums.c @@ -65,7 +65,7 @@ Text_t Enum$as_text(const void *obj, bool colorize, const TypeInfo_t *type) { NamedType_t value = type->EnumInfo.tags[tag - 1]; if (!value.type || value.type->size == 0) { Text_t text = Text$from_str(value.name); - return colorize ? Texts(Text("\x1b[1m"), text, Text("\x1b[m")) : text; + return colorize ? Text$concat(Text("\x1b[1m"), text, Text("\x1b[m")) : text; } return generic_as_text(obj + value_offset(type), colorize, value.type); diff --git a/src/stdlib/floatX.c.h b/src/stdlib/floatX.c.h index 54477ee3..cfef29fd 100644 --- a/src/stdlib/floatX.c.h +++ b/src/stdlib/floatX.c.h @@ -50,7 +50,7 @@ PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo if (!x) return Text(TYPE_STR); static const Text_t color_prefix = Text("\x1b[35m"), color_suffix = Text("\x1b[m"); Text_t text = NAMESPACED(value_as_text)(*(FLOAT_T *)x); - return colorize ? Texts(color_prefix, text, color_suffix) : text; + return colorize ? Text$concat(color_prefix, text, color_suffix) : text; } public PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) { @@ -66,7 +66,9 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf } #elif FLOATX_C_H__BITS == 32 public -PUREFUNC Text_t NAMESPACED(value_as_text)(FLOAT_T x) { return Float64$value_as_text((double)x); } +PUREFUNC Text_t NAMESPACED(value_as_text)(FLOAT_T x) { + return Float64$value_as_text((double)x); +} public PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *info) { (void)info; @@ -115,7 +117,7 @@ public Text_t NAMESPACED(percent)(FLOAT_T x, FLOAT_T precision) { FLOAT_T d = SUFFIXED(100.) * x; d = NAMESPACED(with_precision)(d, precision); - return Texts(NAMESPACED(value_as_text)(d), Text("%")); + return Text$concat(NAMESPACED(value_as_text)(d), Text("%")); } public @@ -186,11 +188,17 @@ CONSTFUNC bool NAMESPACED(is_none)(const void *n, const TypeInfo_t *info) { } public -CONSTFUNC bool NAMESPACED(isinf)(FLOAT_T n) { return (fpclassify(n) == FP_INFINITE); } +CONSTFUNC bool NAMESPACED(isinf)(FLOAT_T n) { + return (fpclassify(n) == FP_INFINITE); +} public -CONSTFUNC bool NAMESPACED(finite)(FLOAT_T n) { return (fpclassify(n) != FP_INFINITE); } +CONSTFUNC bool NAMESPACED(finite)(FLOAT_T n) { + return (fpclassify(n) != FP_INFINITE); +} public -CONSTFUNC bool NAMESPACED(isnan)(FLOAT_T n) { return (fpclassify(n) == FP_NAN); } +CONSTFUNC bool NAMESPACED(isnan)(FLOAT_T n) { + return (fpclassify(n) == FP_NAN); +} public const TypeInfo_t NAMESPACED(info) = { diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h index 0910c7f1..04c8ef3b 100644 --- a/src/stdlib/intX.c.h +++ b/src/stdlib/intX.c.h @@ -55,12 +55,12 @@ public void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *info) { (void)info, (void)pointers; #if INTX_C_H__INT_BITS < 32 - fwrite(obj, sizeof(INT_T), 1, out); + if (fwrite(obj, sizeof(INT_T), 1, out) != sizeof(INT_T)) fail("Failed to write whole integer"); #else INT_T i = *(INT_T *)obj; UINT_T z = (UINT_T)((i << 1L) ^ (i >> (INTX_C_H__INT_BITS - 1L))); // Zigzag encode while (z >= 0x80L) { - fputc((uint8_t)(z | 0x80L), out); + if (fputc((uint8_t)(z | 0x80L), out) == EOF) fail("Failed to write full integer"); z >>= 7L; } fputc((uint8_t)z, out); @@ -71,11 +71,13 @@ public void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *info) { (void)info, (void)pointers; #if INTX_C_H__INT_BITS < 32 - fread(outval, sizeof(INT_T), 1, in); + if (fread(outval, sizeof(INT_T), 1, in) != sizeof(INT_T)) fail("Failed to read full integer"); #else UINT_T z = 0; for (size_t shift = 0;; shift += 7) { - uint8_t byte = (uint8_t)fgetc(in); + int i = fgetc(in); + if (i == EOF) fail("Failed to read whole integer"); + uint8_t byte = (uint8_t)i; z |= ((UINT_T)(byte & 0x7F)) << shift; if ((byte & 0x80) == 0) break; } @@ -96,10 +98,12 @@ Text_t NAMESPACED(as_text)(const void *i, bool colorize, const TypeInfo_t *info) (void)info; if (!i) return Text(NAME_STR); Text_t text = _int64_to_text((int64_t)(*(INT_T *)i)); - return colorize ? Texts(Text("\033[35m"), text, Text("\033[m")) : text; + return colorize ? Text$concat(Text("\033[35m"), text, Text("\033[m")) : text; } public -Text_t NAMESPACED(value_as_text)(INT_T i) { return _int64_to_text((int64_t)i); } +Text_t NAMESPACED(value_as_text)(INT_T i) { + return _int64_to_text((int64_t)i); +} public PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) { (void)info; @@ -112,10 +116,12 @@ PUREFUNC bool NAMESPACED(equal)(const void *x, const void *y, const TypeInfo_t * } public CONSTFUNC bool NAMESPACED(is_between)(const INT_T x, const INT_T low, const INT_T high) { - return low <= x && x <= high; + return (low <= x && x <= high) || (high <= x && x <= low); } public -CONSTFUNC INT_T NAMESPACED(clamped)(INT_T x, INT_T min, INT_T max) { return x < min ? min : (x > max ? max : x); } +CONSTFUNC INT_T NAMESPACED(clamped)(INT_T x, INT_T min, INT_T max) { + return x < min ? min : (x > max ? max : x); +} public Text_t NAMESPACED(hex)(INT_T i, Int_t digits_int, bool uppercase, bool prefix) { Int_t as_int = Int$from_int64((int64_t)i); diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h index 4d8f8e3d..c90babcb 100644 --- a/src/stdlib/intX.h +++ b/src/stdlib/intX.h @@ -49,8 +49,12 @@ Closure_t NAMESPACED(onward)(INTX_T first, INTX_T step); PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder); CONSTFUNC bool NAMESPACED(is_between)(const INTX_T x, const INTX_T low, const INTX_T high); CONSTFUNC INTX_T NAMESPACED(clamped)(INTX_T x, INTX_T min, INTX_T max); -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) { return (INTX_T)b; } -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_bool)(Bool_t b) { return (INTX_T)b; } +MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) { + return (INTX_T)b; +} +MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_bool)(Bool_t b) { + return (INTX_T)b; +} CONSTFUNC INTX_T NAMESPACED(gcd)(INTX_T x, INTX_T y); extern const INTX_T NAMESPACED(min), NAMESPACED(max); extern const TypeInfo_t NAMESPACED(info); @@ -75,15 +79,25 @@ MACROLIKE INTX_T NAMESPACED(modulo)(INTX_T D, INTX_T d) { return r; } -MACROLIKE INTX_T NAMESPACED(modulo1)(INTX_T D, INTX_T d) { return NAMESPACED(modulo)(D - 1, d) + 1; } +MACROLIKE INTX_T NAMESPACED(modulo1)(INTX_T D, INTX_T d) { + return NAMESPACED(modulo)(D - 1, d) + 1; +} -MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_plus)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x + (UINTX_T)y); } +MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_plus)(INTX_T x, INTX_T y) { + return (INTX_T)((UINTX_T)x + (UINTX_T)y); +} -MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_minus)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x + (UINTX_T)y); } +MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_minus)(INTX_T x, INTX_T y) { + return (INTX_T)((UINTX_T)x + (UINTX_T)y); +} -MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_left_shifted)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x << y); } +MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_left_shifted)(INTX_T x, INTX_T y) { + return (INTX_T)((UINTX_T)x << y); +} -MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x >> y); } +MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) { + return (INTX_T)((UINTX_T)x >> y); +} void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *, const TypeInfo_t *); void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *, const TypeInfo_t *); @@ -122,7 +136,9 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int64)(Int64_t i64, bool truncate) { return i; } #elif INTX_H__INT_BITS > 64 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int64)(Int64_t i) { return (INTX_T)i; } +MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int64)(Int64_t i) { + return (INTX_T)i; +} #endif #if INTX_H__INT_BITS < 32 @@ -132,7 +148,9 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int32)(Int32_t i32, bool truncate) { return i; } #elif INTX_H__INT_BITS > 32 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int32)(Int32_t i) { return (INTX_T)i; } +MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int32)(Int32_t i) { + return (INTX_T)i; +} #endif #if INTX_H__INT_BITS < 16 @@ -142,11 +160,15 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int16)(Int16_t i16, bool truncate) { return i; } #elif INTX_H__INT_BITS > 16 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int16)(Int16_t i) { return (INTX_T)i; } +MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int16)(Int16_t i) { + return (INTX_T)i; +} #endif #if INTX_H__INT_BITS > 8 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int8)(Int8_t i) { return (INTX_T)i; } +MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int8)(Int8_t i) { + return (INTX_T)i; +} #endif #undef PASTE3_ diff --git a/src/stdlib/lists.c b/src/stdlib/lists.c index 1a47f2e3..93dc63e9 100644 --- a/src/stdlib/lists.c +++ b/src/stdlib/lists.c @@ -440,10 +440,14 @@ List_t List$sample(List_t list, Int_t int_n, List_t weights, OptionalClosure_t r } public -List_t List$from(List_t list, Int_t first) { return List$slice(list, first, I_small(-1)); } +List_t List$from(List_t list, Int_t first) { + return List$slice(list, first, I_small(-1)); +} public -List_t List$to(List_t list, Int_t last) { return List$slice(list, I_small(1), last); } +List_t List$to(List_t list, Int_t last) { + return List$slice(list, I_small(1), last); +} public List_t List$by(List_t list, Int_t int_stride, int64_t padded_item_size) { @@ -554,7 +558,9 @@ bool List$has(List_t list, void *item, const TypeInfo_t *type) { } public -void List$clear(List_t *list) { *list = list->atomic ? EMPTY_ATOMIC_LIST : EMPTY_LIST; } +void List$clear(List_t *list) { + *list = list->atomic ? EMPTY_ATOMIC_LIST : EMPTY_LIST; +} public int32_t List$compare(const void *vx, const void *vy, const TypeInfo_t *type) { diff --git a/src/stdlib/lists.h b/src/stdlib/lists.h index 457fed52..9ac8bf1b 100644 --- a/src/stdlib/lists.h +++ b/src/stdlib/lists.h @@ -20,8 +20,9 @@ extern char _EMPTY_LIST_SENTINEL; int64_t index = index_expr; \ int64_t off = index + (index < 0) * (list.length + 1) - 1; \ if (unlikely(off < 0 || off >= list.length)) \ - fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \ - (int64_t)list.length, ")\n"); \ + fail_source(__SOURCE_FILE__, start, end, \ + Text$concat(Text("Invalid list index: "), convert_to_text(index), Text(" (list has length "), \ + convert_to_text((int64_t)list.length), Text(")\n"))); \ *(item_type *)(list.data + list.stride * off); \ }) #define List_get(list_expr, index_expr, item_type, var, optional_expr, none_expr) \ @@ -40,8 +41,9 @@ extern char _EMPTY_LIST_SENTINEL; int64_t index = index_expr; \ int64_t off = index + (index < 0) * (list->length + 1) - 1; \ if (unlikely(off < 0 || off >= list->length)) \ - fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \ - (int64_t)list->length, ")\n"); \ + fail_source(__SOURCE_FILE__, start, end, \ + Text$concat(Text("Invalid list index: "), convert_to_text(index), Text(" (list has length "), \ + convert_to_text((int64_t)list->length), Text(")\n"))); \ if (list->data_refcount > 0) List$compact(list, sizeof(item_type)); \ (item_type *)(list->data + list->stride * off); \ }) @@ -61,7 +63,8 @@ extern char _EMPTY_LIST_SENTINEL; t items[] = {__VA_ARGS__}; \ (List_t){.length = sizeof(items) / sizeof(items[0]), \ .stride = (int64_t)&items[1] - (int64_t)&items[0], \ - .data = memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \ + .data = sizeof(items) == 0 ? &_EMPTY_LIST_SENTINEL \ + : memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \ .atomic = 0, \ .data_refcount = 0}; \ }) @@ -70,7 +73,7 @@ extern char _EMPTY_LIST_SENTINEL; t items[N] = {__VA_ARGS__}; \ (List_t){.length = N, \ .stride = (int64_t)&items[1] - (int64_t)&items[0], \ - .data = memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \ + .data = N == 0 ? &_EMPTY_LIST_SENTINEL : memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \ .atomic = 0, \ .data_refcount = 0}; \ }) diff --git a/src/stdlib/mapmacro.h b/src/stdlib/mapmacro.h index 7b0e3c4e..5e9eaa36 100644 --- a/src/stdlib/mapmacro.h +++ b/src/stdlib/mapmacro.h @@ -7,9 +7,7 @@ #define EVAL0(...) __VA_ARGS__ #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) #define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) -#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) -#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) -#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__))) +#define EVAL(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) #define MAP_END(...) #define MAP_OUT diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c index fd396463..53a180fb 100644 --- a/src/stdlib/memory.c +++ b/src/stdlib/memory.c @@ -18,7 +18,7 @@ Text_t Memory$as_text(const void *p, bool colorize, const TypeInfo_t *info) { (void)info; if (!p) return Text("Memory"); Text_t text = Text$from_str(String("Memory<", (void *)p, ">")); - return colorize ? Texts(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text; + return colorize ? Text$concat(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text; } public diff --git a/src/stdlib/numX.h b/src/stdlib/numX.h new file mode 100644 index 00000000..87794762 --- /dev/null +++ b/src/stdlib/numX.h @@ -0,0 +1,115 @@ +// Template header for 64 and 32 bit Nums +// This file expects `NUMX_H__BITS` to be defined before including: +// +// #define NUMX_H__BITS 64 +// #include "numX.h" +// + +#include <stdbool.h> +#include <stdint.h> + +#include "datatypes.h" +#include "stdlib.h" +#include "types.h" +#include "util.h" + +#ifndef NUMX_H__BITS +#define NUMX_H__BITS 64 +#endif + +#if NUMX_H__BITS == 64 +#define NUM_T double +#define OPT_T double +#define NAMESPACED(x) Num$##x +#define TYPE_STR "Num" +#define SUFFIXED(x) x +#elif NUMX_H__BITS == 32 +#define NUM_T float +#define OPT_T float +#define NAMESPACED(x) Num32$##x +#define TYPE_STR "Num32" +#define SUFFIXED(x) x##f +#else +#error "Unsupported bit width for Num" +#endif + +Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *type); +Text_t NAMESPACED(value_as_text)(NUM_T x); +PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *type); +PUREFUNC bool NAMESPACED(equal)(const void *x, const void *y, const TypeInfo_t *type); +CONSTFUNC bool NAMESPACED(near)(NUM_T a, NUM_T b, NUM_T ratio, NUM_T absolute); +Text_t NAMESPACED(percent)(NUM_T x, NUM_T precision); +NUM_T CONSTFUNC NAMESPACED(with_precision)(NUM_T num, NUM_T precision); +NUM_T NAMESPACED(mod)(NUM_T num, NUM_T modulus); +NUM_T NAMESPACED(mod1)(NUM_T num, NUM_T modulus); +CONSTFUNC bool NAMESPACED(isinf)(NUM_T n); +CONSTFUNC bool NAMESPACED(finite)(NUM_T n); +CONSTFUNC bool NAMESPACED(isnan)(NUM_T n); +bool NAMESPACED(is_none)(const void *n, const TypeInfo_t *info); +NUM_T NAMESPACED(nan)(Text_t tag); +CONSTFUNC NUM_T NAMESPACED(mix)(NUM_T amount, NUM_T x, NUM_T y); +OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder); +CONSTFUNC bool NAMESPACED(is_between)(const NUM_T x, const NUM_T low, const NUM_T high); +CONSTFUNC NUM_T NAMESPACED(clamped)(NUM_T x, NUM_T low, NUM_T high); + +#if NUMX_H__BITS == 64 +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_num32)(float n) { + return (NUM_T)n; +} +#elif NUMX_H__BITS == 32 +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_num64)(double n) { + return (NUM_T)n; +} +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int)(Int_t i, bool truncate) { + if likely (i.small & 0x1) { + NUM_T ret = (NUM_T)(i.small >> 2); + if unlikely (!truncate && (int64_t)ret != (i.small >> 2)) + fail("Could not convert integer to " TYPE_STR " without losing precision: ", i.small >> 2); + return ret; + } else { + NUM_T ret = mpz_get_d(i.big); + if (!truncate) { + mpz_t roundtrip; + mpz_init_set_d(roundtrip, (double)ret); + if unlikely (mpz_cmp(i.big, roundtrip) != 0) + fail("Could not convert integer to " TYPE_STR " without losing precision: ", i); + } + return ret; + } +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int64)(Int64_t i, bool truncate) { + NUM_T n = (NUM_T)i; + if unlikely (!truncate && (Int64_t)n != i) + fail("Could not convert integer to " TYPE_STR " without losing precision: ", i); + return n; +} +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int32)(Int32_t i) { + return (NUM_T)i; +} +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int16)(Int16_t i) { + return (NUM_T)i; +} +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int8)(Int8_t i) { + return (NUM_T)i; +} +MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_byte)(Byte_t i) { + return (NUM_T)i; +} + +extern const TypeInfo_t NAMESPACED(info); + +#undef NUM_T +#undef OPT_T +#undef NAMESPACED +#undef TYPE_STR +#undef SUFFIXED +#undef NUMX_H__BITS diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index ed8383fd..dcedcd03 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -98,7 +98,9 @@ Path_t Path$from_str(const char *str) { } public -Path_t Path$from_text(Text_t text) { return Path$from_str(Text$as_c_string(text)); } +Path_t Path$from_text(Text_t text) { + return Path$from_str(Text$as_c_string(text)); +} public Path_t Path$expand_home(Path_t path) { @@ -324,6 +326,79 @@ Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) { return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } +typedef struct { + const char *path_str; + int fd; + int mode; + int permissions; +} writer_data_t; + +static Result_t _write_bytes_to_fd(List_t bytes, bool close_file, void *userdata) { + writer_data_t *data = userdata; + if (bytes.length > 0) { + if (data->fd == -1) { + data->fd = open(data->path_str, data->mode, data->permissions); + if (data->fd == -1) { + if (errno == EMFILE || errno == ENFILE) { + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles + // that will be closed by GC finalizers. + GC_gcollect(); + data->fd = open(data->path_str, data->mode, data->permissions); + } + if (data->fd == -1) + return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")"); + } + } + + if (bytes.stride != 1) List$compact(&bytes, 1); + ssize_t written = write(data->fd, bytes.data, (size_t)bytes.length); + if (written != (ssize_t)bytes.length) + return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")"); + } + // After first successful write, all writes are appends + data->mode = (O_WRONLY | O_CREAT | O_APPEND); + + if (close_file && data->fd != -1) { + if (close(data->fd) == -1) + return FailureResult("Failed to close file: ", data->path_str, " (", strerror(errno), ")"); + data->fd = -1; + } + return SuccessResult; +} + +static Result_t _write_text_to_fd(Text_t text, bool close_file, void *userdata) { + return _write_bytes_to_fd(Text$utf8(text), close_file, userdata); +} + +static void _writer_cleanup(writer_data_t *data) { + if (data && data->fd != -1) { + close(data->fd); + data->fd = -1; + } +} + +public +Closure_t Path$byte_writer(Path_t path, bool append, int permissions) { + path = Path$expand_home(path); + const char *path_str = Path$as_c_string(path); + int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC); + writer_data_t *userdata = + new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions); + GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL); + return (Closure_t){.fn = _write_bytes_to_fd, .userdata = userdata}; +} + +public +Closure_t Path$writer(Path_t path, bool append, int permissions) { + path = Path$expand_home(path); + const char *path_str = Path$as_c_string(path); + int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC); + writer_data_t *userdata = + new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions); + GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL); + return (Closure_t){.fn = _write_text_to_fd, .userdata = userdata}; +} + public OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { path = Path$expand_home(path); @@ -331,8 +406,8 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { int fd = open(path_str, O_RDONLY); if (fd == -1) { if (errno == EMFILE || errno == ENFILE) { - // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will - // be closed by GC finalizers. + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. GC_gcollect(); fd = open(path_str, O_RDONLY); } @@ -574,7 +649,9 @@ OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) { } public -OptionalPath_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); } +OptionalPath_t Path$write_unique(Path_t path, Text_t text) { + return Path$write_unique_bytes(path, Text$utf8(text)); +} public OptionalPath_t Path$parent(Path_t path) { @@ -619,7 +696,7 @@ bool Path$has_extension(Path_t path, Text_t extension) { if (extension.length == 0) return !Text$has(Text$from(last, I(2)), Text(".")) || Text$equal_values(last, Text("..")); - if (!Text$starts_with(extension, Text("."), NULL)) extension = Texts(Text("."), extension); + if (!Text$starts_with(extension, Text("."), NULL)) extension = Text$concat(Text("."), extension); return Text$ends_with(Text$from(last, I(2)), extension, NULL); } @@ -638,7 +715,9 @@ Path_t Path$child(Path_t path, Text_t name) { } public -Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); } +Path_t Path$sibling(Path_t path, Text_t name) { + return Path$child(Path$parent(path), name); +} public OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) { @@ -705,8 +784,8 @@ OptionalClosure_t Path$by_line(Path_t path) { FILE *f = fopen(path_str, "r"); if (f == NULL) { if (errno == EMFILE || errno == ENFILE) { - // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will - // be closed by GC finalizers. + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. GC_gcollect(); f = fopen(path_str, "r"); } @@ -726,8 +805,8 @@ OptionalList_t Path$lines(Path_t path) { FILE *f = fopen(path_str, "r"); if (f == NULL) { if (errno == EMFILE || errno == ENFILE) { - // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will - // be closed by GC finalizers. + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. GC_gcollect(); f = fopen(path_str, "r"); } @@ -794,7 +873,9 @@ int Path$print(FILE *f, Path_t path) { } public -const char *Path$as_c_string(Path_t path) { return String(path); } +const char *Path$as_c_string(Path_t path) { + return String(path); +} public Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) { @@ -808,7 +889,7 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) { && (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text("..")))) text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text); - if (color) text = Texts(Text("\033[32;1m"), text, Text("\033[m")); + if (color) text = Text$concat(Text("\033[32;1m"), text, Text("\033[m")); return text; } diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 881a3c78..c272314c 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -39,6 +39,8 @@ Result_t Path$write(Path_t path, Text_t text, int permissions); Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions); Result_t Path$append(Path_t path, Text_t text, int permissions); Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions); +Closure_t Path$byte_writer(Path_t path, bool append, int permissions); +Closure_t Path$writer(Path_t path, bool append, int permissions); OptionalText_t Path$read(Path_t path); OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t limit); Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); diff --git a/src/stdlib/pointers.c b/src/stdlib/pointers.c index 74037613..0bf9a274 100644 --- a/src/stdlib/pointers.c +++ b/src/stdlib/pointers.c @@ -38,14 +38,14 @@ Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *type) { if (top_level) { root = ptr; } else if (ptr == root) { - Text_t text = Texts(Text$from_str(ptr_info.sigil), Text("~1")); - return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; + Text_t text = Text$concat(Text$from_str(ptr_info.sigil), Text("~1")); + return colorize ? Text$concat(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; } else { 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$value_as_text(*id)); - return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; + Text_t text = Text$concat(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id)); + return colorize ? Text$concat(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; } int64_t next_id = (int64_t)pending.entries.length + 2; Table$set(&pending, x, &next_id, &rec_table); diff --git a/src/stdlib/print.h b/src/stdlib/print.h index 65de72a0..0837390f 100644 --- a/src/stdlib/print.h +++ b/src/stdlib/print.h @@ -81,10 +81,18 @@ int _print_real(FILE *f, Real_t x); int _print_hex(FILE *f, hex_format_t hex); int _print_hex_double(FILE *f, hex_double_t hex); int _print_oct(FILE *f, oct_format_t oct); -PRINT_FN _print_float(FILE *f, float x) { return _print_double(f, (double)x); } -PRINT_FN _print_pointer(FILE *f, void *p) { return _print_hex(f, hex((uint64_t)p)); } -PRINT_FN _print_bool(FILE *f, bool b) { return fputs(b ? "yes" : "no", f); } -PRINT_FN _print_str(FILE *f, const char *s) { return fputs(s ? s : "(null)", f); } +PRINT_FN _print_float(FILE *f, float x) { + return _print_double(f, (double)x); +} +PRINT_FN _print_pointer(FILE *f, void *p) { + return _print_hex(f, hex((uint64_t)p)); +} +PRINT_FN _print_bool(FILE *f, bool b) { + return fputs(b ? "yes" : "no", f); +} +PRINT_FN _print_str(FILE *f, const char *s) { + return fputs(s ? s : "(null)", f); +} int _print_char(FILE *f, char c); int _print_quoted(FILE *f, quoted_t quoted); PRINT_FN _print_string_slice(FILE *f, string_slice_t slice) { diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c index ea939f62..1eba8188 100644 --- a/src/stdlib/stacktrace.c +++ b/src/stdlib/stacktrace.c @@ -98,7 +98,7 @@ void print_stacktrace(FILE *out, int offset) { cwd[cwd_len++] = '/'; cwd[cwd_len] = '\0'; - const char *install_dir = String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/"); + const char *install_dir = String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/"); static void *stack[1024]; int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0])); diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index f4e6d678..a05b8753 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -17,6 +17,7 @@ #include "files.h" #include "metamethods.h" #include "optionals.h" +#include "paths.h" #include "print.h" #include "siphash.h" #include "stacktrace.h" @@ -39,11 +40,91 @@ static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) { public bool USE_COLOR; + +public +const char *TOMO_PATH = "/usr/local"; + public -Text_t TOMO_VERSION_TEXT = Text(TOMO_VERSION); +const char *TOMO_VERSION = "v0"; + +public +Text_t TOMO_VERSION_TEXT = Text("v0"); + +#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#include <dlfcn.h> + +static inline const char *get_library_path(void *func) { + static Dl_info info; + if (dladdr(func, &info)) { + return info.dli_fname; // full path of the library + } + return NULL; +} + +#elif defined(_WIN32) +#include <windows.h> + +static inline const char *get_library_path(void *func) { + static char path[MAX_PATH]; + HMODULE hm = NULL; + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)func, &hm)) { + if (GetModuleFileName(hm, path, MAX_PATH)) { + return path; + } + } + return NULL; +} + +#else +#error "Unsupported platform" +#endif + +char *find_in_path(const char *name) { + if (strchr(name, '/')) { + // name contains a slash → treat as path + char *abs = realpath(name, NULL); + if (abs == NULL) fail("Couldn't find real path of: ", name); + char *ret = String(abs); + free(abs); + return ret; + } + + char *path_env = getenv("PATH"); + if (!path_env) return NULL; + + char *paths = strdup(path_env); + if (!paths) return NULL; + + char *token = strtok(paths, ":"); + while (token) { + char candidate[PATH_MAX]; + snprintf(candidate, sizeof(candidate), "%s/%s", token, name); + if (access(candidate, X_OK) == 0) { + char *abs = realpath(candidate, NULL); + free(paths); + char *ret = String(abs); + free(abs); + return ret; + } + token = strtok(NULL, ":"); + } + + free(paths); + return NULL; // not found +} public -const char *TOMO_PATH = TOMO_INSTALL; +void tomo_configure(void) { + const char *p = get_library_path(get_library_path); + p = find_in_path(p); + Path_t path = Path$from_str(p); + TOMO_PATH = Path$as_c_string(Path$parent(Path$parent(path))); + Text_t base_name = Path$base_name(path); + TOMO_VERSION_TEXT = Text$without_suffix( + Text$without_prefix(Text$without_prefix(base_name, Text("lib")), Text("tomo@")), Text(".so")); + TOMO_VERSION = Text$as_c_string(TOMO_VERSION_TEXT); +} static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) { (void)info, (void)userdata; @@ -60,6 +141,7 @@ static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) { public void tomo_init(void) { GC_INIT(); + tomo_configure(); const char *color_env = getenv("COLOR"); USE_COLOR = color_env ? strcmp(color_env, "1") == 0 : isatty(STDOUT_FILENO); const char *no_color_env = getenv("NO_COLOR"); @@ -77,10 +159,14 @@ void tomo_init(void) { } public -_Noreturn void fail_text(Text_t message) { fail(message); } +_Noreturn void fail_text(Text_t message) { + fail(message); +} public -Text_t builtin_last_err() { return Text$from_str(strerror(errno)); } +Text_t builtin_last_err() { + return Text$from_str(strerror(errno)); +} static int _inspect_depth = 0; static file_t *file = NULL; @@ -246,7 +332,9 @@ typedef struct cleanup_s { static cleanup_t *cleanups = NULL; public -void tomo_at_cleanup(Closure_t fn) { cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); } +void tomo_at_cleanup(Closure_t fn) { + cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); +} public void tomo_cleanup(void) { diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 3afe3529..087ed4bf 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -15,6 +15,7 @@ extern bool USE_COLOR; extern Text_t TOMO_VERSION_TEXT; +void tomo_configure(void); void tomo_init(void); void tomo_at_cleanup(Closure_t fn); void tomo_cleanup(void); @@ -36,7 +37,7 @@ void tomo_cleanup(void); exit(1); \ }) -#define fail_source(filename, start, end, ...) \ +#define fail_source(filename, start, end, message) \ ({ \ tomo_cleanup(); \ fflush(stdout); \ @@ -45,7 +46,7 @@ void tomo_cleanup(void); print_stacktrace(stderr, 0); \ fputs("\n", stderr); \ if (USE_COLOR) fputs("\x1b[31;1m", stderr); \ - fprint_inline(stderr, __VA_ARGS__); \ + Text$print(stderr, message); \ file_t *_file = (filename) ? load_file(filename) : NULL; \ if ((filename) && _file) { \ fputs("\n", stderr); \ diff --git a/src/stdlib/structs.c b/src/stdlib/structs.c index da8f1461..d1b9f824 100644 --- a/src/stdlib/structs.c +++ b/src/stdlib/structs.c @@ -126,10 +126,10 @@ PUREFUNC public Text_t Struct$as_text(const void *obj, bool colorize, const Type Text_t name = Text$from_str(type->StructInfo.name); if (type->StructInfo.is_secret || type->StructInfo.is_opaque) { - return colorize ? Texts(Text("\x1b[0;1m"), name, Text("\x1b[m(...)")) : Texts(name, Text("(...)")); + return colorize ? Text$concat(Text("\x1b[0;1m"), name, Text("\x1b[m(...)")) : Text$concat(name, Text("(...)")); } - Text_t text = colorize ? Texts(Text("\x1b[0;1m"), name, Text("\x1b[m(")) : Texts(name, Text("(")); + Text_t text = colorize ? Text$concat(Text("\x1b[0;1m"), name, Text("\x1b[m(")) : Text$concat(name, Text("(")); ptrdiff_t byte_offset = 0; ptrdiff_t bit_offset = 0; for (int i = 0; i < type->StructInfo.num_fields; i++) { diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index a801957f..753059c8 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -24,14 +24,6 @@ #include "types.h" #include "util.h" -// #define DEBUG_TABLES - -#ifdef DEBUG_TABLES -#define hdebug(...) print_inline("\x1b[2m", __VA_ARGS__, "\x1b[m") -#else -#define hdebug(...) (void)0 -#endif - // Helper accessors for type functions/values: #define HASH_KEY(t, k) (generic_hash((k), type->TableInfo.key) % ((t).bucket_info->count)) #define EQUAL_KEYS(x, y) (generic_equal((x), (y), type->TableInfo.key)) @@ -76,18 +68,6 @@ PUREFUNC static INLINE size_t value_offset(const TypeInfo_t *info) { return offset; } -static INLINE void hshow(const Table_t *t) { - hdebug("{"); - for (uint32_t i = 0; t->bucket_info && i < t->bucket_info->count; i++) { - if (i > 0) hdebug(" "); - if (t->bucket_info->buckets[i].occupied) - hdebug("[", i, "]=", (uint32_t)t->bucket_info->buckets[i].index, "(", - t->bucket_info->buckets[i].next_bucket, ")"); - else hdebug("[", i, "]=_"); - } - hdebug("}\n"); -} - static void maybe_copy_on_write(Table_t *t, const TypeInfo_t *type) { if (t->entries.data_refcount != 0) List$compact(&t->entries, (int64_t)entry_size(type)); @@ -104,14 +84,10 @@ PUREFUNC public void *Table$get_raw(Table_t t, const void *key, const TypeInfo_t if (!key || !t.bucket_info) return NULL; uint64_t hash = HASH_KEY(t, key); - hshow(&t); - hdebug("Getting value with initial probe at ", hash, "\n"); bucket_t *buckets = t.bucket_info->buckets; for (uint64_t i = hash; buckets[i].occupied; i = buckets[i].next_bucket) { - hdebug("Checking against key in bucket ", i, "\n"); void *entry = GET_ENTRY(t, buckets[i].index); if (EQUAL_KEYS(entry, key)) { - hdebug("Found key!\n"); return entry + value_offset(type); } if (buckets[i].next_bucket == END_OF_CHAIN) break; @@ -130,24 +106,18 @@ PUREFUNC public void *Table$get(Table_t t, const void *key, const TypeInfo_t *ty static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const TypeInfo_t *type) { assert(t->bucket_info); - hshow(t); const void *key = entry; bucket_t *buckets = t->bucket_info->buckets; uint64_t hash = HASH_KEY(*t, key); - hdebug("Hash value (mod ", (int32_t)t->bucket_info->count, ") = ", hash, "\n"); bucket_t *bucket = &buckets[hash]; if (!bucket->occupied) { - hdebug("Got an empty space\n"); // Empty space: bucket->occupied = 1; bucket->index = index; bucket->next_bucket = END_OF_CHAIN; - hshow(t); return; } - hdebug("Collision detected in bucket ", hash, " (entry ", (uint32_t)bucket->index, ")\n"); - while (buckets[t->bucket_info->last_free].occupied) { assert(t->bucket_info->last_free > 0); --t->bucket_info->last_free; @@ -155,7 +125,6 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const uint64_t collided_hash = HASH_KEY(*t, GET_ENTRY(*t, bucket->index)); if (collided_hash != hash) { // Collided with a mid-chain entry - hdebug("Hit a mid-chain entry at bucket ", hash, " (chain starting at ", collided_hash, ")\n"); // Find chain predecessor uint64_t predecessor = collided_hash; while (buckets[predecessor].next_bucket != hash) @@ -168,20 +137,18 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const bucket->occupied = 1; bucket->index = index; bucket->next_bucket = END_OF_CHAIN; - } else { // Collided with the start of a chain, put the new entry in chain position #2 - hdebug("Hit start of a chain\n"); + } else { // Collided with the start of a chain, put the new entry in chain + // position #2 buckets[t->bucket_info->last_free] = (bucket_t){.occupied = 1, .index = index, .next_bucket = bucket->next_bucket}; bucket->next_bucket = t->bucket_info->last_free; } - hshow(t); } static void hashmap_resize_buckets(Table_t *t, uint32_t new_capacity, const TypeInfo_t *type) { if (unlikely(new_capacity > TABLE_MAX_BUCKETS)) - fail("Table has exceeded the maximum table size (2^31) and cannot grow further!"); - hdebug("About to resize from ", t->bucket_info ? (int32_t)t->bucket_info->count : 0, " to ", new_capacity, "\n"); - hshow(t); + fail("Table has exceeded the maximum table size (2^31) and cannot grow " + "further!"); size_t alloc_size = sizeof(bucket_info_t) + sizeof(bucket_t[new_capacity]); t->bucket_info = GC_MALLOC_ATOMIC(alloc_size); memset(t->bucket_info->buckets, 0, sizeof(bucket_t[new_capacity])); @@ -189,12 +156,8 @@ static void hashmap_resize_buckets(Table_t *t, uint32_t new_capacity, const Type t->bucket_info->last_free = new_capacity - 1; // Rehash: for (int64_t i = 0; i < (int64_t)Table$length(*t); i++) { - hdebug("Rehashing ", i, "\n"); Table$set_bucket(t, GET_ENTRY(*t, i), i, type); } - - hshow(t); - hdebug("Finished resizing\n"); } // Return address of value @@ -206,7 +169,6 @@ public void *Table$reserve(Table_t *t, const void *key, const void *value, const TypeInfo_t *type) { assert(type->tag == TableInfo); if (!t || !key) return NULL; - hshow(t); t->hash = 0; @@ -295,12 +257,10 @@ void Table$remove(Table_t *t, const void *key, const TypeInfo_t *type) { // maybe update lastfree_index1 to removed bucket's index uint64_t hash = HASH_KEY(*t, key); - hdebug("Removing key with hash ", hash, "\n"); bucket_t *bucket, *prev = NULL; for (uint64_t i = hash; t->bucket_info->buckets[i].occupied; i = t->bucket_info->buckets[i].next_bucket) { if (EQUAL_KEYS(GET_ENTRY(*t, t->bucket_info->buckets[i].index), key)) { bucket = &t->bucket_info->buckets[i]; - hdebug("Found key to delete in bucket ", i, "\n"); goto found_it; } if (t->bucket_info->buckets[i].next_bucket == END_OF_CHAIN) return; @@ -319,8 +279,6 @@ found_it:; // instead of O(N) int64_t last_entry = (int64_t)t->entries.length - 1; if (bucket->index != last_entry) { - hdebug("Removing key/value from the middle of the entries list\n"); - // Find the bucket that points to the last entry's index: uint64_t i = HASH_KEY(*t, GET_ENTRY(*t, last_entry)); while (t->bucket_info->buckets[i].index != last_entry) @@ -341,22 +299,17 @@ found_it:; int64_t bucket_to_clear; if (prev) { // Middle (or end) of a chain - hdebug("Removing from middle of a chain\n"); bucket_to_clear = (bucket - t->bucket_info->buckets); prev->next_bucket = bucket->next_bucket; } else if (bucket->next_bucket != END_OF_CHAIN) { // Start of a chain - hdebug("Removing from start of a chain\n"); bucket_to_clear = bucket->next_bucket; *bucket = t->bucket_info->buckets[bucket_to_clear]; } else { // Empty chain - hdebug("Removing from empty chain\n"); bucket_to_clear = (bucket - t->bucket_info->buckets); } t->bucket_info->buckets[bucket_to_clear] = (bucket_t){0}; if (bucket_to_clear > t->bucket_info->last_free) t->bucket_info->last_free = bucket_to_clear; - - hshow(t); } CONSTFUNC public void *Table$entry(Table_t t, int64_t n) { @@ -365,7 +318,9 @@ CONSTFUNC public void *Table$entry(Table_t t, int64_t n) { } public -void Table$clear(Table_t *t) { memset(t, 0, sizeof(Table_t)); } +void Table$clear(Table_t *t) { + *t = EMPTY_TABLE; +} public Table_t Table$sorted(Table_t t, const TypeInfo_t *type) { @@ -537,9 +492,9 @@ Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *type) { __typeof(type->TableInfo) table = type->TableInfo; if (!t) { - return table.value->size > 0 ? Texts("{", generic_as_text(NULL, false, table.key), ":", - generic_as_text(NULL, false, table.value), "}") - : Texts("{", generic_as_text(NULL, false, table.key), "}"); + return table.value->size > 0 ? Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text(":"), + generic_as_text(NULL, false, table.value), Text("}")) + : Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text("}")); } int64_t val_off = (int64_t)value_offset(type); @@ -584,7 +539,8 @@ Table_t Table$from_entries(List_t entries, const TypeInfo_t *type) { // "set intersection" in formal terms public Table_t Table$intersection(Table_t a, Table_t b, const TypeInfo_t *type) { - // Return a table such that t[k]==a[k] for all k such that a.has(k), b.has(k), and a[k]==b[k] + // Return a table such that t[k]==a[k] for all k such that a.has(k), b.has(k), + // and a[k]==b[k] Table_t result = EMPTY_TABLE; const size_t offset = value_offset(type); for (Table_t *t = &a; t; t = t->fallback) { @@ -602,8 +558,8 @@ Table_t Table$intersection(Table_t a, Table_t b, const TypeInfo_t *type) { // "set union" in formal terms public Table_t Table$with(Table_t a, Table_t b, const TypeInfo_t *type) { - // return a table such that t[k]==b[k] for all k such that b.has(k), and t[k]==a[k] for all k such that a.has(k) and - // not b.has(k) + // return a table such that t[k]==b[k] for all k such that b.has(k), and + // t[k]==a[k] for all k such that a.has(k) and not b.has(k) Table_t result = EMPTY_TABLE; const size_t offset = value_offset(type); for (Table_t *t = &a; t; t = t->fallback) { @@ -645,7 +601,8 @@ Table_t Table$difference(Table_t a, Table_t b, const TypeInfo_t *type) { // "without" is "set difference" in formal terms public Table_t Table$without(Table_t a, Table_t b, const TypeInfo_t *type) { - // Return a table such that t[k]==a[k] for all k such that not b.has(k) or b[k] != a[k] + // Return a table such that t[k]==a[k] for all k such that not b.has(k) or + // b[k] != a[k] Table_t result = EMPTY_TABLE; const size_t offset = value_offset(type); for (Table_t *t = &a; t; t = t->fallback) { @@ -704,12 +661,18 @@ void *Table$str_reserve(Table_t *t, const char *key, const void *value) { } public -void Table$str_set(Table_t *t, const char *key, const void *value) { Table$set(t, &key, &value, &CStrToVoidStarTable); } +void Table$str_set(Table_t *t, const char *key, const void *value) { + Table$set(t, &key, &value, &CStrToVoidStarTable); +} public -void Table$str_remove(Table_t *t, const char *key) { return Table$remove(t, &key, &CStrToVoidStarTable); } +void Table$str_remove(Table_t *t, const char *key) { + return Table$remove(t, &key, &CStrToVoidStarTable); +} -CONSTFUNC public void *Table$str_entry(Table_t t, int64_t n) { return Table$entry(t, n); } +CONSTFUNC public void *Table$str_entry(Table_t t, int64_t n) { + return Table$entry(t, n); +} PUREFUNC public bool Table$is_none(const void *obj, const TypeInfo_t *info) { (void)info; diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h index cf1c3625..f3f9d2c7 100644 --- a/src/stdlib/tables.h +++ b/src/stdlib/tables.h @@ -4,7 +4,7 @@ #include <stdbool.h> #include <stdint.h> -#include <string.h> +#include <string.h> // IWYU pragma: export #include "datatypes.h" #include "lists.h" @@ -47,8 +47,8 @@ void *Table$get(Table_t t, const void *key, const TypeInfo_t *type); val_t *value = Table$get(t, &key, info); \ if (unlikely(value == NULL)) \ fail_source(__SOURCE_FILE__, start, end, \ - "This key was not found in the table: ", generic_as_text(&key, false, info->TableInfo.key), \ - "\n"); \ + Text$concat(Text("This key was not found in the table: "), \ + generic_as_text(&key, false, info->TableInfo.key), Text("\n"))); \ *value; \ }) #define Table$get_or_setdefault(table_expr, key_t, val_t, key_expr, default_expr, info_expr) \ diff --git a/src/stdlib/text.c b/src/stdlib/text.c index b4b27fed..4bf6d999 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -606,8 +606,8 @@ Text_t Text$middle_pad(Text_t text, Int_t width, Text_t padding, Text_t language if (padding.length == 0) fail("Cannot pad with an empty text!"); int64_t needed = Int64$from_int(width, false) - Int64$from_int(Text$width(text, language), false); - return Texts(Text$repeat_to_width(padding, needed / 2, language), text, - Text$repeat_to_width(padding, (needed + 1) / 2, language)); + return Text$concat(Text$repeat_to_width(padding, needed / 2, language), text, + Text$repeat_to_width(padding, (needed + 1) / 2, language)); } public @@ -669,10 +669,14 @@ Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int) { } public -Text_t Text$from(Text_t text, Int_t first) { return Text$slice(text, first, I_small(-1)); } +Text_t Text$from(Text_t text, Int_t first) { + return Text$slice(text, first, I_small(-1)); +} public -Text_t Text$to(Text_t text, Int_t last) { return Text$slice(text, I_small(1), last); } +Text_t Text$to(Text_t text, Int_t last) { + return Text$slice(text, I_small(1), last); +} public Text_t Text$reversed(Text_t text) { @@ -814,7 +818,9 @@ OptionalText_t Text$from_strn(const char *str, size_t len) { } public -OptionalText_t Text$from_str(const char *str) { return str ? Text$from_strn(str, strlen(str)) : Text(""); } +OptionalText_t Text$from_str(const char *str) { + return str ? Text$from_strn(str, strlen(str)) : Text(""); +} static void u8_buf_append(Text_t text, Byte_t **buf, int64_t *capacity, int64_t *i) { switch (text.tag) { @@ -1506,8 +1512,8 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) { 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); + ret = Text$concat(Text("$"), quotation_mark, ret, quotation_mark); + else ret = Text$concat(quotation_mark, ret, quotation_mark); return ret; } @@ -1803,11 +1809,11 @@ 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$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_ASCII: return Text$concat(Text("ASCII("), Int64$value_as_text(text.length), Text(")")); + case TEXT_GRAPHEMES: return Text$concat(Text("Graphemes("), Int64$value_as_text(text.length), Text(")")); + case TEXT_BLOB: return Text$concat(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(")")); + return Text$concat(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 8c0c43b3..989fcb8b 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -33,7 +33,9 @@ static inline Text_t Text_from_str_literal(const char *str) { return (Text_t){.length = strlen(str), .tag = TEXT_ASCII, .ascii = str}; } -static inline Text_t Text_from_text(Text_t t) { return t; } +static inline Text_t Text_from_text(Text_t t) { + return t; +} #define convert_to_text(x) \ _Generic(x, \ @@ -45,7 +47,8 @@ static inline Text_t Text_from_text(Text_t t) { return t; } int32_t: Int32$value_as_text, \ int64_t: Int64$value_as_text, \ double: Float64$value_as_text, \ - float: Float32$value_as_text)(x) + float: Float32$value_as_text, \ + Int_t: Int$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__}) @@ -63,8 +66,9 @@ OptionalText_t Text$cluster(Text_t text, Int_t index_int); Int_t index = index_expr; \ OptionalText_t cluster = Text$cluster(text, index); \ if (unlikely(cluster.tag == TEXT_NONE)) \ - fail_source(__SOURCE_FILE__, start, end, "Invalid text index: ", index, " (text has length ", \ - (int64_t)text.length, ")\n"); \ + fail_source(__SOURCE_FILE__, start, end, \ + Text$concat(Text("Invalid text index: "), convert_to_text(index), Text(" (text has length "), \ + convert_to_text((int64_t)text.length), Text(")\n"))); \ cluster; \ }) OptionalText_t Text$from_str(const char *str); @@ -14,7 +14,6 @@ #endif #include "ast.h" -#include "changes.md.h" #include "compile/cli.h" #include "compile/files.h" #include "compile/headers.h" @@ -70,14 +69,9 @@ static const char *paths_str(List_t paths) { return Text$as_c_string(result); } -#ifdef __APPLE__ -#define SHARED_SUFFIX ".dylib" -#else -#define SHARED_SUFFIX ".so" -#endif - static OptionalBool_t verbose = false, quiet = false, show_version = false, show_prefix = false, clean_build = false, - source_mapping = true, show_changelog = false, should_install = false; + source_mapping = true, should_install = false; +static bool is_gcc = false, is_clang = false; static List_t format_files = EMPTY_LIST, format_files_inplace = EMPTY_LIST, parse_files = EMPTY_LIST, transpile_files = EMPTY_LIST, compile_objects = EMPTY_LIST, compile_executables = EMPTY_LIST, @@ -92,8 +86,8 @@ static OptionalText_t show_codegen = NONE_TEXT, " -D_BSD_SOURCE" #endif " -DGC_THREADS"), - ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo@" TOMO_VERSION), ldflags = Text(""), - optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER); + ldlibs = Text("-lgc -lm -lgmp -lunistring"), ldflags = Text(""), 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>" @@ -139,6 +133,9 @@ static List_t normalize_tm_paths(List_t paths) { } int main(int argc, char *argv[]) { + GC_INIT(); + tomo_configure(); + #ifdef __linux__ // Get the file modification time of the compiler, so we // can recompile files after changing the compiler: @@ -168,7 +165,7 @@ int main(int argc, char *argv[]) { if (getenv("TOMO_PATH")) TOMO_PATH = getenv("TOMO_PATH"); - cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "' ", cflags); + cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "' ", cflags); // Set up environment variables: const char *PATH = getenv("PATH"); @@ -187,7 +184,7 @@ int main(int argc, char *argv[]) { // Run a tool: if ((streq(argv[1], "-r") || streq(argv[1], "--run")) && argc >= 3) { if (strcspn(argv[2], "/;$") == strlen(argv[2])) { - const char *program = String("'", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", argv[2], "/", argv[2]); + const char *program = String("'", TOMO_PATH, "'/lib/tomo@", TOMO_VERSION, "/", argv[2], "/", argv[2]); execv(program, &argv[2]); } print_err("This is not an installed tomo program: ", argv[2]); @@ -219,7 +216,7 @@ int main(int argc, char *argv[]) { " --source-mapping|-m <yes|no>: toggle source mapping in generated code\n" " --changelog: show the Tomo changelog\n" " --run|-r: run a program from ", - TOMO_PATH, "/share/tomo@" TOMO_VERSION "/installed\n"); + TOMO_PATH, "/share/tomo@", TOMO_VERSION, "/installed\n"); Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage); cli_arg_t tomo_args[] = { {"run", &run_files, List$info(&Path$info), .short_flag = 'r'}, // @@ -240,7 +237,6 @@ int main(int argc, char *argv[]) { {"optimization", &optimization, &Text$info, .short_flag = 'O'}, // {"force-rebuild", &clean_build, &Bool$info, .short_flag = 'f'}, // {"source-mapping", &source_mapping, &Bool$info, .short_flag = 'm'}, - {"changelog", &show_changelog, &Bool$info}, // }; tomo_parse_args(argc, argv, usage, help, TOMO_VERSION, sizeof(tomo_args) / sizeof(tomo_args[0]), tomo_args); @@ -249,29 +245,26 @@ int main(int argc, char *argv[]) { return 0; } - if (show_changelog) { - print_inline(string_slice((const char *)CHANGES_md, (size_t)CHANGES_md_len)); - return 0; - } - if (show_version) { if (verbose) print(TOMO_VERSION, " ", GIT_VERSION); else print(TOMO_VERSION); return 0; } - bool is_gcc = (system(String(cc, " -v 2>&1 | grep -q 'gcc version'")) == 0); + is_gcc = (system(String(cc, " -v 2>&1 | grep -q 'gcc version'")) == 0); if (is_gcc) { cflags = Texts(cflags, Text(" -fsanitize=signed-integer-overflow -fno-sanitize-recover" " -fno-signaling-nans -fno-trapping-math -fno-finite-math-only")); } - bool is_clang = (system(String(cc, " -v 2>&1 | grep -q 'clang version'")) == 0); + is_clang = (system(String(cc, " -v 2>&1 | grep -q 'clang version'")) == 0); if (is_clang) { cflags = Texts(cflags, Text(" -Wno-parentheses-equality")); } - ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib' ", ldflags); + ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib' ", ldflags, " -ffunction-sections -fdata-sections"); + if (is_gcc) ldflags = Texts(ldflags, " -Wl,--gc-sections"); + else if (is_clang) ldflags = Texts(ldflags, " -Wl,-dead_strip"); #ifdef __APPLE__ cflags = Texts(cflags, Text(" -I/opt/homebrew/include")); @@ -293,7 +286,7 @@ int main(int argc, char *argv[]) { // Uninstall libraries: for (int64_t i = 0; i < (int64_t)uninstall_libraries.length; i++) { Text_t *u = (Text_t *)(uninstall_libraries.data + i * uninstall_libraries.stride); - xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", *u, " '", TOMO_PATH, "'/bin/", *u, + xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo@", TOMO_VERSION, "/", *u, " '", TOMO_PATH, "'/bin/", *u, " '", TOMO_PATH, "'/man/man1/", *u, ".1"); print("Uninstalled ", *u); } @@ -400,6 +393,32 @@ int main(int argc, char *argv[]) { run_files = normalize_tm_paths(run_files); + if (run_files.length == 0 && format_files.length == 0 && format_files_inplace.length == 0 && parse_files.length == 0 + && transpile_files.length == 0 && compile_objects.length == 0 && compile_executables.length == 0 + && run_files.length == 0 && uninstall_libraries.length == 0 && libraries.length == 0) { + Path_t path = Path$from_str(String("~/.local/tomo/state/tomo@", TOMO_VERSION, "/run.tm")); + path = Path$expand_home(path); + Path$create_directory(Path$parent(path), 0755, true); + if (!Path$exists(path)) { + Path$write(path, + Text("# This is a handy Tomo REPL-like runner\n" // + "# Normally you would run `tomo ./file.tm` to run a script\n" // + "# See `tomo --help` for full usage\n" // + "\n" // + "func main()\n" // + " # Put your code here:\n" // + " pass\n" // + "\n" // + "# Save and exit to run\n"), + 0644); + } + List$insert(&run_files, &path, I(0), sizeof(path)); + const char *editor = getenv("EDITOR"); + if (!editor || editor[0] == '\0') editor = "vim"; + int status = system(String(editor, " ", path)); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return 1; + } + // Compile runnable files in parallel, then execute in serial: for (int64_t i = 0; i < (int64_t)run_files.length; i++) { Path_t path = *(Path_t *)(run_files.data + i * run_files.stride); @@ -484,33 +503,21 @@ void build_library(Path_t lib_dir) { compile_files(env, tm_files, &object_files, &extra_ldlibs, COMPILE_OBJ); Text_t lib_name = get_library_name(lib_dir); - Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_name, Text(SHARED_SUFFIX))); - if (!is_stale_for_any(shared_lib, object_files, false)) { - if (verbose) whisper("Unchanged: ", shared_lib); - return; + Path_t archive = Path$child(lib_dir, Texts(Text("lib"), lib_name, ".a")); + if (is_stale_for_any(archive, object_files, false)) { + FILE *prog = run_cmd("ar -rcs '", archive, "' ", paths_str(object_files)); + if (!prog) print_err("Failed to run `ar`"); + int status = pclose(prog); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE); + if (!quiet) print("Compiled static library:\t", Path$relative_to(archive, Path$current_dir())); + } else { + if (verbose) whisper("Unchanged: ", archive); } - - FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), -#ifdef __APPLE__ - " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX, - "'" -#else - " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX, - "'" -#endif - " -shared ", - paths_str(object_files), " -o '", shared_lib, "'"); - - if (!prog) print_err("Failed to run C compiler: ", cc); - int status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE); - - if (!quiet) print("Compiled library:\t", Path$relative_to(shared_lib, Path$current_dir())); } void install_library(Path_t lib_dir) { Text_t lib_name = get_library_name(lib_dir); - Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION)), lib_name); + Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION)), lib_name); print("Installing ", lib_dir, " into ", dest); if (!Enum$equal(&lib_dir, &dest, &Path$info)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_name); @@ -522,15 +529,15 @@ void install_library(Path_t lib_dir) { } // If we have `debugedit` on this system, use it to remap the debugging source information // to point to the installed version of the source file. Otherwise, fail silently. - if (verbose) whisper("Updating debug symbols for ", dest, "/lib", lib_name, SHARED_SUFFIX); + if (verbose) whisper("Updating debug symbols for ", dest, "/lib", lib_name, ".a"); int result = system(String(as_owner, "debugedit -b ", lib_dir, " -d '", dest, "'" " '", - dest, "/lib", lib_name, SHARED_SUFFIX, + dest, "/lib", lib_name, ".a", "' " ">/dev/null 2>/dev/null")); (void)result; - print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", lib_name); + print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", lib_name); } void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs, compile_mode_t mode) { @@ -692,13 +699,12 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l case USE_MODULE: { module_info_t mod = get_used_module_info(stmt_ast); const char *full_name = mod.version ? String(mod.name, "@", mod.version) : mod.name; - Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), - "' '", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), "/lib", - Text$from_str(full_name), SHARED_SUFFIX "'"); + Text_t lib = Texts(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", Text$from_str(full_name), "/lib", + Text$from_str(full_name), ".a"); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); - List_t children = - Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); + 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 < (int64_t)children.length; i++) { Path_t *child = (Path_t *)(children.data + i * children.stride); Table_t discarded = {.entries = EMPTY_LIST, .fallback = to_compile}; @@ -920,8 +926,36 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t Path_t runner_file = build_file(path, ".runner.c"); Path$write(runner_file, program, 0644); - FILE *runner = run_cmd(cc, " ", cflags, " -O", optimization, " ", ldflags, " ", ldlibs, " ", - list_text(extra_ldlibs), " ", paths_str(object_files), " ", runner_file, " -o ", exe_path); + // .a archive files need to go later in the positional order: + List_t archives = EMPTY_LIST; + for (int64_t i = 0; i < (int64_t)extra_ldlibs.length;) { + Text_t *lib = (Text_t *)(extra_ldlibs.data + i * extra_ldlibs.stride); + if (Text$ends_with(*lib, Text(".a"), NULL)) { + List$insert(&archives, lib, I(0), sizeof(Text_t)); + List$remove_at(&extra_ldlibs, I(i + 1), I(1), sizeof(Text_t)); + } else { + i += 1; + } + } + + FILE *runner = run_cmd( + cc, + // C flags: + " ", cflags, " -O", optimization, + // Linker flags and dynamically linked shared libraries: + " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), " ", + // Object files: + paths_str(object_files), + // Input file: + " ", runner_file, + // Statically linked archive files (must come after runner): + // Libraries are grouped to allow for circular dependencies among + // the libraries that are used. + " ", is_gcc ? Texts("-Wl,--start-group ", list_text(archives), " -Wl,--end-group") : list_text(archives), + // Tomo static library: + " ", TOMO_PATH, "/lib/libtomo@", TOMO_VERSION, ".a", + // Output file: + " -o ", exe_path); if (show_codegen.length > 0) { FILE *out = run_cmd(show_codegen); diff --git a/src/typecheck.c b/src/typecheck.c index 9ba7284e..dc234bac 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -225,7 +225,8 @@ static env_t *load_module(env_t *env, ast_t *use_ast) { module_info_t mod = get_used_module_info(use_ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name; - if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) + if (glob(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(use_ast, "Couldn't find or install library: ", folder); } @@ -1530,11 +1531,7 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { case Metadata: return true; default: break; } - type_t *t = get_type(env, ast); - if (t->tag == StructType) { - return (Match(t, StructType)->fields == NULL); - } - return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); + return is_discardable_type(get_type(env, ast)); } type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) { diff --git a/src/types.c b/src/types.c index aeadbceb..bffeb653 100644 --- a/src/types.c +++ b/src/types.c @@ -138,7 +138,9 @@ bool type_is_a(type_t *t, type_t *req) { return false; } -type_t *non_optional(type_t *t) { return t->tag == OptionalType ? Match(t, OptionalType)->type : t; } +type_t *non_optional(type_t *t) { + return t->tag == OptionalType ? Match(t, OptionalType)->type : t; +} PUREFUNC type_t *value_type(type_t *t) { while (t->tag == PointerType) @@ -146,6 +148,13 @@ PUREFUNC type_t *value_type(type_t *t) { return t; } +PUREFUNC bool is_discardable_type(type_t *t) { + if (t->tag == StructType) { + return (Match(t, StructType)->fields == NULL); + } + return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); +} + type_t *type_or_type(type_t *a, type_t *b) { if (!a) return b; if (!b) return a; @@ -155,6 +164,8 @@ type_t *type_or_type(type_t *a, type_t *b) { return a->tag == OptionalType ? a : Type(OptionalType, a); if (a->tag == ReturnType && b->tag == ReturnType) return Type(ReturnType, .ret = type_or_type(Match(a, ReturnType)->ret, Match(b, ReturnType)->ret)); + if ((a->tag == VoidType && is_discardable_type(b)) || (is_discardable_type(a) && b->tag == VoidType)) + return Type(VoidType); if (is_incomplete_type(a) && type_eq(b, most_complete_type(a, b))) return b; if (is_incomplete_type(b) && type_eq(a, most_complete_type(a, b))) return a; @@ -458,7 +469,9 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed) { return false; } -PUREFUNC bool is_int_type(type_t *t) { return t->tag == IntType || t->tag == BigIntType || t->tag == ByteType; } +PUREFUNC bool is_int_type(type_t *t) { + return t->tag == IntType || t->tag == BigIntType || t->tag == ByteType; +} PUREFUNC bool is_numeric_type(type_t *t) { return t->tag == IntType || t->tag == BigIntType || t->tag == FloatType || t->tag == ByteType || t->tag == RealType; diff --git a/src/types.h b/src/types.h index 9f468c1e..f6b2f43f 100644 --- a/src/types.h +++ b/src/types.h @@ -146,6 +146,7 @@ PUREFUNC bool type_eq(type_t *a, type_t *b); PUREFUNC bool type_is_a(type_t *t, type_t *req); type_t *type_or_type(type_t *a, type_t *b); type_t *value_type(type_t *a); +PUREFUNC bool is_discardable_type(type_t *t); typedef enum { NUM_PRECISION_EQUAL, NUM_PRECISION_LESS, diff --git a/test/integers.tm b/test/integers.tm index 67175f7a..2d07beda 100644 --- a/test/integers.tm +++ b/test/integers.tm @@ -94,6 +94,7 @@ func main() assert (3).is_between(1, 5) == yes assert (3).is_between(1, 3) == yes + assert (3).is_between(5, 1) == yes assert (3).is_between(100, 200) == no assert (6).get_bit(1) == no |
