mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-29 06:37:41 +00:00 
			
		
		
		
	Compare commits
	
		
			125 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 73334f3485 | ||
|   | a5b8da8d67 | ||
|   | e8cccfced5 | ||
|   | 88984f7ffb | ||
|   | 182170b3be | ||
|   | f92412841b | ||
|   | 18c00e89da | ||
|   | 7c38a55a9a | ||
|   | a15916ec9c | ||
|   | 3583d4c92f | ||
|   | a456c67a7b | ||
|   | 6e226e4073 | ||
|   | d23e6614b1 | ||
|   | ab6afa72fd | ||
|   | 9538b8a77c | ||
|   | 36d3804dd2 | ||
|   | a34b8ea68f | ||
|   | 55c10f98bb | ||
|   | 4b5a2a14c0 | ||
|   | 665705d06d | ||
|   | cc8cd4bace | ||
|   | 8f8b6ed001 | ||
|   | aa9efee868 | ||
|   | e0a0e2ed42 | ||
|   | 39f5c539d7 | ||
|   | 1b6437a4f8 | ||
|   | c62c1a58f0 | ||
|   | 3441bcbd69 | ||
|   | 2e6001316a | ||
|   | 53bcc15207 | ||
|   | dd609bb1bb | ||
|   | 5c56c7fa91 | ||
|   | 9a892363a3 | ||
|   | 5f550ea5d4 | ||
|   | 1b6cc023a5 | ||
|   | 410f8d69bc | ||
|   | 6da44bdb6a | ||
|   | d30fd27575 | ||
|   | 1b278fc657 | ||
|   | eecffe01a5 | ||
|   | f63a33884f | ||
|   | fa75a395cb | ||
|   | 1f34ec9902 | ||
|   | f75c08a78e | ||
|   | 5e93f0e34b | ||
|   | 49f151e265 | ||
|   | 2b73a15ad8 | ||
|   | 06d581dde3 | ||
|   | 2b49903c82 | ||
|   | a17ae977a5 | ||
|   | 8a6b44cb4e | ||
|   | 60d9f97750 | ||
|   | f252933f62 | ||
|   | 6dbd7b476c | ||
|   | a47eb847fb | ||
|   | ba5990ef21 | ||
|   | 753911fe2d | ||
|   | 746ced5501 | ||
|   | 1b49934e4f | ||
|   | 682f0f584f | ||
|   | 611b2a6c3a | ||
|   | 8043caf581 | ||
|   | b2d2690eb9 | ||
|   | 7f745a34c3 | ||
|   | b16cf17246 | ||
|   | 67e8518ba6 | ||
|   | e94e8dc484 | ||
|   | 1a24d4fc86 | ||
|   | 6ee05785d1 | ||
|   | 268ff666d2 | ||
|   | 91bb34c3bf | ||
|   | 17d5fb3210 | ||
|   | 687b987f7e | ||
|   | 4daecc9a41 | ||
|   | a85eacadda | ||
|   | ed63987fd1 | ||
|   | ff173047f4 | ||
|   | 83e8aab289 | ||
|   | 85cb35e68f | ||
|   | 952906279c | ||
|   | 5b79b48ae0 | ||
|   | 7c44127bcb | ||
|   | 9338312103 | ||
|   | a0eeb630e7 | ||
|   | 6535d72bd4 | ||
|   | a7d424bc81 | ||
|   | 2bceba4a7a | ||
|   | e3159bb0f5 | ||
|   | 5d1bd8a932 | ||
|   | bafa6bfff0 | ||
|   | e2eb7ab4b2 | ||
|   | 9f4497a5ae | ||
|   | 70de8bf092 | ||
|   | e52575e23a | ||
|   | 10994cbc6a | ||
|   | abad9d7db9 | ||
|   | 5e443cd29d | ||
|   | 7bf3a9d24c | ||
|   | d80a7094ae | ||
|   | ad77bc391c | ||
|   | 2b84fb14b4 | ||
|   | 07155ce657 | ||
|   | 046d28662d | ||
|   | 84dd3db620 | ||
|   | 282f2671ea | ||
|   | 3fc2be3e6e | ||
|   | d10c1fe759 | ||
|   | d18472b07d | ||
|   | 43a68dcd2a | ||
|   | 3d93028088 | ||
|   | 99f0af92bd | ||
|   | 71d81b14a2 | ||
|   | 3894f4021a | ||
|   | 72c659d1ee | ||
|   | 8f879b4adc | ||
|   | 18f2847dc1 | ||
|   | 89b7ff9daf | ||
|   | 26c263d6be | ||
|   | 2570e0f7a0 | ||
|   | 8084e4c728 | ||
|   | ee90f9df62 | ||
|   | 906a982ace | ||
|   | 88e60c309c | ||
|   | 9694aee819 | ||
|   | 2697b0e425 | 
| @@ -19,3 +19,8 @@ tasks: | ||||
|     ninja | ||||
|     ninja test | ||||
|     sudo ninja install | ||||
| - meson_min: | | ||||
|     cd janet | ||||
|     meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false | ||||
|     cd build_meson_min | ||||
|     ninja | ||||
|   | ||||
							
								
								
									
										7
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							| @@ -27,15 +27,16 @@ jobs: | ||||
|         uses: actions/checkout@v3 | ||||
|  | ||||
|       - name: Initialize CodeQL | ||||
|         uses: github/codeql-action/init@v2 | ||||
|         uses: github/codeql-action/init@v3 | ||||
|         with: | ||||
|           languages: ${{ matrix.language }} | ||||
|           queries: +security-and-quality | ||||
|           tools: linked | ||||
|  | ||||
|       - name: Autobuild | ||||
|         uses: github/codeql-action/autobuild@v2 | ||||
|         uses: github/codeql-action/autobuild@v3 | ||||
|  | ||||
|       - name: Perform CodeQL Analysis | ||||
|         uses: github/codeql-action/analyze@v2 | ||||
|         uses: github/codeql-action/analyze@v3 | ||||
|         with: | ||||
|           category: "/language:${{ matrix.language }}" | ||||
|   | ||||
							
								
								
									
										31
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ ubuntu-latest, macos-latest ] | ||||
|         os: [ ubuntu-latest, macos-13 ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
| @@ -39,6 +39,35 @@ jobs: | ||||
|             build/c/janet.c | ||||
|             build/c/shell.c | ||||
|  | ||||
|   release-arm: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|     name: Build release binaries | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ macos-latest ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Set the version | ||||
|         run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV | ||||
|       - name: Set the platform | ||||
|         run: echo "platform=$(tr '[A-Z]' '[a-z]' <<< $RUNNER_OS)" >> $GITHUB_ENV | ||||
|       - name: Compile the project | ||||
|         run: make clean && make | ||||
|       - name: Build the artifact | ||||
|         run: JANET_DIST_DIR=janet-${{ env.version }}-${{ env.platform }} make build/janet-${{ env.version }}-${{ env.platform }}-aarch64.tar.gz | ||||
|       - name: Draft the release | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           draft: true | ||||
|           files: | | ||||
|             build/*.gz | ||||
|             build/janet.h | ||||
|             build/c/janet.c | ||||
|             build/c/shell.c | ||||
|  | ||||
|   release-windows: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|   | ||||
							
								
								
									
										58
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -12,7 +12,7 @@ jobs: | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ ubuntu-latest, macos-latest ] | ||||
|         os: [ ubuntu-latest, macos-latest, macos-13 ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
| @@ -23,7 +23,10 @@ jobs: | ||||
|  | ||||
|   test-windows: | ||||
|     name: Build and test on Windows | ||||
|     runs-on: windows-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ windows-latest, windows-2019 ] | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
| @@ -35,28 +38,61 @@ jobs: | ||||
|       - name: Test the project | ||||
|         shell: cmd | ||||
|         run: build_win test | ||||
|       - name: Test installer build | ||||
|         shell: cmd | ||||
|         run: build_win dist | ||||
|  | ||||
|   test-windows-min: | ||||
|     name: Build and test on Windows Minimal build | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ windows-2019 ] | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup MSVC | ||||
|         uses: ilammy/msvc-dev-cmd@v1 | ||||
|       - name: Setup Python | ||||
|         uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: '3.x' | ||||
|       - name: Install Python Dependencies | ||||
|         run: pip install meson ninja | ||||
|       - name: Build | ||||
|         shell: cmd | ||||
|         run: | | ||||
|           meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false | ||||
|           cd build_meson_min | ||||
|           ninja | ||||
|  | ||||
|   test-mingw: | ||||
|     name: Build on Windows with Mingw (no test yet) | ||||
|     name: Build on Windows with Mingw | ||||
|     runs-on: windows-latest | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: msys2 {0} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         msystem: [ UCRT64, CLANG64 ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup Mingw | ||||
|         uses: msys2/setup-msys2@v2 | ||||
|         with: | ||||
|           msystem: UCRT64 | ||||
|           msystem: ${{ matrix.msystem }} | ||||
|           update: true | ||||
|           install: >- | ||||
|             base-devel | ||||
|             git | ||||
|             gcc | ||||
|       - name: Build the project | ||||
|       - name: Build | ||||
|         shell: cmd | ||||
|         run: make -j4 CC=gcc JANET_NO_AMALG=1 | ||||
|         run: make -j4 CC=gcc | ||||
|       - name: Test | ||||
|         shell: cmd | ||||
|         run: make -j4 CC=gcc test | ||||
|  | ||||
|   test-mingw-linux: | ||||
|     name: Build and test with Mingw on Linux + Wine | ||||
| @@ -86,7 +122,7 @@ jobs: | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install gcc-arm-linux-gnueabi qemu-user | ||||
|       - name: Compile the project | ||||
|         run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc  | ||||
|         run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc | ||||
|       - name: Test the project | ||||
|         run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" SUBRUN="qemu-arm -L /usr/arm-linux-gnueabi/" test VERBOSE=1 | ||||
|  | ||||
| @@ -96,7 +132,7 @@ jobs: | ||||
|     steps: | ||||
|     - name: Checkout the repository | ||||
|       uses: actions/checkout@master | ||||
|     - name: Do Qemu build and test | ||||
|       run: | | ||||
|         docker run --rm --privileged multiarch/qemu-user-static --reset -p yes | ||||
|         docker run --rm -v .:/janet s390x/ubuntu bash -c "apt-get -y update && apt-get -y install git build-essential && cd /janet && make -j3 && make test" | ||||
|     - name: Enable qemu | ||||
|       run: docker run --privileged --rm tonistiigi/binfmt --install s390x | ||||
|     - name: Build and run on emulated architecture | ||||
|       run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test" | ||||
|   | ||||
							
								
								
									
										29
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,35 @@ | ||||
| # Changelog | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| ## 1.38.0 - 2025-03-18 | ||||
| - Add `bundle/replace` | ||||
| - Add CLI flags for the `bundle/` module to install and manage bundles. | ||||
| - Improve `?` peg special termination behavior | ||||
| - Add IEEE hex floats to grammar. | ||||
| - Add buffer peg literal support | ||||
| - Improve `split` peg special edge case behavior | ||||
| - Add Arm64 .msi support | ||||
| - Add `no-reuse` argument to `net/listen` to disable reusing server sockets | ||||
| - Add `struct/rawget` | ||||
| - Fix `deep=` and `deep-not=` to better handle degenerate cases with mutable table keys | ||||
| - Long strings will now dedent on `\r\n` instead of just `\n`. | ||||
| - Add `ev/to-file` for synchronous resource operations | ||||
| - Improve `file/open` error message by including path | ||||
|  | ||||
| ## 1.37.1 - 2024-12-05 | ||||
| - Fix meson cross compilation | ||||
| - Update timeout documentation for networking APIs: timeouts raise errors and do not return nil. | ||||
| - Add `janet_addtimeout_nil(double sec);` to the C API. | ||||
| - Change string hashing. | ||||
| - Fix string equality bug. | ||||
| - Add `assertf` | ||||
| - Change how JANET_PROFILE is loaded to allow more easily customizing the environment. | ||||
| - Add `*repl-prompt*` dynamic binding to allow customizing the built in repl. | ||||
| - Add multiple path support in the `JANET_PATH` environment variables. This lets | ||||
|   user more easily import modules from many directories. | ||||
| - Add `nth` and `only-tags` PEG specials to select from sub-captures while | ||||
|   dropping the rest. | ||||
|  | ||||
| ## 1.36.0 - 2024-09-07 | ||||
| - Improve error messages in `bundle/add*` functions. | ||||
| - Add CI testing and verify tests pass on the s390x architecture. | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| Copyright (c) 2023 Calvin Rose and contributors | ||||
| Copyright (c) 2025 Calvin Rose and contributors | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
|   | ||||
							
								
								
									
										25
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -43,6 +43,7 @@ JANET_DIST_DIR?=janet-dist | ||||
| JANET_BOOT_FLAGS:=. JANET_PATH '$(JANET_PATH)' | ||||
| JANET_TARGET_OBJECTS=build/janet.o build/shell.o | ||||
| JPM_TAG?=master | ||||
| SPORK_TAG?=master | ||||
| HAS_SHARED?=1 | ||||
| DEBUGGER=gdb | ||||
| SONAME_SETTER=-Wl,-soname, | ||||
| @@ -56,6 +57,7 @@ LDFLAGS?=-rdynamic | ||||
| LIBJANET_LDFLAGS?=$(LD_FLAGS) | ||||
| RUN:=$(RUN) | ||||
|  | ||||
|  | ||||
| COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC | ||||
| BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 $(COMMON_CFLAGS) -g | ||||
| BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS) | ||||
| @@ -93,12 +95,18 @@ endif | ||||
| endif | ||||
|  | ||||
| # Mingw | ||||
| MINGW_COMPILER= | ||||
| ifeq ($(findstring MINGW,$(UNAME)), MINGW) | ||||
| 	MINGW_COMPILER=gcc | ||||
| 	CLIBS:=-lws2_32 -lpsapi -lwsock32 | ||||
| 	LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB) | ||||
| 	LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB) | ||||
| 	JANET_TARGET:=$(JANET_TARGET).exe | ||||
| 	JANET_BOOT:=$(JANET_BOOT).exe | ||||
| 	COMPILER_VERSION:=$(shell $(CC) --version) | ||||
| 	ifeq ($(findstring clang,$(COMPILER_VERSION)), clang) | ||||
| 		MINGW_COMPILER=clang | ||||
| 	endif | ||||
| endif | ||||
|  | ||||
|  | ||||
| @@ -205,9 +213,14 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile | ||||
| ######################## | ||||
|  | ||||
| ifeq ($(UNAME), Darwin) | ||||
| SONAME=libjanet.1.36.dylib | ||||
| SONAME=libjanet.1.38.dylib | ||||
| else | ||||
| SONAME=libjanet.so.1.36 | ||||
| SONAME=libjanet.so.1.38 | ||||
| endif | ||||
|  | ||||
| ifeq ($(MINGW_COMPILER), clang) | ||||
| 	SONAME= | ||||
| 	SONAME_SETTER= | ||||
| endif | ||||
|  | ||||
| build/c/shell.c: src/mainclient/shell.c | ||||
| @@ -359,6 +372,12 @@ install-jpm-git: $(JANET_TARGET) | ||||
| 		JANET_LIBPATH='$(LIBDIR)' \ | ||||
| 		$(RUN) ../../$(JANET_TARGET) ./bootstrap.janet | ||||
|  | ||||
| install-spork-git: $(JANET_TARGET) | ||||
| 	mkdir -p build | ||||
| 	rm -rf build/spork | ||||
| 	git clone --depth=1 --branch='$(SPORK_TAG)' https://github.com/janet-lang/spork.git build/spork | ||||
| 	$(JANET_TARGET) -e '(bundle/install "build/spork")' | ||||
|  | ||||
| uninstall: | ||||
| 	-rm '$(DESTDIR)$(BINDIR)/janet' | ||||
| 	-rm -rf '$(DESTDIR)$(INCLUDEDIR)/janet' | ||||
|   | ||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| [](https://gitter.im/janet-language/community) | ||||
| [](https://janet.zulipchat.com) | ||||
|   | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/master/freebsd.yml?) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/master/openbsd.yml?) | ||||
| @@ -18,9 +18,6 @@ to run script files. This client program is separate from the core runtime, so | ||||
| Janet can be embedded in other programs. Try Janet in your browser at | ||||
| <https://janet-lang.org>. | ||||
|  | ||||
| If you'd like to financially support the ongoing development of Janet, consider | ||||
| [sponsoring its primary author](https://github.com/sponsors/bakpakin) through GitHub. | ||||
|  | ||||
| <br> | ||||
|  | ||||
| ## Examples | ||||
| @@ -210,7 +207,7 @@ Alternatively, install the package directly with `pkgin install janet`. | ||||
|  | ||||
| To build an `.msi` installer executable, in addition to the above steps, you will have to: | ||||
|  | ||||
| 5. Install, or otherwise add to your PATH the [WiX 3.11 Toolset](https://github.com/wixtoolset/wix3/releases). | ||||
| 5. Install, or otherwise add to your PATH the [WiX 3.14 Toolset](https://github.com/wixtoolset/wix3/releases). | ||||
| 6. Run `build_win dist`. | ||||
|  | ||||
| Now you should have an `.msi`. You can run `build_win install` to install the `.msi`, or execute the file itself. | ||||
| @@ -253,8 +250,10 @@ Emacs, and Atom each have syntax packages for the Janet language, though. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| See the [Introduction](https://janet-lang.org/docs/index.html) for more details. If you just want | ||||
| to try out the language, you don't need to install anything. You can also move the `janet` executable wherever you want on your system and run it. | ||||
| If you just want to try out the language, you don't need to install anything. | ||||
| In this case you can also move the `janet` executable wherever you want on | ||||
| your system and run it.  However, for a fuller setup, please see the | ||||
| [Introduction](https://janet-lang.org/docs/index.html) for more details. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
|   | ||||
| @@ -91,7 +91,7 @@ exit /b 0 | ||||
|  | ||||
| @rem Clean build artifacts | ||||
| :CLEAN | ||||
| del *.exe *.lib *.exp | ||||
| del *.exe *.lib *.exp *.msi *.wixpdb | ||||
| rd /s /q build | ||||
| if exist dist ( | ||||
|     rd /s /q dist | ||||
| @@ -138,11 +138,18 @@ if defined APPVEYOR_REPO_TAG_NAME ( | ||||
|     set RELEASE_VERSION=%JANET_VERSION% | ||||
| ) | ||||
| if defined CI ( | ||||
|     set WIXBIN="c:\Program Files (x86)\WiX Toolset v3.11\bin\" | ||||
|     set WIXBIN="%WIX%bin\" | ||||
|     echo WIXBIN = %WIXBIN% | ||||
| ) else ( | ||||
|     set WIXBIN= | ||||
| ) | ||||
| %WIXBIN%candle.exe tools\msi\janet.wxs -arch %BUILDARCH% -out build\ | ||||
|  | ||||
| set WIXARCH=%BUILDARCH% | ||||
| if "%WIXARCH%"=="aarch64" ( | ||||
|     set WIXARCH=arm64 | ||||
| ) | ||||
|  | ||||
| %WIXBIN%candle.exe tools\msi\janet.wxs -arch %WIXARCH% -out build\ | ||||
| %WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi | ||||
| exit /b 0 | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								examples/sample-bad-bundle/badmod.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/sample-bad-bundle/badmod.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| (def abc 123) | ||||
							
								
								
									
										7
									
								
								examples/sample-bad-bundle/bundle.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/sample-bad-bundle/bundle.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "badmod.janet")) | ||||
|  | ||||
| (defn check | ||||
|   [&] | ||||
|   (error "Check failed!")) | ||||
							
								
								
									
										3
									
								
								janet.1
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								janet.1
									
									
									
									
									
								
							| @@ -255,7 +255,8 @@ and then arguments to the script. | ||||
| .RS | ||||
| The location to look for Janet libraries. This is the only environment variable Janet needs to | ||||
| find native and source code modules. If no JANET_PATH is set, Janet will look in | ||||
| the default location set at compile time. | ||||
| the default location set at compile time. This should be a list of as well as a colon | ||||
| separate list of such directories. | ||||
| .RE | ||||
|  | ||||
| .B JANET_PROFILE | ||||
|   | ||||
							
								
								
									
										40
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								meson.build
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2024 Calvin Rose and contributors | ||||
| # Copyright (c) 2025 Calvin Rose and contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -20,16 +20,34 @@ | ||||
|  | ||||
| project('janet', 'c', | ||||
|   default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], | ||||
|   version : '1.36.0') | ||||
|   version : '1.38.0') | ||||
|  | ||||
| # Global settings | ||||
| janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') | ||||
| header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet') | ||||
|  | ||||
| # Link math library on all systems | ||||
| # Compilers | ||||
| cc = meson.get_compiler('c') | ||||
| native_cc = meson.get_compiler('c', native : true) | ||||
|  | ||||
| # Native deps | ||||
| native_m_dep = native_cc.find_library('m', required : false) | ||||
| native_dl_dep = native_cc.find_library('dl', required : false) | ||||
| native_android_spawn_dep = native_cc.find_library('android-spawn', required : false) | ||||
| native_thread_dep = dependency('threads', native : true) | ||||
|  | ||||
| # Deps | ||||
| m_dep = cc.find_library('m', required : false) | ||||
| dl_dep = cc.find_library('dl', required : false) | ||||
|  | ||||
| # for MINGW/MSYS2 | ||||
| native_ws2_dep = native_cc.find_library('ws2_32', required: false) | ||||
| native_psapi_dep = native_cc.find_library('psapi', required: false) | ||||
| native_wsock_dep = native_cc.find_library('wsock32', required: false) | ||||
| ws2_dep = cc.find_library('ws2_32', required: false) | ||||
| psapi_dep = cc.find_library('psapi', required: false) | ||||
| wsock_dep = cc.find_library('wsock32', required: false) | ||||
|  | ||||
| android_spawn_dep = cc.find_library('android-spawn', required : false) | ||||
| thread_dep = dependency('threads') | ||||
|  | ||||
| @@ -164,11 +182,18 @@ mainclient_src = [ | ||||
|   'src/mainclient/shell.c' | ||||
| ] | ||||
|  | ||||
| janet_dependencies = [m_dep, dl_dep, android_spawn_dep, ws2_dep, psapi_dep, wsock_dep] | ||||
| janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep, native_ws2_dep, native_psapi_dep, native_wsock_dep] | ||||
| if not get_option('single_threaded') | ||||
|   janet_dependencies += thread_dep | ||||
|   janet_native_dependencies += native_thread_dep | ||||
| endif | ||||
|  | ||||
| # Build boot binary | ||||
| janet_boot = executable('janet-boot', core_src, boot_src, | ||||
|   include_directories : incdir, | ||||
|   c_args : '-DJANET_BOOTSTRAP', | ||||
|   dependencies : [m_dep, dl_dep, thread_dep, android_spawn_dep], | ||||
|   dependencies : janet_native_dependencies, | ||||
|   native : true) | ||||
|  | ||||
| # Build janet.c | ||||
| @@ -181,11 +206,6 @@ janetc = custom_target('janetc', | ||||
|     'JANET_PATH', janet_path | ||||
|   ]) | ||||
|  | ||||
| janet_dependencies = [m_dep, dl_dep, android_spawn_dep] | ||||
| if not get_option('single_threaded') | ||||
|   janet_dependencies += thread_dep | ||||
| endif | ||||
|  | ||||
| # Allow building with no shared library | ||||
| if cc.has_argument('-fvisibility=hidden') | ||||
|   lib_cflags = ['-fvisibility=hidden'] | ||||
| @@ -231,7 +251,7 @@ if meson.is_cross_build() | ||||
|   endif | ||||
|   janet_nativeclient = executable('janet-native', janetc, mainclient_src, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     dependencies : janet_native_dependencies, | ||||
|     c_args : extra_native_cflags, | ||||
|     native : true) | ||||
| else | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| # The core janet library | ||||
| # Copyright 2024 © Calvin Rose | ||||
| # Copyright 2025 © Calvin Rose | ||||
|  | ||||
| ### | ||||
| ### | ||||
| @@ -204,6 +204,16 @@ | ||||
|   [fmt & args] | ||||
|   (error (string/format fmt ;args))) | ||||
|  | ||||
| (defmacro assertf | ||||
|   "Convenience macro that combines `assert` and `string/format`." | ||||
|   [x fmt & args] | ||||
|   (def v (gensym)) | ||||
|   ~(do | ||||
|      (def ,v ,x) | ||||
|      (if ,v | ||||
|        ,v | ||||
|        (,errorf ,fmt ,;args)))) | ||||
|  | ||||
| (defmacro default | ||||
|   ``Define a default value for an optional argument. | ||||
|   Expands to `(def sym (if (= nil sym) val sym))`.`` | ||||
| @@ -986,7 +996,7 @@ | ||||
|  | ||||
| (defn reduce2 | ||||
|   ``The 2-argument version of `reduce` that does not take an initialization value. | ||||
|   Instead, the first element of the array is used for initialization.`` | ||||
|   Instead, the first element of the array is used for initialization. If `ind` is empty, will evaluate to nil.`` | ||||
|   [f ind] | ||||
|   (var k (next ind)) | ||||
|   (if (= nil k) (break nil)) | ||||
| @@ -1301,7 +1311,7 @@ | ||||
| (defdyn *redef* "When set, allow dynamically rebinding top level defs. Will slow generated code and is intended to be used for development.") | ||||
| (defdyn *debug* "Enables a built in debugger on errors and other useful features for debugging in a repl.") | ||||
| (defdyn *exit* "When set, will cause the current context to complete. Can be set to exit from repl (or file), for example.") | ||||
| (defdyn *exit-value* "Set the return value from `run-context` upon an exit. By default, `run-context` will return nil.") | ||||
| (defdyn *exit-value* "Set the return value from `run-context` upon an exit.") | ||||
| (defdyn *task-id* "When spawning a thread or fiber, the task-id can be assigned for concurrency control.") | ||||
|  | ||||
| (defdyn *current-file* | ||||
| @@ -1853,6 +1863,9 @@ | ||||
| (defdyn *pretty-format* | ||||
|   "Format specifier for the `pp` function") | ||||
|  | ||||
| (defdyn *repl-prompt* | ||||
|   "Allow setting a custom prompt at the default REPL. Not all REPLs will respect this binding.") | ||||
|  | ||||
| (defn pp | ||||
|   ``Pretty-print to stdout or `(dyn *out*)`. The format string used is `(dyn *pretty-format* "%q")`.`` | ||||
|   [x] | ||||
| @@ -2206,56 +2219,31 @@ | ||||
|   (map-template :some res pred ind inds) | ||||
|   res) | ||||
|  | ||||
| (defn deep-not= | ||||
|   ``Like `not=`, but mutable types (arrays, tables, buffers) are considered | ||||
|   equal if they have identical structure. Much slower than `not=`.`` | ||||
|   [x y] | ||||
|   (def tx (type x)) | ||||
|   (or | ||||
|     (not= tx (type y)) | ||||
|     (case tx | ||||
|       :tuple (or (not= (length x) (length y)) | ||||
|                  (do | ||||
|                    (var ret false) | ||||
|                    (forv i 0 (length x) | ||||
|                      (def xx (in x i)) | ||||
|                      (def yy (in y i)) | ||||
|                      (if (deep-not= xx yy) | ||||
|                        (break (set ret true)))) | ||||
|                    ret)) | ||||
|       :array (or (not= (length x) (length y)) | ||||
|                  (do | ||||
|                    (var ret false) | ||||
|                    (forv i 0 (length x) | ||||
|                      (def xx (in x i)) | ||||
|                      (def yy (in y i)) | ||||
|                      (if (deep-not= xx yy) | ||||
|                        (break (set ret true)))) | ||||
|                    ret)) | ||||
|       :struct (deep-not= (kvs x) (kvs y)) | ||||
|       :table (deep-not= (table/to-struct x) (table/to-struct y)) | ||||
|       :buffer (not= (string x) (string y)) | ||||
|       (not= x y)))) | ||||
|  | ||||
| (defn deep= | ||||
|   ``Like `=`, but mutable types (arrays, tables, buffers) are considered | ||||
|   equal if they have identical structure. Much slower than `=`.`` | ||||
|   [x y] | ||||
|   (not (deep-not= x y))) | ||||
|  | ||||
| (defn freeze | ||||
|   `Freeze an object (make it immutable) and do a deep copy, making | ||||
|   child values also immutable. Closures, fibers, and abstract types | ||||
|   will not be recursively frozen, but all other types will.` | ||||
|   [x] | ||||
|   (case (type x) | ||||
|     :array (tuple/slice (map freeze x)) | ||||
|     :tuple (tuple/slice (map freeze x)) | ||||
|     :table (if-let [p (table/getproto x)] | ||||
|              (freeze (merge (table/clone p) x)) | ||||
|              (struct ;(map freeze (kvs x)))) | ||||
|     :struct (struct ;(map freeze (kvs x))) | ||||
|     :buffer (string x) | ||||
|   (def tx (type x)) | ||||
|   (cond | ||||
|     (or (= tx :array) (= tx :tuple)) | ||||
|     (tuple/slice (map freeze x)) | ||||
|  | ||||
|     (or (= tx :table) (= tx :struct)) | ||||
|     (let [temp-tab @{}] | ||||
|       # Handle multiple unique keys that freeze. Result should | ||||
|       # be independent of iteration order. | ||||
|       (eachp [k v] x | ||||
|         (def kk (freeze k)) | ||||
|         (def vv (freeze v)) | ||||
|         (def old (get temp-tab kk)) | ||||
|         (def new (if (= nil old) vv (max vv old))) | ||||
|         (put temp-tab kk new)) | ||||
|       (table/to-struct temp-tab (freeze (getproto x)))) | ||||
|  | ||||
|     (= tx :buffer) | ||||
|     (string x) | ||||
|  | ||||
|     x)) | ||||
|  | ||||
| (defn thaw | ||||
| @@ -2271,6 +2259,41 @@ | ||||
|     :string (buffer ds) | ||||
|     ds)) | ||||
|  | ||||
| (defn deep-not= | ||||
|   ``Like `not=`, but mutable types (arrays, tables, buffers) are considered | ||||
|   equal if they have identical structure. Much slower than `not=`.`` | ||||
|   [x y] | ||||
|   (def tx (type x)) | ||||
|   (or | ||||
|     (not= tx (type y)) | ||||
|     (cond | ||||
|       (or (= tx :tuple) (= tx :array)) | ||||
|       (or (not= (length x) (length y)) | ||||
|           (do | ||||
|             (var ret false) | ||||
|             (forv i 0 (length x) | ||||
|               (def xx (in x i)) | ||||
|               (def yy (in y i)) | ||||
|               (if (deep-not= xx yy) | ||||
|                 (break (set ret true)))) | ||||
|             ret)) | ||||
|       (or (= tx :struct) (= tx :table)) | ||||
|       (or (not= (length x) (length y)) | ||||
|           (do | ||||
|             (def rawget (if (= tx :struct) struct/rawget table/rawget)) | ||||
|             (var ret false) | ||||
|             (eachp [k v] x | ||||
|               (if (deep-not= (rawget y k) v) (break (set ret true)))) | ||||
|             ret)) | ||||
|       (= tx :buffer) (not= 0 (- (length x) (length y)) (memcmp x y)) | ||||
|       (not= x y)))) | ||||
|  | ||||
| (defn deep= | ||||
|   ``Like `=`, but mutable types (arrays, tables, buffers) are considered | ||||
|   equal if they have identical structure. Much slower than `=`.`` | ||||
|   [x y] | ||||
|   (not (deep-not= x y))) | ||||
|  | ||||
| (defn macex | ||||
|   ``Expand macros completely. | ||||
|   `on-binding` is an optional callback for whenever a normal symbolic binding | ||||
| @@ -2322,17 +2345,11 @@ | ||||
|  | ||||
| (defmacro short-fn | ||||
|   ``` | ||||
|   Shorthand for `fn`. Arguments are given as `$n`, where `n` is the 0-indexed | ||||
|   argument of the function. `$` is also an alias for the first (index 0) argument. | ||||
|   The `$&` symbol will make the anonymous function variadic if it appears in the | ||||
|   body of the function, and can be combined with positional arguments. | ||||
|  | ||||
|   Example usage: | ||||
|  | ||||
|       (short-fn (+ $ $)) # A function that doubles its arguments. | ||||
|       (short-fn (string $0 $1)) # accepting multiple args. | ||||
|       |(+ $ $) # use pipe reader macro for terse function literals. | ||||
|       |(+ $&)  # variadic functions | ||||
|   Shorthand for `fn`. Arguments are given as `$n`, where `n` is the | ||||
|   0-indexed argument of the function. `$` is also an alias for the | ||||
|   first (index 0) argument. The `$&` symbol will make the anonymous | ||||
|   function variadic if it appears in the body of the function, and | ||||
|   can be combined with positional arguments. | ||||
|   ``` | ||||
|   [arg &opt name] | ||||
|   (var max-param-seen -1) | ||||
| @@ -2652,7 +2669,6 @@ | ||||
|  | ||||
|       (do | ||||
|         (var pindex 0) | ||||
|         (var pstatus nil) | ||||
|         (def len (length buf)) | ||||
|         (when (= len 0) | ||||
|           (:eof p) | ||||
| @@ -2827,6 +2843,24 @@ | ||||
|   (array/insert mp curall-index [(string ":cur:/:all:" ext) loader check-relative]) | ||||
|   mp) | ||||
|  | ||||
| # Don't expose this externally yet - could break if custom module/paths is setup. | ||||
| (defn- module/add-syspath | ||||
|   ``` | ||||
|   Add a custom syspath to `module/paths` by duplicating all entries that being with `:sys:` and | ||||
|   adding duplicates with a specific path prefix instead. | ||||
|   ``` | ||||
|   [path] | ||||
|   (def copies @[]) | ||||
|   (var last-index 0) | ||||
|   (def mp (dyn *module-paths* module/paths)) | ||||
|   (eachp [index entry] mp | ||||
|     (def pattern (first entry)) | ||||
|     (when (and (string? pattern) (string/has-prefix? ":sys:/" pattern)) | ||||
|       (set last-index index) | ||||
|       (array/push copies [(string/replace ":sys:" path pattern) ;(drop 1 entry)]))) | ||||
|   (array/insert mp (+ 1 last-index) ;copies) | ||||
|   mp) | ||||
|  | ||||
| (module/add-paths ":native:" :native) | ||||
| (module/add-paths "/init.janet" :source) | ||||
| (module/add-paths ".janet" :source) | ||||
| @@ -3843,8 +3877,8 @@ | ||||
| (compwhen (dyn 'net/listen) | ||||
|   (defn net/server | ||||
|     "Start a server asynchronously with `net/listen` and `net/accept-loop`. Returns the new server stream." | ||||
|     [host port &opt handler type] | ||||
|     (def s (net/listen host port type)) | ||||
|     [host port &opt handler type no-reuse] | ||||
|     (def s (net/listen host port type no-reuse)) | ||||
|     (if handler | ||||
|       (ev/go (fn [] (net/accept-loop s handler)))) | ||||
|     s)) | ||||
| @@ -3913,7 +3947,7 @@ | ||||
|     (defn make-sig [] | ||||
|       (ffi/signature :default real-ret-type ;computed-type-args)) | ||||
|     (defn make-ptr [] | ||||
|       (assert (ffi/lookup (if lazy (llib) lib) raw-symbol) (string "failed to find ffi symbol " raw-symbol))) | ||||
|       (assertf (ffi/lookup (if lazy (llib) lib) raw-symbol) "failed to find ffi symbol %v" raw-symbol)) | ||||
|     (if lazy | ||||
|       ~(defn ,alias ,;meta [,;formal-args] | ||||
|          (,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args)) | ||||
| @@ -3950,7 +3984,7 @@ | ||||
|  | ||||
| (def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true | ||||
|                   'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- is-safe-def | ||||
|                   'defglobal is-safe-def 'varglobal is-safe-def}) | ||||
|                   'defglobal is-safe-def 'varglobal is-safe-def 'defdyn true}) | ||||
|  | ||||
| (def- importers {'import true 'import* true 'dofile true 'require true}) | ||||
| (defn- use-2 [evaluator args] | ||||
| @@ -4066,7 +4100,7 @@ | ||||
|             (when (empty? b) (buffer/trim b) (os/chmod to perm) (break)) | ||||
|             (file/write fto b) | ||||
|             (buffer/clear b))) | ||||
|          (errorf "destination file %s cannot be opened for writing" to)) | ||||
|         (errorf "destination file %s cannot be opened for writing" to)) | ||||
|       (errorf "source file %s cannot be opened for reading" from))) | ||||
|  | ||||
|   (defn- copyrf | ||||
| @@ -4084,13 +4118,17 @@ | ||||
|     [manifest] | ||||
|     (def bn (get manifest :name)) | ||||
|     (def manifest-name (get-manifest-filename bn)) | ||||
|     (spit manifest-name (string/format "%j\n" manifest))) | ||||
|     (def b @"") | ||||
|     (buffer/format b "%j" manifest) # make sure it is valid jdn | ||||
|     (buffer/clear b) | ||||
|     (buffer/format b "%.99m\n" manifest) | ||||
|     (spit manifest-name b)) | ||||
|  | ||||
|   (defn bundle/manifest | ||||
|     "Get the manifest for a give installed bundle" | ||||
|     [bundle-name] | ||||
|     (def name (get-manifest-filename bundle-name)) | ||||
|     (assert (fexists name) (string "no bundle " bundle-name " found")) | ||||
|     (assertf (fexists name) "no bundle %v found" bundle-name) | ||||
|     (parse (slurp name))) | ||||
|  | ||||
|   (defn- get-bundle-module | ||||
| @@ -4103,7 +4141,7 @@ | ||||
|       (os/cd workdir) | ||||
|       ([_] (print "cannot enter source directory " workdir " for bundle " bundle-name))) | ||||
|     (defer (os/cd dir) | ||||
|       (def new-env (make-env (curenv))) | ||||
|       (def new-env (make-env)) | ||||
|       (put new-env *module-cache* @{}) | ||||
|       (put new-env *module-loading* @{}) | ||||
|       (put new-env *module-make-env* (fn make-bundle-env [&] (make-env new-env))) | ||||
| @@ -4118,7 +4156,6 @@ | ||||
|     [module bundle-name hook & args] | ||||
|     (def hookf (module/value module (symbol hook))) | ||||
|     (unless hookf (break)) | ||||
|     (def manifest (bundle/manifest bundle-name)) | ||||
|     (def dir (os/cwd)) | ||||
|     (os/cd (get module :workdir ".")) | ||||
|     (defer (os/cd dir) | ||||
| @@ -4233,14 +4270,12 @@ | ||||
|       (def missing (seq [d :in deps :when (not (bundle/installed? d))] (string d))) | ||||
|       (when (next missing) (errorf "missing dependencies %s" (string/join missing ", ")))) | ||||
|     (def bundle-name (get config :name default-bundle-name)) | ||||
|     (assert bundle-name (errorf "unable to infer bundle name for %v, use :name argument" path)) | ||||
|     (assert (not (string/check-set "\\/" bundle-name)) | ||||
|             (string "bundle name " | ||||
|                     bundle-name | ||||
|                     " cannot contain path separators")) | ||||
|     (assertf bundle-name "unable to infer bundle name for %v, use :name argument" path) | ||||
|     (assertf (not (string/check-set "\\/" bundle-name)) | ||||
|              "bundle name %v cannot contain path separators" bundle-name) | ||||
|     (assert (next bundle-name) "cannot use empty bundle-name") | ||||
|     (assert (not (fexists (get-manifest-filename bundle-name))) | ||||
|             "bundle is already installed") | ||||
|     (assertf (not (fexists (get-manifest-filename bundle-name))) | ||||
|              "bundle %v is already installed" bundle-name) | ||||
|     # Setup installed paths | ||||
|     (prime-bundle-paths) | ||||
|     (os/mkdir (bundle-dir bundle-name)) | ||||
| @@ -4274,10 +4309,10 @@ | ||||
|         (do-hook module bundle-name :clean man)) | ||||
|       (do-hook module bundle-name :build man) | ||||
|       (do-hook module bundle-name :install man) | ||||
|       (when check | ||||
|         (do-hook module bundle-name :check man)) | ||||
|       (if (empty? (get man :files)) (print "no files installed, is this a valid bundle?")) | ||||
|       (sync-manifest man)) | ||||
|       (sync-manifest man) | ||||
|       (when check | ||||
|         (do-hook module bundle-name :check man))) | ||||
|     (print "installed " bundle-name) | ||||
|     bundle-name) | ||||
|  | ||||
| @@ -4289,7 +4324,7 @@ | ||||
|     (var i 0) | ||||
|     (def man (bundle/manifest bundle-name)) | ||||
|     (def files (get man :files @[])) | ||||
|     (assert (os/mkdir dest-dir) (string "could not create directory " dest-dir " (or it already exists)")) | ||||
|     (assertf (os/mkdir dest-dir) "could not create directory %v (or it already exists)" dest-dir) | ||||
|     (def s (sep)) | ||||
|     (os/mkdir (string dest-dir s "bundle")) | ||||
|     (def install-hook (string dest-dir s "bundle" s "init.janet")) | ||||
| @@ -4313,14 +4348,15 @@ | ||||
|       (spit install-hook b)) | ||||
|     dest-dir) | ||||
|  | ||||
|   (defn bundle/reinstall | ||||
|     "Reinstall an existing bundle from the local source code." | ||||
|     [bundle-name &keys new-config] | ||||
|   (defn bundle/replace | ||||
|     "Reinstall an existing bundle from a new directory. Similar to bundle/reinstall, | ||||
|      but installs the replacement bundle from any directory. This is necesarry to replace a package without | ||||
|      breaking any dependencies." | ||||
|     [bundle-name path &keys new-config] | ||||
|     (def manifest (bundle/manifest bundle-name)) | ||||
|     (def path (get manifest :local-source)) | ||||
|     (def config (get manifest :config @{})) | ||||
|     (def s (sep)) | ||||
|     (assert (= :directory (os/stat path :mode)) "local source not available") | ||||
|     (assertf (= :directory (os/stat path :mode)) "local source %v not available" path) | ||||
|     (def backup-dir (string (dyn *syspath*) s bundle-name ".backup")) | ||||
|     (rmrf backup-dir) | ||||
|     (def backup-bundle-source (bundle/pack bundle-name backup-dir true)) | ||||
| @@ -4333,6 +4369,14 @@ | ||||
|     (rmrf backup-bundle-source) | ||||
|     bundle-name) | ||||
|  | ||||
|   (defn bundle/reinstall | ||||
|     "Reinstall an existing bundle from the local source code." | ||||
|     [bundle-name &keys new-config] | ||||
|     (def manifest (bundle/manifest bundle-name)) | ||||
|     (def path (get manifest :local-source)) | ||||
|     (bundle/replace bundle-name path ;(kvs new-config)) | ||||
|     bundle-name) | ||||
|  | ||||
|   (defn bundle/add-directory | ||||
|     "Add a directory during the install process relative to `(dyn *syspath*)`" | ||||
|     [manifest dest &opt chmod-mode] | ||||
| @@ -4399,10 +4443,11 @@ | ||||
|     `Shorthand for adding scripts during an install. Scripts will be installed to | ||||
|     (string (dyn *syspath*) "/bin") by default and will be set to be executable.` | ||||
|     [manifest src &opt dest chmod-mode] | ||||
|     (default dest (last (string/split "/" src))) | ||||
|     (def s (sep)) | ||||
|     (default dest (last (string/split s src))) | ||||
|     (default chmod-mode 8r755) | ||||
|     (os/mkdir (string (dyn *syspath*) (sep) "bin")) | ||||
|     (bundle/add-file manifest src (string "bin" (sep) dest) chmod-mode)) | ||||
|     (os/mkdir (string (dyn *syspath*) s "bin")) | ||||
|     (bundle/add-file manifest src (string "bin" s dest) chmod-mode)) | ||||
|  | ||||
|   (defn bundle/update-all | ||||
|     "Reinstall all bundles" | ||||
| @@ -4465,6 +4510,12 @@ | ||||
|    "-nocolor" "n" | ||||
|    "-color" "N" | ||||
|    "-library" "l" | ||||
|    "-install" "b" | ||||
|    "-reinstall" "B" | ||||
|    "-uninstall" "u" | ||||
|    "-update-all" "U" | ||||
|    "-list" "L" | ||||
|    "-prune" "P" | ||||
|    "-lint-warn" "w" | ||||
|    "-lint-error" "x"}) | ||||
|  | ||||
| @@ -4475,7 +4526,7 @@ | ||||
|  | ||||
|   (setdyn *args* args) | ||||
|  | ||||
|   (var should-repl false) | ||||
|   (var should-repl nil) | ||||
|   (var no-file true) | ||||
|   (var quiet false) | ||||
|   (var raw-stdin false) | ||||
| @@ -4488,7 +4539,12 @@ | ||||
|   (var error-level nil) | ||||
|   (var expect-image false) | ||||
|  | ||||
|   (if-let [jp (getenv-alias "JANET_PATH")] (setdyn *syspath* jp)) | ||||
|   (when-let [jp (getenv-alias "JANET_PATH")] | ||||
|     (def path-sep (if (index-of (os/which) [:windows :mingw]) ";" ":")) | ||||
|     (def paths (reverse! (string/split path-sep jp))) | ||||
|     (for i 1 (length paths) | ||||
|       (module/add-syspath (get paths i))) | ||||
|     (setdyn *syspath* (first paths))) | ||||
|   (if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile)) | ||||
|   (set colorize (and | ||||
|                   (not (getenv-alias "NO_COLOR")) | ||||
| @@ -4525,6 +4581,12 @@ | ||||
|                --library (-l) lib      : Use a module before processing more arguments | ||||
|                --lint-warn (-w) level  : Set the lint warning level - default is "normal" | ||||
|                --lint-error (-x) level : Set the lint error level - default is "none" | ||||
|                --install (-b) dirpath  : Install a bundle from a directory | ||||
|                --reinstall (-B) name   : Reinstall a bundle by bundle name | ||||
|                --uninstall (-u) name   : Uninstall a bundle by bundle name | ||||
|                --update-all (-U)       : Reinstall all installed bundles | ||||
|                --prune (-P)            : Uninstalled all bundles that are orphaned | ||||
|                --list (-L)             : List all installed bundles | ||||
|                --                      : Stop handling options | ||||
|              ```) | ||||
|            (os/exit 0) | ||||
| @@ -4569,6 +4631,30 @@ | ||||
|              ((thunk) ;subargs) | ||||
|              (error (get thunk :error))) | ||||
|            math/inf) | ||||
|      "b" | ||||
|      (compif (dyn 'bundle/install) | ||||
|        (fn [i &] (bundle/install (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2) | ||||
|        (fn [i &] (eprint "--install not supported with reduced os") 2)) | ||||
|      "B" | ||||
|      (compif (dyn 'bundle/reinstall) | ||||
|        (fn [i &] (bundle/reinstall (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2) | ||||
|        (fn [i &] (eprint "--reinstall not supported with reduced os") 2)) | ||||
|      "u" | ||||
|      (compif (dyn 'bundle/uninstall) | ||||
|        (fn [i &] (bundle/uninstall (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2) | ||||
|        (fn [i &] (eprint "--uninstall not supported with reduced os") 2)) | ||||
|      "P" | ||||
|      (compif (dyn 'bundle/prune) | ||||
|        (fn [i &] (bundle/prune) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1) | ||||
|        (fn [i &] (eprint "--prune not supported with reduced os") 1)) | ||||
|      "U" | ||||
|      (compif (dyn 'bundle/update-all) | ||||
|        (fn [i &] (bundle/update-all) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1) | ||||
|        (fn [i &] (eprint "--update-all not supported with reduced os") 1)) | ||||
|      "L" | ||||
|      (compif (dyn 'bundle/list) | ||||
|        (fn [i &] (each l (bundle/list) (print l)) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1) | ||||
|        (fn [i &] (eprint "--list not supported with reduced os") 1)) | ||||
|      "d" (fn [&] (set debug-flag true) 1) | ||||
|      "w" (fn [i &] (set warn-level (get-lint-level i)) 2) | ||||
|      "x" (fn [i &] (set error-level (get-lint-level i)) 2) | ||||
| @@ -4620,17 +4706,15 @@ | ||||
|         (if-not quiet | ||||
|           (print "Janet " janet/version "-" janet/build " " (os/which) "/" (os/arch) "/" (os/compiler) " - '(doc)' for help")) | ||||
|         (flush) | ||||
|         (def env (make-env)) | ||||
|         (defn getprompt [p] | ||||
|           (when-let [custom-prompt (get env *repl-prompt*)] (break (custom-prompt p))) | ||||
|           (def [line] (parser/where p)) | ||||
|           (string "repl:" line ":" (parser/state p :delimiters) "> ")) | ||||
|         (defn getstdin [prompt buf _] | ||||
|           (file/write stdout prompt) | ||||
|           (file/flush stdout) | ||||
|           (file/read stdin :line buf)) | ||||
|         (def env (make-env)) | ||||
|         (when-let [profile.janet (dyn *profilepath*)] | ||||
|           (def new-env (dofile profile.janet :exit true)) | ||||
|           (merge-module env new-env "" false)) | ||||
|         (when debug-flag | ||||
|           (put env *debug* true) | ||||
|           (put env *redef* true)) | ||||
| @@ -4642,6 +4726,9 @@ | ||||
|         (setdyn *doc-color* (if colorize true)) | ||||
|         (setdyn *lint-error* error-level) | ||||
|         (setdyn *lint-warn* error-level) | ||||
|         (when-let [profile.janet (dyn *profilepath*)] | ||||
|           (dofile profile.janet :exit true :env env) | ||||
|           (put env *current-file* nil)) | ||||
|         (repl getchunk nil env))))) | ||||
|  | ||||
| ### | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -4,10 +4,10 @@ | ||||
| #define JANETCONF_H | ||||
|  | ||||
| #define JANET_VERSION_MAJOR 1 | ||||
| #define JANET_VERSION_MINOR 36 | ||||
| #define JANET_VERSION_MINOR 38 | ||||
| #define JANET_VERSION_PATCH 0 | ||||
| #define JANET_VERSION_EXTRA "" | ||||
| #define JANET_VERSION "1.36.0" | ||||
| #define JANET_VERSION "1.38.0" | ||||
|  | ||||
| /* #define JANET_BUILD "local" */ | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -31,11 +31,13 @@ | ||||
| #ifndef JANET_SINGLE_THREADED | ||||
| #ifndef JANET_WINDOWS | ||||
| #include <pthread.h> | ||||
| #else | ||||
| #include <windows.h> | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_USE_STDATOMIC | ||||
| #include <stdatomic.h> | ||||
| /* We don't need stdatomic on most compilers since we use compiler builtins for atomic operations. | ||||
| @@ -60,6 +62,18 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) { | ||||
|  | ||||
| void janet_signalv(JanetSignal sig, Janet message) { | ||||
|     if (janet_vm.return_reg != NULL) { | ||||
|         /* Should match logic in janet_call for coercing everything not ok to an error (no awaits, yields, etc.) */ | ||||
|         if (janet_vm.coerce_error && sig != JANET_SIGNAL_OK) { | ||||
| #ifdef JANET_EV | ||||
|             if (NULL != janet_vm.root_fiber && sig == JANET_SIGNAL_EVENT) { | ||||
|                 janet_vm.root_fiber->sched_id++; | ||||
|             } | ||||
| #endif | ||||
|             if (sig != JANET_SIGNAL_ERROR) { | ||||
|                 message = janet_wrap_string(janet_formatc("%v coerced from %s to error", message, janet_signal_names[sig])); | ||||
|             } | ||||
|             sig = JANET_SIGNAL_ERROR; | ||||
|         } | ||||
|         *janet_vm.return_reg = message; | ||||
|         if (NULL != janet_vm.fiber) { | ||||
|             janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP; | ||||
| @@ -546,8 +560,8 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA | ||||
| /* Atomic refcounts */ | ||||
|  | ||||
| JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     return InterlockedIncrement(x); | ||||
| #ifdef _MSC_VER | ||||
|     return _InterlockedIncrement(x); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1; | ||||
| #else | ||||
| @@ -556,8 +570,8 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { | ||||
| } | ||||
|  | ||||
| JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     return InterlockedDecrement(x); | ||||
| #ifdef _MSC_VER | ||||
|     return _InterlockedDecrement(x); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1; | ||||
| #else | ||||
| @@ -566,8 +580,8 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { | ||||
| } | ||||
|  | ||||
| JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     return InterlockedOr(x, 0); | ||||
| #ifdef _MSC_VER | ||||
|     return _InterlockedOr(x, 0); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_load_explicit(x, memory_order_acquire); | ||||
| #else | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -449,8 +449,9 @@ JANET_CORE_FN(janet_core_range, | ||||
|     } | ||||
|     count = (count > 0) ? count : 0; | ||||
|     int32_t int_count; | ||||
|     janet_assert(count >= 0, "bad range code"); | ||||
|     if (count > (double) INT32_MAX) { | ||||
|         int_count = INT32_MAX; | ||||
|         janet_panicf("range is too large, %f elements", count); | ||||
|     } else { | ||||
|         int_count = (int32_t) ceil(count); | ||||
|     } | ||||
| @@ -700,7 +701,15 @@ JANET_CORE_FN(janet_core_is_lengthable, | ||||
|  | ||||
| JANET_CORE_FN(janet_core_signal, | ||||
|               "(signal what x)", | ||||
|               "Raise a signal with payload x. ") { | ||||
|               "Raise a signal with payload x. `what` can be an integer\n" | ||||
|               "from 0 through 7 indicating user(0-7), or one of:\n\n" | ||||
|               "* :ok\n" | ||||
|               "* :error\n" | ||||
|               "* :debug\n" | ||||
|               "* :yield\n" | ||||
|               "* :user(0-7)\n" | ||||
|               "* :interrupt\n" | ||||
|               "* :await") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     Janet payload = argc == 2 ? argv[1] : janet_wrap_nil(); | ||||
|     if (janet_checkint(argv[0])) { | ||||
| @@ -993,12 +1002,11 @@ static void make_apply(JanetTable *env) { | ||||
|     janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG, | ||||
|                     "apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm), | ||||
|                     JDOC("(apply f & args)\n\n" | ||||
|                          "Applies a function to a variable number of arguments. Each element in args " | ||||
|                          "is used as an argument to f, except the last element in args, which is expected to " | ||||
|                          "be an array-like. Each element in this last argument is then also pushed as an argument to " | ||||
|                          "f. For example:\n\n" | ||||
|                          "\t(apply + 1000 (range 10))\n\n" | ||||
|                          "sums the first 10 integers and 1000.")); | ||||
|          "Applies a function f to a variable number of arguments. Each " | ||||
|          "element in args is used as an argument to f, except the last " | ||||
|          "element in args, which is expected to be an array or a tuple. " | ||||
|          "Each element in this last argument is then also pushed as an " | ||||
|          "argument to f.")); | ||||
| } | ||||
|  | ||||
| static const uint32_t error_asm[] = { | ||||
| @@ -1151,82 +1159,82 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|     janet_quick_asm(env, JANET_FUN_CMP, | ||||
|                     "cmp", 2, 2, 2, 2, cmp_asm, sizeof(cmp_asm), | ||||
|                     JDOC("(cmp x y)\n\n" | ||||
|                          "Returns -1 if x is strictly less than y, 1 if y is strictly greater " | ||||
|                          "than x, and 0 otherwise. To return 0, x and y must be the exact same type.")); | ||||
|          "Returns -1 if x is strictly less than y, 1 if y is strictly greater " | ||||
|          "than x, and 0 otherwise. To return 0, x and y must be the exact same type.")); | ||||
|     janet_quick_asm(env, JANET_FUN_NEXT, | ||||
|                     "next", 2, 1, 2, 2, next_asm, sizeof(next_asm), | ||||
|                     JDOC("(next ds &opt key)\n\n" | ||||
|                          "Gets the next key in a data structure. Can be used to iterate through " | ||||
|                          "the keys of a data structure in an unspecified order. Keys are guaranteed " | ||||
|                          "to be seen only once per iteration if the data structure is not mutated " | ||||
|                          "during iteration. If key is nil, next returns the first key. If next " | ||||
|                          "returns nil, there are no more keys to iterate through.")); | ||||
|          "Gets the next key in a data structure. Can be used to iterate through " | ||||
|          "the keys of a data structure in an unspecified order. Keys are guaranteed " | ||||
|          "to be seen only once per iteration if the data structure is not mutated " | ||||
|          "during iteration. If key is nil, next returns the first key. If next " | ||||
|          "returns nil, there are no more keys to iterate through.")); | ||||
|     janet_quick_asm(env, JANET_FUN_PROP, | ||||
|                     "propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm), | ||||
|                     JDOC("(propagate x fiber)\n\n" | ||||
|                          "Propagate a signal from a fiber to the current fiber and " | ||||
|                          "set the last value of the current fiber to `x`.  The signal " | ||||
|                          "value is then available as the status of the current fiber. " | ||||
|                          "The resulting stack trace from the current fiber will include " | ||||
|                          "frames from fiber. If fiber is in a state that can be resumed, " | ||||
|                          "resuming the current fiber will first resume `fiber`. " | ||||
|                          "This function can be used to re-raise an error without losing " | ||||
|                          "the original stack trace.")); | ||||
|          "Propagate a signal from a fiber to the current fiber and " | ||||
|          "set the last value of the current fiber to `x`.  The signal " | ||||
|          "value is then available as the status of the current fiber. " | ||||
|          "The resulting stack trace from the current fiber will include " | ||||
|          "frames from fiber. If fiber is in a state that can be resumed, " | ||||
|          "resuming the current fiber will first resume `fiber`. " | ||||
|          "This function can be used to re-raise an error without losing " | ||||
|          "the original stack trace.")); | ||||
|     janet_quick_asm(env, JANET_FUN_DEBUG, | ||||
|                     "debug", 1, 0, 1, 1, debug_asm, sizeof(debug_asm), | ||||
|                     JDOC("(debug &opt x)\n\n" | ||||
|                          "Throws a debug signal that can be caught by a parent fiber and used to inspect " | ||||
|                          "the running state of the current fiber. Returns the value passed in by resume.")); | ||||
|          "Throws a debug signal that can be caught by a parent fiber and used to inspect " | ||||
|          "the running state of the current fiber. Returns the value passed in by resume.")); | ||||
|     janet_quick_asm(env, JANET_FUN_ERROR, | ||||
|                     "error", 1, 1, 1, 1, error_asm, sizeof(error_asm), | ||||
|                     JDOC("(error e)\n\n" | ||||
|                          "Throws an error e that can be caught and handled by a parent fiber.")); | ||||
|          "Throws an error e that can be caught and handled by a parent fiber.")); | ||||
|     janet_quick_asm(env, JANET_FUN_YIELD, | ||||
|                     "yield", 1, 0, 1, 2, yield_asm, sizeof(yield_asm), | ||||
|                     JDOC("(yield &opt x)\n\n" | ||||
|                          "Yield a value to a parent fiber. When a fiber yields, its execution is paused until " | ||||
|                          "another thread resumes it. The fiber will then resume, and the last yield call will " | ||||
|                          "return the value that was passed to resume.")); | ||||
|          "Yield a value to a parent fiber. When a fiber yields, its execution is paused until " | ||||
|          "another thread resumes it. The fiber will then resume, and the last yield call will " | ||||
|          "return the value that was passed to resume.")); | ||||
|     janet_quick_asm(env, JANET_FUN_CANCEL, | ||||
|                     "cancel", 2, 2, 2, 2, cancel_asm, sizeof(cancel_asm), | ||||
|                     JDOC("(cancel fiber err)\n\n" | ||||
|                          "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " | ||||
|                          "Returns the same result as resume.")); | ||||
|          "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " | ||||
|          "Returns the same result as resume.")); | ||||
|     janet_quick_asm(env, JANET_FUN_RESUME, | ||||
|                     "resume", 2, 1, 2, 2, resume_asm, sizeof(resume_asm), | ||||
|                     JDOC("(resume fiber &opt x)\n\n" | ||||
|                          "Resume a new or suspended fiber and optionally pass in a value to the fiber that " | ||||
|                          "will be returned to the last yield in the case of a pending fiber, or the argument to " | ||||
|                          "the dispatch function in the case of a new fiber. Returns either the return result of " | ||||
|                          "the fiber's dispatch function, or the value from the next yield call in fiber.")); | ||||
|          "Resume a new or suspended fiber and optionally pass in a value to the fiber that " | ||||
|          "will be returned to the last yield in the case of a pending fiber, or the argument to " | ||||
|          "the dispatch function in the case of a new fiber. Returns either the return result of " | ||||
|          "the fiber's dispatch function, or the value from the next yield call in fiber.")); | ||||
|     janet_quick_asm(env, JANET_FUN_IN, | ||||
|                     "in", 3, 2, 3, 4, in_asm, sizeof(in_asm), | ||||
|                     JDOC("(in ds key &opt dflt)\n\n" | ||||
|                          "Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, " | ||||
|                          "strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, " | ||||
|                          "and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can " | ||||
|                          "take any value as a key except nil and will return nil or dflt if not found.")); | ||||
|          "Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, " | ||||
|          "strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, " | ||||
|          "and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can " | ||||
|          "take any value as a key except nil and will return nil or dflt if not found.")); | ||||
|     janet_quick_asm(env, JANET_FUN_GET, | ||||
|                     "get", 3, 2, 3, 4, get_asm, sizeof(in_asm), | ||||
|                     JDOC("(get ds key &opt dflt)\n\n" | ||||
|                          "Get the value mapped to key in data structure ds, and return dflt or nil if not found. " | ||||
|                          "Similar to in, but will not throw an error if the key is invalid for the data structure " | ||||
|                          "unless the data structure is an abstract type. In that case, the abstract type getter may throw " | ||||
|                          "an error.")); | ||||
|          "Get the value mapped to key in data structure ds, and return dflt or nil if not found. " | ||||
|          "Similar to in, but will not throw an error if the key is invalid for the data structure " | ||||
|          "unless the data structure is an abstract type. In that case, the abstract type getter may throw " | ||||
|          "an error.")); | ||||
|     janet_quick_asm(env, JANET_FUN_PUT, | ||||
|                     "put", 3, 3, 3, 3, put_asm, sizeof(put_asm), | ||||
|                     JDOC("(put ds key value)\n\n" | ||||
|                          "Associate a key with a value in any mutable associative data structure. Indexed data structures " | ||||
|                          "(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds " | ||||
|                          "value is provided. In an array, extra space will be filled with nils, and in a buffer, extra " | ||||
|                          "space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype " | ||||
|                          "will hide the association defined by the prototype, but will not mutate the prototype table. Putting " | ||||
|                          "a value nil into a table will remove the key from the table. Returns the data structure ds.")); | ||||
|          "Associate a key with a value in any mutable associative data structure. Indexed data structures " | ||||
|          "(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds " | ||||
|          "value is provided. In an array, extra space will be filled with nils, and in a buffer, extra " | ||||
|          "space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype " | ||||
|          "will hide the association defined by the prototype, but will not mutate the prototype table. Putting " | ||||
|          "a value nil into a table will remove the key from the table. Returns the data structure ds.")); | ||||
|     janet_quick_asm(env, JANET_FUN_LENGTH, | ||||
|                     "length", 1, 1, 1, 1, length_asm, sizeof(length_asm), | ||||
|                     JDOC("(length ds)\n\n" | ||||
|                          "Returns the length or count of a data structure in constant time as an integer. For " | ||||
|                          "structs and tables, returns the number of key-value pairs in the data structure.")); | ||||
|          "Returns the length or count of a data structure in constant time as an integer. For " | ||||
|          "structs and tables, returns the number of key-value pairs in the data structure.")); | ||||
|     janet_quick_asm(env, JANET_FUN_BNOT, | ||||
|                     "bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm), | ||||
|                     JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x.")); | ||||
| @@ -1235,74 +1243,74 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|     /* Variadic ops */ | ||||
|     templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD, | ||||
|                      JDOC("(+ & xs)\n\n" | ||||
|                           "Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0.")); | ||||
|          "Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0.")); | ||||
|     templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT, | ||||
|                      JDOC("(- & xs)\n\n" | ||||
|                           "Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the " | ||||
|                           "negative value of that element. Otherwise, returns the first element in xs minus the sum of " | ||||
|                           "the rest of the elements.")); | ||||
|          "Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the " | ||||
|          "negative value of that element. Otherwise, returns the first element in xs minus the sum of " | ||||
|          "the rest of the elements.")); | ||||
|     templatize_varop(env, JANET_FUN_MULTIPLY, "*", 1, 1, JOP_MULTIPLY, | ||||
|                      JDOC("(* & xs)\n\n" | ||||
|                           "Returns the product of all elements in xs. If xs is empty, returns 1.")); | ||||
|          "Returns the product of all elements in xs. If xs is empty, returns 1.")); | ||||
|     templatize_varop(env, JANET_FUN_DIVIDE, "/", 1, 1, JOP_DIVIDE, | ||||
|                      JDOC("(/ & xs)\n\n" | ||||
|                           "Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|                           "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|                           "values.")); | ||||
|          "Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|          "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|          "values.")); | ||||
|     templatize_varop(env, JANET_FUN_DIVIDE_FLOOR, "div", 1, 1, JOP_DIVIDE_FLOOR, | ||||
|                      JDOC("(div & xs)\n\n" | ||||
|                           "Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|                           "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|                           "values.")); | ||||
|          "Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|          "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|          "values.")); | ||||
|     templatize_varop(env, JANET_FUN_MODULO, "mod", 0, 1, JOP_MODULO, | ||||
|                      JDOC("(mod & xs)\n\n" | ||||
|                           "Returns the result of applying the modulo operator on the first value of xs with each remaining value. " | ||||
|                           "`(mod x 0)` is defined to be `x`.")); | ||||
|          "Returns the result of applying the modulo operator on the first value of xs with each remaining value. " | ||||
|          "`(mod x 0)` is defined to be `x`.")); | ||||
|     templatize_varop(env, JANET_FUN_REMAINDER, "%", 0, 1, JOP_REMAINDER, | ||||
|                      JDOC("(% & xs)\n\n" | ||||
|                           "Returns the remainder of dividing the first value of xs by each remaining value.")); | ||||
|          "Returns the remainder of dividing the first value of xs by each remaining value.")); | ||||
|     templatize_varop(env, JANET_FUN_BAND, "band", -1, -1, JOP_BAND, | ||||
|                      JDOC("(band & xs)\n\n" | ||||
|                           "Returns the bit-wise and of all values in xs. Each x in xs must be an integer.")); | ||||
|          "Returns the bit-wise and of all values in xs. Each x in xs must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_BOR, "bor", 0, 0, JOP_BOR, | ||||
|                      JDOC("(bor & xs)\n\n" | ||||
|                           "Returns the bit-wise or of all values in xs. Each x in xs must be an integer.")); | ||||
|          "Returns the bit-wise or of all values in xs. Each x in xs must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR, | ||||
|                      JDOC("(bxor & xs)\n\n" | ||||
|                           "Returns the bit-wise xor of all values in xs. Each in xs must be an integer.")); | ||||
|          "Returns the bit-wise xor of all values in xs. Each in xs must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT, | ||||
|                      JDOC("(blshift x & shifts)\n\n" | ||||
|                           "Returns the value of x bit shifted left by the sum of all values in shifts. x " | ||||
|                           "and each element in shift must be an integer.")); | ||||
|          "Returns the value of x bit shifted left by the sum of all values in shifts. x " | ||||
|          "and each element in shift must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT, | ||||
|                      JDOC("(brshift x & shifts)\n\n" | ||||
|                           "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|                           "and each element in shift must be an integer.")); | ||||
|          "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|          "and each element in shift must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED, | ||||
|                      JDOC("(brushift x & shifts)\n\n" | ||||
|                           "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|                           "and each element in shift must be an integer. The sign of x is not preserved, so " | ||||
|                           "for positive shifts the return value will always be positive.")); | ||||
|          "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|          "and each element in shift must be an integer. The sign of x is not preserved, so " | ||||
|          "for positive shifts the return value will always be positive.")); | ||||
|  | ||||
|     /* Variadic comparators */ | ||||
|     templatize_comparator(env, JANET_FUN_GT, ">", 0, JOP_GREATER_THAN, | ||||
|                           JDOC("(> & xs)\n\n" | ||||
|                                "Check if xs is in descending order. Returns a boolean.")); | ||||
|          "Check if xs is in descending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_LT, "<", 0, JOP_LESS_THAN, | ||||
|                           JDOC("(< & xs)\n\n" | ||||
|                                "Check if xs is in ascending order. Returns a boolean.")); | ||||
|          "Check if xs is in ascending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_GTE, ">=", 0, JOP_GREATER_THAN_EQUAL, | ||||
|                           JDOC("(>= & xs)\n\n" | ||||
|                                "Check if xs is in non-ascending order. Returns a boolean.")); | ||||
|          "Check if xs is in non-ascending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_LTE, "<=", 0, JOP_LESS_THAN_EQUAL, | ||||
|                           JDOC("(<= & xs)\n\n" | ||||
|                                "Check if xs is in non-descending order. Returns a boolean.")); | ||||
|          "Check if xs is in non-descending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_EQ, "=", 0, JOP_EQUALS, | ||||
|                           JDOC("(= & xs)\n\n" | ||||
|                                "Check if all values in xs are equal. Returns a boolean.")); | ||||
|          "Check if all values in xs are equal. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_NEQ, "not=", 1, JOP_EQUALS, | ||||
|                           JDOC("(not= & xs)\n\n" | ||||
|                                "Check if any values in xs are not equal. Returns a boolean.")); | ||||
|          "Check if any values in xs are not equal. Returns a boolean.")); | ||||
|  | ||||
|     /* Platform detection */ | ||||
|     janet_def(env, "janet/version", janet_cstringv(JANET_VERSION), | ||||
| @@ -1311,7 +1319,7 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|               JDOC("The build identifier of the running janet program.")); | ||||
|     janet_def(env, "janet/config-bits", janet_wrap_integer(JANET_CURRENT_CONFIG_BITS), | ||||
|               JDOC("The flag set of config options from janetconf.h which is used to check " | ||||
|                    "if native modules are compatible with the host program.")); | ||||
|          "if native modules are compatible with the host program.")); | ||||
|  | ||||
|     /* Allow references to the environment */ | ||||
|     janet_def(env, "root-env", janet_wrap_table(env), | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
							
								
								
									
										102
									
								
								src/core/ev.c
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								src/core/ev.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -32,9 +32,11 @@ | ||||
| #ifdef JANET_EV | ||||
|  | ||||
| #include <math.h> | ||||
| #include <fcntl.h> | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <winsock2.h> | ||||
| #include <windows.h> | ||||
| #include <io.h> | ||||
| #else | ||||
| #include <pthread.h> | ||||
| #include <limits.h> | ||||
| @@ -43,7 +45,6 @@ | ||||
| #include <signal.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <sys/types.h> | ||||
| #include <fcntl.h> | ||||
| #include <netinet/in.h> | ||||
| #include <netinet/tcp.h> | ||||
| #include <netdb.h> | ||||
| @@ -625,6 +626,18 @@ void janet_addtimeout(double sec) { | ||||
|     add_timeout(to); | ||||
| } | ||||
|  | ||||
| /* Set timeout for the current root fiber but resume with nil instead of raising an error */ | ||||
| void janet_addtimeout_nil(double sec) { | ||||
|     JanetFiber *fiber = janet_vm.root_fiber; | ||||
|     JanetTimeout to; | ||||
|     to.when = ts_delta(ts_now(), sec); | ||||
|     to.fiber = fiber; | ||||
|     to.curr_fiber = NULL; | ||||
|     to.sched_id = fiber->sched_id; | ||||
|     to.is_error = 0; | ||||
|     add_timeout(to); | ||||
| } | ||||
|  | ||||
| void janet_ev_inc_refcount(void) { | ||||
|     janet_atomic_inc(&janet_vm.listener_count); | ||||
| } | ||||
| @@ -1024,6 +1037,9 @@ JANET_CORE_FN(cfun_channel_push, | ||||
|               "Returns the channel if the write succeeded, nil otherwise.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetChannel *channel = janet_getchannel(argv, 0); | ||||
|     if (janet_vm.coerce_error) { | ||||
|         janet_panic("cannot give to channel inside janet_call"); | ||||
|     } | ||||
|     if (janet_channel_push(channel, argv[1], 0)) { | ||||
|         janet_await(); | ||||
|     } | ||||
| @@ -1036,6 +1052,9 @@ JANET_CORE_FN(cfun_channel_pop, | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetChannel *channel = janet_getchannel(argv, 0); | ||||
|     Janet item; | ||||
|     if (janet_vm.coerce_error) { | ||||
|         janet_panic("cannot take from channel inside janet_call"); | ||||
|     } | ||||
|     if (janet_channel_pop(channel, &item, 0)) { | ||||
|         janet_schedule(janet_vm.root_fiber, item); | ||||
|     } | ||||
| @@ -1072,6 +1091,10 @@ JANET_CORE_FN(cfun_channel_choice, | ||||
|     int32_t len; | ||||
|     const Janet *data; | ||||
|  | ||||
|     if (janet_vm.coerce_error) { | ||||
|         janet_panic("cannot select from channel inside janet_call"); | ||||
|     } | ||||
|  | ||||
|     /* Check channels for immediate reads and writes */ | ||||
|     for (int32_t i = 0; i < argc; i++) { | ||||
|         if (janet_indexed_view(argv[i], &data, &len) && len == 2) { | ||||
| @@ -1776,6 +1799,22 @@ void janet_stream_edge_triggered(JanetStream *stream) { | ||||
| } | ||||
|  | ||||
| void janet_stream_level_triggered(JanetStream *stream) { | ||||
|     /* On macos, we seem to need to delete any registered events before re-registering without | ||||
|      * EV_CLEAR, otherwise the new event will still have EV_CLEAR set erroneously. This could be a | ||||
|      * kernel bug, but unfortunately the specification is vague here, esp. in regards to where and when | ||||
|      * EV_CLEAR is set automatically. */ | ||||
|     struct kevent kevs[2]; | ||||
|     int length = 0; | ||||
|     if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) { | ||||
|         EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_DELETE, 0, 0, stream); | ||||
|     } | ||||
|     if (stream->flags & JANET_STREAM_WRITABLE) { | ||||
|         EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_DELETE, 0, 0, stream); | ||||
|     } | ||||
|     int status; | ||||
|     do { | ||||
|         status = kevent(janet_vm.kq, kevs, length, NULL, 0, NULL); | ||||
|     } while (status == -1 && errno == EINTR); | ||||
|     janet_register_stream_impl(stream, 0); | ||||
| } | ||||
|  | ||||
| @@ -3263,6 +3302,64 @@ JANET_CORE_FN(janet_cfun_rwlock_write_release, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| static JanetFile *get_file_for_stream(JanetStream *stream) { | ||||
|     int32_t flags = 0; | ||||
|     char fmt[4] = {0}; | ||||
|     int index = 0; | ||||
|     if (stream->flags & JANET_STREAM_READABLE) { | ||||
|         flags |= JANET_FILE_READ; | ||||
|         janet_sandbox_assert(JANET_SANDBOX_FS_READ); | ||||
|         fmt[index++] = 'r'; | ||||
|     } | ||||
|     if (stream->flags & JANET_STREAM_WRITABLE) { | ||||
|         flags |= JANET_FILE_WRITE; | ||||
|         janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); | ||||
|         int currindex = index; | ||||
|         fmt[index++] = (currindex == 0) ? 'w' : '+'; | ||||
|     } | ||||
|     if (index == 0) return NULL; | ||||
|     /* duplicate handle when converting stream to file */ | ||||
| #ifdef JANET_WINDOWS | ||||
|     int htype = 0; | ||||
|     if (fmt[0] == 'r' && fmt[1] == '+') { | ||||
|         htype = _O_RDWR; | ||||
|     } else if (fmt[0] == 'r') { | ||||
|         htype = _O_RDONLY; | ||||
|     } else if (fmt[0] == 'w') { | ||||
|         htype = _O_WRONLY; | ||||
|     } | ||||
|     int fd = _open_osfhandle((intptr_t) stream->handle, htype); | ||||
|     if (fd < 0) return NULL; | ||||
|     int fd_dup = _dup(fd); | ||||
|     if (fd_dup < 0) return NULL; | ||||
|     FILE *f = _fdopen(fd_dup, fmt); | ||||
|     if (NULL == f) { | ||||
|         _close(fd_dup); | ||||
|         return NULL; | ||||
|     } | ||||
| #else | ||||
|     int fd_dup = dup(stream->handle); | ||||
|     if (fd_dup < 0) return NULL; | ||||
|     FILE *f = fdopen(fd_dup, fmt); | ||||
|     if (NULL == f) { | ||||
|         close(fd_dup); | ||||
|         return NULL; | ||||
|     } | ||||
| #endif | ||||
|     return janet_makejfile(f, flags); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_to_file, | ||||
|               "(ev/to-file)", | ||||
|               "Create core/file copy of the stream. This value can be used " | ||||
|               "when blocking IO behavior is needed.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     JanetFile *iof = get_file_for_stream(stream); | ||||
|     if (iof == NULL) janet_panic("cannot make file from stream"); | ||||
|     return janet_wrap_abstract(iof); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_ev_all_tasks, | ||||
|               "(ev/all-tasks)", | ||||
|               "Get an array of all active fibers that are being used by the scheduler.") { | ||||
| @@ -3307,6 +3404,7 @@ void janet_lib_ev(JanetTable *env) { | ||||
|         JANET_CORE_REG("ev/acquire-wlock", janet_cfun_rwlock_write_lock), | ||||
|         JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release), | ||||
|         JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release), | ||||
|         JANET_CORE_REG("ev/to-file", janet_cfun_to_file), | ||||
|         JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -400,7 +400,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { | ||||
|  | ||||
|     JanetFFIStruct *st = janet_abstract(&janet_struct_type, | ||||
|                                         sizeof(JanetFFIStruct) + argc * sizeof(JanetFFIStructMember)); | ||||
|     st->field_count = member_count; | ||||
|     st->field_count = 0; | ||||
|     st->size = 0; | ||||
|     st->align = 1; | ||||
|     if (argc == 0) { | ||||
| @@ -418,6 +418,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { | ||||
|         st->fields[i].type = decode_ffi_type(argv[j]); | ||||
|         size_t el_size = type_size(st->fields[i].type); | ||||
|         size_t el_align = type_align(st->fields[i].type); | ||||
|         if (el_align <= 0) janet_panicf("bad field type %V", argv[j]); | ||||
|         if (all_packed || pack_one) { | ||||
|             if (st->size % el_align != 0) is_aligned = 0; | ||||
|             st->fields[i].offset = st->size; | ||||
| @@ -433,6 +434,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { | ||||
|     st->size += (st->align - 1); | ||||
|     st->size /= st->align; | ||||
|     st->size *= st->align; | ||||
|     st->field_count = member_count; | ||||
|     return st; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2024 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -47,7 +47,7 @@ typedef struct { | ||||
| #ifndef JANET_WINDOWS | ||||
|     JanetStream *stream; | ||||
| #endif | ||||
|     JanetTable* watch_descriptors; | ||||
|     JanetTable *watch_descriptors; | ||||
|     JanetChannel *channel; | ||||
|     uint32_t default_flags; | ||||
|     int is_watching; | ||||
| @@ -85,9 +85,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|         } | ||||
|         JanetKeyword keyw = janet_unwrap_keyword(options[i]); | ||||
|         const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_linux, | ||||
|                 sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName), | ||||
|                 sizeof(JanetWatchFlagName), | ||||
|                 keyw); | ||||
|                                            sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName), | ||||
|                                            sizeof(JanetWatchFlagName), | ||||
|                                            keyw); | ||||
|         if (!result) { | ||||
|             janet_panicf("unknown inotify flag %v", options[i]); | ||||
|         } | ||||
| @@ -154,99 +154,97 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_ERR: | ||||
|             { | ||||
|                 janet_schedule(fiber, janet_wrap_nil()); | ||||
|         case JANET_ASYNC_EVENT_ERR: { | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             break; | ||||
|         } | ||||
|     read_more: | ||||
|         case JANET_ASYNC_EVENT_HUP: | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|         case JANET_ASYNC_EVENT_READ: { | ||||
|             Janet name = janet_wrap_nil(); | ||||
|  | ||||
|             /* Assumption - read will never return partial events * | ||||
|              * From documentation: | ||||
|              * | ||||
|              * The behavior when the buffer given to read(2) is too small to | ||||
|              * return information about the next event depends on the kernel | ||||
|              * version: before Linux 2.6.21, read(2) returns 0; since Linux | ||||
|              * 2.6.21, read(2) fails with the error EINVAL.  Specifying a buffer | ||||
|              * of size | ||||
|              * | ||||
|              *     sizeof(struct inotify_event) + NAME_MAX + 1 | ||||
|              * | ||||
|              * will be sufficient to read at least one event. */ | ||||
|             ssize_t nread; | ||||
|             do { | ||||
|                 nread = read(stream->handle, buf, sizeof(buf)); | ||||
|             } while (nread == -1 && errno == EINTR); | ||||
|  | ||||
|             /* Check for errors - special case errors that can just be waited on to fix */ | ||||
|             if (nread == -1) { | ||||
|                 if (errno == EAGAIN || errno == EWOULDBLOCK) { | ||||
|                     break; | ||||
|                 } | ||||
|                 janet_cancel(fiber, janet_ev_lasterr()); | ||||
|                 fiber->ev_state = NULL; | ||||
|                 janet_async_end(fiber); | ||||
|                 break; | ||||
|             } | ||||
|     read_more: | ||||
|     case JANET_ASYNC_EVENT_HUP: | ||||
|     case JANET_ASYNC_EVENT_INIT: | ||||
|     case JANET_ASYNC_EVENT_READ: | ||||
|             { | ||||
|                 Janet name = janet_wrap_nil(); | ||||
|             if (nread < (ssize_t) sizeof(struct inotify_event)) break; | ||||
|  | ||||
|                 /* Assumption - read will never return partial events * | ||||
|                  * From documentation: | ||||
|                  * | ||||
|                  * The behavior when the buffer given to read(2) is too small to | ||||
|                  * return information about the next event depends on the kernel | ||||
|                  * version: before Linux 2.6.21, read(2) returns 0; since Linux | ||||
|                  * 2.6.21, read(2) fails with the error EINVAL.  Specifying a buffer | ||||
|                  * of size | ||||
|                  * | ||||
|                  *     sizeof(struct inotify_event) + NAME_MAX + 1 | ||||
|                  * | ||||
|                  * will be sufficient to read at least one event. */ | ||||
|                 ssize_t nread; | ||||
|                 do { | ||||
|                     nread = read(stream->handle, buf, sizeof(buf)); | ||||
|                 } while (nread == -1 && errno == EINTR); | ||||
|  | ||||
|                 /* Check for errors - special case errors that can just be waited on to fix */ | ||||
|                 if (nread == -1) { | ||||
|                     if (errno == EAGAIN || errno == EWOULDBLOCK) { | ||||
|                         break; | ||||
|                     } | ||||
|                     janet_cancel(fiber, janet_ev_lasterr()); | ||||
|                     fiber->ev_state = NULL; | ||||
|                     janet_async_end(fiber); | ||||
|                     break; | ||||
|             /* Iterate through all events read from the buffer */ | ||||
|             char *cursor = buf; | ||||
|             while (cursor < buf + nread) { | ||||
|                 struct inotify_event inevent; | ||||
|                 memcpy(&inevent, cursor, sizeof(inevent)); | ||||
|                 cursor += sizeof(inevent); | ||||
|                 /* Read path of inevent */ | ||||
|                 if (inevent.len) { | ||||
|                     name = janet_cstringv(cursor); | ||||
|                     cursor += inevent.len; | ||||
|                 } | ||||
|                 if (nread < (ssize_t) sizeof(struct inotify_event)) break; | ||||
|  | ||||
|                 /* Iterate through all events read from the buffer */ | ||||
|                 char *cursor = buf; | ||||
|                 while (cursor < buf + nread) { | ||||
|                     struct inotify_event inevent; | ||||
|                     memcpy(&inevent, cursor, sizeof(inevent)); | ||||
|                     cursor += sizeof(inevent); | ||||
|                     /* Read path of inevent */ | ||||
|                     if (inevent.len) { | ||||
|                         name = janet_cstringv(cursor); | ||||
|                         cursor += inevent.len; | ||||
|                 /* Got an event */ | ||||
|                 Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); | ||||
|                 JanetKV *event = janet_struct_begin(6); | ||||
|                 janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd)); | ||||
|                 janet_struct_put(event, janet_ckeywordv("wd-path"), path); | ||||
|                 if (janet_checktype(name, JANET_NIL)) { | ||||
|                     /* We were watching a file directly, so path is the full path. Split into dirname / basename */ | ||||
|                     JanetString spath = janet_unwrap_string(path); | ||||
|                     const uint8_t *cursor = spath + janet_string_length(spath); | ||||
|                     const uint8_t *cursor_end = cursor; | ||||
|                     while (cursor > spath && cursor[0] != '/') { | ||||
|                         cursor--; | ||||
|                     } | ||||
|  | ||||
|                     /* Got an event */ | ||||
|                     Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); | ||||
|                     JanetKV *event = janet_struct_begin(6); | ||||
|                     janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd)); | ||||
|                     janet_struct_put(event, janet_ckeywordv("wd-path"), path); | ||||
|                     if (janet_checktype(name, JANET_NIL)) { | ||||
|                         /* We were watching a file directly, so path is the full path. Split into dirname / basename */ | ||||
|                         JanetString spath = janet_unwrap_string(path); | ||||
|                         const uint8_t *cursor = spath + janet_string_length(spath); | ||||
|                         const uint8_t *cursor_end = cursor; | ||||
|                         while (cursor > spath && cursor[0] != '/') { | ||||
|                             cursor--; | ||||
|                         } | ||||
|                         if (cursor == spath) { | ||||
|                             janet_struct_put(event, janet_ckeywordv("dir-name"), path); | ||||
|                             janet_struct_put(event, janet_ckeywordv("file-name"), name); | ||||
|                         } else { | ||||
|                             janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath)))); | ||||
|                             janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1)))); | ||||
|                         } | ||||
|                     } else { | ||||
|                     if (cursor == spath) { | ||||
|                         janet_struct_put(event, janet_ckeywordv("dir-name"), path); | ||||
|                         janet_struct_put(event, janet_ckeywordv("file-name"), name); | ||||
|                     } else { | ||||
|                         janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath)))); | ||||
|                         janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1)))); | ||||
|                     } | ||||
|                     janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie)); | ||||
|                     Janet etype = janet_ckeywordv("type"); | ||||
|                     const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]); | ||||
|                     for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) { | ||||
|                         if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name)); | ||||
|                     } | ||||
|                     Janet eventv = janet_wrap_struct(janet_struct_end(event)); | ||||
|  | ||||
|                     janet_channel_give(watcher->channel, eventv); | ||||
|                 } else { | ||||
|                     janet_struct_put(event, janet_ckeywordv("dir-name"), path); | ||||
|                     janet_struct_put(event, janet_ckeywordv("file-name"), name); | ||||
|                 } | ||||
|                 janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie)); | ||||
|                 Janet etype = janet_ckeywordv("type"); | ||||
|                 const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]); | ||||
|                 for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) { | ||||
|                     if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name)); | ||||
|                 } | ||||
|                 Janet eventv = janet_wrap_struct(janet_struct_end(event)); | ||||
|  | ||||
|                 /* Read some more if possible */ | ||||
|                 goto read_more; | ||||
|                 janet_channel_give(watcher->channel, eventv); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|             /* Read some more if possible */ | ||||
|             goto read_more; | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -273,7 +271,8 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
| #define WATCHFLAG_RECURSIVE 0x100000u | ||||
|  | ||||
| static const JanetWatchFlagName watcher_flags_windows[] = { | ||||
|     {"all",  | ||||
|     { | ||||
|         "all", | ||||
|         FILE_NOTIFY_CHANGE_ATTRIBUTES | | ||||
|         FILE_NOTIFY_CHANGE_CREATION | | ||||
|         FILE_NOTIFY_CHANGE_DIR_NAME | | ||||
| @@ -282,7 +281,8 @@ static const JanetWatchFlagName watcher_flags_windows[] = { | ||||
|         FILE_NOTIFY_CHANGE_LAST_WRITE | | ||||
|         FILE_NOTIFY_CHANGE_SECURITY | | ||||
|         FILE_NOTIFY_CHANGE_SIZE | | ||||
|         WATCHFLAG_RECURSIVE}, | ||||
|         WATCHFLAG_RECURSIVE | ||||
|     }, | ||||
|     {"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES}, | ||||
|     {"creation", FILE_NOTIFY_CHANGE_CREATION}, | ||||
|     {"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME}, | ||||
| @@ -302,9 +302,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|         } | ||||
|         JanetKeyword keyw = janet_unwrap_keyword(options[i]); | ||||
|         const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows, | ||||
|                 sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), | ||||
|                 sizeof(JanetWatchFlagName), | ||||
|                 keyw); | ||||
|                                            sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), | ||||
|                                            sizeof(JanetWatchFlagName), | ||||
|                                            keyw); | ||||
|         if (!result) { | ||||
|             janet_panicf("unknown windows filewatch flag %v", options[i]); | ||||
|         } | ||||
| @@ -339,19 +339,19 @@ typedef struct { | ||||
|  | ||||
| static void read_dir_changes(OverlappedWatch *ow) { | ||||
|     BOOL result = ReadDirectoryChangesW(ow->stream->handle, | ||||
|             (NotifyChange *) ow->buf, | ||||
|             FILE_INFO_PADDING, | ||||
|             (ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE, | ||||
|             ow->flags & ~WATCHFLAG_RECURSIVE, | ||||
|             NULL, | ||||
|             (OVERLAPPED *) ow, | ||||
|             NULL); | ||||
|                                         (NotifyChange *) ow->buf, | ||||
|                                         FILE_INFO_PADDING, | ||||
|                                         (ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE, | ||||
|                                         ow->flags & ~WATCHFLAG_RECURSIVE, | ||||
|                                         NULL, | ||||
|                                         (OVERLAPPED *) ow, | ||||
|                                         NULL); | ||||
|     if (!result) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static const char* watcher_actions_windows[] = { | ||||
| static const char *watcher_actions_windows[] = { | ||||
|     "unknown", | ||||
|     "added", | ||||
|     "removed", | ||||
| @@ -382,48 +382,47 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|         case JANET_ASYNC_EVENT_FAILED: | ||||
|             janet_stream_close(ow->stream); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_COMPLETE: | ||||
|             { | ||||
|                 if (!watcher->is_watching) { | ||||
|                     janet_stream_close(ow->stream); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 NotifyChange *fni = (NotifyChange *) ow->buf; | ||||
|  | ||||
|                 while (1) { | ||||
|                     /* Got an event */ | ||||
|  | ||||
|                     /* Extract name */ | ||||
|                     Janet filename; | ||||
|                     if (fni->FileNameLength) { | ||||
|                         int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL); | ||||
|                         janet_assert(nbytes, "bad utf8 path"); | ||||
|                         uint8_t *into = janet_string_begin(nbytes); | ||||
|                         WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL); | ||||
|                         filename = janet_wrap_string(janet_string_end(into)); | ||||
|                     } else { | ||||
|                         filename = janet_cstringv(""); | ||||
|                     } | ||||
|  | ||||
|                     JanetKV *event = janet_struct_begin(3); | ||||
|                     janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action])); | ||||
|                     janet_struct_put(event, janet_ckeywordv("file-name"), filename); | ||||
|                     janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path)); | ||||
|                     Janet eventv = janet_wrap_struct(janet_struct_end(event)); | ||||
|  | ||||
|                     janet_channel_give(watcher->channel, eventv); | ||||
|  | ||||
|                     /* Next event */ | ||||
|                     if (!fni->NextEntryOffset) break; | ||||
|                     fni = (NotifyChange *) ((char *)fni + fni->NextEntryOffset); | ||||
|                 } | ||||
|  | ||||
|                 /* Make another call to read directory changes */ | ||||
|                 read_dir_changes(ow); | ||||
|                 janet_async_in_flight(fiber); | ||||
|         case JANET_ASYNC_EVENT_COMPLETE: { | ||||
|             if (!watcher->is_watching) { | ||||
|                 janet_stream_close(ow->stream); | ||||
|                 break; | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|             NotifyChange *fni = (NotifyChange *) ow->buf; | ||||
|  | ||||
|             while (1) { | ||||
|                 /* Got an event */ | ||||
|  | ||||
|                 /* Extract name */ | ||||
|                 Janet filename; | ||||
|                 if (fni->FileNameLength) { | ||||
|                     int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL); | ||||
|                     janet_assert(nbytes, "bad utf8 path"); | ||||
|                     uint8_t *into = janet_string_begin(nbytes); | ||||
|                     WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL); | ||||
|                     filename = janet_wrap_string(janet_string_end(into)); | ||||
|                 } else { | ||||
|                     filename = janet_cstringv(""); | ||||
|                 } | ||||
|  | ||||
|                 JanetKV *event = janet_struct_begin(3); | ||||
|                 janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action])); | ||||
|                 janet_struct_put(event, janet_ckeywordv("file-name"), filename); | ||||
|                 janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path)); | ||||
|                 Janet eventv = janet_wrap_struct(janet_struct_end(event)); | ||||
|  | ||||
|                 janet_channel_give(watcher->channel, eventv); | ||||
|  | ||||
|                 /* Next event */ | ||||
|                 if (!fni->NextEntryOffset) break; | ||||
|                 fni = (NotifyChange *)((char *)fni + fni->NextEntryOffset); | ||||
|             } | ||||
|  | ||||
|             /* Make another call to read directory changes */ | ||||
|             read_dir_changes(ow); | ||||
|             janet_async_in_flight(fiber); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -439,12 +438,12 @@ static void start_listening_ow(OverlappedWatch *ow) { | ||||
|  | ||||
| static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { | ||||
|     HANDLE handle = CreateFileA(path, | ||||
|             FILE_LIST_DIRECTORY | GENERIC_READ, | ||||
|             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | ||||
|             NULL, | ||||
|             OPEN_EXISTING, | ||||
|             FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, | ||||
|             NULL); | ||||
|                                 FILE_LIST_DIRECTORY | GENERIC_READ, | ||||
|                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | ||||
|                                 NULL, | ||||
|                                 OPEN_EXISTING, | ||||
|                                 FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, | ||||
|                                 NULL); | ||||
|     if (handle == INVALID_HANDLE_VALUE) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
| @@ -574,20 +573,21 @@ static const JanetAbstractType janet_filewatch_at = { | ||||
| }; | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_make, | ||||
|         "(filewatch/new channel &opt default-flags)", | ||||
|         "Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n" | ||||
|         "When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. " | ||||
|         "The contents of the channel depend on the OS, but will contain some common keys:\n\n" | ||||
|         "* `:type` -- the type of the event that was raised.\n\n" | ||||
|         "* `:file-name` -- the base file name of the file that triggered the event.\n\n" | ||||
|         "* `:dir-name` -- the directory name of the file that triggered the event.\n\n" | ||||
|         "Events also will contain keys specific to the host OS.\n\n" | ||||
|         "Windows has no extra properties on events.\n\n" | ||||
|         "Linux has the following extra properties on events:\n\n" | ||||
|         "* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n" | ||||
|         "* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n" | ||||
|         "* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n" | ||||
|         "") { | ||||
|               "(filewatch/new channel &opt default-flags)", | ||||
|               "Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n" | ||||
|               "When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. " | ||||
|               "The contents of the channel depend on the OS, but will contain some common keys:\n\n" | ||||
|               "* `:type` -- the type of the event that was raised.\n\n" | ||||
|               "* `:file-name` -- the base file name of the file that triggered the event.\n\n" | ||||
|               "* `:dir-name` -- the directory name of the file that triggered the event.\n\n" | ||||
|               "Events also will contain keys specific to the host OS.\n\n" | ||||
|               "Windows has no extra properties on events.\n\n" | ||||
|               "Linux has the following extra properties on events:\n\n" | ||||
|               "* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n" | ||||
|               "* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n" | ||||
|               "* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n" | ||||
|               "") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_FS_READ); | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetChannel *channel = janet_getchannel(argv, 0); | ||||
|     JanetWatcher *watcher = janet_abstract(&janet_filewatch_at, sizeof(JanetWatcher)); | ||||
| @@ -597,44 +597,44 @@ JANET_CORE_FN(cfun_filewatch_make, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_add, | ||||
|         "(filewatch/add watcher path &opt flags)", | ||||
|         "Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n" | ||||
|         "Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n" | ||||
|         "* `:all` - trigger an event for all of the below triggers.\n\n" | ||||
|         "* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n" | ||||
|         "* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n" | ||||
|         "* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n" | ||||
|         "* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n" | ||||
|         "* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n" | ||||
|         "* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n" | ||||
|         "* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n" | ||||
|         "* `:recursive` - watch subdirectories recursively\n\n" | ||||
|         "Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n" | ||||
|         "* `:access` - IN_ACCESS\n\n" | ||||
|         "* `:all` - IN_ALL_EVENTS\n\n" | ||||
|         "* `:attrib` - IN_ATTRIB\n\n" | ||||
|         "* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n" | ||||
|         "* `:close-write` - IN_CLOSE_WRITE\n\n" | ||||
|         "* `:create` - IN_CREATE\n\n" | ||||
|         "* `:delete` - IN_DELETE\n\n" | ||||
|         "* `:delete-self` - IN_DELETE_SELF\n\n" | ||||
|         "* `:ignored` - IN_IGNORED\n\n" | ||||
|         "* `:modify` - IN_MODIFY\n\n" | ||||
|         "* `:move-self` - IN_MOVE_SELF\n\n" | ||||
|         "* `:moved-from` - IN_MOVED_FROM\n\n" | ||||
|         "* `:moved-to` - IN_MOVED_TO\n\n" | ||||
|         "* `:open` - IN_OPEN\n\n" | ||||
|         "* `:q-overflow` - IN_Q_OVERFLOW\n\n" | ||||
|         "* `:unmount` - IN_UNMOUNT\n\n\n" | ||||
|         "On Windows, events will have the following possible types:\n\n" | ||||
|         "* `:unknown`\n\n" | ||||
|         "* `:added`\n\n" | ||||
|         "* `:removed`\n\n" | ||||
|         "* `:modified`\n\n" | ||||
|         "* `:renamed-old`\n\n" | ||||
|         "* `:renamed-new`\n\n" | ||||
|         "On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n" | ||||
|         "") { | ||||
|               "(filewatch/add watcher path &opt flags)", | ||||
|               "Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n" | ||||
|               "Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n" | ||||
|               "* `:all` - trigger an event for all of the below triggers.\n\n" | ||||
|               "* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n" | ||||
|               "* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n" | ||||
|               "* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n" | ||||
|               "* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n" | ||||
|               "* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n" | ||||
|               "* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n" | ||||
|               "* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n" | ||||
|               "* `:recursive` - watch subdirectories recursively\n\n" | ||||
|               "Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n" | ||||
|               "* `:access` - IN_ACCESS\n\n" | ||||
|               "* `:all` - IN_ALL_EVENTS\n\n" | ||||
|               "* `:attrib` - IN_ATTRIB\n\n" | ||||
|               "* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n" | ||||
|               "* `:close-write` - IN_CLOSE_WRITE\n\n" | ||||
|               "* `:create` - IN_CREATE\n\n" | ||||
|               "* `:delete` - IN_DELETE\n\n" | ||||
|               "* `:delete-self` - IN_DELETE_SELF\n\n" | ||||
|               "* `:ignored` - IN_IGNORED\n\n" | ||||
|               "* `:modify` - IN_MODIFY\n\n" | ||||
|               "* `:move-self` - IN_MOVE_SELF\n\n" | ||||
|               "* `:moved-from` - IN_MOVED_FROM\n\n" | ||||
|               "* `:moved-to` - IN_MOVED_TO\n\n" | ||||
|               "* `:open` - IN_OPEN\n\n" | ||||
|               "* `:q-overflow` - IN_Q_OVERFLOW\n\n" | ||||
|               "* `:unmount` - IN_UNMOUNT\n\n\n" | ||||
|               "On Windows, events will have the following possible types:\n\n" | ||||
|               "* `:unknown`\n\n" | ||||
|               "* `:added`\n\n" | ||||
|               "* `:removed`\n\n" | ||||
|               "* `:modified`\n\n" | ||||
|               "* `:renamed-old`\n\n" | ||||
|               "* `:renamed-new`\n\n" | ||||
|               "On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n" | ||||
|               "") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     const char *path = janet_getcstring(argv, 1); | ||||
| @@ -644,8 +644,8 @@ JANET_CORE_FN(cfun_filewatch_add, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_remove, | ||||
|         "(filewatch/remove watcher path)", | ||||
|         "Remove a path from the watcher.") { | ||||
|               "(filewatch/remove watcher path)", | ||||
|               "Remove a path from the watcher.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     const char *path = janet_getcstring(argv, 1); | ||||
| @@ -654,8 +654,8 @@ JANET_CORE_FN(cfun_filewatch_remove, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_listen, | ||||
|         "(filewatch/listen watcher)", | ||||
|         "Listen for changes in the watcher.") { | ||||
|               "(filewatch/listen watcher)", | ||||
|               "Listen for changes in the watcher.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     janet_watcher_listen(watcher); | ||||
| @@ -663,8 +663,8 @@ JANET_CORE_FN(cfun_filewatch_listen, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_unlisten, | ||||
|         "(filewatch/unlisten watcher)", | ||||
|         "Stop listening for changes on a given watcher.") { | ||||
|               "(filewatch/unlisten watcher)", | ||||
|               "Stop listening for changes on a given watcher.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     janet_watcher_unlisten(watcher); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose & contributors | ||||
| * Copyright (c) 2025 Calvin Rose & contributors | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -191,21 +191,21 @@ Janet janet_wrap_u64(uint64_t x) { | ||||
|  | ||||
| JANET_CORE_FN(cfun_it_s64_new, | ||||
|               "(int/s64 value)", | ||||
|               "Create a boxed signed 64 bit integer from a string value.") { | ||||
|               "Create a boxed signed 64 bit integer from a string value or a number.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_s64(janet_unwrap_s64(argv[0])); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_it_u64_new, | ||||
|               "(int/u64 value)", | ||||
|               "Create a boxed unsigned 64 bit integer from a string value.") { | ||||
|               "Create a boxed unsigned 64 bit integer from a string value or a number.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_u64(janet_unwrap_u64(argv[0])); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_to_number, | ||||
|               "(int/to-number value)", | ||||
|               "Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int32.") { | ||||
|               "Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int64.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     if (janet_type(argv[0]) == JANET_ABSTRACT) { | ||||
|         void *abst = janet_unwrap_abstract(argv[0]); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -31,6 +31,7 @@ | ||||
|  | ||||
| #ifndef JANET_WINDOWS | ||||
| #include <fcntl.h> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/wait.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
| @@ -164,6 +165,14 @@ JANET_CORE_FN(cfun_io_fopen, | ||||
|     } | ||||
|     FILE *f = fopen((const char *)fname, (const char *)fmode); | ||||
|     if (f != NULL) { | ||||
| #ifndef JANET_WINDOWS | ||||
|         struct stat st; | ||||
|         fstat(fileno(f), &st); | ||||
|         if (S_ISDIR(st.st_mode)) { | ||||
|             fclose(f); | ||||
|             janet_panicf("cannot open directory: %s", fname); | ||||
|         } | ||||
| #endif | ||||
|         size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ); | ||||
|         if (bufsize != BUFSIZ) { | ||||
|             int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose and contributors. | ||||
| * Copyright (c) 2025 Calvin Rose and contributors. | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -554,7 +554,10 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     int err = WSAGetLastError(); | ||||
|     freeaddrinfo(ai); | ||||
| #else | ||||
|     int status = connect(sock, addr, addrlen); | ||||
|     int status; | ||||
|     do { | ||||
|         status = connect(sock, addr, addrlen); | ||||
|     } while (status == -1 && errno == EINTR); | ||||
|     int err = errno; | ||||
|     if (is_unix) { | ||||
|         janet_free(ai); | ||||
| @@ -578,17 +581,23 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     net_sched_connect(stream); | ||||
| } | ||||
|  | ||||
| static const char *serverify_socket(JSock sfd) { | ||||
| static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) { | ||||
|     /* Set various socket options */ | ||||
|     int enable = 1; | ||||
|     if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) { | ||||
|         return "setsockopt(SO_REUSEADDR) failed"; | ||||
|     if (reuse_addr) { | ||||
|         if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) { | ||||
|             return "setsockopt(SO_REUSEADDR) failed"; | ||||
|         } | ||||
|     } | ||||
|     if (reuse_port) { | ||||
| #ifdef SO_REUSEPORT | ||||
|     if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { | ||||
|         return "setsockopt(SO_REUSEPORT) failed"; | ||||
|     } | ||||
|         if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { | ||||
|             return "setsockopt(SO_REUSEPORT) failed"; | ||||
|         } | ||||
| #else | ||||
|         (void) reuse_port; | ||||
| #endif | ||||
|     } | ||||
|     janet_net_socknoblock(sfd); | ||||
|     return NULL; | ||||
| } | ||||
| @@ -642,19 +651,21 @@ JANET_CORE_FN(cfun_net_shutdown, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_net_listen, | ||||
|               "(net/listen host port &opt type)", | ||||
|               "(net/listen host port &opt type no-reuse)", | ||||
|               "Creates a server. Returns a new stream that is neither readable nor " | ||||
|               "writeable. Use net/accept or net/accept-loop be to handle connections and start the server. " | ||||
|               "The type parameter specifies the type of network connection, either " | ||||
|               "a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is " | ||||
|               ":stream. The host and port arguments are the same as in net/address.") { | ||||
|               ":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will " | ||||
|               "disable the use of SO_REUSEADDR and SO_REUSEPORT when creating a server on some operating systems.") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN); | ||||
|     janet_arity(argc, 2, 3); | ||||
|     janet_arity(argc, 2, 4); | ||||
|  | ||||
|     /* Get host, port, and handler*/ | ||||
|     int socktype = janet_get_sockettype(argv, argc, 2); | ||||
|     int is_unix = 0; | ||||
|     struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix); | ||||
|     int reuse = !(argc >= 4 && janet_truthy(argv[3])); | ||||
|  | ||||
|     JSock sfd = JSOCKDEFAULT; | ||||
| #ifndef JANET_WINDOWS | ||||
| @@ -664,7 +675,7 @@ JANET_CORE_FN(cfun_net_listen, | ||||
|             janet_free(ai); | ||||
|             janet_panicf("could not create socket: %V", janet_ev_lasterr()); | ||||
|         } | ||||
|         const char *err = serverify_socket(sfd); | ||||
|         const char *err = serverify_socket(sfd, reuse, 0); | ||||
|         if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) { | ||||
|             JSOCKCLOSE(sfd); | ||||
|             janet_free(ai); | ||||
| @@ -687,7 +698,7 @@ JANET_CORE_FN(cfun_net_listen, | ||||
|             sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol); | ||||
| #endif | ||||
|             if (!JSOCKVALID(sfd)) continue; | ||||
|             const char *err = serverify_socket(sfd); | ||||
|             const char *err = serverify_socket(sfd, reuse, reuse); | ||||
|             if (NULL != err) { | ||||
|                 JSOCKCLOSE(sfd); | ||||
|                 continue; | ||||
| @@ -829,7 +840,7 @@ JANET_CORE_FN(cfun_stream_accept_loop, | ||||
| JANET_CORE_FN(cfun_stream_accept, | ||||
|               "(net/accept stream &opt timeout)", | ||||
|               "Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns a new duplex stream which represents a connection to the client.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -844,7 +855,7 @@ JANET_CORE_FN(cfun_stream_read, | ||||
|               "Read up to n bytes from a stream, suspending the current fiber until the bytes are available. " | ||||
|               "`n` can also be the keyword `:all` to read into the buffer until end of stream. " | ||||
|               "If less than n bytes are available (and more than 0), will push those bytes and return early. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") { | ||||
|     janet_arity(argc, 2, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -864,7 +875,7 @@ JANET_CORE_FN(cfun_stream_read, | ||||
| JANET_CORE_FN(cfun_stream_chunk, | ||||
|               "(net/chunk stream nbytes &opt buf timeout)", | ||||
|               "Same a net/read, but will wait for all n bytes to arrive rather than return early. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil.") { | ||||
|               "Takes an optional timeout in seconds, after which will raise an error.") { | ||||
|     janet_arity(argc, 2, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET); | ||||
| @@ -878,7 +889,7 @@ JANET_CORE_FN(cfun_stream_chunk, | ||||
| JANET_CORE_FN(cfun_stream_recv_from, | ||||
|               "(net/recv-from stream nbytes buf &opt timeout)", | ||||
|               "Receives data from a server stream and puts it into a buffer. Returns the socket-address the " | ||||
|               "packet came from. Takes an optional timeout in seconds, after which will return nil.") { | ||||
|               "packet came from. Takes an optional timeout in seconds, after which will raise an error.") { | ||||
|     janet_arity(argc, 3, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET); | ||||
| @@ -892,7 +903,7 @@ JANET_CORE_FN(cfun_stream_recv_from, | ||||
| JANET_CORE_FN(cfun_stream_write, | ||||
|               "(net/write stream data &opt timeout)", | ||||
|               "Write data to a stream, suspending the current fiber until the write " | ||||
|               "completes. Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "completes. Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns nil, or raises an error if the write failed.") { | ||||
|     janet_arity(argc, 2, 3); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -911,7 +922,7 @@ JANET_CORE_FN(cfun_stream_write, | ||||
| JANET_CORE_FN(cfun_stream_send_to, | ||||
|               "(net/send-to stream dest data &opt timeout)", | ||||
|               "Writes a datagram to a server stream. dest is a the destination address of the packet. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns stream.") { | ||||
|     janet_arity(argc, 3, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|   | ||||
							
								
								
									
										129
									
								
								src/core/os.c
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								src/core/os.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose and contributors. | ||||
| * Copyright (c) 2025 Calvin Rose and contributors. | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -27,9 +27,10 @@ | ||||
| #include "gc.h" | ||||
| #endif | ||||
|  | ||||
| #include <stdlib.h> | ||||
|  | ||||
| #ifndef JANET_REDUCED_OS | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <time.h> | ||||
| #include <fcntl.h> | ||||
| #include <errno.h> | ||||
| @@ -251,7 +252,7 @@ JANET_CORE_FN(os_exit, | ||||
|     } | ||||
|     janet_deinit(); | ||||
|     if (argc >= 2 && janet_truthy(argv[1])) { | ||||
|         _exit(status); | ||||
|         _Exit(status); | ||||
|     } else { | ||||
|         exit(status); | ||||
|     } | ||||
| @@ -540,11 +541,12 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) { | ||||
|         proc->flags &= ~JANET_PROC_WAITING; | ||||
|         janet_gcunroot(janet_wrap_abstract(proc)); | ||||
|         janet_gcunroot(janet_wrap_fiber(args.fiber)); | ||||
|         if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) { | ||||
|             JanetString s = janet_formatc("command failed with non-zero exit code %d", status); | ||||
|             janet_cancel(args.fiber, janet_wrap_string(s)); | ||||
|         } else { | ||||
|             if (janet_fiber_can_resume(args.fiber)) { | ||||
|         uint32_t sched_id = (uint32_t) args.argi; | ||||
|         if (janet_fiber_can_resume(args.fiber) && args.fiber->sched_id == sched_id) { | ||||
|             if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) { | ||||
|                 JanetString s = janet_formatc("command failed with non-zero exit code %d", status); | ||||
|                 janet_cancel(args.fiber, janet_wrap_string(s)); | ||||
|             } else { | ||||
|                 janet_schedule(args.fiber, janet_wrap_integer(status)); | ||||
|             } | ||||
|         } | ||||
| @@ -602,6 +604,7 @@ os_proc_wait_impl(JanetProc *proc) { | ||||
|     memset(&targs, 0, sizeof(targs)); | ||||
|     targs.argp = proc; | ||||
|     targs.fiber = janet_root_fiber(); | ||||
|     targs.argi = (uint32_t) targs.fiber->sched_id; | ||||
|     janet_gcroot(janet_wrap_abstract(proc)); | ||||
|     janet_gcroot(janet_wrap_fiber(targs.fiber)); | ||||
|     janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb); | ||||
| @@ -628,16 +631,15 @@ os_proc_wait_impl(JanetProc *proc) { | ||||
|  | ||||
| JANET_CORE_FN(os_proc_wait, | ||||
|               "(os/proc-wait proc)", | ||||
|               "Suspend the current fiber until the subprocess completes. Returns the subprocess return code. " | ||||
|               "os/proc-wait cannot be called twice on the same process. If `ev/with-deadline` cancels `os/proc-wait` " | ||||
|               "with an error or os/proc-wait is cancelled with any error caused by anything else, os/proc-wait still " | ||||
|               "finishes in the background. Only after os/proc-wait finishes, a process is cleaned up by the operating " | ||||
|               "system. Thus, a process becomes a zombie process if os/proc-wait is not called.") { | ||||
|               "Suspend the current fiber until the subprocess `proc` completes. Once `proc` " | ||||
|               "completes, return the exit code of `proc`. If called more than once on the same " | ||||
|               "core/process value, will raise an error. When creating subprocesses using " | ||||
|               "`os/spawn`, this function should be called on the returned value to avoid zombie " | ||||
|               "processes.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
| #ifdef JANET_EV | ||||
|     os_proc_wait_impl(proc); | ||||
|     return janet_wrap_nil(); | ||||
| #else | ||||
|     return os_proc_wait_impl(proc); | ||||
| #endif | ||||
| @@ -742,12 +744,13 @@ static int get_signal_kw(const Janet *argv, int32_t n) { | ||||
|  | ||||
| JANET_CORE_FN(os_proc_kill, | ||||
|               "(os/proc-kill proc &opt wait signal)", | ||||
|               "Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process " | ||||
|               "handle on windows. If os/proc-wait already finished for proc, os/proc-kill raises an error. After " | ||||
|               "sending signal to proc, if `wait` is truthy, will wait for the process to finish and return the exit " | ||||
|               "code by calling os/proc-wait. Otherwise, returns `proc`. If signal is specified, send it instead. " | ||||
|               "Signal keywords are named after their C counterparts but in lowercase with the leading `SIG` stripped. " | ||||
|               "Signals are ignored on windows.") { | ||||
|               "Kill the subprocess `proc` by sending SIGKILL to it on POSIX systems, or by closing " | ||||
|               "the process handle on Windows. If `proc` has already completed, raise an error. If " | ||||
|               "`wait` is truthy, will wait for `proc` to complete and return the exit code (this " | ||||
|               "will raise an error if `proc` is being waited for). Otherwise, return `proc`. If " | ||||
|               "`signal` is provided, send it instead of SIGKILL. Signal keywords are named after " | ||||
|               "their C counterparts but in lowercase with the leading SIG stripped. `signal` is " | ||||
|               "ignored on Windows.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
|     if (proc->flags & JANET_PROC_WAITED) { | ||||
| @@ -775,7 +778,6 @@ JANET_CORE_FN(os_proc_kill, | ||||
|     if (argc > 1 && janet_truthy(argv[1])) { | ||||
| #ifdef JANET_EV | ||||
|         os_proc_wait_impl(proc); | ||||
|         return janet_wrap_nil(); | ||||
| #else | ||||
|         return os_proc_wait_impl(proc); | ||||
| #endif | ||||
| @@ -786,9 +788,9 @@ JANET_CORE_FN(os_proc_kill, | ||||
|  | ||||
| JANET_CORE_FN(os_proc_close, | ||||
|               "(os/proc-close proc)", | ||||
|               "Close pipes created by `os/spawn` if they have not been closed. Then, if os/proc-wait was not already " | ||||
|               "called on proc, os/proc-wait is called on it, and it returns the exit code returned by os/proc-wait. " | ||||
|               "Otherwise, returns nil.") { | ||||
|               "Close pipes created for subprocess `proc` by `os/spawn` if they have not been " | ||||
|               "closed. Then, if `proc` is not being waited for, wait. If this function waits, when " | ||||
|               "`proc` completes, return the exit code of `proc`. Otherwise, return nil.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
| #ifdef JANET_EV | ||||
| @@ -806,7 +808,6 @@ JANET_CORE_FN(os_proc_close, | ||||
|     } | ||||
| #ifdef JANET_EV | ||||
|     os_proc_wait_impl(proc); | ||||
|     return janet_wrap_nil(); | ||||
| #else | ||||
|     return os_proc_wait_impl(proc); | ||||
| #endif | ||||
| @@ -1267,9 +1268,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { | ||||
|  | ||||
|     /* exec mode */ | ||||
|     if (mode == JANET_EXECUTE_EXEC) { | ||||
| #ifdef JANET_WINDOWS | ||||
|         janet_panic("not supported on windows"); | ||||
| #else | ||||
|         int status; | ||||
|         if (!use_environ) { | ||||
|             environ = envp; | ||||
| @@ -1282,7 +1280,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { | ||||
|             } | ||||
|         } while (status == -1 && errno == EINTR); | ||||
|         janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT)); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     /* Use posix_spawn to spawn new process */ | ||||
| @@ -1383,45 +1380,56 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { | ||||
|  | ||||
| JANET_CORE_FN(os_execute, | ||||
|               "(os/execute args &opt flags env)", | ||||
|               "Execute a program on the system and pass it string arguments. `flags` " | ||||
|               "is a keyword that modifies how the program will execute.\n" | ||||
|               "* :e - enables passing an environment to the program. Without :e, the " | ||||
|               "Execute a program on the system and return the exit code. `args` is an array/tuple " | ||||
|               "of strings. The first string is the name of the program and the remainder are " | ||||
|               "arguments passed to the program. `flags` is a keyword made from the following " | ||||
|               "characters that modifies how the program executes:\n" | ||||
|               "* :e - enables passing an environment to the program. Without 'e', the " | ||||
|               "current environment is inherited.\n" | ||||
|               "* :p - allows searching the current PATH for the binary to execute. " | ||||
|               "Without this flag, binaries must use absolute paths.\n" | ||||
|               "* :x - raise error if exit code is non-zero.\n" | ||||
|               "* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n" | ||||
|               "`env` is a table or struct mapping environment variables to values. It can also " | ||||
|               "contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. " | ||||
|               ":in, :out, and :err should be core/file values or core/stream values. core/file values and core/stream " | ||||
|               "values passed to :in, :out, and :err should be closed manually because os/execute doesn't close them. " | ||||
|               "Returns the exit code of the program.") { | ||||
|               "* :p - allows searching the current PATH for the program to execute. " | ||||
|               "Without this flag, the first element of `args` must be an absolute path.\n" | ||||
|               "* :x - raises error if exit code is non-zero.\n" | ||||
|               "* :d - prevents the garbage collector terminating the program (if still running) " | ||||
|               "and calling the equivalent of `os/proc-wait` (allows zombie processes).\n" | ||||
|               "`env` is a table/struct mapping environment variables to values. It can also " | ||||
|               "contain the keys :in, :out, and :err, which allow redirecting stdio in the " | ||||
|               "subprocess. :in, :out, and :err should be core/file or core/stream values. " | ||||
|               "If core/stream values are used, the caller is responsible for ensuring pipes do not " | ||||
|               "cause the program to block and deadlock.") { | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_EXECUTE); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_spawn, | ||||
|               "(os/spawn args &opt flags env)", | ||||
|               "Execute a program on the system and return a handle to the process. Otherwise, takes the " | ||||
|               "same arguments as `os/execute`. Does not wait for the process. For each of the :in, :out, and :err keys " | ||||
|               "of the `env` argument, one can also pass in the keyword `:pipe` to get streams for standard IO of the " | ||||
|               "subprocess that can be read from and written to. The returned value `proc` has the fields :in, :out, " | ||||
|               ":err, and the additional field :pid on unix-like platforms. `(os/proc-wait proc)` must be called to " | ||||
|               "rejoin the subprocess. After `(os/proc-wait proc)` finishes, proc gains a new field, :return-code. " | ||||
|               "If :x flag is passed to os/spawn, non-zero exit code will cause os/proc-wait to raise an error. " | ||||
|               "If pipe streams created with :pipe keyword are not closed in time, janet can run out of file " | ||||
|               "descriptors. They can be closed individually, or `os/proc-close` can close all pipe streams on proc. " | ||||
|               "If pipe streams aren't read before `os/proc-wait` finishes, then pipe buffers become full, and the " | ||||
|               "process cannot finish because the process cannot print more on pipe buffers which are already full. " | ||||
|               "If the process cannot finish, os/proc-wait cannot finish, either.") { | ||||
|               "Execute a program on the system and return a core/process value representing the " | ||||
|               "spawned subprocess. Takes the same arguments as `os/execute` but does not wait for " | ||||
|               "the subprocess to complete. Unlike `os/execute`, the value `:pipe` can be used for " | ||||
|               ":in, :out and :err keys in `env`. If used, the returned core/process will have a " | ||||
|               "writable stream in the :in field and readable streams in the :out and :err fields. " | ||||
|               "On non-Windows systems, the subprocess PID will be in the :pid field. The caller is " | ||||
|               "responsible for waiting on the process (e.g. by calling `os/proc-wait` on the " | ||||
|               "returned core/process value) to avoid creating zombie process. After the subprocess " | ||||
|               "completes, the exit value is in the :return-code field. If `flags` includes 'x', a " | ||||
|               "non-zero exit code will cause a waiting fiber to raise an error. The use of " | ||||
|               "`:pipe` may fail if there are too many active file descriptors. The caller is " | ||||
|               "responsible for closing pipes created by `:pipe` (either individually or using " | ||||
|               "`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring " | ||||
|               "pipes do not cause the program to block and deadlock.") { | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_posix_exec, | ||||
|               "(os/posix-exec args &opt flags env)", | ||||
|               "Use the execvpe or execve system calls to replace the current process with an interface similar to os/execute. " | ||||
|               "However, instead of creating a subprocess, the current process is replaced. Is not supported on windows, and " | ||||
|               "However, instead of creating a subprocess, the current process is replaced. Is not supported on Windows, and " | ||||
|               "does not allow redirection of stdio.") { | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_posix_fork, | ||||
| @@ -1432,7 +1440,7 @@ JANET_CORE_FN(os_posix_fork, | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
| #ifdef JANET_WINDOWS | ||||
|     janet_panic("not supported"); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     pid_t result; | ||||
|     do { | ||||
| @@ -1879,7 +1887,6 @@ JANET_CORE_FN(os_mktime, | ||||
|         /* utc time */ | ||||
| #ifdef JANET_NO_UTC_MKTIME | ||||
|         janet_panic("os/mktime UTC not supported on this platform"); | ||||
|         return janet_wrap_nil(); | ||||
| #else | ||||
|         t = timegm(&t_info); | ||||
| #endif | ||||
| @@ -1946,8 +1953,7 @@ JANET_CORE_FN(os_link, | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("os/link not supported on Windows"); | ||||
|     return janet_wrap_nil(); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     const char *oldpath = janet_getcstring(argv, 0); | ||||
|     const char *newpath = janet_getcstring(argv, 1); | ||||
| @@ -1965,8 +1971,7 @@ JANET_CORE_FN(os_symlink, | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("os/symlink not supported on Windows"); | ||||
|     return janet_wrap_nil(); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     const char *oldpath = janet_getcstring(argv, 0); | ||||
|     const char *newpath = janet_getcstring(argv, 1); | ||||
| @@ -2068,8 +2073,7 @@ JANET_CORE_FN(os_readlink, | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("os/readlink not supported on Windows"); | ||||
|     return janet_wrap_nil(); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     static char buffer[PATH_MAX]; | ||||
|     const char *path = janet_getcstring(argv, 0); | ||||
| @@ -2325,7 +2329,6 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) { | ||||
|             return sg->fn(&st); | ||||
|         } | ||||
|         janet_panicf("unexpected keyword %v", janet_wrap_keyword(key)); | ||||
|         return janet_wrap_nil(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -231,7 +231,7 @@ static void delim_error(JanetParser *parser, size_t stack_index, char c, const c | ||||
|                 janet_buffer_push_u8(buffer, '`'); | ||||
|             } | ||||
|         } | ||||
|         janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column); | ||||
|         janet_formatb(buffer, " opened at line %d, column %d", (int32_t) s->line, (int32_t) s->column); | ||||
|     } | ||||
|     parser->error = (const char *) janet_string(buffer->data, buffer->count); | ||||
|     parser->flag |= JANET_PARSER_GENERATED_ERROR; | ||||
| @@ -363,8 +363,7 @@ static int stringend(JanetParser *p, JanetParseState *state) { | ||||
|         JanetParseState top = p->states[p->statecount - 1]; | ||||
|         int32_t indent_col = (int32_t) top.column - 1; | ||||
|         uint8_t *r = bufstart, *end = r + buflen; | ||||
|         /* Check if there are any characters before the start column - | ||||
|          * if so, do not reindent. */ | ||||
|         /* Unless there are only spaces before EOLs, disable reindenting */ | ||||
|         int reindent = 1; | ||||
|         while (reindent && (r < end)) { | ||||
|             if (*r++ == '\n') { | ||||
| @@ -374,34 +373,36 @@ static int stringend(JanetParser *p, JanetParseState *state) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') reindent = 1; | ||||
|             } | ||||
|         } | ||||
|         /* Now reindent if able to, otherwise just drop leading newline. */ | ||||
|         if (!reindent) { | ||||
|             if (buflen > 0 && bufstart[0] == '\n') { | ||||
|                 buflen--; | ||||
|                 bufstart++; | ||||
|             } | ||||
|         } else { | ||||
|         /* Now reindent if able */ | ||||
|         if (reindent) { | ||||
|             uint8_t *w = bufstart; | ||||
|             r = bufstart; | ||||
|             while (r < end) { | ||||
|                 if (*r == '\n') { | ||||
|                     if (r == bufstart) { | ||||
|                         /* Skip leading newline */ | ||||
|                         r++; | ||||
|                     } else { | ||||
|                         *w++ = *r++; | ||||
|                     } | ||||
|                     *w++ = *r++; | ||||
|                     for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++); | ||||
|                     if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') *w++ = *r++; | ||||
|                 } else { | ||||
|                     *w++ = *r++; | ||||
|                 } | ||||
|             } | ||||
|             buflen = (int32_t)(w - bufstart); | ||||
|         } | ||||
|         /* Check for trailing newline character so we can remove it */ | ||||
|         if (buflen > 0 && bufstart[buflen - 1] == '\n') { | ||||
|         /* Check for leading EOL so we can remove it */ | ||||
|         if (buflen > 1 && bufstart[0] == '\r' && bufstart[1] == '\n') { /* Windows EOL */ | ||||
|             buflen = buflen - 2; | ||||
|             bufstart = bufstart + 2; | ||||
|         } else if (buflen > 0 && bufstart[0] == '\n') { /* Unix EOL */ | ||||
|             buflen--; | ||||
|             bufstart++; | ||||
|         } | ||||
|         /* Check for trailing EOL so we can remove it */ | ||||
|         if (buflen > 1 && bufstart[buflen - 2] == '\r' && bufstart[buflen - 1] == '\n') { /* Windows EOL */ | ||||
|             buflen = buflen - 2; | ||||
|         } else if (buflen > 0 && bufstart[buflen - 1] == '\n') { /* Unix EOL */ | ||||
|             buflen--; | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										145
									
								
								src/core/peg.c
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								src/core/peg.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -342,7 +342,7 @@ tail: | ||||
|             while (captured < hi) { | ||||
|                 CapState cs2 = cap_save(s); | ||||
|                 next_text = peg_rule(s, rule_a, text); | ||||
|                 if (!next_text || next_text == text) { | ||||
|                 if (!next_text || ((next_text == text) && (hi == UINT32_MAX))) { | ||||
|                     cap_load(s, cs2); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -465,6 +465,16 @@ tail: | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_ONLY_TAGS: { | ||||
|             CapState cs = cap_save(s); | ||||
|             down1(s); | ||||
|             const uint8_t *result = peg_rule(s, s->bytecode + rule[1], text); | ||||
|             up1(s); | ||||
|             if (!result) return NULL; | ||||
|             cap_load_keept(s, cs); | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_GROUP: { | ||||
|             uint32_t tag = rule[2]; | ||||
|             int oldmode = s->mode; | ||||
| @@ -486,6 +496,30 @@ tail: | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_NTH: { | ||||
|             uint32_t nth = rule[1]; | ||||
|             if (nth > INT32_MAX) nth = INT32_MAX; | ||||
|             uint32_t tag = rule[3]; | ||||
|             int oldmode = s->mode; | ||||
|             CapState cs = cap_save(s); | ||||
|             s->mode = PEG_MODE_NORMAL; | ||||
|             down1(s); | ||||
|             const uint8_t *result = peg_rule(s, s->bytecode + rule[2], text); | ||||
|             up1(s); | ||||
|             s->mode = oldmode; | ||||
|             if (!result) return NULL; | ||||
|             int32_t num_sub_captures = s->captures->count - cs.cap; | ||||
|             Janet cap; | ||||
|             if (num_sub_captures > (int32_t) nth) { | ||||
|                 cap = s->captures->data[cs.cap + nth]; | ||||
|             } else { | ||||
|                 return NULL; | ||||
|             } | ||||
|             cap_load_keept(s, cs); | ||||
|             pushcap(s, cap, tag); | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_SUB: { | ||||
|             const uint8_t *text_start = text; | ||||
|             const uint32_t *rule_window = s->bytecode + rule[1]; | ||||
| @@ -510,41 +544,80 @@ tail: | ||||
|             return window_end; | ||||
|         } | ||||
|  | ||||
|         case RULE_TIL: { | ||||
|             const uint32_t *rule_terminus = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_subpattern = s->bytecode + rule[2]; | ||||
|  | ||||
|             const uint8_t *terminus_start = text; | ||||
|             const uint8_t *terminus_end = NULL; | ||||
|             down1(s); | ||||
|             while (terminus_start <= s->text_end) { | ||||
|                 CapState cs2 = cap_save(s); | ||||
|                 terminus_end = peg_rule(s, rule_terminus, terminus_start); | ||||
|                 cap_load(s, cs2); | ||||
|                 if (terminus_end) { | ||||
|                     break; | ||||
|                 } | ||||
|                 terminus_start++; | ||||
|             } | ||||
|             up1(s); | ||||
|  | ||||
|             if (!terminus_end) { | ||||
|                 return NULL; | ||||
|             } | ||||
|  | ||||
|             const uint8_t *saved_end = s->text_end; | ||||
|             s->text_end = terminus_start; | ||||
|             down1(s); | ||||
|             const uint8_t *matched = peg_rule(s, rule_subpattern, text); | ||||
|             up1(s); | ||||
|             s->text_end = saved_end; | ||||
|  | ||||
|             if (!matched) { | ||||
|                 return NULL; | ||||
|             } | ||||
|  | ||||
|             return terminus_end; | ||||
|         } | ||||
|  | ||||
|         case RULE_SPLIT: { | ||||
|             const uint8_t *saved_end = s->text_end; | ||||
|             const uint32_t *rule_separator = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_subpattern = s->bytecode + rule[2]; | ||||
|  | ||||
|             const uint8_t *separator_end = NULL; | ||||
|             do { | ||||
|                 const uint8_t *text_start = text; | ||||
|             const uint8_t *chunk_start = text; | ||||
|             const uint8_t *chunk_end = NULL; | ||||
|  | ||||
|             while (text <= saved_end) { | ||||
|                 /* Find next split (or end of text) */ | ||||
|                 CapState cs = cap_save(s); | ||||
|                 down1(s); | ||||
|                 while (text <= s->text_end) { | ||||
|                     separator_end = peg_rule(s, rule_separator, text); | ||||
|                 while (text <= saved_end) { | ||||
|                     chunk_end = text; | ||||
|                     const uint8_t *check = peg_rule(s, rule_separator, text); | ||||
|                     cap_load(s, cs); | ||||
|                     if (separator_end) { | ||||
|                     if (check) { | ||||
|                         text = check; | ||||
|                         break; | ||||
|                     } | ||||
|                     text++; | ||||
|                 } | ||||
|                 up1(s); | ||||
|  | ||||
|                 if (separator_end) { | ||||
|                     s->text_end = text; | ||||
|                     text = separator_end; | ||||
|                 } | ||||
|  | ||||
|                 /* Match between splits */ | ||||
|                 s->text_end = chunk_end; | ||||
|                 down1(s); | ||||
|                 const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, text_start); | ||||
|                 const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, chunk_start); | ||||
|                 up1(s); | ||||
|                 s->text_end = saved_end; | ||||
|                 if (!subpattern_end) return NULL; /* Don't match anything */ | ||||
|  | ||||
|                 if (!subpattern_end) { | ||||
|                     return NULL; | ||||
|                 } | ||||
|             } while (separator_end); | ||||
|                 /* Ensure forward progress */ | ||||
|                 if (text == chunk_start) return NULL; | ||||
|                 chunk_start = text; | ||||
|             } | ||||
|  | ||||
|             s->text_end = saved_end; | ||||
|             return s->text_end; | ||||
|         } | ||||
|  | ||||
| @@ -1061,6 +1134,9 @@ static void spec_thru(Builder *b, int32_t argc, const Janet *argv) { | ||||
| static void spec_drop(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     spec_onerule(b, argc, argv, RULE_DROP); | ||||
| } | ||||
| static void spec_only_tags(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     spec_onerule(b, argc, argv, RULE_ONLY_TAGS); | ||||
| } | ||||
|  | ||||
| /* Rule of the form [rule, tag] */ | ||||
| static void spec_cap1(Builder *b, int32_t argc, const Janet *argv, uint32_t op) { | ||||
| @@ -1084,6 +1160,15 @@ static void spec_unref(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     spec_cap1(b, argc, argv, RULE_UNREF); | ||||
| } | ||||
|  | ||||
| static void spec_nth(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_arity(b, argc, 2, 3); | ||||
|     Reserve r = reserve(b, 4); | ||||
|     uint32_t nth = peg_getnat(b, argv[0]); | ||||
|     uint32_t rule = peg_compile1(b, argv[1]); | ||||
|     uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0; | ||||
|     emit_3(r, RULE_NTH, nth, rule, tag); | ||||
| } | ||||
|  | ||||
| static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_arity(b, argc, 1, 3); | ||||
|     Reserve r = reserve(b, 4); | ||||
| @@ -1181,6 +1266,14 @@ static void spec_sub(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     emit_2(r, RULE_SUB, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| static void spec_til(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_fixarity(b, argc, 2); | ||||
|     Reserve r = reserve(b, 3); | ||||
|     uint32_t subrule1 = peg_compile1(b, argv[0]); | ||||
|     uint32_t subrule2 = peg_compile1(b, argv[1]); | ||||
|     emit_2(r, RULE_TIL, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| static void spec_split(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_fixarity(b, argc, 2); | ||||
|     Reserve r = reserve(b, 3); | ||||
| @@ -1262,7 +1355,9 @@ static const SpecialPair peg_specials[] = { | ||||
|     {"line", spec_line}, | ||||
|     {"look", spec_look}, | ||||
|     {"not", spec_not}, | ||||
|     {"nth", spec_nth}, | ||||
|     {"number", spec_capture_number}, | ||||
|     {"only-tags", spec_only_tags}, | ||||
|     {"opt", spec_opt}, | ||||
|     {"position", spec_position}, | ||||
|     {"quote", spec_capture}, | ||||
| @@ -1275,6 +1370,7 @@ static const SpecialPair peg_specials[] = { | ||||
|     {"split", spec_split}, | ||||
|     {"sub", spec_sub}, | ||||
|     {"thru", spec_thru}, | ||||
|     {"til", spec_til}, | ||||
|     {"to", spec_to}, | ||||
|     {"uint", spec_uint_le}, | ||||
|     {"uint-be", spec_uint_be}, | ||||
| @@ -1368,6 +1464,11 @@ static uint32_t peg_compile1(Builder *b, Janet peg) { | ||||
|             emit_bytes(b, RULE_LITERAL, len, str); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_BUFFER: { | ||||
|             const JanetBuffer *buf = janet_unwrap_buffer(peg); | ||||
|             emit_bytes(b, RULE_LITERAL, buf->count, buf->data); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_TABLE: { | ||||
|             /* Build grammar table */ | ||||
|             JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg)); | ||||
| @@ -1609,6 +1710,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 i += 4; | ||||
|                 break; | ||||
|             case RULE_SUB: | ||||
|             case RULE_TIL: | ||||
|             case RULE_SPLIT: | ||||
|                 /* [rule, rule] */ | ||||
|                 if (rule[1] >= blen) goto bad; | ||||
| @@ -1619,6 +1721,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 break; | ||||
|             case RULE_ERROR: | ||||
|             case RULE_DROP: | ||||
|             case RULE_ONLY_TAGS: | ||||
|             case RULE_NOT: | ||||
|             case RULE_TO: | ||||
|             case RULE_THRU: | ||||
| @@ -1632,6 +1735,12 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 if (rule[1] > JANET_MAX_READINT_WIDTH) goto bad; | ||||
|                 i += 3; | ||||
|                 break; | ||||
|             case RULE_NTH: | ||||
|                 /* [nth, rule, tag] */ | ||||
|                 if (rule[2] >= blen) goto bad; | ||||
|                 op_flags[rule[2]] |= 0x01; | ||||
|                 i += 4; | ||||
|                 break; | ||||
|             default: | ||||
|                 goto bad; | ||||
|         } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -28,7 +28,7 @@ | ||||
|  | ||||
| /* Run a string */ | ||||
| int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) { | ||||
|     JanetParser parser; | ||||
|     JanetParser *parser; | ||||
|     int errflags = 0, done = 0; | ||||
|     int32_t index = 0; | ||||
|     Janet ret = janet_wrap_nil(); | ||||
| @@ -37,14 +37,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|  | ||||
|     if (where) janet_gcroot(janet_wrap_string(where)); | ||||
|     if (NULL == sourcePath) sourcePath = "<unknown>"; | ||||
|     janet_parser_init(&parser); | ||||
|     parser = janet_abstract(&janet_parser_type, sizeof(JanetParser)); | ||||
|     janet_parser_init(parser); | ||||
|     janet_gcroot(janet_wrap_abstract(parser)); | ||||
|  | ||||
|     /* While we haven't seen an error */ | ||||
|     while (!done) { | ||||
|  | ||||
|         /* Evaluate parsed values */ | ||||
|         while (janet_parser_has_more(&parser)) { | ||||
|             Janet form = janet_parser_produce(&parser); | ||||
|         while (janet_parser_has_more(parser)) { | ||||
|             Janet form = janet_parser_produce(parser); | ||||
|             JanetCompileResult cres = janet_compile(form, env, where); | ||||
|             if (cres.status == JANET_COMPILE_OK) { | ||||
|                 JanetFunction *f = janet_thunk(cres.funcdef); | ||||
| @@ -58,8 +60,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|                 } | ||||
|             } else { | ||||
|                 ret = janet_wrap_string(cres.error); | ||||
|                 int32_t line = (int32_t) parser.line; | ||||
|                 int32_t col = (int32_t) parser.column; | ||||
|                 int32_t line = (int32_t) parser->line; | ||||
|                 int32_t col = (int32_t) parser->column; | ||||
|                 if ((cres.error_mapping.line > 0) && | ||||
|                         (cres.error_mapping.column > 0)) { | ||||
|                     line = cres.error_mapping.line; | ||||
| @@ -81,16 +83,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|         if (done) break; | ||||
|  | ||||
|         /* Dispatch based on parse state */ | ||||
|         switch (janet_parser_status(&parser)) { | ||||
|         switch (janet_parser_status(parser)) { | ||||
|             case JANET_PARSE_DEAD: | ||||
|                 done = 1; | ||||
|                 break; | ||||
|             case JANET_PARSE_ERROR: { | ||||
|                 const char *e = janet_parser_error(&parser); | ||||
|                 const char *e = janet_parser_error(parser); | ||||
|                 errflags |= 0x04; | ||||
|                 ret = janet_cstringv(e); | ||||
|                 int32_t line = (int32_t) parser.line; | ||||
|                 int32_t col = (int32_t) parser.column; | ||||
|                 int32_t line = (int32_t) parser->line; | ||||
|                 int32_t col = (int32_t) parser->column; | ||||
|                 janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e); | ||||
|                 done = 1; | ||||
|                 break; | ||||
| @@ -98,9 +100,9 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|             case JANET_PARSE_ROOT: | ||||
|             case JANET_PARSE_PENDING: | ||||
|                 if (index >= len) { | ||||
|                     janet_parser_eof(&parser); | ||||
|                     janet_parser_eof(parser); | ||||
|                 } else { | ||||
|                     janet_parser_consume(&parser, bytes[index++]); | ||||
|                     janet_parser_consume(parser, bytes[index++]); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
| @@ -108,7 +110,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|     } | ||||
|  | ||||
|     /* Clean up and return errors */ | ||||
|     janet_parser_deinit(&parser); | ||||
|     janet_gcunroot(janet_wrap_abstract(parser)); | ||||
|     if (where) janet_gcunroot(janet_wrap_string(where)); | ||||
| #ifdef JANET_EV | ||||
|     /* Enter the event loop if we are not already in it */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -100,6 +100,7 @@ struct JanetVM { | ||||
|      * return point for panics. */ | ||||
|     jmp_buf *signal_buf; | ||||
|     Janet *return_reg; | ||||
|     int coerce_error; | ||||
|  | ||||
|     /* The global registry for c functions. Used to store meta-data | ||||
|      * along with otherwise bare c function pointers. */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -71,10 +71,10 @@ int janet_string_compare(const uint8_t *lhs, const uint8_t *rhs) { | ||||
| int janet_string_equalconst(const uint8_t *lhs, const uint8_t *rhs, int32_t rlen, int32_t rhash) { | ||||
|     int32_t lhash = janet_string_hash(lhs); | ||||
|     int32_t llen = janet_string_length(lhs); | ||||
|     if (lhs == rhs) | ||||
|         return 1; | ||||
|     if (lhash != rhash || llen != rlen) | ||||
|         return 0; | ||||
|     if (lhs == rhs) | ||||
|         return 1; | ||||
|     return !memcmp(lhs, rhs, rlen); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -301,6 +301,7 @@ int janet_scan_number_base( | ||||
|     if (base == 0) { | ||||
|         base = 10; | ||||
|     } | ||||
|     int exp_base = base; | ||||
|  | ||||
|     /* Skip leading zeros */ | ||||
|     while (str < end && (*str == '0' || *str == '.')) { | ||||
| @@ -322,6 +323,12 @@ int janet_scan_number_base( | ||||
|         } else if (*str == '&') { | ||||
|             foundexp = 1; | ||||
|             break; | ||||
|         } else if (base == 16 && (*str == 'P' || *str == 'p')) { /* IEEE hex float */ | ||||
|             foundexp = 1; | ||||
|             exp_base = 10; | ||||
|             base = 2; | ||||
|             ex *= 4; /* We need to correct the current exponent after we change the base */ | ||||
|             break; | ||||
|         } else if (base == 10 && (*str == 'E' || *str == 'e')) { | ||||
|             foundexp = 1; | ||||
|             break; | ||||
| @@ -360,9 +367,9 @@ int janet_scan_number_base( | ||||
|         } | ||||
|         while (str < end) { | ||||
|             int digit = digit_lookup[*str & 0x7F]; | ||||
|             if (*str > 127 || digit >= base) goto error; | ||||
|             if (*str > 127 || digit >= exp_base) goto error; | ||||
|             if (ee < (INT32_MAX / 40)) { | ||||
|                 ee = base * ee + digit; | ||||
|                 ee = exp_base * ee + digit; | ||||
|             } | ||||
|             str++; | ||||
|             seenadigit = 1; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -294,6 +294,16 @@ JANET_CORE_FN(cfun_struct_to_table, | ||||
|     return janet_wrap_table(tab); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_struct_rawget, | ||||
|               "(struct/rawget st key)", | ||||
|               "Gets a value from a struct `st` without looking at the prototype struct. " | ||||
|               "If `st` does not contain the key directly, the function will return " | ||||
|               "nil without checking the prototype. Returns the value in the struct.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetStruct st = janet_getstruct(argv, 0); | ||||
|     return janet_struct_rawget(st, argv[1]); | ||||
| } | ||||
|  | ||||
| /* Load the struct module */ | ||||
| void janet_lib_struct(JanetTable *env) { | ||||
|     JanetRegExt struct_cfuns[] = { | ||||
| @@ -301,6 +311,7 @@ void janet_lib_struct(JanetTable *env) { | ||||
|         JANET_CORE_REG("struct/getproto", cfun_struct_getproto), | ||||
|         JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten), | ||||
|         JANET_CORE_REG("struct/to-table", cfun_struct_to_table), | ||||
|         JANET_CORE_REG("struct/rawget", cfun_struct_rawget), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, struct_cfuns); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -372,12 +372,14 @@ JANET_CORE_FN(cfun_table_setproto, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_tostruct, | ||||
|               "(table/to-struct tab)", | ||||
|               "Convert a table to a struct. Returns a new struct. This function " | ||||
|               "does not take into account prototype tables.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|               "(table/to-struct tab &opt proto)", | ||||
|               "Convert a table to a struct. Returns a new struct.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetTable *t = janet_gettable(argv, 0); | ||||
|     return janet_wrap_struct(janet_table_to_struct(t)); | ||||
|     JanetStruct proto = janet_optstruct(argv, argc, 1, NULL); | ||||
|     JanetStruct st = janet_table_to_struct(t); | ||||
|     janet_struct_proto(st) = proto; | ||||
|     return janet_wrap_struct(st); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_rawget, | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -79,6 +79,7 @@ const char *const janet_type_names[16] = { | ||||
|     "pointer" | ||||
| }; | ||||
|  | ||||
| /* Docstring for signal lists these */ | ||||
| const char *const janet_signal_names[14] = { | ||||
|     "ok", | ||||
|     "error", | ||||
| @@ -96,6 +97,7 @@ const char *const janet_signal_names[14] = { | ||||
|     "await" | ||||
| }; | ||||
|  | ||||
| /* Docstring for fiber/status lists these */ | ||||
| const char *const janet_status_names[16] = { | ||||
|     "dead", | ||||
|     "error", | ||||
| @@ -115,14 +117,20 @@ const char *const janet_status_names[16] = { | ||||
|     "alive" | ||||
| }; | ||||
|  | ||||
| uint32_t janet_hash_mix(uint32_t input, uint32_t more) { | ||||
|     uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2)); | ||||
|     return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2)); | ||||
| } | ||||
|  | ||||
| #ifndef JANET_PRF | ||||
|  | ||||
| int32_t janet_string_calchash(const uint8_t *str, int32_t len) { | ||||
|     if (NULL == str) return 5381; | ||||
|     if (NULL == str || len == 0) return 5381; | ||||
|     const uint8_t *end = str + len; | ||||
|     uint32_t hash = 5381; | ||||
|     while (str < end) | ||||
|         hash = (hash << 5) + hash + *str++; | ||||
|     hash = janet_hash_mix(hash, (uint32_t) len); | ||||
|     return (int32_t) hash; | ||||
| } | ||||
|  | ||||
| @@ -238,11 +246,6 @@ int32_t janet_string_calchash(const uint8_t *str, int32_t len) { | ||||
|  | ||||
| #endif | ||||
|  | ||||
| uint32_t janet_hash_mix(uint32_t input, uint32_t more) { | ||||
|     uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2)); | ||||
|     return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2)); | ||||
| } | ||||
|  | ||||
| /* Computes hash of an array of values */ | ||||
| int32_t janet_array_calchash(const Janet *array, int32_t len) { | ||||
|     const Janet *end = array + len; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -205,9 +205,9 @@ int janet_make_pipe(JanetHandle handles[2], int mode); | ||||
| #ifdef JANET_FILEWATCH | ||||
| void janet_lib_filewatch(JanetTable *env); | ||||
| #endif | ||||
| #endif | ||||
| #ifdef JANET_FFI | ||||
| void janet_lib_ffi(JanetTable *env); | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -798,14 +798,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     vm_pcnext(); | ||||
|  | ||||
|     VM_OP(JOP_JUMP) | ||||
|     pc += DS; | ||||
|     vm_maybe_auto_suspend(DS <= 0); | ||||
|     pc += DS; | ||||
|     vm_next(); | ||||
|  | ||||
|     VM_OP(JOP_JUMP_IF) | ||||
|     if (janet_truthy(stack[A])) { | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|     } else { | ||||
|         pc++; | ||||
|     } | ||||
| @@ -815,15 +815,15 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     if (janet_truthy(stack[A])) { | ||||
|         pc++; | ||||
|     } else { | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|     } | ||||
|     vm_next(); | ||||
|  | ||||
|     VM_OP(JOP_JUMP_IF_NIL) | ||||
|     if (janet_checktype(stack[A], JANET_NIL)) { | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|     } else { | ||||
|         pc++; | ||||
|     } | ||||
| @@ -833,8 +833,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     if (janet_checktype(stack[A], JANET_NIL)) { | ||||
|         pc++; | ||||
|     } else { | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|     } | ||||
|     vm_next(); | ||||
|  | ||||
| @@ -1373,7 +1373,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) { | ||||
|  | ||||
|     /* Run vm */ | ||||
|     janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP; | ||||
|     int old_coerce_error = janet_vm.coerce_error; | ||||
|     janet_vm.coerce_error = 1; | ||||
|     JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil()); | ||||
|     janet_vm.coerce_error = old_coerce_error; | ||||
|  | ||||
|     /* Teardown */ | ||||
|     janet_vm.stackn = oldn; | ||||
| @@ -1384,6 +1387,15 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) { | ||||
|     } | ||||
|  | ||||
|     if (signal != JANET_SIGNAL_OK) { | ||||
|         /* Should match logic in janet_signalv */ | ||||
| #ifdef JANET_EV | ||||
|         if (janet_vm.root_fiber != NULL && signal == JANET_SIGNAL_EVENT) { | ||||
|             janet_vm.root_fiber->sched_id++; | ||||
|         } | ||||
| #endif | ||||
|         if (signal != JANET_SIGNAL_ERROR) { | ||||
|             *janet_vm.return_reg = janet_wrap_string(janet_formatc("%v coerced from %s to error", *janet_vm.return_reg, janet_signal_names[signal])); | ||||
|         } | ||||
|         janet_panicv(*janet_vm.return_reg); | ||||
|     } | ||||
|  | ||||
| @@ -1430,8 +1442,10 @@ void janet_try_init(JanetTryState *state) { | ||||
|     state->vm_fiber = janet_vm.fiber; | ||||
|     state->vm_jmp_buf = janet_vm.signal_buf; | ||||
|     state->vm_return_reg = janet_vm.return_reg; | ||||
|     state->coerce_error = janet_vm.coerce_error; | ||||
|     janet_vm.return_reg = &(state->payload); | ||||
|     janet_vm.signal_buf = &(state->buf); | ||||
|     janet_vm.coerce_error = 0; | ||||
| } | ||||
|  | ||||
| void janet_restore(JanetTryState *state) { | ||||
| @@ -1440,6 +1454,7 @@ void janet_restore(JanetTryState *state) { | ||||
|     janet_vm.fiber = state->vm_fiber; | ||||
|     janet_vm.signal_buf = state->vm_jmp_buf; | ||||
|     janet_vm.return_reg = state->vm_return_reg; | ||||
|     janet_vm.coerce_error = state->coerce_error; | ||||
| } | ||||
|  | ||||
| static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -1261,6 +1261,7 @@ typedef struct { | ||||
|     /* new state */ | ||||
|     jmp_buf buf; | ||||
|     Janet payload; | ||||
|     int coerce_error; | ||||
| } JanetTryState; | ||||
|  | ||||
| /***** END SECTION TYPES *****/ | ||||
| @@ -1442,6 +1443,7 @@ JANET_NO_RETURN JANET_API void janet_sleep_await(double sec); | ||||
| /* For use inside listeners - adds a timeout to the current fiber, such that | ||||
|  * it will be resumed after sec seconds if no other event schedules the current fiber. */ | ||||
| JANET_API void janet_addtimeout(double sec); | ||||
| JANET_API void janet_addtimeout_nil(double sec); | ||||
| JANET_API void janet_ev_inc_refcount(void); | ||||
| JANET_API void janet_ev_dec_refcount(void); | ||||
|  | ||||
| @@ -2180,7 +2182,10 @@ typedef enum { | ||||
|     RULE_UNREF,        /* [rule, tag] */ | ||||
|     RULE_CAPTURE_NUM,  /* [rule, tag] */ | ||||
|     RULE_SUB,          /* [rule, rule] */ | ||||
|     RULE_SPLIT         /* [rule, rule] */ | ||||
|     RULE_TIL,          /* [rule, rule] */ | ||||
|     RULE_SPLIT,        /* [rule, rule] */ | ||||
|     RULE_NTH,          /* [nth, rule, tag] */ | ||||
|     RULE_ONLY_TAGS,    /* [rule] */ | ||||
| } JanetPegOpcod; | ||||
|  | ||||
| typedef struct { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -39,7 +39,7 @@ | ||||
| (defmacro assert | ||||
|   [x &opt e] | ||||
|   (def xx (gensym)) | ||||
|   (default e ~',x) | ||||
|   (default e (string/format "%j" x)) | ||||
|   ~(do | ||||
|      (def ,xx ,x) | ||||
|      (,assert-no-tail ,xx ,e) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -896,11 +896,18 @@ | ||||
|   (struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2")) | ||||
| (table/setproto table-to-freeze @{:a @[1 2 3]}) | ||||
|  | ||||
| (assert (deep= {:a [1 2 3] :b [1 2 3 4] :c 22 :d "test" :e "test2"} | ||||
|                (freeze table-to-freeze))) | ||||
| (assert (deep= struct-to-thaw (freeze table-to-freeze))) | ||||
| (assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze))) | ||||
| (assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw))) | ||||
|  | ||||
| # Check that freezing mutable keys is deterministic | ||||
| # for issue #1535 | ||||
| (def hashes @{}) | ||||
| (repeat 200 | ||||
|   (def x (freeze {@"" 1 @"" 2 @"" 3 @"" 4 @"" 5})) | ||||
|   (put hashes (hash x) true)) | ||||
| (assert (= 1 (length hashes)) "freeze mutable keys is deterministic") | ||||
|  | ||||
| # Make sure Carriage Returns don't end up in doc strings | ||||
| # e528b86 | ||||
| (assert (not (string/find "\r" | ||||
| @@ -986,4 +993,27 @@ | ||||
| (assert (deep= (get (dyn 'a) :source-form) source)) | ||||
| (setdyn *debug* nil) | ||||
|  | ||||
| # issue #1516 | ||||
| (assert-error "assertf 1 argument" (macex '(assertf true))) | ||||
| (assert (assertf true "fun message") "assertf 2 arguments") | ||||
| (assert (assertf true "%s message" "mystery") "assertf 3 arguments") | ||||
| (assert (assertf (not nil) "%s message" "ordinary") "assertf not nil") | ||||
| (assert-error "assertf error 2" (assertf false "fun message")) | ||||
| (assert-error "assertf error 3" (assertf false "%s message" "mystery")) | ||||
| (assert-error "assertf error 4" (assertf nil "%s %s" "alice" "bob")) | ||||
|  | ||||
| # issue #1535 | ||||
| (loop [i :range [1 1000]] | ||||
|   (assert (deep-not= @{:key1 "value1" @"key" "value2"} | ||||
|                      @{:key1 "value1" @"key" "value2"}) "deep= mutable keys")) | ||||
| (assert (deep-not= {"abc" 123} {@"abc" 123}) "deep= mutable keys vs immutable key") | ||||
| (assert (deep-not= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys") | ||||
| (assert (deep-not= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2") | ||||
| (assert (deep-not= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3") | ||||
| (assert (deep-not= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4") | ||||
| (assert (deep-not= @{:key1 "value1" @"key2" @"value2"} | ||||
|                    @{:key1 "value1" @"key2" "value2"}) "deep= mutable keys") | ||||
| (assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"} | ||||
|                    @{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys") | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2024 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2024 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -117,6 +117,11 @@ | ||||
| (assert (= 0 (length (bundle/list))) "bundles are listed correctly 7") | ||||
| (assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8") | ||||
|  | ||||
| # Try installing a bundle that fails check | ||||
| (assert-error "bad test" (bundle/install "./examples/sample-bad-bundle" :check true)) | ||||
| (assert (= 0 (length (bundle/list))) "check failure 0") | ||||
| (assert (= 0 (length (bundle/topolist))) "check failure 1") | ||||
|  | ||||
| (rmrf syspath) | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -174,6 +174,7 @@ | ||||
| (assert (deep= (range 0 17 4) @[0 4 8 12 16]) "(range 0 17 4)") | ||||
| (assert (deep= (range 16 0 -4) @[16 12 8 4]) "(range 16 0 -4)") | ||||
| (assert (deep= (range 17 0 -4) @[17 13 9 5 1]) "(range 17 0 -4)") | ||||
| (assert-error "large range" (range 0xFFFFFFFFFF)) | ||||
|  | ||||
| (assert (= (length (range 10)) 10) "(range 10)") | ||||
| (assert (= (length (range -10)) 0) "(range -10)") | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose & contributors | ||||
| # Copyright (c) 2025 Calvin Rose & contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -199,7 +199,7 @@ | ||||
|   (assert s "made server 1") | ||||
|  | ||||
|   (defn test-echo [msg] | ||||
|     (with [conn (net/connect test-host test-port)] | ||||
|     (with [conn (assert (net/connect test-host test-port))] | ||||
|       (net/write conn msg) | ||||
|       (def res (net/read conn 1024)) | ||||
|       (assert (= (string res) msg) (string "echo " msg)))) | ||||
| @@ -213,6 +213,7 @@ | ||||
|  | ||||
| # Test on both server and client | ||||
| # 504411e | ||||
| (var iterations 0) | ||||
| (defn names-handler | ||||
|   [stream] | ||||
|   (defer (:close stream) | ||||
| @@ -220,21 +221,26 @@ | ||||
|     (ev/read stream 1) | ||||
|     (def [host port] (net/localname stream)) | ||||
|     (assert (= host test-host) "localname host server") | ||||
|     (assert (= port (scan-number test-port)) "localname port server"))) | ||||
|     (assert (= port (scan-number test-port)) "localname port server") | ||||
|     (++ iterations) | ||||
|     (ev/write stream " "))) | ||||
|  | ||||
| # Test localname and peername | ||||
| # 077bf5eba | ||||
| (repeat 10 | ||||
|   (with [s (net/server test-host test-port names-handler)] | ||||
|     (repeat 10 | ||||
|       (with [conn (net/connect test-host test-port)] | ||||
|       (with [conn (assert (net/connect test-host test-port))] | ||||
|         (def [host port] (net/peername conn)) | ||||
|         (assert (= host test-host) "peername host client ") | ||||
|         (assert (= port (scan-number test-port)) "peername port client") | ||||
|         # let server close | ||||
|         (ev/write conn " ")))) | ||||
|         (++ iterations) | ||||
|         (ev/write conn " ") | ||||
|         (ev/read conn 1)))) | ||||
|   (gccollect)) | ||||
|  | ||||
| (assert (= iterations 200) "localname and peername not enough checks") | ||||
|  | ||||
| # Create pipe | ||||
| # 12f09ad2d | ||||
| (var pipe-counter 0) | ||||
| @@ -410,6 +416,10 @@ | ||||
|         (ev/call handler connection) | ||||
|         (break)))) | ||||
|  | ||||
| # Make sure we can't bind again with no-reuse | ||||
| (assert-error "no-reuse" | ||||
|               (net/listen test-host test-port :stream true)) | ||||
|  | ||||
| # Read from socket | ||||
|  | ||||
| (defn expect-read | ||||
| @@ -418,11 +428,17 @@ | ||||
|   (assert (= result text) (string/format "expected %v, got %v" text result))) | ||||
|  | ||||
| # Now do our telnet chat | ||||
| (def bob (net/connect test-host test-port)) | ||||
| (def bob (assert (net/connect test-host test-port :stream))) | ||||
| (expect-read bob "Whats your name?\n") | ||||
| (net/write bob "bob") | ||||
| (if (= :mingw (os/which)) | ||||
|   (net/write bob "bob") | ||||
|   (do | ||||
|     (def fbob (ev/to-file bob)) | ||||
|     (file/write fbob "bob") | ||||
|     (file/flush fbob) | ||||
|     (:close fbob))) | ||||
| (expect-read bob "Welcome bob\n") | ||||
| (def alice (net/connect test-host test-port)) | ||||
| (def alice (assert (net/connect test-host test-port))) | ||||
| (expect-read alice "Whats your name?\n") | ||||
| (net/write alice "alice") | ||||
| (expect-read alice "Welcome alice\n") | ||||
| @@ -436,7 +452,7 @@ | ||||
| (expect-read bob "[alice]:hi\n") | ||||
|  | ||||
| # Ted joins the chat server | ||||
| (def ted (net/connect test-host test-port)) | ||||
| (def ted (assert (net/connect test-host test-port))) | ||||
| (expect-read ted "Whats your name?\n") | ||||
| (net/write ted "ted") | ||||
| (expect-read ted "Welcome ted\n") | ||||
| @@ -465,4 +481,73 @@ | ||||
| # Close chat server | ||||
| (:close chat-server) | ||||
|  | ||||
| # Issue #1531 | ||||
| (defn sleep-print [x] (ev/sleep 0) (print x)) | ||||
| (protect (with-dyns [*out* sleep-print] (prin :foo))) | ||||
| (defn level-trigger-handling [conn &] (:close conn)) | ||||
| (def s (assert (net/server test-host test-port level-trigger-handling))) | ||||
| (def c (assert (net/connect test-host test-port))) | ||||
| (:close s) | ||||
|  | ||||
| # Issue #1531 no. 2 | ||||
| (def c (ev/chan 0)) | ||||
| (ev/spawn (while (def x (ev/take c)))) | ||||
| (defn print-to-chan [x] (ev/give c x)) | ||||
| (assert-error "coerce await inside janet_call to error" | ||||
|               (with-dyns [*out* print-to-chan] | ||||
|                 (pp :foo))) | ||||
| (ev/chan-close c) | ||||
|  | ||||
| # soreuseport on unix domain sockets | ||||
| (compwhen (or (= :macos (os/which)) (= :linux (os/which))) | ||||
|   (assert-no-error "unix-domain socket reuseaddr" | ||||
|                    (let [s (net/listen :unix "./unix-domain-socket" :stream)] | ||||
|                      (:close s)))) | ||||
|  | ||||
| # net/accept-loop level triggering | ||||
| (gccollect) | ||||
| (def maxconn 50) | ||||
| (var connect-count 0) | ||||
| (defn level-trigger-handling | ||||
|   [conn &] | ||||
|   (with [conn conn] | ||||
|     (ev/write conn (ev/read conn 4096)) | ||||
|     (++ connect-count))) | ||||
| (def s (assert (net/server test-host test-port level-trigger-handling))) | ||||
| (def cons @[]) | ||||
| (repeat maxconn (array/push cons (assert (net/connect test-host test-port)))) | ||||
| (assert (= maxconn (length cons))) | ||||
| (defn do-connect [i] | ||||
|   (with [c (get cons i)] | ||||
|     (ev/write c "abc123") | ||||
|     (ev/read c 4096))) | ||||
| (for i 0 maxconn (ev/spawn (do-connect i))) | ||||
| (ev/sleep 0.1) | ||||
| (assert (= maxconn connect-count)) | ||||
| (:close s) | ||||
|  | ||||
| # Cancel os/proc-wait with ev/deadline | ||||
| (let [p (os/spawn [;run janet "-e" "(os/sleep 4)"] :p)] | ||||
|   (var terminated-normally false) | ||||
|   (assert-error "deadline expired" | ||||
|                 (ev/with-deadline 0.01 | ||||
|                   (os/proc-wait p) | ||||
|                   (print "uhoh") | ||||
|                   (set terminated-normally true))) | ||||
|   (assert (not terminated-normally) "early termination failure") | ||||
|   # Without this kill, janet will wait the full 4 seconds for the subprocess to complete before exiting. | ||||
|   (assert-no-error "kill proc after wait failed" (os/proc-kill p))) | ||||
|  | ||||
| # Cancel os/proc-wait with ev/deadline 2 | ||||
| (let [p (os/spawn [;run janet "-e" "(os/sleep 0.1)"] :p)] | ||||
|   (var terminated-normally false) | ||||
|   (assert-error "deadline expired" | ||||
|                 (ev/with-deadline 0.05 | ||||
|                   (os/proc-wait p) | ||||
|                   (print "uhoh") | ||||
|                   (set terminated-normally true))) | ||||
|   (assert (not terminated-normally) "early termination failure 2") | ||||
|   (ev/sleep 0.15) | ||||
|   (assert (not terminated-normally) "early termination failure 3")) | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose & contributors | ||||
| # Copyright (c) 2025 Calvin Rose & contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -52,5 +52,7 @@ | ||||
|   (assert (= 26 (ffi/size [:char :pack :int @[:char 21]])) | ||||
|           "array struct size")) | ||||
|  | ||||
| (end-suite) | ||||
| (compwhen has-ffi | ||||
|   (assert-error "bad struct issue #1512" (ffi/struct :void))) | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2024 Calvin Rose & contributors | ||||
| # Copyright (c) 2025 Calvin Rose & contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose & contributors | ||||
| # Copyright (c) 2025 Calvin Rose & contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose & contributors | ||||
| # Copyright (c) 2025 Calvin Rose & contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -207,7 +207,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 | ||||
| (assert (= 2 (length tclone)) "table/weak-values marsh 2") | ||||
| (gccollect) | ||||
| (assert (= 1 (length t)) "table/weak-value marsh 3") | ||||
| (assert (deep= t tclone) "table/weak-values marsh 4") | ||||
| (assert (deep= (freeze t) (freeze tclone)) "table/weak-values marsh 4") | ||||
|  | ||||
| # tables with prototypes | ||||
| (def t (table/weak-values 1)) | ||||
| @@ -219,7 +219,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 | ||||
| (assert (= 2 (length tclone)) "marsh weak tables with prototypes 2") | ||||
| (gccollect) | ||||
| (assert (= 1 (length t)) "marsh weak tables with prototypes 3") | ||||
| (assert (deep= t tclone) "marsh weak tables with prototypes 4") | ||||
| (assert (deep= (freeze t) (freeze tclone)) "marsh weak tables with prototypes 4") | ||||
| (assert (deep= (getproto t) (getproto tclone)) "marsh weak tables with prototypes 5") | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -57,6 +57,8 @@ | ||||
|     (for i (+ index 1) (+ index indent 1) | ||||
|       (case (get text i) | ||||
|         nil (break) | ||||
|         (chr "\r") (if-not (= (chr "\n") (get text (inc i))) | ||||
|                      (set rewrite false)) | ||||
|         (chr "\n") (break) | ||||
|         (chr " ") nil | ||||
|         (set rewrite false)))) | ||||
| @@ -64,12 +66,17 @@ | ||||
|   # Only re-indent if no dedented characters. | ||||
|   (def str | ||||
|     (if rewrite | ||||
|       (peg/replace-all ~(* "\n" (between 0 ,indent " ")) "\n" text) | ||||
|       (peg/replace-all ~(* '(* (? "\r") "\n") (between 0 ,indent " ")) | ||||
|                       (fn [mtch eol] eol) text) | ||||
|       text)) | ||||
|  | ||||
|   (def first-nl (= (chr "\n") (first str))) | ||||
|   (def last-nl (= (chr "\n") (last str))) | ||||
|   (string/slice str (if first-nl 1 0) (if last-nl -2))) | ||||
|   (def first-eol (cond | ||||
|                    (string/has-prefix? "\r\n" str) :crlf | ||||
|                    (string/has-prefix? "\n" str) :lf)) | ||||
|   (def last-eol (cond | ||||
|                   (string/has-suffix? "\r\n" str) :crlf | ||||
|                   (string/has-suffix? "\n" str) :lf)) | ||||
|   (string/slice str (case first-eol :crlf 2 :lf 1 0) (case last-eol :crlf -3 :lf -2))) | ||||
|  | ||||
| (defn reindent-reference | ||||
|   "Same as reindent but use parser functionality. Useful for | ||||
| @@ -89,8 +96,10 @@ | ||||
|   (let [a (reindent text indent) | ||||
|         b (reindent-reference text indent)] | ||||
|     (assert (= a b) | ||||
|             (string "indent " indent-counter " (indent=" indent ")")))) | ||||
|             (string/format "reindent: %q, parse: %q (indent-test #%d with indent of %d)" a b indent-counter indent) | ||||
|             ))) | ||||
|  | ||||
| # Unix EOLs | ||||
| (check-indent "" 0) | ||||
| (check-indent "\n" 0) | ||||
| (check-indent "\n" 1) | ||||
| @@ -106,6 +115,17 @@ | ||||
| (check-indent "\n    Hello, world!\n    " 4) | ||||
| (check-indent "\n    Hello, world!\n   dedented text\n    " 4) | ||||
| (check-indent "\n    Hello, world!\n    indented text\n    " 4) | ||||
| # Windows EOLs | ||||
| (check-indent "\r\n" 0) | ||||
| (check-indent "\r\n" 1) | ||||
| (check-indent "\r\n\r\n" 0) | ||||
| (check-indent "\r\n\r\n" 1) | ||||
| (check-indent "\r\nHello, world!" 0) | ||||
| (check-indent "\r\nHello, world!" 1) | ||||
| (check-indent "\r\n    Hello, world!\r\n   " 4) | ||||
| (check-indent "\r\n    Hello, world!\r\n    " 4) | ||||
| (check-indent "\r\n    Hello, world!\r\n   dedented text\r\n    " 4) | ||||
| (check-indent "\r\n    Hello, world!\r\n    indented text\r\n    " 4) | ||||
|  | ||||
| # Symbols with @ character | ||||
| # d68eae9 | ||||
| @@ -188,5 +208,14 @@ | ||||
| (parser/consume p `")`) | ||||
| (assert (= (parser/produce p) ["hello"])) | ||||
|  | ||||
| # Hex floats | ||||
| (assert (= math/pi +0x1.921fb54442d18p+0001)) | ||||
| (assert (= math/int-max +0x1.ffff_ffff_ffff_ffp+0052)) | ||||
| (assert (= math/int-min -0x1.ffff_ffff_ffff_ffp+0052)) | ||||
| (assert (= 1 0x1P0)) | ||||
| (assert (= 2 0x1P1)) | ||||
| (assert (= -2 -0x1p1)) | ||||
| (assert (= -0.5 -0x1p-1)) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -664,6 +664,8 @@ | ||||
|   @[]) "peg if not") | ||||
|  | ||||
| (defn test [name peg input expected] | ||||
|   (assert-no-error "compile peg" (peg/compile peg)) | ||||
|   (assert-no-error "marshal/unmarshal peg" (-> peg marshal unmarshal)) | ||||
|   (assert (deep= (peg/match peg input) expected) name)) | ||||
|  | ||||
| (test "sub: matches the same input twice" | ||||
| @@ -711,6 +713,41 @@ | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "til: basic matching" | ||||
|   ~(til "d" "abc") | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "til: second pattern can't see past the first occurrence of first pattern" | ||||
|   ~(til "d" (* "abc" -1)) | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "til: fails if first pattern fails" | ||||
|   ~(til "x" "abc") | ||||
|   "abcdef" | ||||
|   nil) | ||||
|  | ||||
| (test "til: fails if second pattern fails" | ||||
|   ~(til "abc" "x") | ||||
|   "abcdef" | ||||
|   nil) | ||||
|  | ||||
| (test "til: discards captures from initial pattern" | ||||
|   ~(til '"d" '"abc") | ||||
|   "abcdef" | ||||
|   @["abc"]) | ||||
|  | ||||
| (test "til: positions inside second match are still relative to the entire input" | ||||
|   ~(* "one\ntw" (til 0 (* ($) (line) (column)))) | ||||
|   "one\ntwo\nthree\n" | ||||
|   @[6 2 3]) | ||||
|  | ||||
| (test "til: advances to the end of the first pattern's first occurrence" | ||||
|   ~(* (til "d" "ab") "e") | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "split: basic functionality" | ||||
|   ~(split "," '1) | ||||
|   "a,b,c" | ||||
| @@ -756,5 +793,47 @@ | ||||
|   "a,b,c" | ||||
|   @["a" "b" "c"]) | ||||
|  | ||||
| (test "nth 1" | ||||
|   ~{:prefix (number :d+ nil :n) | ||||
|     :word '(lenprefix (-> :n) :w) | ||||
|     :main (some (nth 1 (* :prefix ":" :word)))} | ||||
|   "5:apple6:banana6:cherry" | ||||
|   @["apple" "banana" "cherry"]) | ||||
|  | ||||
| (test "only-tags 1" | ||||
|   ~{:prefix (number :d+ nil :n) | ||||
|     :word (capture (lenprefix (-> :n) :w) :W) | ||||
|     :main (some (* (only-tags (* :prefix ":" :word)) (-> :W)))} | ||||
|   "5:apple6:banana6:cherry" | ||||
|   @["apple" "banana" "cherry"]) | ||||
|  | ||||
| # Issue #1539 - make sure split with "" doesn't infinite loop/oom | ||||
| (test "issue 1539" | ||||
|       ~(split "" (capture (to -1))) | ||||
|       "hello there friends" | ||||
|       nil) | ||||
|  | ||||
| (test "issue 1539 pt. 2" | ||||
|   ~(split "," (capture 0)) | ||||
|   "abc123,,,," | ||||
|   @["" "" "" "" ""]) | ||||
|  | ||||
| # Issue #1549 - allow buffers as peg literals | ||||
| (test "issue 1549" | ||||
|       ''@"abc123" | ||||
|       "abc123" | ||||
|       @["abc123"]) | ||||
|  | ||||
| # Issue 1554 - 0-width match termination behavior | ||||
| (test "issue 1554 case 1" '(any (> '1)) "abc" @[]) | ||||
| (test "issue 1554 case 2" '(any (? (> '1))) "abc" @[]) | ||||
| (test "issue 1554 case 3" '(any (> (? '1))) "abc" @[]) | ||||
| (test "issue 1554 case 4" '(* "a" (> '1)) "abc" @["b"]) | ||||
| (test "issue 1554 case 5" '(* "a" (? (> '1))) "abc" @["b"]) | ||||
| (test "issue 1554 case 6" '(* "a" (> (? '1))) "abc" @["b"]) | ||||
| (test "issue 1554 case 7" '(between 0 2 (> '1)) "abc" @["a" "a"]) | ||||
| (test "issue 1554 case 8" '(between 2 3 (? (> '1))) "abc" @["a" "a" "a"]) | ||||
| (test "issue 1554 case 9" '(between 0 0 (> (? '1))) "abc" @[]) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose & contributors | ||||
| # Copyright (c) 2025 Calvin Rose & contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user