diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-12-23 15:28:53 -0500 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-12-23 15:28:53 -0500 |
| commit | 4771d6394d84eefaa45b661c1af8e20ac092a225 (patch) | |
| tree | 6c0f08c7a6386eb454ef5b3a9059b84883a9ecdb | |
| parent | 0ea6cdf216ca765039f3c01f5b32dc1103265b58 (diff) | |
| parent | 1a62de25c448d2661864ba25a98686ed506e66af (diff) | |
Merge branch 'dev'
41 files changed, 637 insertions, 251 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d6c5dba..0e4f08d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,103 +5,144 @@ on: tags: - 'v*' +permissions: + contents: write + +env: + PKGNAME: tomo + jobs: - linux-x86_64: - runs-on: ubuntu-latest + 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 \ - xxd \ - binutils + sudo apt-get install -y build-essential libgmp-dev libunistring-dev libgc-dev binutils - name: Build run: | - make clean make -j - name: Package run: | - TAG=${GITHUB_REF#refs/tags/} - tar -C build -czf tomo-linux-x86_64.tar.gz tomo@${TAG} - sha256sum tomo-linux-x86_64.tar.gz > tomo-linux-x86_64.tar.gz.sha256 + TAG=${GITHUB_REF_NAME} + FILE=${PKGNAME}-linux-${{ matrix.arch }}.tar.gz + tar -C build -czf "$FILE" ${PKGNAME}@${TAG} + sha256sum "$FILE" > "$FILE.sha256" - - name: Upload - uses: softprops/action-gh-release@v2 + - name: Upload artifacts + uses: actions/upload-artifact@v3 with: - files: | - tomo-linux-x86_64.tar.gz - tomo-linux-x86_64.tar.gz.sha256 + name: linux-${{ matrix.arch }} + path: | + ${PKGNAME}-linux-${{ matrix.arch }}.tar.gz + ${PKGNAME}-linux-${{ matrix.arch }}.tar.gz.sha256 - linux-aarch64: - runs-on: ubuntu-latest + build-macos: + runs-on: macos-latest 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 \ - xxd \ - binutils + brew update + brew install gmp libunistring bdw-gc llvm binutils - - name: Build + - name: Build arm64 run: | - make clean make -j - name: Package run: | - TAG=${GITHUB_REF#refs/tags/} - tar -C build -czf tomo-linux-aarch64.tar.gz tomo@${TAG} - sha256sum tomo-linux-aarch64.tar.gz > tomo-linux-aarch64.tar.gz.sha256 + 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: Upload + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: | - tomo-linux-aarch64.tar.gz - tomo-linux-aarch64.tar.gz.sha256 + files: release/* - macos: - runs-on: macos-latest + 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: | - brew update - brew install gmp libunistring bdw-gc llvm binutils + sudo apt-get install -y pacman-contrib jq gh - - name: Build + - name: Set up SSH run: | - make clean - make -j + mkdir -p ~/.ssh + echo "$AUR_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts - - name: Package + - name: Wait for release assets run: | - TAG=${GITHUB_REF#refs/tags/} - tar -C build -czf tomo-macos-universal.tar.gz tomo@${TAG} - shasum -a 256 tomo-macos-universal.tar.gz > tomo-macos-universal.tar.gz.sha256 - - - name: Upload - uses: softprops/action-gh-release@v2 - with: - files: | - tomo-macos-universal.tar.gz - tomo-macos-universal.tar.gz.sha256 + 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 -permissions: - contents: write - packages: write @@ -1,5 +1,20 @@ # Version History +## 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. @@ -95,10 +95,9 @@ TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print 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)"' + -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 @@ -110,11 +109,6 @@ 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) @@ -161,10 +155,6 @@ $(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/$(LIB_FILE): $(STDLIB_OBJS) | $(BUILD_DIR)/lib - @$(ECHO) $(CC) $^ $(CFLAGS_PLACEHOLDER) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ - @$(CC) $^ $(CFLAGS) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ - $(BUILD_DIR)/lib/$(AR_FILE): $(STDLIB_OBJS) | $(BUILD_DIR)/lib ar -rcs $@ $^ @@ -174,7 +164,7 @@ $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION)/modules.ini: modules/core.ini modules/exam $(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/$(LIB_FILE) \ +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) @@ -2646,6 +2646,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 +3490,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/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/examples/learnxiny.tm b/examples/learnxiny.tm index 748f8957..6971f2a9 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/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 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/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/functions.c b/src/compile/functions.c index ec37c0ad..01377a89 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -630,6 +630,8 @@ 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); code_err(var, "This variable was assigned to, but never read from."); } 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 83f387e2..75dff935 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 5b0ccb95..482e5ed0 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -61,12 +61,12 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede // Automatic optional checking for nums: if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) { int64_t line = get_line_number(ast->file, ast->start); - *code = - Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", - check_none(actual, Text("opt")), ")\n", "#line ", 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/text.c b/src/compile/text.c index 3a8a227c..2a64ca45 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/environment.c b/src/environment.c index cf662749..a82274e7 100644 --- a/src/environment.c +++ b/src/environment.c @@ -341,6 +341,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?)"}), diff --git a/src/modules.c b/src/modules.c index 056cd5cc..c7c29d24 100644 --- a/src/modules.c +++ b/src/modules.c @@ -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/stdlib/bytes.c b/src/stdlib/bytes.c index 4416d804..a1e953a0 100644 --- a/src/stdlib/bytes.c +++ b/src/stdlib/bytes.c @@ -25,7 +25,7 @@ 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; } 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/intX.c.h b/src/stdlib/intX.c.h index 72dfe6ed..03322e5b 100644 --- a/src/stdlib/intX.c.h +++ b/src/stdlib/intX.c.h @@ -98,7 +98,7 @@ 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); } diff --git a/src/stdlib/lists.h b/src/stdlib/lists.h index 6a0a1d43..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); \ }) 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.c.h b/src/stdlib/numX.c.h index 0e84708f..2fde8c45 100644 --- a/src/stdlib/numX.c.h +++ b/src/stdlib/numX.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)(*(NUM_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) { @@ -73,7 +73,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 = Num$value_as_text((double)*(NUM_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) { @@ -115,7 +115,7 @@ public Text_t NAMESPACED(percent)(NUM_T x, NUM_T precision) { NUM_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 diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index ed8383fd..e3028cce 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -324,6 +324,77 @@ 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) { + int fd = open(data->path_str, data->mode, data->permissions); + 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. + GC_gcollect(); + fd = open(data->path_str, data->mode, data->permissions); + } + if (fd == -1) return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")"); + } + data->fd = fd; + + 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 +402,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); } @@ -619,7 +690,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); } @@ -705,8 +776,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 +797,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"); } @@ -808,7 +879,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/stdlib.h b/src/stdlib/stdlib.h index 234048e9..087ed4bf 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -37,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); \ @@ -46,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..d417545d 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) @@ -169,19 +138,15 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const 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"); 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); 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 +154,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 +167,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 +255,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 +277,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 +297,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) { @@ -537,9 +488,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); diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h index cf1c3625..93da465e 100644 --- a/src/stdlib/tables.h +++ b/src/stdlib/tables.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..411f3546 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 @@ -1506,8 +1506,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 +1803,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 9ad7441c..776ea8ec 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -63,8 +63,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); @@ -69,12 +69,6 @@ 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, should_install = false; @@ -151,7 +145,6 @@ int main(int argc, char *argv[]) { if (stat(compiler_path, &compiler_stat) != 0) err(1, "Could not find age of compiler"); #endif - ldlibs = Texts(ldlibs, " -ltomo@", TOMO_VERSION); #ifdef __OpenBSD__ ldlibs = Texts(ldlibs, Text(" -lexecinfo")); #endif @@ -268,7 +261,8 @@ int main(int argc, char *argv[]) { 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 -Wl,--gc-sections"); #ifdef __APPLE__ cflags = Texts(cflags, Text(" -I/opt/homebrew/include")); @@ -397,6 +391,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); @@ -481,28 +501,16 @@ 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) { @@ -519,11 +527,11 @@ 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; @@ -689,9 +697,8 @@ 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( @@ -917,8 +924,35 @@ 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. + " -Wl,--start-group ", list_text(archives), " -Wl,--end-group ", + // 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 1ac3943c..a073cb2e 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1531,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 46df5c64..1ccb7952 100644 --- a/src/types.c +++ b/src/types.c @@ -144,6 +144,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; @@ -153,6 +160,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; diff --git a/src/types.h b/src/types.h index 66e6ba12..df5729ca 100644 --- a/src/types.h +++ b/src/types.h @@ -142,6 +142,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, |
