mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-27 13:47:42 +00:00 
			
		
		
		
	Compare commits
	
		
			291 Commits
		
	
	
		
			ev-epoll-f
			...
			filewatch
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c5a9602be9 | ||
|   | e88aab6d68 | ||
|   | ce528251d5 | ||
|   | 9e334da2d6 | ||
|   | c0e508e334 | ||
|   | b63b3bef74 | ||
|   | 05d0b5ac05 | ||
|   | c56d6e8fc1 | ||
|   | 33d2f9a522 | ||
|   | e53d22fad2 | ||
|   | 33f55dc32f | ||
|   | 7e6aad2221 | ||
|   | 3c0c22259c | ||
|   | 42f6af4bf1 | ||
|   | f274b02653 | ||
|   | 70c29b4e5d | ||
|   | 84d43d1039 | ||
|   | 5c67c1165d | ||
|   | 85028967d8 | ||
|   | 6ceff6ecc9 | ||
|   | 06eec06ff0 | ||
|   | 2dcc0adc0e | ||
|   | 8ca1e44af1 | ||
|   | 2aedc6beff | ||
|   | af2eb06298 | ||
|   | 7ff545bd2e | ||
|   | a59b5765b6 | ||
|   | 6bd58dd4c0 | ||
|   | e3406cd922 | ||
|   | ab70524d85 | ||
|   | ce36c4c0d6 | ||
|   | 2b01b780da | ||
|   | f3048a3d6b | ||
|   | accac6c662 | ||
|   | 631622aa48 | ||
|   | aaeaa3a944 | ||
|   | d1104b5a65 | ||
|   | 1f074671ce | ||
|   | 872b39cc32 | ||
|   | 9eab57d194 | ||
|   | 8edd873c3e | ||
|   | 771956b5b6 | ||
|   | ecc4da5113 | ||
|   | f5555d21b9 | ||
|   | 342a29c7be | ||
|   | 368b891499 | ||
|   | f62539ad55 | ||
|   | 4835ecb950 | ||
|   | 31f0ff0d84 | ||
|   | b7b594205c | ||
|   | 190056b863 | ||
|   | ae6b359109 | ||
|   | 3078686f8f | ||
|   | 0f4ecd93ab | ||
|   | 4af187d0ca | ||
|   | a5d6b22838 | ||
|   | fda0a081f5 | ||
|   | 94b7a69741 | ||
|   | 6518257129 | ||
|   | dc325188d0 | ||
|   | 0b51ab157d | ||
|   | f95de25b15 | ||
|   | f424f2936b | ||
|   | 2d6c2ee7c0 | ||
|   | 7cd106a10c | ||
|   | 0d9e999113 | ||
|   | 75710ccabd | ||
|   | 0f60115f27 | ||
|   | 16a3c85baa | ||
|   | 92ff1d3be4 | ||
|   | 58441dc49f | ||
|   | dbc5d688e2 | ||
|   | e2a8951f68 | ||
|   | f0f03ad519 | ||
|   | e37575e763 | ||
|   | f4fd481415 | ||
|   | 8fca6b7af4 | ||
|   | 600e822933 | ||
|   | 2028ac8a20 | ||
|   | 7bae7d9efd | ||
|   | cb54fb02c1 | ||
|   | 7529abb542 | ||
|   | 16ac681ed9 | ||
|   | 74560ff805 | ||
|   | fe348187cc | ||
|   | fd5315793c | ||
|   | 87db463f4e | ||
|   | 1225cd31c8 | ||
|   | 6998865d7b | ||
|   | b8aec50763 | ||
|   | 7efb39d608 | ||
|   | f7c90bc1ff | ||
|   | aee077c1bd | ||
|   | 6968275ddf | ||
|   | 074ae4fc0d | ||
|   | 6cd35ed9c8 | ||
|   | 7911e74222 | ||
|   | 2fafe2b5d1 | ||
|   | de977819ce | ||
|   | 1844beecc3 | ||
|   | cb529bbd63 | ||
|   | 25990867e2 | ||
|   | 4fbc71c70d | ||
|   | eb21d4fff4 | ||
|   | 6d5fc1d743 | ||
|   | e88042b2fa | ||
|   | 750b448f75 | ||
|   | 14d1dc8749 | ||
|   | 8e0340252b | ||
|   | 641a16c133 | ||
|   | 533d78bffe | ||
|   | ae2c5820a1 | ||
|   | 8334504f4e | ||
|   | 2260a593bd | ||
|   | 7d8af2f99a | ||
|   | 46bdcece4d | ||
|   | 7387a1d91e | ||
|   | ae4b8078df | ||
|   | 60e0c8ea92 | ||
|   | 7d3acc0ed6 | ||
|   | 2637b33957 | ||
|   | 58ccb66659 | ||
|   | 634429cf61 | ||
|   | 6ac65e603d | ||
|   | 03166a745a | ||
|   | 4d61ba20ce | ||
|   | 751ff677fe | ||
|   | ace60e1898 | ||
|   | 876b7f106f | ||
|   | 809b6589a1 | ||
|   | 02f53ca014 | ||
|   | 0b03ddb21b | ||
|   | ea5d4fd3af | ||
|   | e6b73f8cd1 | ||
|   | af232ef729 | ||
|   | 2e2f8abfc0 | ||
|   | 91a583db27 | ||
|   | dc5cc630ff | ||
|   | c1647a74c5 | ||
|   | 721f280966 | ||
|   | 258ebb9145 | ||
|   | e914eaf055 | ||
|   | fe54013679 | ||
|   | fdaf2e1594 | ||
|   | f0092ef69b | ||
|   | a88ae7e1d9 | ||
|   | 9946f3bdf4 | ||
|   | c747e8d16c | ||
|   | 3e402d397e | ||
|   | 0350834cd3 | ||
|   | 980981c9ee | ||
|   | 3c8346f24e | ||
|   | 42bd27c24b | ||
|   | 4a0f67f3bd | ||
|   | 09b6fc4670 | ||
|   | 4d9bcd6bcc | ||
|   | cd34b89977 | ||
|   | 3151fa3988 | ||
|   | 5e58110e19 | ||
|   | e1cdd0f8cc | ||
|   | 1f39a0f180 | ||
|   | 367c4b14f5 | ||
|   | 9c437796d3 | ||
|   | 60e22d9703 | ||
|   | ee7362e847 | ||
|   | 369f96b80e | ||
|   | 7c5ed04ab1 | ||
|   | 4779a445e0 | ||
|   | f0f1b7ce9e | ||
|   | 7c9157a0ed | ||
|   | 522a6cb435 | ||
|   | d0d551d739 | ||
|   | 71a123fef7 | ||
|   | 3f40c8d7fb | ||
|   | 983c2e5499 | ||
|   | eebb4c3ade | ||
|   | 50425eac72 | ||
|   | 382ff77bbe | ||
|   | bf680fb5d3 | ||
|   | 4ed7db4f91 | ||
|   | bf19920d65 | ||
|   | 174b5f6686 | ||
|   | 4173645b81 | ||
|   | af511f1f55 | ||
|   | 83c6080380 | ||
|   | 2f0c789ea1 | ||
|   | a9b8f8e8a9 | ||
|   | f92f3eb6fa | ||
|   | 89e74dca3e | ||
|   | f2e86d2f8d | ||
|   | 623da131e5 | ||
|   | e89ec31ae5 | ||
|   | 68a6ed208e | ||
|   | c01b32c4f3 | ||
|   | ee11ff9da9 | ||
|   | ed56d5d6ff | ||
|   | b317ab755c | ||
|   | 9819994999 | ||
|   | e9dbaa81d2 | ||
|   | 9f9146ffae | ||
|   | 9d9732af97 | ||
|   | ebb8fa9787 | ||
|   | 9e6abbf4d4 | ||
|   | 6032a6d658 | ||
|   | c29ab22e6d | ||
|   | 592ac4904c | ||
|   | 03ae2ec153 | ||
|   | 3bc42d0d37 | ||
|   | 12630d3e54 | ||
|   | c9897f99c3 | ||
|   | e66dc14b3a | ||
|   | 7a2868c147 | ||
|   | 9e0daaee09 | ||
|   | c293c7de93 | ||
|   | 49eb5f8563 | ||
|   | 674b375b2c | ||
|   | 7e94c091eb | ||
|   | 5885ccba61 | ||
|   | 431ecd3d1a | ||
|   | f6df8ff935 | ||
|   | 3fd70f0951 | ||
|   | bebb635d4f | ||
|   | 354896bc4b | ||
|   | 5ddefff27e | ||
|   | 91827eef4f | ||
|   | 9c14c09962 | ||
|   | e85a84171f | ||
|   | 3a4f86c3d7 | ||
|   | 5e75963312 | ||
|   | 184d9289b5 | ||
|   | b7ff9577c0 | ||
|   | 942a1aaac6 | ||
|   | 69f0fe004d | ||
|   | 2a04347a42 | ||
|   | 1394f1a5c0 | ||
|   | cf4d19a8ea | ||
|   | 23b0fe9f8e | ||
|   | 1ba718b15e | ||
|   | df5f79ff35 | ||
|   | 6d7e8528ea | ||
|   | 197bb73a62 | ||
|   | f91e599451 | ||
|   | 5b9aa9237c | ||
|   | 61f38fab37 | ||
|   | 9142f38cbc | ||
|   | e8ed961572 | ||
|   | be11a2a1ad | ||
|   | ea75086300 | ||
|   | 9eeefbd79a | ||
|   | c573a98363 | ||
|   | 11d7af3f95 | ||
|   | a10b4f61d8 | ||
|   | a0cb7514f1 | ||
|   | b066edc116 | ||
|   | 938f5a689e | ||
|   | 772f4c26e8 | ||
|   | 6b5d151beb | ||
|   | a9176a77e6 | ||
|   | 16f409c6a9 | ||
|   | 9593c930de | ||
|   | 56f33f514b | ||
|   | 1ccd544b94 | ||
|   | 93c83a2ee2 | ||
|   | f459e32ada | ||
|   | 9b640c8e9c | ||
|   | a3228f4997 | ||
|   | 715eb69d92 | ||
|   | df2d5cb3d3 | ||
|   | 3b189eab64 | ||
|   | 609b629c22 | ||
|   | e74365fe38 | ||
|   | 46b34833c2 | ||
|   | 045c80869d | ||
|   | 2ea2e72ddd | ||
|   | 1b17e12fd6 | ||
|   | cc5beda0d2 | ||
|   | a363fd926d | ||
|   | 21ebede529 | ||
|   | 15d67e9191 | ||
|   | b5996f5f02 | ||
|   | 83204dc293 | ||
|   | e3f4142d2a | ||
|   | f18ad36b1b | ||
|   | cb25a2ecd6 | ||
|   | 741a5036e8 | ||
|   | 549ee95f3d | ||
|   | 6ae81058aa | ||
|   | 267c603824 | ||
|   | a8f583a372 | ||
|   | 2b5d90f73a | ||
|   | 4139e426fe | 
| @@ -1,4 +1,4 @@ | ||||
| image: freebsd/12.x | ||||
| image: freebsd/14.x | ||||
| sources: | ||||
| - https://git.sr.ht/~bakpakin/janet | ||||
| packages: | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| image: openbsd/latest | ||||
| image: openbsd/7.4 | ||||
| sources: | ||||
| - https://git.sr.ht/~bakpakin/janet | ||||
| packages: | ||||
|   | ||||
							
								
								
									
										38
									
								
								.github/cosmo/build
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								.github/cosmo/build
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| #!/bin/sh | ||||
| set -eux | ||||
|  | ||||
| COSMO_DIR="/sc/cosmocc" | ||||
|  | ||||
| # build x86_64 | ||||
| X86_64_CC="/sc/cosmocc/bin/x86_64-unknown-cosmo-cc" | ||||
| X86_64_AR="/sc/cosmocc/bin/x86_64-unknown-cosmo-ar" | ||||
| mkdir -p /sc/cosmocc/x86_64 | ||||
| make -j CC="$X86_64_CC" AR="$X86_64_AR" HAS_SHARED=0 JANET_NO_AMALG=1 | ||||
| cp build/janet /sc/cosmocc/x86_64/janet | ||||
| make clean | ||||
|  | ||||
| # build aarch64 | ||||
| AARCH64_CC="/sc/cosmocc/bin/aarch64-unknown-cosmo-cc" | ||||
| AARCH64_AR="/sc/cosmocc/bin/aarch64-unknown-cosmo-ar" | ||||
| mkdir -p /sc/cosmocc/aarch64 | ||||
| make -j CC="$AARCH64_CC" AR="$AARCH64_AR" HAS_SHARED=0 JANET_NO_AMALG=1 | ||||
| cp build/janet /sc/cosmocc/aarch64/janet | ||||
| make clean | ||||
|  | ||||
| # fat binary | ||||
| apefat () { | ||||
|     OUTPUT="$1" | ||||
|     OLDNAME_X86_64="$(basename -- "$2")" | ||||
|     OLDNAME_AARCH64="$(basename -- "$3")" | ||||
|     TARG_FOLD="$(dirname "$OUTPUT")" | ||||
|     "$COSMO_DIR/bin/apelink" -l "$COSMO_DIR/bin/ape-x86_64.elf" \ | ||||
|         -l "$COSMO_DIR/bin/ape-aarch64.elf" \ | ||||
|         -M "$COSMO_DIR/bin/ape-m1.c" \ | ||||
|         -o "$OUTPUT" \ | ||||
|         "$2" \ | ||||
|         "$3" | ||||
|     cp "$2" "$TARG_FOLD/$OLDNAME_X86_64.x86_64" | ||||
|     cp "$3" "$TARG_FOLD/$OLDNAME_AARCH64.aarch64" | ||||
| } | ||||
|  | ||||
| apefat /sc/cosmocc/janet.com /sc/cosmocc/x86_64/janet /sc/cosmocc/aarch64/janet | ||||
							
								
								
									
										21
									
								
								.github/cosmo/setup
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.github/cosmo/setup
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #!/bin/sh | ||||
| set -e | ||||
|  | ||||
| sudo apt update | ||||
| sudo apt-get install -y ca-certificates libssl-dev\ | ||||
|     qemu qemu-utils qemu-user-static\ | ||||
|     texinfo groff\ | ||||
|     cmake ninja-build bison zip\ | ||||
|     pkg-config build-essential autoconf re2c | ||||
|  | ||||
| # download cosmocc | ||||
| cd /sc | ||||
| wget https://github.com/jart/cosmopolitan/releases/download/3.3.3/cosmocc-3.3.3.zip | ||||
| mkdir -p cosmocc | ||||
| cd cosmocc | ||||
| unzip ../cosmocc-3.3.3.zip | ||||
|  | ||||
| # register | ||||
| cd /sc/cosmocc | ||||
| sudo cp ./bin/ape-x86_64.elf /usr/bin/ape | ||||
| sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" | ||||
							
								
								
									
										27
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -60,3 +60,30 @@ jobs: | ||||
|             ./dist/*.zip | ||||
|             ./*.zip | ||||
|             ./*.msi | ||||
|  | ||||
|   release-cosmo: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|     name: Build release binaries for Cosmo | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: create build folder | ||||
|         run: | | ||||
|           sudo mkdir -p /sc | ||||
|           sudo chmod -R 0777 /sc | ||||
|       - name: setup Cosmopolitan Libc | ||||
|         run: bash ./.github/cosmo/setup | ||||
|       - name: Set the version | ||||
|         run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV | ||||
|       - name: Set the platform | ||||
|         run: echo "platform=cosmo" >> $GITHUB_ENV | ||||
|       - name: build Janet APE binary | ||||
|         run: bash ./.github/cosmo/build | ||||
|       - name: push binary to github | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           draft: true | ||||
|           files: | | ||||
|             /sc/cosmocc/janet.com | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -56,7 +56,7 @@ jobs: | ||||
|             gcc | ||||
|       - name: Build the project | ||||
|         shell: cmd | ||||
|         run: make -j CC=gcc | ||||
|         run: make -j4 CC=gcc JANET_NO_AMALG=1 | ||||
|  | ||||
|   test-mingw-linux: | ||||
|     name: Build and test with Mingw on Linux + Wine | ||||
| @@ -73,7 +73,7 @@ jobs: | ||||
|       - name: Compile the project | ||||
|         run: make clean && make CC=x86_64-w64-mingw32-gcc LD=x86_64-w64-mingw32-gcc UNAME=MINGW RUN=wine | ||||
|       - name: Test the project | ||||
|         run: make test UNAME=MINGW RUN=wine | ||||
|         run: make test UNAME=MINGW RUN=wine VERBOSE=1 | ||||
|  | ||||
|   test-arm-linux: | ||||
|     name: Build and test ARM32 cross compilation | ||||
| @@ -88,4 +88,4 @@ jobs: | ||||
|       - name: Compile the project | ||||
|         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 | ||||
|         run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" SUBRUN="qemu-arm -L /usr/arm-linux-gnueabi/" test VERBOSE=1 | ||||
|   | ||||
							
								
								
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -34,8 +34,11 @@ local | ||||
|  | ||||
| # Common test files I use. | ||||
| temp.janet | ||||
| temp*.janet | ||||
| temp.c | ||||
| temp*janet | ||||
| temp*.c | ||||
| scratch.janet | ||||
| scratch.c | ||||
|  | ||||
| # Emscripten | ||||
| *.bc | ||||
| @@ -45,6 +48,8 @@ janet.wasm | ||||
| # Generated files | ||||
| *.gen.h | ||||
| *.gen.c | ||||
| *.tmp | ||||
| temp.* | ||||
|  | ||||
| # Generate test files | ||||
| *.out | ||||
| @@ -123,6 +128,9 @@ vgcore.* | ||||
| *.idb | ||||
| *.pdb | ||||
|  | ||||
| # GGov | ||||
| *.gcov | ||||
|  | ||||
| # Kernel Module Compile Results | ||||
| *.mod* | ||||
| *.cmd | ||||
| @@ -131,6 +139,9 @@ Module.symvers | ||||
| Mkfile.old | ||||
| dkms.conf | ||||
|  | ||||
| # Coverage files | ||||
| *.cov | ||||
|  | ||||
| # End of https://www.gitignore.io/api/c | ||||
|  | ||||
| # Created by https://www.gitignore.io/api/cmake | ||||
|   | ||||
							
								
								
									
										79
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,6 +2,75 @@ | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| ## Unreleased - ??? | ||||
| - Add experimental `filewatch/` module for listening to file system changes. | ||||
| - Add `bundle/who-is` to query which bundle a file on disk was installed by. | ||||
| - Add `geomean` function | ||||
| - Add `:R` and `:W` flags to `os/pipe` to create blocking pipes on Posix and Windows systems. | ||||
|   These streams cannot be directly read to and written from, but can be passed to subprocesses. | ||||
| - Add `array/join` | ||||
| - Add `tuple/join` | ||||
|  | ||||
| ## 1.35.2 - 2024-06-16 | ||||
| - Add `bundle/add-bin` to make installing scripts easier. This also establishes a packaging convention for it. | ||||
| - Let range take non-integer values. | ||||
| - Fix some documentation typos. | ||||
| - Allow using `:only` in import without quoting. | ||||
|  | ||||
| ## 1.35.0 - 2024-06-15 | ||||
| - Add `:only` argument to `import` to allow for easier control over imported bindings. | ||||
| - Add extra optional `env` argument to `eval` and `eval-string`. | ||||
| - Allow naming function literals with a keyword. This allows better stacktraces for macros without | ||||
|   accidentally adding new bindings. | ||||
| - Add `bundle/` module for managing packages within Janet. This should replace the jpm packaging | ||||
|   format eventually and is much simpler and amenable to more complicated builds. | ||||
| - Add macros `ev/with-lock`, `ev/with-rlock`, and `ev/with-wlock` for using mutexes and rwlocks. | ||||
| - Add `with-env` | ||||
| - Add *module-make-env* dynamic binding | ||||
| - Add buffer/format-at | ||||
| - Add long form command line options for readable CLI usage | ||||
| - Fix bug with `net/accept-loop` that would sometimes miss connections. | ||||
|  | ||||
| ## 1.34.0 - 2024-03-22 | ||||
| - Add a new (split) PEG special by @ianthehenry | ||||
| - Add buffer/push-* sized int and float by @pnelson | ||||
| - Documentation improvements: @amano-kenji, @llmII, @MaxGyver83, @pepe, @sogaiu. | ||||
| - Expose _exit to skip certain cleanup with os/exit. | ||||
| - Swap set / body order for each by @sogaiu. | ||||
| - Abort on assert failure instead of exit. | ||||
| - Fix: os/proc-wait by @llmII. | ||||
| - Fix macex1 to keep syntax location for all tuples. | ||||
| - Restore if-let tail calls. | ||||
| - Don't try and resume fibers that can't be resumed. | ||||
| - Register stream on unmarshal. | ||||
| - Fix asm roundtrip issue. | ||||
|  | ||||
| ## 1.33.0 - 2024-01-07 | ||||
| - Add more + and * keywords to default-peg-grammar by @sogaiu. | ||||
| - Use libc strlen in janet_buffer_push_cstring by @williewillus. | ||||
| - Be a bit safer with reference counting. | ||||
| - Add support for atomic loads in Janet's atomic abstraction. | ||||
| - Fix poll event loop CPU usage issue. | ||||
| - Add ipv6, shared, and cryptorand options to meson. | ||||
| - Add more ipv6 feature detection. | ||||
| - Fix loop for forever loop. | ||||
| - Cleaned up unused NetStateConnect, fixed janet_async_end() ev refcount by @zevv. | ||||
| - Fix warnings w/ MSVC and format. | ||||
| - Fix marshal_one_env w/ JANET_MARSHAL_UNSAFE. | ||||
| - Fix `(default)`. | ||||
| - Fix cannot marshal fiber with c stackframe, in a dynamic way that is fairly conservative. | ||||
| - Fix typo for SIGALARM in os/proc-kill. | ||||
| - Prevent bytecode optimization from remove mk* instructions. | ||||
| - Fix arity typo in peg.c by @pepe. | ||||
| - Update Makefile for MinGW. | ||||
| - Fix canceling waiting fiber. | ||||
| - Add a new (sub) PEG special by @ianthehenry. | ||||
| - Fix if net/server's handler has incorrect arity. | ||||
| - Fix macex raising on (). | ||||
|  | ||||
| ## 1.32.1 - 2023-10-15 | ||||
| - Fix return value from C function `janet_dobytes` when called on Janet functions that yield to event loop. | ||||
| - Change C API for event loop interaction - get rid of JanetListener and instead use `janet_async_start` and `janet_async_end`. | ||||
| - Rework event loop to make fewer system calls on kqueue and epoll. | ||||
| - Expose atomic refcount abstraction in janet.h | ||||
| - Add `array/weak` for weak references in arrays | ||||
| - Add support for weak tables via `table/weak`, `table/weak-keys`, and `table/weak-values`. | ||||
| @@ -60,7 +129,7 @@ All notable changes to this project will be documented in this file. | ||||
|   See http://no-color.org/ | ||||
| - Disallow using `(splice x)` in contexts where it doesn't make sense rather than silently coercing to `x`. | ||||
|   Instead, raise a compiler error. | ||||
| - Change the names of `:user8` and `:user9` sigals to `:interrupt` and `:await` | ||||
| - Change the names of `:user8` and `:user9` signals to `:interrupt` and `:await` | ||||
| - Change the names of `:user8` and `:user9` fiber statuses to `:interrupted` and `:suspended`. | ||||
| - Add `ev/all-tasks` to see all currently suspended fibers. | ||||
| - Add `keep-syntax` and `keep-syntax!` functions to make writing macros easier. | ||||
| @@ -231,7 +300,7 @@ All notable changes to this project will be documented in this file. | ||||
| - Add the ability to close channels with `ev/chan-close` (or `:close`). | ||||
| - Add threaded channels with `ev/thread-chan`. | ||||
| - Add `JANET_FN` and `JANET_REG` macros to more easily define C functions that export their source mapping information. | ||||
| - Add `janet_interpreter_interupt` and `janet_loop1_interrupt` to interrupt the interpreter while running. | ||||
| - Add `janet_interpreter_interrupt` and `janet_loop1_interrupt` to interrupt the interpreter while running. | ||||
| - Add `table/clear` | ||||
| - Add build option to disable the threading library without disabling all threads. | ||||
| - Remove JPM from the main Janet distribution. Instead, JPM must be installed | ||||
| @@ -285,7 +354,7 @@ saving and restoring the entire VM state. | ||||
| - Sort keys in pretty printing output. | ||||
|  | ||||
| ## 1.15.3 - 2021-02-28 | ||||
| - Fix a fiber bug that occured in deeply nested fibers | ||||
| - Fix a fiber bug that occurred in deeply nested fibers | ||||
| - Add `unref` combinator to pegs. | ||||
| - Small docstring changes. | ||||
|  | ||||
| @@ -435,13 +504,13 @@ saving and restoring the entire VM state. | ||||
| - Add `symbol/slice` | ||||
| - Add `keyword/slice` | ||||
| - Allow cross compilation with Makefile. | ||||
| - Change `compare-primitve` to `cmp` and make it more efficient. | ||||
| - Change `compare-primitive` to `cmp` and make it more efficient. | ||||
| - Add `reverse!` for reversing an array or buffer in place. | ||||
| - `janet_dobytes` and `janet_dostring` return parse errors in \*out | ||||
| - Add `repeat` macro for iterating something n times. | ||||
| - Add `eachy` (each yield) macro for iterating a fiber. | ||||
| - Fix `:generate` verb in loop macro to accept non symbols as bindings. | ||||
| - Add `:h`, `:h+`, and `:h*` in `default-peg-grammar` for hexidecimal digits. | ||||
| - Add `:h`, `:h+`, and `:h*` in `default-peg-grammar` for hexadecimal digits. | ||||
| - Fix `%j` formatter to print numbers precisely (using the `%.17g` format string to printf). | ||||
|  | ||||
| ## 1.10.1 - 2020-06-18 | ||||
|   | ||||
							
								
								
									
										26
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								Makefile
									
									
									
									
									
								
							| @@ -33,6 +33,7 @@ CLIBS=-lm -lpthread | ||||
| JANET_TARGET=build/janet | ||||
| JANET_BOOT=build/janet_boot | ||||
| JANET_IMPORT_LIB=build/janet.lib | ||||
| JANET_LIBRARY_IMPORT_LIB=build/libjanet.lib | ||||
| JANET_LIBRARY=build/libjanet.so | ||||
| JANET_STATIC_LIBRARY=build/libjanet.a | ||||
| JANET_PATH?=$(LIBDIR)/janet | ||||
| @@ -42,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 | ||||
| HAS_SHARED?=1 | ||||
| DEBUGGER=gdb | ||||
| SONAME_SETTER=-Wl,-soname, | ||||
|  | ||||
| @@ -51,6 +53,7 @@ HOSTAR?=$(AR) | ||||
| # Symbols are (optionally) removed later, keep -g as default! | ||||
| CFLAGS?=-O2 -g | ||||
| LDFLAGS?=-rdynamic | ||||
| LIBJANET_LDFLAGS?=$(LD_FLAGS) | ||||
| RUN:=$(RUN) | ||||
|  | ||||
| COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC | ||||
| @@ -93,12 +96,17 @@ endif | ||||
| ifeq ($(findstring MINGW,$(UNAME)), MINGW) | ||||
| 	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 | ||||
| endif | ||||
|  | ||||
|  | ||||
| $(shell mkdir -p build/core build/c build/boot build/mainclient) | ||||
| all: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h | ||||
| all: $(JANET_TARGET) $(JANET_STATIC_LIBRARY) build/janet.h | ||||
| ifeq ($(HAS_SHARED), 1) | ||||
| all: $(JANET_LIBRARY) | ||||
| endif | ||||
|  | ||||
| ###################### | ||||
| ##### Name Files ##### | ||||
| @@ -131,6 +139,7 @@ JANET_CORE_SOURCES=src/core/abstract.c \ | ||||
| 				   src/core/ev.c \ | ||||
| 				   src/core/ffi.c \ | ||||
| 				   src/core/fiber.c \ | ||||
| 				   src/core/filewatch.c \ | ||||
| 				   src/core/gc.c \ | ||||
| 				   src/core/inttypes.c \ | ||||
| 				   src/core/io.c \ | ||||
| @@ -196,9 +205,9 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile | ||||
| ######################## | ||||
|  | ||||
| ifeq ($(UNAME), Darwin) | ||||
| SONAME=libjanet.1.31.dylib | ||||
| SONAME=libjanet.1.35.dylib | ||||
| else | ||||
| SONAME=libjanet.so.1.31 | ||||
| SONAME=libjanet.so.1.35 | ||||
| endif | ||||
|  | ||||
| build/c/shell.c: src/mainclient/shell.c | ||||
| @@ -220,7 +229,7 @@ $(JANET_TARGET): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTCC) $(LDFLAGS) $(BUILD_CFLAGS) -o $@ $^ $(CLIBS) | ||||
|  | ||||
| $(JANET_LIBRARY): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTCC) $(LDFLAGS) $(BUILD_CFLAGS) $(SONAME_SETTER)$(SONAME) -shared -o $@ $^ $(CLIBS) | ||||
| 	$(HOSTCC) $(LIBJANET_LDFLAGS) $(BUILD_CFLAGS) $(SONAME_SETTER)$(SONAME) -shared -o $@ $^ $(CLIBS) | ||||
|  | ||||
| $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTAR) rcs $@ $^ | ||||
| @@ -263,7 +272,7 @@ dist: build/janet-dist.tar.gz | ||||
|  | ||||
| build/janet-%.tar.gz: $(JANET_TARGET) \ | ||||
| 	build/janet.h \ | ||||
| 	janet.1 LICENSE CONTRIBUTING.md $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) \ | ||||
| 	janet.1 LICENSE CONTRIBUTING.md $(JANET_STATIC_LIBRARY) \ | ||||
| 	README.md build/c/janet.c build/c/shell.c | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/bin | ||||
| 	cp $(JANET_TARGET) build/$(JANET_DIST_DIR)/bin/ | ||||
| @@ -271,13 +280,17 @@ build/janet-%.tar.gz: $(JANET_TARGET) \ | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/include | ||||
| 	cp build/janet.h build/$(JANET_DIST_DIR)/include/ | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/lib/ | ||||
| 	cp $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/$(JANET_DIST_DIR)/lib/ | ||||
| 	cp $(JANET_STATIC_LIBRARY) build/$(JANET_DIST_DIR)/lib/ | ||||
| 	cp $(JANET_LIBRARY) build/$(JANET_DIST_DIR)/lib/ || true | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/man/man1/ | ||||
| 	cp janet.1 build/$(JANET_DIST_DIR)/man/man1/janet.1 | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/src/ | ||||
| 	cp build/c/janet.c build/c/shell.c build/$(JANET_DIST_DIR)/src/ | ||||
| 	cp CONTRIBUTING.md LICENSE README.md build/$(JANET_DIST_DIR)/ | ||||
| 	cd build && tar -czvf ../$@ ./$(JANET_DIST_DIR) | ||||
| ifeq ($(HAS_SHARED), 1) | ||||
| build/janet-%.tar.gz: $(JANET_LIBRARY) | ||||
| endif | ||||
|  | ||||
| ######################### | ||||
| ##### Documentation ##### | ||||
| @@ -331,6 +344,7 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc | ||||
| 	mkdir -p '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)' | ||||
| 	cp build/janet.pc '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc' | ||||
| 	cp '$(JANET_IMPORT_LIB)' '$(DESTDIR)$(LIBDIR)' || echo 'no import lib to install (mingw only)' | ||||
| 	cp '$(JANET_LIBRARY_IMPORT_LIB)' '$(DESTDIR)$(LIBDIR)' || echo 'no import lib to install (mingw only)' | ||||
| 	[ -z '$(DESTDIR)' ] && $(LDCONFIG) || echo "You can ignore this error for non-Linux systems or local installs" | ||||
|  | ||||
| install-jpm-git: $(JANET_TARGET) | ||||
|   | ||||
| @@ -315,8 +315,7 @@ See the [Embedding Section](https://janet-lang.org/capi/embedding.html) on the w | ||||
|  | ||||
| ## Discussion | ||||
|  | ||||
| Feel free to ask questions and join the discussion on the [Janet Gitter channel](https://gitter.im/janet-language/community). | ||||
| Gitter provides Matrix and IRC bridges as well. | ||||
| Feel free to ask questions and join the discussion on the [Janet Zulip Instance](https://janet.zulipchat.com/) | ||||
|  | ||||
| ## FAQ | ||||
|  | ||||
|   | ||||
| @@ -50,6 +50,7 @@ for %%f in (src\boot\*.c) do ( | ||||
| %JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
| build\janet_boot . > build\c\janet.c | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| @rem Build the sources | ||||
| %JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c | ||||
| @@ -59,6 +60,7 @@ build\janet_boot . > build\c\janet.c | ||||
|  | ||||
| @rem Build the resources | ||||
| rc /nologo /fobuild\janet_win.res janet_win.rc | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| @rem Link everything to main client | ||||
| %JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res | ||||
| @@ -91,7 +93,9 @@ exit /b 0 | ||||
| :CLEAN | ||||
| del *.exe *.lib *.exp | ||||
| rd /s /q build | ||||
| rd /s /q dist | ||||
| if exist dist ( | ||||
|     rd /s /q dist | ||||
| ) | ||||
| exit /b 0 | ||||
|  | ||||
| @rem Run tests | ||||
|   | ||||
| @@ -35,6 +35,11 @@ typedef struct { | ||||
|     int c; | ||||
| } intintint; | ||||
|  | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
| } uint64pair; | ||||
|  | ||||
| typedef struct { | ||||
|     int64_t a; | ||||
|     int64_t b; | ||||
| @@ -203,3 +208,20 @@ EXPORTER | ||||
| int sixints_fn_3(SixInts s, int x) { | ||||
|     return x + s.u + s.v + s.w + s.x + s.y + s.z; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| intint stack_spill_fn(uint8_t a, uint8_t b, uint8_t c, uint8_t d, | ||||
|                       uint8_t e, uint8_t f, uint8_t g, uint8_t h, | ||||
|                       float i, float j, float k, float l, | ||||
|                       float m, float n, float o, float p, | ||||
|                       float s1, int8_t s2, uint8_t s3, double s4, uint8_t s5, intint s6) { | ||||
|     return (intint) { | ||||
|         (a | b | c | d | e | f | g | h) + (i + j + k + l + m + n + o + p), | ||||
|         s1 *s6.a + s2 *s6.b + s3 *s4 *s5 | ||||
|     }; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double stack_spill_fn_2(uint64pair a, uint64pair b, uint64pair c, int8_t d, uint64pair e, int8_t f) { | ||||
|     return (double)(a.a * c.a + a.b * c.b + b.a * e.a) * f - (double)(b.b * e.b) + d; | ||||
| } | ||||
|   | ||||
| @@ -8,11 +8,13 @@ | ||||
|  | ||||
| (if is-windows | ||||
|   (os/execute ["cl.exe" "/nologo" "/LD" ffi/source-loc "/link" "/DLL" (string "/OUT:" ffi/loc)] :px) | ||||
|   (os/execute ["cc" ffi/source-loc "-shared" "-o" ffi/loc] :px)) | ||||
|   (os/execute ["cc" ffi/source-loc "-g" "-shared" "-o" ffi/loc] :px)) | ||||
|  | ||||
| (ffi/context ffi/loc) | ||||
|  | ||||
| (def intint (ffi/struct :int :int)) | ||||
| (def intintint (ffi/struct :int :int :int)) | ||||
| (def uint64pair (ffi/struct :u64 :u64)) | ||||
| (def big (ffi/struct :s64 :s64 :s64)) | ||||
| (def split (ffi/struct :int :int :float :float)) | ||||
| (def split-flip (ffi/struct :float :float :int :int)) | ||||
| @@ -55,6 +57,14 @@ | ||||
| (ffi/defbind sixints-fn six-ints []) | ||||
| (ffi/defbind sixints-fn-2 :int [x :int s six-ints]) | ||||
| (ffi/defbind sixints-fn-3 :int [s six-ints x :int]) | ||||
| (ffi/defbind stack-spill-fn intint | ||||
|              [a :u8 b :u8 c :u8 d :u8 | ||||
|               e :u8 f :u8 g :u8 h :u8 | ||||
|               i :float j :float k :float l :float | ||||
|               m :float n :float o :float p :float | ||||
|               s1 :float s2 :s8 s3 :u8 s4 :double s5 :u8 s6 intint]) | ||||
| (ffi/defbind stack-spill-fn-2 :double [a uint64pair b uint64pair c uint64pair d :s8 e uint64pair f :s8]) | ||||
| (ffi/defbind-alias int-fn int-fn-aliased :int [a :int b :int]) | ||||
|  | ||||
| # | ||||
| # Struct reading and writing | ||||
| @@ -119,6 +129,7 @@ | ||||
| (tracev (return-struct 42)) | ||||
| (tracev (double-lots 1 2 3 4 5 6 700 800 9 10)) | ||||
| (tracev (struct-big 11 99.5)) | ||||
| (tracev (int-fn-aliased 10 20)) | ||||
|  | ||||
| (assert (= [10 10 12 12] (split-ret-fn 10 12))) | ||||
| (assert (= [12 12 10 10] (split-flip-ret-fn 10 12))) | ||||
| @@ -130,5 +141,10 @@ | ||||
| (assert (= 21 (math/round (double-many 1 2 3 4 5 6.01)))) | ||||
| (assert (= 19 (double-lots 1 2 3 4 5 6 7 8 9 10))) | ||||
| (assert (= 204 (float-fn 8 4 17))) | ||||
| (assert (= [0 38534415] (stack-spill-fn | ||||
|                           0 0 0 0 0 0 0 0 | ||||
|                           0 0 0 0 0 0 0 0 | ||||
|                           1.5 -32 196 65536.5 3 [-15 32]))) | ||||
| (assert (= -2806 (stack-spill-fn-2 [2 3] [5 7] [9 11] -19 [13 17] -23))) | ||||
|  | ||||
| (print "Done.") | ||||
|   | ||||
							
								
								
									
										5
									
								
								examples/posix-exec.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								examples/posix-exec.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # Switch to python | ||||
|  | ||||
| (print "running in Janet") | ||||
| (os/posix-exec ["python"] :p) | ||||
| (print "will not print") | ||||
							
								
								
									
										1
									
								
								examples/sample-bundle-aliases/aliases-mod.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/sample-bundle-aliases/aliases-mod.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| (defn fun [x] (range x)) | ||||
							
								
								
									
										3
									
								
								examples/sample-bundle-aliases/bundle.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-bundle-aliases/bundle.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "aliases-mod.janet")) | ||||
							
								
								
									
										4
									
								
								examples/sample-bundle-aliases/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/sample-bundle-aliases/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| @{ | ||||
|   :name "sample-bundle-aliases" | ||||
|   :dependencies ["sample-dep1" "sample-dep2"] | ||||
| } | ||||
							
								
								
									
										4
									
								
								examples/sample-bundle/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/sample-bundle/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| @{ | ||||
|   :name "sample-bundle" | ||||
|   :dependencies ["sample-dep1" "sample-dep2"] | ||||
| } | ||||
							
								
								
									
										3
									
								
								examples/sample-bundle/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-bundle/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "mymod.janet")) | ||||
							
								
								
									
										7
									
								
								examples/sample-bundle/mymod.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/sample-bundle/mymod.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| (import dep1) | ||||
| (import dep2) | ||||
|  | ||||
| (defn myfn | ||||
|   [x] | ||||
|   (def y (dep2/function x)) | ||||
|   (dep1/function y)) | ||||
							
								
								
									
										4
									
								
								examples/sample-dep1/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/sample-dep1/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| @{ | ||||
|   :name "sample-dep1" | ||||
|   :dependencies ["sample-dep2"] | ||||
| } | ||||
							
								
								
									
										3
									
								
								examples/sample-dep1/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep1/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "dep1.janet")) | ||||
							
								
								
									
										3
									
								
								examples/sample-dep1/dep1.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep1/dep1.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn function | ||||
|   [x] | ||||
|   (+ x x)) | ||||
							
								
								
									
										3
									
								
								examples/sample-dep2/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep2/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| @{ | ||||
|   :name "sample-dep2" | ||||
| } | ||||
							
								
								
									
										3
									
								
								examples/sample-dep2/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep2/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "dep2.janet")) | ||||
							
								
								
									
										3
									
								
								examples/sample-dep2/dep2.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep2/dep2.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn function | ||||
|   [x] | ||||
|   (* x x)) | ||||
							
								
								
									
										67
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								meson.build
									
									
									
									
									
								
							| @@ -20,7 +20,7 @@ | ||||
|  | ||||
| project('janet', 'c', | ||||
|   default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], | ||||
|   version : '1.31.0') | ||||
|   version : '1.35.2') | ||||
|  | ||||
| # Global settings | ||||
| janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') | ||||
| @@ -61,6 +61,7 @@ conf.set('JANET_NO_SOURCEMAPS', not get_option('sourcemaps')) | ||||
| conf.set('JANET_NO_ASSEMBLER', not get_option('assembler')) | ||||
| conf.set('JANET_NO_PEG', not get_option('peg')) | ||||
| conf.set('JANET_NO_NET', not get_option('net')) | ||||
| conf.set('JANET_NO_IPV6', not get_option('ipv6')) | ||||
| conf.set('JANET_NO_EV', not get_option('ev') or get_option('single_threaded')) | ||||
| conf.set('JANET_REDUCED_OS', get_option('reduced_os')) | ||||
| conf.set('JANET_NO_INT_TYPES', not get_option('int_types')) | ||||
| @@ -78,6 +79,8 @@ conf.set('JANET_EV_NO_KQUEUE', not get_option('kqueue')) | ||||
| conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt')) | ||||
| conf.set('JANET_NO_FFI', not get_option('ffi')) | ||||
| conf.set('JANET_NO_FFI_JIT', not get_option('ffi_jit')) | ||||
| conf.set('JANET_NO_FILEWATCH', not get_option('filewatch')) | ||||
| conf.set('JANET_NO_CRYPTORAND', not get_option('cryptorand')) | ||||
| if get_option('os_name') != '' | ||||
|   conf.set('JANET_OS_NAME', get_option('os_name')) | ||||
| endif | ||||
| @@ -120,6 +123,7 @@ core_src = [ | ||||
|   'src/core/ev.c', | ||||
|   'src/core/ffi.c', | ||||
|   'src/core/fiber.c', | ||||
|   'src/core/filewatch.c', | ||||
|   'src/core/gc.c', | ||||
|   'src/core/inttypes.c', | ||||
|   'src/core/io.c', | ||||
| @@ -182,32 +186,41 @@ 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'] | ||||
| else | ||||
|   lib_cflags = [] | ||||
| endif | ||||
| libjanet = library('janet', janetc, | ||||
|   include_directories : incdir, | ||||
|   dependencies : janet_dependencies, | ||||
|   version: meson.project_version(), | ||||
|   soversion: version_parts[0] + '.' + version_parts[1], | ||||
|   c_args : lib_cflags, | ||||
|   install : true) | ||||
|  | ||||
| if get_option('shared') | ||||
|   libjanet = library('janet', janetc, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     version: meson.project_version(), | ||||
|     soversion: version_parts[0] + '.' + version_parts[1], | ||||
|     c_args : lib_cflags, | ||||
|     install : true) | ||||
| # Extra c flags - adding -fvisibility=hidden matches the Makefile and | ||||
| # shaves off about 10k on linux x64, likely similar on other platforms. | ||||
| if cc.has_argument('-fvisibility=hidden') | ||||
|   extra_cflags = ['-fvisibility=hidden', '-DJANET_DLL_IMPORT'] | ||||
|   if cc.has_argument('-fvisibility=hidden') | ||||
|     extra_cflags = ['-fvisibility=hidden', '-DJANET_DLL_IMPORT'] | ||||
|   else | ||||
|     extra_cflags = ['-DJANET_DLL_IMPORT'] | ||||
|   endif | ||||
|   janet_mainclient = executable('janet', mainclient_src, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     link_with: [libjanet], | ||||
|     c_args : extra_cflags, | ||||
|     install : true) | ||||
| else | ||||
|   extra_cflags = ['-DJANET_DLL_IMPORT'] | ||||
|   # No shared library | ||||
|   janet_mainclient = executable('janet', mainclient_src, janetc, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     c_args : lib_cflags, | ||||
|     install : true) | ||||
| endif | ||||
| janet_mainclient = executable('janet', mainclient_src, | ||||
|   include_directories : incdir, | ||||
|   dependencies : janet_dependencies, | ||||
|   link_with: [libjanet], | ||||
|   c_args : extra_cflags, | ||||
|   install : true) | ||||
|  | ||||
| if meson.is_cross_build() | ||||
|   native_cc = meson.get_compiler('c', native: true) | ||||
| @@ -238,6 +251,7 @@ test_files = [ | ||||
|   'test/suite-asm.janet', | ||||
|   'test/suite-boot.janet', | ||||
|   'test/suite-buffer.janet', | ||||
|   'test/suite-bundle.janet', | ||||
|   'test/suite-capi.janet', | ||||
|   'test/suite-cfuns.janet', | ||||
|   'test/suite-compile.janet', | ||||
| @@ -245,6 +259,7 @@ test_files = [ | ||||
|   'test/suite-debug.janet', | ||||
|   'test/suite-ev.janet', | ||||
|   'test/suite-ffi.janet', | ||||
|   'test/suite-filewatch.janet', | ||||
|   'test/suite-inttypes.janet', | ||||
|   'test/suite-io.janet', | ||||
|   'test/suite-marsh.janet', | ||||
| @@ -259,6 +274,7 @@ test_files = [ | ||||
|   'test/suite-struct.janet', | ||||
|   'test/suite-symcache.janet', | ||||
|   'test/suite-table.janet', | ||||
|   'test/suite-tuple.janet', | ||||
|   'test/suite-unknown.janet', | ||||
|   'test/suite-value.janet', | ||||
|   'test/suite-vm.janet' | ||||
| @@ -271,14 +287,15 @@ endforeach | ||||
| run_target('repl', command : [janet_nativeclient]) | ||||
|  | ||||
| # For use as meson subproject (wrap) | ||||
| janet_dep = declare_dependency(include_directories : incdir, | ||||
|   link_with : libjanet) | ||||
|  | ||||
| if get_option('shared') | ||||
|   janet_dep = declare_dependency(include_directories : incdir, | ||||
|     link_with : libjanet) | ||||
| # pkgconfig | ||||
| pkg = import('pkgconfig') | ||||
| pkg.generate(libjanet, | ||||
|   subdirs: 'janet', | ||||
|   description: 'Library for the Janet programming language.') | ||||
|   pkg = import('pkgconfig') | ||||
|   pkg.generate(libjanet, | ||||
|     subdirs: 'janet', | ||||
|     description: 'Library for the Janet programming language.') | ||||
| endif | ||||
|  | ||||
| # Installation | ||||
| install_man('janet.1') | ||||
|   | ||||
| @@ -11,16 +11,18 @@ option('peg', type : 'boolean', value : true) | ||||
| option('int_types', type : 'boolean', value : true) | ||||
| option('prf', type : 'boolean', value : false) | ||||
| option('net', type : 'boolean', value : true) | ||||
| option('ipv6', type : 'boolean', value : true) | ||||
| option('ev', type : 'boolean', value : true) | ||||
| option('processes', type : 'boolean', value : true) | ||||
| option('umask', type : 'boolean', value : true) | ||||
| option('realpath', type : 'boolean', value : true) | ||||
| option('simple_getline', type : 'boolean', value : false) | ||||
| option('epoll', type : 'boolean', value : false) | ||||
| option('kqueue', type : 'boolean', value : false) | ||||
| option('epoll', type : 'boolean', value : true) | ||||
| option('kqueue', type : 'boolean', value : true) | ||||
| option('interpreter_interrupt', type : 'boolean', value : true) | ||||
| option('ffi', type : 'boolean', value : true) | ||||
| option('ffi_jit', type : 'boolean', value : true) | ||||
| option('filewatch', type : 'boolean', value : true) | ||||
|  | ||||
| option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024) | ||||
| option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200) | ||||
| @@ -29,3 +31,5 @@ option('stack_max', type : 'integer', min : 8096, max : 0x7fffffff, value : 0x7f | ||||
|  | ||||
| option('arch_name', type : 'string', value: '') | ||||
| option('os_name', type : 'string', value: '') | ||||
| option('shared', type : 'boolean', value: true) | ||||
| option('cryptorand', type : 'boolean', value: true) | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| #include <janet.h> | ||||
| #include <assert.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <math.h> | ||||
|  | ||||
| #include "tests.h" | ||||
| @@ -35,6 +35,11 @@ int system_test() { | ||||
|     assert(sizeof(void *) == 8); | ||||
| #endif | ||||
|  | ||||
|     /* Check the version defines are self consistent */ | ||||
|     char version_combined[256]; | ||||
|     sprintf(version_combined, "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA); | ||||
|     assert(!strcmp(JANET_VERSION, version_combined)); | ||||
|  | ||||
|     /* Reflexive testing and nanbox testing */ | ||||
|     assert(janet_equals(janet_wrap_nil(), janet_wrap_nil())); | ||||
|     assert(janet_equals(janet_wrap_false(), janet_wrap_false())); | ||||
|   | ||||
| @@ -4,10 +4,10 @@ | ||||
| #define JANETCONF_H | ||||
|  | ||||
| #define JANET_VERSION_MAJOR 1 | ||||
| #define JANET_VERSION_MINOR 31 | ||||
| #define JANET_VERSION_MINOR 36 | ||||
| #define JANET_VERSION_PATCH 0 | ||||
| #define JANET_VERSION_EXTRA "" | ||||
| #define JANET_VERSION "1.31.0" | ||||
| #define JANET_VERSION_EXTRA "-dev" | ||||
| #define JANET_VERSION "1.36.0-dev" | ||||
|  | ||||
| /* #define JANET_BUILD "local" */ | ||||
|  | ||||
| @@ -29,6 +29,7 @@ | ||||
| /* #define JANET_NO_NET */ | ||||
| /* #define JANET_NO_INT_TYPES */ | ||||
| /* #define JANET_NO_EV */ | ||||
| /* #define JANET_NO_FILEWATCH */ | ||||
| /* #define JANET_NO_REALPATH */ | ||||
| /* #define JANET_NO_SYMLINKS */ | ||||
| /* #define JANET_NO_UMASK */ | ||||
| @@ -52,6 +53,9 @@ | ||||
| /* #define JANET_EV_NO_EPOLL */ | ||||
| /* #define JANET_EV_NO_KQUEUE */ | ||||
| /* #define JANET_NO_INTERPRETER_INTERRUPT */ | ||||
| /* #define JANET_NO_IPV6 */ | ||||
| /* #define JANET_NO_CRYPTORAND */ | ||||
| /* #define JANET_USE_STDATOMIC */ | ||||
|  | ||||
| /* Custom vm allocator support */ | ||||
| /* #include <mimalloc.h> */ | ||||
|   | ||||
| @@ -31,8 +31,6 @@ | ||||
| #ifdef JANET_EV | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #else | ||||
| #include <stdatomic.h> | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -275,6 +275,31 @@ JANET_CORE_FN(cfun_array_concat, | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_join, | ||||
|               "(array/join arr & parts)", | ||||
|               "Join a variable number of arrays and tuples into the first argument, " | ||||
|               "which must be an array. " | ||||
|               "Return the modified array `arr`.") { | ||||
|     int32_t i; | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetArray *array = janet_getarray(argv, 0); | ||||
|     for (i = 1; i < argc; i++) { | ||||
|         int32_t j, len = 0; | ||||
|         const Janet *vals = NULL; | ||||
|         if (!janet_indexed_view(argv[i], &vals, &len)) { | ||||
|             janet_panicf("expected indexed type for argument %d, got %v", i, argv[i]); | ||||
|         } | ||||
|         if (array->data == vals) { | ||||
|             int32_t newcount = array->count + len; | ||||
|             janet_array_ensure(array, newcount, 2); | ||||
|             janet_indexed_view(argv[i], &vals, &len); | ||||
|         } | ||||
|         for (j = 0; j < len; j++) | ||||
|             janet_array_push(array, vals[j]); | ||||
|     } | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_insert, | ||||
|               "(array/insert arr at & xs)", | ||||
|               "Insert all `xs` into array `arr` at index `at`. `at` should be an integer between " | ||||
| @@ -385,6 +410,7 @@ void janet_lib_array(JanetTable *env) { | ||||
|         JANET_CORE_REG("array/remove", cfun_array_remove), | ||||
|         JANET_CORE_REG("array/trim", cfun_array_trim), | ||||
|         JANET_CORE_REG("array/clear", cfun_array_clear), | ||||
|         JANET_CORE_REG("array/join", cfun_array_join), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, array_cfuns); | ||||
|   | ||||
| @@ -560,6 +560,9 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
|     x = janet_get1(s, janet_ckeywordv("vararg")); | ||||
|     if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_VARARG; | ||||
|  | ||||
|     /* Initialize slotcount */ | ||||
|     def->slotcount = !!(def->flags & JANET_FUNCDEF_FLAG_VARARG) + def->arity; | ||||
|  | ||||
|     /* Check structarg */ | ||||
|     x = janet_get1(s, janet_ckeywordv("structarg")); | ||||
|     if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG; | ||||
| @@ -784,8 +787,9 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
|     } | ||||
|  | ||||
|     /* Verify the func def */ | ||||
|     if (janet_verify(def)) { | ||||
|         janet_asm_error(&a, "invalid assembly"); | ||||
|     int verify_status = janet_verify(def); | ||||
|     if (verify_status) { | ||||
|         janet_asm_errorv(&a, janet_formatc("invalid assembly (%d)", verify_status)); | ||||
|     } | ||||
|  | ||||
|     /* Add final flags */ | ||||
|   | ||||
| @@ -135,8 +135,7 @@ void janet_buffer_extra(JanetBuffer *buffer, int32_t n) { | ||||
|  | ||||
| /* Push a cstring to buffer */ | ||||
| void janet_buffer_push_cstring(JanetBuffer *buffer, const char *cstring) { | ||||
|     int32_t len = 0; | ||||
|     while (cstring[len]) ++len; | ||||
|     int32_t len = (int32_t) strlen(cstring); | ||||
|     janet_buffer_push_bytes(buffer, (const uint8_t *) cstring, len); | ||||
| } | ||||
|  | ||||
| @@ -321,6 +320,143 @@ JANET_CORE_FN(cfun_buffer_chars, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| static int should_reverse_bytes(const Janet *argv, int32_t argc) { | ||||
|     JanetKeyword order_kw = janet_getkeyword(argv, argc); | ||||
|     if (!janet_cstrcmp(order_kw, "le")) { | ||||
| #if JANET_BIG_ENDIAN | ||||
|         return 1; | ||||
| #endif | ||||
|     } else if (!janet_cstrcmp(order_kw, "be")) { | ||||
| #if JANET_LITTLE_ENDIAN | ||||
|         return 1; | ||||
| #endif | ||||
|     } else if (!janet_cstrcmp(order_kw, "native")) { | ||||
|         return 0; | ||||
|     } else { | ||||
|         janet_panicf("expected endianness :le, :be or :native, got %v", argv[1]); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static void reverse_u32(uint8_t bytes[4]) { | ||||
|     uint8_t temp; | ||||
|     temp = bytes[3]; | ||||
|     bytes[3] = bytes[0]; | ||||
|     bytes[0] = temp; | ||||
|     temp = bytes[2]; | ||||
|     bytes[2] = bytes[1]; | ||||
|     bytes[1] = temp; | ||||
| } | ||||
|  | ||||
| static void reverse_u64(uint8_t bytes[8]) { | ||||
|     uint8_t temp; | ||||
|     temp = bytes[7]; | ||||
|     bytes[7] = bytes[0]; | ||||
|     bytes[0] = temp; | ||||
|     temp = bytes[6]; | ||||
|     bytes[6] = bytes[1]; | ||||
|     bytes[1] = temp; | ||||
|     temp = bytes[5]; | ||||
|     bytes[5] = bytes[2]; | ||||
|     bytes[2] = temp; | ||||
|     temp = bytes[4]; | ||||
|     bytes[4] = bytes[3]; | ||||
|     bytes[3] = temp; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_uint16, | ||||
|               "(buffer/push-uint16 buffer order data)", | ||||
|               "Push a 16 bit unsigned integer data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     union { | ||||
|         uint16_t data; | ||||
|         uint8_t bytes[2]; | ||||
|     } u; | ||||
|     u.data = janet_getuinteger16(argv, 2); | ||||
|     if (reverse) { | ||||
|         uint8_t temp = u.bytes[1]; | ||||
|         u.bytes[1] = u.bytes[0]; | ||||
|         u.bytes[0] = temp; | ||||
|     } | ||||
|     janet_buffer_push_u16(buffer, *(uint16_t *) u.bytes); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_uint32, | ||||
|               "(buffer/push-uint32 buffer order data)", | ||||
|               "Push a 32 bit unsigned integer data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     union { | ||||
|         uint32_t data; | ||||
|         uint8_t bytes[4]; | ||||
|     } u; | ||||
|     u.data = janet_getuinteger(argv, 2); | ||||
|     if (reverse) | ||||
|         reverse_u32(u.bytes); | ||||
|     janet_buffer_push_u32(buffer, *(uint32_t *) u.bytes); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_uint64, | ||||
|               "(buffer/push-uint64 buffer order data)", | ||||
|               "Push a 64 bit unsigned integer data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     union { | ||||
|         uint64_t data; | ||||
|         uint8_t bytes[8]; | ||||
|     } u; | ||||
|     u.data = janet_getuinteger64(argv, 2); | ||||
|     if (reverse) | ||||
|         reverse_u64(u.bytes); | ||||
|     janet_buffer_push_u64(buffer, *(uint64_t *) u.bytes); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_float32, | ||||
|               "(buffer/push-float32 buffer order data)", | ||||
|               "Push the underlying bytes of a 32 bit float data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     union { | ||||
|         float data; | ||||
|         uint8_t bytes[4]; | ||||
|     } u; | ||||
|     u.data = (float) janet_getnumber(argv, 2); | ||||
|     if (reverse) | ||||
|         reverse_u32(u.bytes); | ||||
|     janet_buffer_push_u32(buffer, *(uint32_t *) u.bytes); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_float64, | ||||
|               "(buffer/push-float64 buffer order data)", | ||||
|               "Push the underlying bytes of a 64 bit float data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     union { | ||||
|         double data; | ||||
|         uint8_t bytes[8]; | ||||
|     } u; | ||||
|     u.data = janet_getnumber(argv, 2); | ||||
|     if (reverse) | ||||
|         reverse_u64(u.bytes); | ||||
|     janet_buffer_push_u64(buffer, *(uint64_t *) u.bytes); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| static void buffer_push_impl(JanetBuffer *buffer, Janet *argv, int32_t argc_offset, int32_t argc) { | ||||
|     for (int32_t i = argc_offset; i < argc; i++) { | ||||
|         if (janet_checktype(argv[i], JANET_NUMBER)) { | ||||
| @@ -519,6 +655,27 @@ JANET_CORE_FN(cfun_buffer_format, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_format_at, | ||||
|               "(buffer/format-at buffer at format & args)", | ||||
|               "Snprintf like functionality for printing values into a buffer. Returns " | ||||
|               "the modified buffer.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int32_t at = janet_getinteger(argv, 1); | ||||
|     if (at < 0) { | ||||
|         at += buffer->count + 1; | ||||
|     } | ||||
|     if (at > buffer->count || at < 0) janet_panicf("expected index at to be in range [0, %d), got %d", buffer->count, at); | ||||
|     int32_t oldcount = buffer->count; | ||||
|     buffer->count = at; | ||||
|     const char *strfrmt = (const char *) janet_getstring(argv, 2); | ||||
|     janet_buffer_format(buffer, strfrmt, 2, argc, argv); | ||||
|     if (buffer->count < oldcount) { | ||||
|         buffer->count = oldcount; | ||||
|     } | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| void janet_lib_buffer(JanetTable *env) { | ||||
|     JanetRegExt buffer_cfuns[] = { | ||||
|         JANET_CORE_REG("buffer/new", cfun_buffer_new), | ||||
| @@ -529,6 +686,11 @@ void janet_lib_buffer(JanetTable *env) { | ||||
|         JANET_CORE_REG("buffer/push-byte", cfun_buffer_u8), | ||||
|         JANET_CORE_REG("buffer/push-word", cfun_buffer_word), | ||||
|         JANET_CORE_REG("buffer/push-string", cfun_buffer_chars), | ||||
|         JANET_CORE_REG("buffer/push-uint16", cfun_buffer_push_uint16), | ||||
|         JANET_CORE_REG("buffer/push-uint32", cfun_buffer_push_uint32), | ||||
|         JANET_CORE_REG("buffer/push-uint64", cfun_buffer_push_uint64), | ||||
|         JANET_CORE_REG("buffer/push-float32", cfun_buffer_push_float32), | ||||
|         JANET_CORE_REG("buffer/push-float64", cfun_buffer_push_float64), | ||||
|         JANET_CORE_REG("buffer/push", cfun_buffer_push), | ||||
|         JANET_CORE_REG("buffer/push-at", cfun_buffer_push_at), | ||||
|         JANET_CORE_REG("buffer/popn", cfun_buffer_popn), | ||||
| @@ -540,6 +702,7 @@ void janet_lib_buffer(JanetTable *env) { | ||||
|         JANET_CORE_REG("buffer/bit-toggle", cfun_buffer_bittoggle), | ||||
|         JANET_CORE_REG("buffer/blit", cfun_buffer_blit), | ||||
|         JANET_CORE_REG("buffer/format", cfun_buffer_format), | ||||
|         JANET_CORE_REG("buffer/format-at", cfun_buffer_format_at), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, buffer_cfuns); | ||||
|   | ||||
| @@ -140,7 +140,7 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) { | ||||
|                 /* relative pc is in DS field of instruction */ | ||||
|                 old_jump_target = i + (((int32_t)instr) >> 8); | ||||
|                 new_jump_target = pc_map[old_jump_target]; | ||||
|                 instr += (new_jump_target - old_jump_target + (i - j)) << 8; | ||||
|                 instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 8; | ||||
|                 break; | ||||
|             case JOP_JUMP_IF: | ||||
|             case JOP_JUMP_IF_NIL: | ||||
| @@ -149,7 +149,7 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) { | ||||
|                 /* relative pc is in ES field of instruction */ | ||||
|                 old_jump_target = i + (((int32_t)instr) >> 16); | ||||
|                 new_jump_target = pc_map[old_jump_target]; | ||||
|                 instr += (new_jump_target - old_jump_target + (i - j)) << 16; | ||||
|                 instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 16; | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
| @@ -226,6 +226,7 @@ void janet_bytecode_movopt(JanetFuncDef *def) { | ||||
|                 case JOP_LOAD_TRUE: | ||||
|                 case JOP_LOAD_FALSE: | ||||
|                 case JOP_LOAD_SELF: | ||||
|                     break; | ||||
|                 case JOP_MAKE_ARRAY: | ||||
|                 case JOP_MAKE_BUFFER: | ||||
|                 case JOP_MAKE_STRING: | ||||
| @@ -233,6 +234,8 @@ void janet_bytecode_movopt(JanetFuncDef *def) { | ||||
|                 case JOP_MAKE_TABLE: | ||||
|                 case JOP_MAKE_TUPLE: | ||||
|                 case JOP_MAKE_BRACKET_TUPLE: | ||||
|                     /* Reads from the stack, don't remove */ | ||||
|                     janetc_regalloc_touch(&ra, DD); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read A */ | ||||
|   | ||||
| @@ -25,6 +25,7 @@ | ||||
| #include <janet.h> | ||||
| #include "state.h" | ||||
| #include "fiber.h" | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| #ifndef JANET_SINGLE_THREADED | ||||
| @@ -35,6 +36,13 @@ | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_USE_STDATOMIC | ||||
| #include <stdatomic.h> | ||||
| /* We don't need stdatomic on most compilers since we use compiler builtins for atomic operations. | ||||
|  * Some (TCC), explicitly require using stdatomic.h and don't have any exposed builtins (that I know of). | ||||
|  * For TCC and similar compilers, one would need -std=c11 or similar then to get access. */ | ||||
| #endif | ||||
|  | ||||
| JANET_NO_RETURN static void janet_top_level_signal(const char *msg) { | ||||
| #ifdef JANET_TOP_LEVEL_SIGNAL | ||||
|     JANET_TOP_LEVEL_SIGNAL(msg); | ||||
| @@ -296,11 +304,28 @@ int32_t janet_getinteger(const Janet *argv, int32_t n) { | ||||
| uint32_t janet_getuinteger(const Janet *argv, int32_t n) { | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkuint(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 32 bit signed integer, got %v", n, x); | ||||
|         janet_panicf("bad slot #%d, expected 32 bit unsigned integer, got %v", n, x); | ||||
|     } | ||||
|     return janet_unwrap_integer(x); | ||||
|     return (uint32_t) janet_unwrap_number(x); | ||||
| } | ||||
|  | ||||
| int16_t janet_getinteger16(const Janet *argv, int32_t n) { | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkint16(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 16 bit signed integer, got %v", n, x); | ||||
|     } | ||||
|     return (int16_t) janet_unwrap_number(x); | ||||
| } | ||||
|  | ||||
| uint16_t janet_getuinteger16(const Janet *argv, int32_t n) { | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkuint16(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 16 bit unsigned integer, got %v", n, x); | ||||
|     } | ||||
|     return (uint16_t) janet_unwrap_number(x); | ||||
| } | ||||
|  | ||||
|  | ||||
| int64_t janet_getinteger64(const Janet *argv, int32_t n) { | ||||
| #ifdef JANET_INT_TYPES | ||||
|     return janet_unwrap_s64(argv[n]); | ||||
| @@ -338,7 +363,7 @@ int32_t janet_gethalfrange(const Janet *argv, int32_t n, int32_t length, const c | ||||
|     int32_t not_raw = raw; | ||||
|     if (not_raw < 0) not_raw += length + 1; | ||||
|     if (not_raw < 0 || not_raw > length) | ||||
|         janet_panicf("%s index %d out of range [%d,%d]", which, raw, -length - 1, length); | ||||
|         janet_panicf("%s index %d out of range [%d,%d]", which, (int64_t) raw, -(int64_t)length - 1, (int64_t) length); | ||||
|     return not_raw; | ||||
| } | ||||
|  | ||||
| @@ -361,7 +386,7 @@ int32_t janet_getargindex(const Janet *argv, int32_t n, int32_t length, const ch | ||||
|     int32_t not_raw = raw; | ||||
|     if (not_raw < 0) not_raw += length; | ||||
|     if (not_raw < 0 || not_raw > length) | ||||
|         janet_panicf("%s index %d out of range [%d,%d)", which, raw, -length, length); | ||||
|         janet_panicf("%s index %d out of range [%d,%d)", which, (int64_t)raw, -(int64_t)length, (int64_t)length); | ||||
|     return not_raw; | ||||
| } | ||||
|  | ||||
| @@ -439,6 +464,33 @@ void janet_setdyn(const char *name, Janet value) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Create a function that when called, returns X. Trivial in Janet, a pain in C. */ | ||||
| JanetFunction *janet_thunk_delay(Janet x) { | ||||
|     static const uint32_t bytecode[] = { | ||||
|         JOP_LOAD_CONSTANT, | ||||
|         JOP_RETURN | ||||
|     }; | ||||
|     JanetFuncDef *def = janet_funcdef_alloc(); | ||||
|     def->arity = 0; | ||||
|     def->min_arity = 0; | ||||
|     def->max_arity = INT32_MAX; | ||||
|     def->flags = JANET_FUNCDEF_FLAG_VARARG; | ||||
|     def->slotcount = 1; | ||||
|     def->bytecode = janet_malloc(sizeof(bytecode)); | ||||
|     def->bytecode_length = (int32_t)(sizeof(bytecode) / sizeof(uint32_t)); | ||||
|     def->constants = janet_malloc(sizeof(Janet)); | ||||
|     def->constants_length = 1; | ||||
|     def->name = NULL; | ||||
|     if (!def->bytecode || !def->constants) { | ||||
|         JANET_OUT_OF_MEMORY; | ||||
|     } | ||||
|     def->constants[0] = x; | ||||
|     memcpy(def->bytecode, bytecode, sizeof(bytecode)); | ||||
|     janet_def_addflags(def); | ||||
|     /* janet_verify(def); */ | ||||
|     return janet_thunk(def); | ||||
| } | ||||
|  | ||||
| uint64_t janet_getflags(const Janet *argv, int32_t n, const char *flags) { | ||||
|     uint64_t ret = 0; | ||||
|     const uint8_t *keyw = janet_getkeyword(argv, n); | ||||
| @@ -496,6 +548,8 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA | ||||
| JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     return InterlockedIncrement(x); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1; | ||||
| #else | ||||
|     return __atomic_add_fetch(x, 1, __ATOMIC_RELAXED); | ||||
| #endif | ||||
| @@ -504,8 +558,20 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { | ||||
| JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     return InterlockedDecrement(x); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1; | ||||
| #else | ||||
|     return __atomic_add_fetch(x, -1, __ATOMIC_RELAXED); | ||||
|     return __atomic_add_fetch(x, -1, __ATOMIC_ACQ_REL); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     return InterlockedOr(x, 0); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_load_explicit(x, memory_order_acquire); | ||||
| #else | ||||
|     return __atomic_load_n(x, __ATOMIC_ACQUIRE); | ||||
| #endif | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -934,7 +934,7 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { | ||||
|         int32_t slotchunks = (def->slotcount + 31) >> 5; | ||||
|         /* numchunks is min of slotchunks and scope->ua.count */ | ||||
|         int32_t numchunks = slotchunks > scope->ua.count ? scope->ua.count : slotchunks; | ||||
|         uint32_t *chunks = janet_calloc(sizeof(uint32_t), slotchunks); | ||||
|         uint32_t *chunks = janet_calloc(slotchunks, sizeof(uint32_t)); | ||||
|         if (NULL == chunks) { | ||||
|             JANET_OUT_OF_MEMORY; | ||||
|         } | ||||
| @@ -1056,7 +1056,7 @@ JanetCompileResult janet_compile_lint(Janet source, | ||||
|  | ||||
|     if (c.result.status == JANET_COMPILE_OK) { | ||||
|         JanetFuncDef *def = janetc_pop_funcdef(&c); | ||||
|         def->name = janet_cstring("_thunk"); | ||||
|         def->name = janet_cstring("thunk"); | ||||
|         janet_def_addflags(def); | ||||
|         c.result.funcdef = def; | ||||
|     } else { | ||||
|   | ||||
| @@ -262,7 +262,7 @@ void janetc_popscope(JanetCompiler *c); | ||||
| void janetc_popscope_keepslot(JanetCompiler *c, JanetSlot retslot); | ||||
| JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c); | ||||
|  | ||||
| /* Create a destory slots */ | ||||
| /* Create a destroy slot */ | ||||
| JanetSlot janetc_cslot(Janet x); | ||||
|  | ||||
| /* Search for a symbol */ | ||||
|   | ||||
| @@ -69,15 +69,15 @@ JanetModule janet_native(const char *name, const uint8_t **error) { | ||||
|             host.minor < modconf.minor || | ||||
|             host.bits != modconf.bits) { | ||||
|         char errbuf[128]; | ||||
|         sprintf(errbuf, "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", | ||||
|                 host.major, | ||||
|                 host.minor, | ||||
|                 host.patch, | ||||
|                 host.bits, | ||||
|                 modconf.major, | ||||
|                 modconf.minor, | ||||
|                 modconf.patch, | ||||
|                 modconf.bits); | ||||
|         snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", | ||||
|                  host.major, | ||||
|                  host.minor, | ||||
|                  host.patch, | ||||
|                  host.bits, | ||||
|                  modconf.major, | ||||
|                  modconf.minor, | ||||
|                  modconf.patch, | ||||
|                  modconf.bits); | ||||
|         *error = janet_cstring(errbuf); | ||||
|         return NULL; | ||||
|     } | ||||
| @@ -110,14 +110,14 @@ JANET_CORE_FN(janet_core_expand_path, | ||||
|               "(module/expand-path path template)", | ||||
|               "Expands a path template as found in `module/paths` for `module/find`. " | ||||
|               "This takes in a path (the argument to require) and a template string, " | ||||
|               "to expand the path to a path that can be " | ||||
|               "used for importing files. The replacements are as follows:\n\n" | ||||
|               "to expand the path to a path that can be used for importing files. " | ||||
|               "The replacements are as follows:\n\n" | ||||
|               "* :all: -- the value of path verbatim.\n\n" | ||||
|               "* :@all: -- Same as :all:, but if `path` starts with the @ character,\n" | ||||
|               "           the first path segment is replaced with a dynamic binding\n" | ||||
|               "           `(dyn <first path segment as keyword>)`.\n\n" | ||||
|               "* :cur: -- the current file, or (dyn :current-file)\n\n" | ||||
|               "* :dir: -- the directory containing the current file\n\n" | ||||
|               "* :@all: -- Same as :all:, but if `path` starts with the @ character, " | ||||
|               "the first path segment is replaced with a dynamic binding " | ||||
|               "`(dyn <first path segment as keyword>)`.\n\n" | ||||
|               "* :cur: -- the directory portion, if any, of (dyn :current-file)\n\n" | ||||
|               "* :dir: -- the directory portion, if any, of the path argument\n\n" | ||||
|               "* :name: -- the name component of path, with extension if given\n\n" | ||||
|               "* :native: -- the extension used to load natives, .so or .dll\n\n" | ||||
|               "* :sys: -- the system path, or (dyn :syspath)") { | ||||
| @@ -432,27 +432,38 @@ JANET_CORE_FN(janet_core_range, | ||||
|               "With one argument, returns a range [0, end). With two arguments, returns " | ||||
|               "a range [start, end). With three, returns a range with optional step size.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     int32_t start = 0, stop = 0, step = 1, count = 0; | ||||
|     double start = 0, stop = 0, step = 1, count = 0; | ||||
|     if (argc == 3) { | ||||
|         start = janet_getinteger(argv, 0); | ||||
|         stop = janet_getinteger(argv, 1); | ||||
|         step = janet_getinteger(argv, 2); | ||||
|         count = (step > 0) ? (stop - start - 1) / step + 1 : | ||||
|                 ((step < 0) ? (stop - start + 1) / step + 1 : 0); | ||||
|         start = janet_getnumber(argv, 0); | ||||
|         stop = janet_getnumber(argv, 1); | ||||
|         step = janet_getnumber(argv, 2); | ||||
|         count = (step > 0) ? (stop - start) / step : | ||||
|                 ((step < 0) ? (stop - start) / step : 0); | ||||
|     } else if (argc == 2) { | ||||
|         start = janet_getinteger(argv, 0); | ||||
|         stop = janet_getinteger(argv, 1); | ||||
|         start = janet_getnumber(argv, 0); | ||||
|         stop = janet_getnumber(argv, 1); | ||||
|         count = stop - start; | ||||
|     } else { | ||||
|         stop = janet_getinteger(argv, 0); | ||||
|         stop = janet_getnumber(argv, 0); | ||||
|         count = stop; | ||||
|     } | ||||
|     count = (count > 0) ? count : 0; | ||||
|     JanetArray *array = janet_array(count); | ||||
|     for (int32_t i = 0; i < count; i++) { | ||||
|         array->data[i] = janet_wrap_number(start + i * step); | ||||
|     int32_t int_count; | ||||
|     if (count > (double) INT32_MAX) { | ||||
|         int_count = INT32_MAX; | ||||
|     } else { | ||||
|         int_count = (int32_t) ceil(count); | ||||
|     } | ||||
|     array->count = count; | ||||
|     if (step > 0.0) { | ||||
|         janet_assert(start + int_count * step >= stop, "bad range code"); | ||||
|     } else { | ||||
|         janet_assert(start + int_count * step <= stop, "bad range code"); | ||||
|     } | ||||
|     JanetArray *array = janet_array(int_count); | ||||
|     for (int32_t i = 0; i < int_count; i++) { | ||||
|         array->data[i] = janet_wrap_number((double) start + (double) i * step); | ||||
|     } | ||||
|     array->count = int_count; | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| @@ -976,7 +987,7 @@ static void make_apply(JanetTable *env) { | ||||
|         /* Push the array */ | ||||
|         S(JOP_PUSH_ARRAY, 5), | ||||
|  | ||||
|         /* Call the funciton */ | ||||
|         /* Call the function */ | ||||
|         S(JOP_TAILCALL, 0) | ||||
|     }; | ||||
|     janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG, | ||||
| @@ -1121,6 +1132,9 @@ static void janet_load_libs(JanetTable *env) { | ||||
| #endif | ||||
| #ifdef JANET_EV | ||||
|     janet_lib_ev(env); | ||||
| #ifdef JANET_FILEWATCH | ||||
|     janet_lib_filewatch(env); | ||||
| #endif | ||||
| #endif | ||||
| #ifdef JANET_NET | ||||
|     janet_lib_net(env); | ||||
| @@ -1144,17 +1158,20 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|                     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 they data structure is not mutated " | ||||
|                          "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. 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" | ||||
|   | ||||
| @@ -102,7 +102,7 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) { | ||||
| } | ||||
|  | ||||
| /* Error reporting. This can be emulated from within Janet, but for | ||||
|  * consitency with the top level code it is defined once. */ | ||||
|  * consistency with the top level code it is defined once. */ | ||||
| void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|  | ||||
|     int32_t fi; | ||||
| @@ -164,7 +164,7 @@ void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|                 } | ||||
|             } | ||||
|             if (frame->flags & JANET_STACKFRAME_TAILCALL) | ||||
|                 janet_eprintf(" (tailcall)"); | ||||
|                 janet_eprintf(" (tail call)"); | ||||
|             if (frame->func && frame->pc) { | ||||
|                 int32_t off = (int32_t)(frame->pc - def->bytecode); | ||||
|                 if (def->sourcemap) { | ||||
| @@ -180,6 +180,11 @@ void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|                 } | ||||
|             } | ||||
|             janet_eprintf("\n"); | ||||
|             /* Print fiber points optionally. Clutters traces but provides info | ||||
|             if (i <= 0 && fi > 0) { | ||||
|                 janet_eprintf("  in parent fiber\n"); | ||||
|             } | ||||
|             */ | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -388,8 +393,8 @@ JANET_CORE_FN(cfun_debug_stack, | ||||
| JANET_CORE_FN(cfun_debug_stacktrace, | ||||
|               "(debug/stacktrace fiber &opt err prefix)", | ||||
|               "Prints a nice looking stacktrace for a fiber. Can optionally provide " | ||||
|               "an error value to print the stack trace with. If `err` is nil or not " | ||||
|               "provided, and no prefix is given, will skip the error line. Returns the fiber.") { | ||||
|               "an error value to print the stack trace with. If `prefix` is nil or not " | ||||
|               "provided, will skip the error line. Returns the fiber.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
|     Janet x = argc == 1 ? janet_wrap_nil() : argv[1]; | ||||
|   | ||||
							
								
								
									
										351
									
								
								src/core/ev.c
									
									
									
									
									
								
							
							
						
						
									
										351
									
								
								src/core/ev.c
									
									
									
									
									
								
							| @@ -74,7 +74,7 @@ typedef struct { | ||||
|     } mode; | ||||
| } JanetChannelPending; | ||||
|  | ||||
| typedef struct { | ||||
| struct JanetChannel { | ||||
|     JanetQueue items; | ||||
|     JanetQueue read_pending; | ||||
|     JanetQueue write_pending; | ||||
| @@ -86,7 +86,7 @@ typedef struct { | ||||
| #else | ||||
|     pthread_mutex_t lock; | ||||
| #endif | ||||
| } JanetChannel; | ||||
| }; | ||||
|  | ||||
| typedef struct { | ||||
|     JanetFiber *fiber; | ||||
| @@ -255,34 +255,46 @@ static void add_timeout(JanetTimeout to) { | ||||
|  | ||||
| void janet_async_end(JanetFiber *fiber) { | ||||
|     if (fiber->ev_callback) { | ||||
|         fiber->ev_callback(fiber, JANET_ASYNC_EVENT_DEINIT); | ||||
|         janet_gcunroot(janet_wrap_abstract(fiber->ev_stream)); | ||||
|         fiber->ev_callback = NULL; | ||||
|         if (fiber->ev_state) { | ||||
|             if (!(fiber->flags & JANET_FIBER_EV_FLAG_IN_FLIGHT)) { | ||||
|         if (!(fiber->flags & JANET_FIBER_EV_FLAG_IN_FLIGHT)) { | ||||
|             if (fiber->ev_state) { | ||||
|                 janet_free(fiber->ev_state); | ||||
|                 janet_ev_dec_refcount(); | ||||
|                 fiber->ev_state = NULL; | ||||
|             } | ||||
|             fiber->ev_state = NULL; | ||||
|             janet_ev_dec_refcount(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void *janet_async_start(JanetFiber *fiber, JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, size_t data_size) { | ||||
| void janet_async_in_flight(JanetFiber *fiber) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     fiber->flags |= JANET_FIBER_EV_FLAG_IN_FLIGHT; | ||||
| #else | ||||
|     (void) fiber; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void janet_async_start_fiber(JanetFiber *fiber, JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state) { | ||||
|     janet_assert(!fiber->ev_callback, "double async on fiber"); | ||||
|     if (mode & JANET_ASYNC_LISTEN_READ) stream->read_fiber = fiber; | ||||
|     if (mode & JANET_ASYNC_LISTEN_WRITE) stream->write_fiber = fiber; | ||||
|     if (mode & JANET_ASYNC_LISTEN_READ) { | ||||
|         stream->read_fiber = fiber; | ||||
|     } | ||||
|     if (mode & JANET_ASYNC_LISTEN_WRITE) { | ||||
|         stream->write_fiber = fiber; | ||||
|     } | ||||
|     fiber->ev_callback = callback; | ||||
|     fiber->ev_stream = stream; | ||||
|     janet_ev_inc_refcount(); | ||||
|     janet_gcroot(janet_wrap_abstract(stream)); | ||||
|     if (data_size) { | ||||
|         void *data = janet_malloc(data_size); | ||||
|         fiber->ev_state = data; | ||||
|         return data; | ||||
|     } else { | ||||
|         fiber->ev_state = NULL; | ||||
|         return NULL; | ||||
|     } | ||||
|     fiber->ev_state = state; | ||||
|     callback(fiber, JANET_ASYNC_EVENT_INIT); | ||||
| } | ||||
|  | ||||
| void janet_async_start(JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state) { | ||||
|     janet_async_start_fiber(janet_vm.root_fiber, stream, mode, callback, state); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| void janet_fiber_did_resume(JanetFiber *fiber) { | ||||
| @@ -307,8 +319,9 @@ static const JanetMethod ev_default_stream_methods[] = { | ||||
| }; | ||||
|  | ||||
| /* Create a stream*/ | ||||
| JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods) { | ||||
|     JanetStream *stream = janet_abstract(&janet_stream_type, sizeof(JanetStream)); | ||||
| JanetStream *janet_stream_ext(JanetHandle handle, uint32_t flags, const JanetMethod *methods, size_t size) { | ||||
|     janet_assert(size >= sizeof(JanetStream), "bad size"); | ||||
|     JanetStream *stream = janet_abstract(&janet_stream_type, size); | ||||
|     stream->handle = handle; | ||||
|     stream->flags = flags; | ||||
|     stream->read_fiber = NULL; | ||||
| @@ -320,6 +333,10 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod | ||||
|     return stream; | ||||
| } | ||||
|  | ||||
| JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods) { | ||||
|     return janet_stream_ext(handle, flags, methods, sizeof(JanetStream)); | ||||
| } | ||||
|  | ||||
| static void janet_stream_close_impl(JanetStream *stream) { | ||||
|     stream->flags |= JANET_STREAM_CLOSED; | ||||
| #ifdef JANET_WINDOWS | ||||
| @@ -424,7 +441,7 @@ static void janet_stream_marshal(void *p, JanetMarshalContext *ctx) { | ||||
|     } | ||||
|     janet_marshal_int64(ctx, (int64_t)(duph)); | ||||
| #else | ||||
|     /* Marshal after dup becuse it is easier than maintaining our own ref counting. */ | ||||
|     /* Marshal after dup because it is easier than maintaining our own ref counting. */ | ||||
|     int duph = dup(s->handle); | ||||
|     if (duph < 0) janet_panicf("failed to duplicate stream handle: %V", janet_ev_lasterr()); | ||||
|     janet_marshal_int(ctx, (int32_t)(duph)); | ||||
| @@ -445,6 +462,9 @@ static void *janet_stream_unmarshal(JanetMarshalContext *ctx) { | ||||
|     p->handle = (JanetHandle) janet_unmarshal_int64(ctx); | ||||
| #else | ||||
|     p->handle = (JanetHandle) janet_unmarshal_int(ctx); | ||||
| #endif | ||||
| #ifdef JANET_EV_POLL | ||||
|     janet_register_stream(p); | ||||
| #endif | ||||
|     return p; | ||||
| } | ||||
| @@ -454,6 +474,12 @@ static Janet janet_stream_next(void *p, Janet key) { | ||||
|     return janet_nextmethod(stream->methods, key); | ||||
| } | ||||
|  | ||||
| static void janet_stream_tostring(void *p, JanetBuffer *buffer) { | ||||
|     JanetStream *stream = p; | ||||
|     /* Let user print the file descriptor for debugging */ | ||||
|     janet_formatb(buffer, "[fd=%d]", stream->handle); | ||||
| } | ||||
|  | ||||
| const JanetAbstractType janet_stream_type = { | ||||
|     "core/stream", | ||||
|     janet_stream_gc, | ||||
| @@ -462,7 +488,7 @@ const JanetAbstractType janet_stream_type = { | ||||
|     NULL, | ||||
|     janet_stream_marshal, | ||||
|     janet_stream_unmarshal, | ||||
|     NULL, | ||||
|     janet_stream_tostring, | ||||
|     NULL, | ||||
|     NULL, | ||||
|     janet_stream_next, | ||||
| @@ -577,7 +603,7 @@ void janet_ev_deinit_common(void) { | ||||
|  | ||||
| /* Shorthand to yield to event loop */ | ||||
| void janet_await(void) { | ||||
|     /* Store the fiber in a gobal table */ | ||||
|     /* Store the fiber in a global table */ | ||||
|     janet_signalv(JANET_SIGNAL_EVENT, janet_wrap_nil()); | ||||
| } | ||||
|  | ||||
| @@ -848,7 +874,7 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode | ||||
|             /* No root fiber, we are in completion on a root fiber. Don't block. */ | ||||
|             if (mode == 2) { | ||||
|                 janet_chan_unlock(channel); | ||||
|                 return 0; | ||||
|                 return 1; | ||||
|             } | ||||
|             /* Pushed successfully, but should block. */ | ||||
|             JanetChannelPending pending; | ||||
| @@ -904,6 +930,7 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i | ||||
|     int is_threaded = janet_chan_is_threaded(channel); | ||||
|     if (janet_q_pop(&channel->items, item, sizeof(Janet))) { | ||||
|         /* Queue empty */ | ||||
|         if (is_choice == 2) return 0; // Skip pending read | ||||
|         JanetChannelPending pending; | ||||
|         pending.thread = &janet_vm; | ||||
|         pending.fiber = janet_vm.root_fiber, | ||||
| @@ -961,6 +988,28 @@ JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, Janet | ||||
|     } | ||||
| } | ||||
|  | ||||
| int janet_channel_give(JanetChannel *channel, Janet x) { | ||||
|     return janet_channel_push(channel, x, 2); | ||||
| } | ||||
|  | ||||
| int janet_channel_take(JanetChannel *channel, Janet *out) { | ||||
|     return janet_channel_pop(channel, out, 2); | ||||
| } | ||||
|  | ||||
| JanetChannel *janet_channel_make(uint32_t limit) { | ||||
|     janet_assert(limit <= INT32_MAX, "bad limit"); | ||||
|     JanetChannel *channel = janet_abstract(&janet_channel_type, sizeof(JanetChannel)); | ||||
|     janet_chan_init(channel, (int32_t) limit, 0); | ||||
|     return channel; | ||||
| } | ||||
|  | ||||
| JanetChannel *janet_channel_make_threaded(uint32_t limit) { | ||||
|     janet_assert(limit <= INT32_MAX, "bad limit"); | ||||
|     JanetChannel *channel = janet_abstract_threaded(&janet_channel_type, sizeof(JanetChannel)); | ||||
|     janet_chan_init(channel, (int32_t) limit, 0); | ||||
|     return channel; | ||||
| } | ||||
|  | ||||
| /* Channel Methods */ | ||||
|  | ||||
| JANET_CORE_FN(cfun_channel_push, | ||||
| @@ -1161,10 +1210,12 @@ JANET_CORE_FN(cfun_channel_close, | ||||
|                 msg.argj = janet_wrap_nil(); | ||||
|                 janet_ev_post_event(vm, janet_thread_chan_cb, msg); | ||||
|             } else { | ||||
|                 if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) { | ||||
|                     janet_schedule(writer.fiber, make_close_result(channel)); | ||||
|                 } else { | ||||
|                     janet_schedule(writer.fiber, janet_wrap_nil()); | ||||
|                 if (janet_fiber_can_resume(writer.fiber)) { | ||||
|                     if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) { | ||||
|                         janet_schedule(writer.fiber, make_close_result(channel)); | ||||
|                     } else { | ||||
|                         janet_schedule(writer.fiber, janet_wrap_nil()); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -1180,10 +1231,12 @@ JANET_CORE_FN(cfun_channel_close, | ||||
|                 msg.argj = janet_wrap_nil(); | ||||
|                 janet_ev_post_event(vm, janet_thread_chan_cb, msg); | ||||
|             } else { | ||||
|                 if (reader.mode == JANET_CP_MODE_CHOICE_READ) { | ||||
|                     janet_schedule(reader.fiber, make_close_result(channel)); | ||||
|                 } else { | ||||
|                     janet_schedule(reader.fiber, janet_wrap_nil()); | ||||
|                 if (janet_fiber_can_resume(reader.fiber)) { | ||||
|                     if (reader.mode == JANET_CP_MODE_CHOICE_READ) { | ||||
|                         janet_schedule(reader.fiber, make_close_result(channel)); | ||||
|                     } else { | ||||
|                         janet_schedule(reader.fiber, janet_wrap_nil()); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -1279,7 +1332,7 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout); | ||||
| int janet_loop_done(void) { | ||||
|     return !((janet_vm.spawn.head != janet_vm.spawn.tail) || | ||||
|              janet_vm.tq_count || | ||||
|              janet_vm.listener_count); | ||||
|              janet_atomic_load(&janet_vm.listener_count)); | ||||
| } | ||||
|  | ||||
| JanetFiber *janet_loop1(void) { | ||||
| @@ -1341,7 +1394,7 @@ JanetFiber *janet_loop1(void) { | ||||
|     } | ||||
|  | ||||
|     /* Poll for events */ | ||||
|     if (janet_vm.tq_count || janet_vm.listener_count) { | ||||
|     if (janet_vm.tq_count || janet_atomic_load(&janet_vm.listener_count)) { | ||||
|         JanetTimeout to; | ||||
|         memset(&to, 0, sizeof(to)); | ||||
|         int has_timeout; | ||||
| @@ -1360,7 +1413,7 @@ JanetFiber *janet_loop1(void) { | ||||
|             break; | ||||
|         } | ||||
|         /* Run polling implementation only if pending timeouts or pending events */ | ||||
|         if (janet_vm.tq_count || janet_vm.listener_count) { | ||||
|         if (janet_vm.tq_count || janet_atomic_load(&janet_vm.listener_count)) { | ||||
|             janet_loop1_impl(has_timeout, to.when); | ||||
|         } | ||||
|     } | ||||
| @@ -1447,15 +1500,18 @@ void janet_ev_deinit(void) { | ||||
|     CloseHandle(janet_vm.iocp); | ||||
| } | ||||
|  | ||||
| void janet_register_stream(JanetStream *stream) { | ||||
| static void janet_register_stream(JanetStream *stream) { | ||||
|     if (NULL == CreateIoCompletionPort(stream->handle, janet_vm.iocp, (ULONG_PTR) stream, 0)) { | ||||
|         janet_panicf("failed to listen for events: %V", janet_ev_lasterr()); | ||||
|         if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | JANET_STREAM_ACCEPTABLE)) { | ||||
|             janet_panicf("failed to listen for events: %V", janet_ev_lasterr()); | ||||
|         } | ||||
|         stream->flags |= JANET_STREAM_UNREGISTERED; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void janet_loop1_impl(int has_timeout, JanetTimestamp to) { | ||||
|     ULONG_PTR completionKey = 0; | ||||
|     DWORD num_bytes_transfered = 0; | ||||
|     DWORD num_bytes_transferred = 0; | ||||
|     LPOVERLAPPED overlapped = NULL; | ||||
|  | ||||
|     /* Calculate how long to wait before timeout */ | ||||
| @@ -1470,7 +1526,7 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) { | ||||
|     } else { | ||||
|         waittime = INFINITE; | ||||
|     } | ||||
|     BOOL result = GetQueuedCompletionStatus(janet_vm.iocp, &num_bytes_transfered, &completionKey, &overlapped, (DWORD) waittime); | ||||
|     BOOL result = GetQueuedCompletionStatus(janet_vm.iocp, &num_bytes_transferred, &completionKey, &overlapped, (DWORD) waittime); | ||||
|  | ||||
|     if (result || overlapped) { | ||||
|         if (0 == completionKey) { | ||||
| @@ -1493,7 +1549,7 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) { | ||||
|             if (fiber != NULL) { | ||||
|                 fiber->flags &= ~JANET_FIBER_EV_FLAG_IN_FLIGHT; | ||||
|                 /* System is done with this, we can reused this data */ | ||||
|                 overlapped->InternalHigh = (ULONG_PTR) num_bytes_transfered; | ||||
|                 overlapped->InternalHigh = (ULONG_PTR) num_bytes_transferred; | ||||
|                 fiber->ev_callback(fiber, result ? JANET_ASYNC_EVENT_COMPLETE : JANET_ASYNC_EVENT_FAILED); | ||||
|             } else { | ||||
|                 janet_free((void *) overlapped); | ||||
| @@ -1504,6 +1560,14 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void janet_stream_edge_triggered(JanetStream *stream) { | ||||
|     (void) stream; | ||||
| } | ||||
|  | ||||
| void janet_stream_level_triggered(JanetStream *stream) { | ||||
|     (void) stream; | ||||
| } | ||||
|  | ||||
| #elif defined(JANET_EV_EPOLL) | ||||
|  | ||||
| static JanetTimestamp ts_now(void) { | ||||
| @@ -1515,15 +1579,15 @@ static JanetTimestamp ts_now(void) { | ||||
| } | ||||
|  | ||||
| /* Wait for the next event */ | ||||
| static void janet_register_stream(JanetStream *stream) { | ||||
| static void janet_register_stream_impl(JanetStream *stream, int mod, int edge_trigger) { | ||||
|     struct epoll_event ev; | ||||
|     ev.events = EPOLLET; | ||||
|     ev.events = edge_trigger ? EPOLLET : 0; | ||||
|     if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) ev.events |= EPOLLIN; | ||||
|     if (stream->flags & JANET_STREAM_WRITABLE) ev.events |= EPOLLOUT; | ||||
|     ev.data.ptr = stream; | ||||
|     int status; | ||||
|     do { | ||||
|         status = epoll_ctl(janet_vm.epoll, EPOLL_CTL_ADD, stream->handle, &ev); | ||||
|         status = epoll_ctl(janet_vm.epoll, mod ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, stream->handle, &ev); | ||||
|     } while (status == -1 && errno == EINTR); | ||||
|     if (status == -1) { | ||||
|         if (errno == EPERM) { | ||||
| @@ -1537,6 +1601,18 @@ static void janet_register_stream(JanetStream *stream) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_register_stream(JanetStream *stream) { | ||||
|     janet_register_stream_impl(stream, 0, 1); | ||||
| } | ||||
|  | ||||
| void janet_stream_edge_triggered(JanetStream *stream) { | ||||
|     janet_register_stream_impl(stream, 1, 1); | ||||
| } | ||||
|  | ||||
| void janet_stream_level_triggered(JanetStream *stream) { | ||||
|     janet_register_stream_impl(stream, 1, 0); | ||||
| } | ||||
|  | ||||
| #define JANET_EPOLL_MAX_EVENTS 64 | ||||
| void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { | ||||
|     struct itimerspec its; | ||||
| @@ -1666,14 +1742,15 @@ static void timestamp2timespec(struct timespec *t, JanetTimestamp ts) { | ||||
|     t->tv_nsec = ts == 0 ? 0 : (ts % 1000) * 1000000; | ||||
| } | ||||
|  | ||||
| void janet_register_stream(JanetStream *stream) { | ||||
| void janet_register_stream_impl(JanetStream *stream, int edge_trigger) { | ||||
|     struct kevent kevs[2]; | ||||
|     int length = 0; | ||||
|     int clear = edge_trigger ? EV_CLEAR : 0; | ||||
|     if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) { | ||||
|         EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, stream); | ||||
|         EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_ADD | EV_ENABLE | clear, 0, 0, stream); | ||||
|     } | ||||
|     if (stream->flags & JANET_STREAM_WRITABLE) { | ||||
|         EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, stream); | ||||
|         EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_ADD | EV_ENABLE | clear, 0, 0, stream); | ||||
|     } | ||||
|     int status; | ||||
|     do { | ||||
| @@ -1684,6 +1761,18 @@ void janet_register_stream(JanetStream *stream) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void janet_register_stream(JanetStream *stream) { | ||||
|     janet_register_stream_impl(stream, 1); | ||||
| } | ||||
|  | ||||
| void janet_stream_edge_triggered(JanetStream *stream) { | ||||
|     janet_register_stream_impl(stream, 1); | ||||
| } | ||||
|  | ||||
| void janet_stream_level_triggered(JanetStream *stream) { | ||||
|     janet_register_stream_impl(stream, 0); | ||||
| } | ||||
|  | ||||
| #define JANET_KQUEUE_MAX_EVENTS 64 | ||||
|  | ||||
| void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { | ||||
| @@ -1806,15 +1895,30 @@ void janet_register_stream(JanetStream *stream) { | ||||
|     janet_vm.stream_count = new_count; | ||||
| } | ||||
|  | ||||
| void janet_stream_edge_triggered(JanetStream *stream) { | ||||
|     (void) stream; | ||||
| } | ||||
|  | ||||
| void janet_stream_level_triggered(JanetStream *stream) { | ||||
|     (void) stream; | ||||
| } | ||||
|  | ||||
| void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { | ||||
|  | ||||
|     /* set event flags */ | ||||
|     for (size_t i = 0; i < janet_vm.stream_count; i++) { | ||||
|         JanetStream *stream = janet_vm.streams[i]; | ||||
|         janet_vm.fds[i + 1].events = 0; | ||||
|         janet_vm.fds[i + 1].revents = 0; | ||||
|         if (stream->read_fiber) janet_vm.fds[i + 1].events |= POLLIN; | ||||
|         if (stream->write_fiber) janet_vm.fds[i + 1].events |= POLLOUT; | ||||
|         struct pollfd *pfd = janet_vm.fds + i + 1; | ||||
|         pfd->events = 0; | ||||
|         pfd->revents = 0; | ||||
|         JanetFiber *rf = stream->read_fiber; | ||||
|         JanetFiber *wf = stream->write_fiber; | ||||
|         if (rf && rf->ev_callback) pfd->events |= POLLIN; | ||||
|         if (wf && wf->ev_callback) pfd->events |= POLLOUT; | ||||
|         /* Hack to ignore a file descriptor - make file descriptor negative if we want to ignore */ | ||||
|         if (!pfd->events) { | ||||
|             pfd->fd = -pfd->fd; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Poll for events */ | ||||
| @@ -1831,6 +1935,14 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { | ||||
|         JANET_EXIT("failed to poll events"); | ||||
|     } | ||||
|  | ||||
|     /* Undo negative hack */ | ||||
|     for (size_t i = 0; i < janet_vm.stream_count; i++) { | ||||
|         struct pollfd *pfd = janet_vm.fds + i + 1; | ||||
|         if (pfd->fd < 0) { | ||||
|             pfd->fd = -pfd->fd; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Check selfpipe */ | ||||
|     if (janet_vm.fds[0].revents & POLLIN) { | ||||
|         janet_vm.fds[0].revents = 0; | ||||
| @@ -2013,7 +2125,7 @@ void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage ar | ||||
|     int err = pthread_create(&waiter_thread, &janet_vm.new_thread_attr, janet_thread_body, init); | ||||
|     if (err) { | ||||
|         janet_free(init); | ||||
|         janet_panicf("%s", strerror(err)); | ||||
|         janet_panicf("%s", janet_strerror(err)); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
| @@ -2026,33 +2138,35 @@ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) { | ||||
|     if (return_value.fiber == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     switch (return_value.tag) { | ||||
|         default: | ||||
|         case JANET_EV_TCTAG_NIL: | ||||
|             janet_schedule(return_value.fiber, janet_wrap_nil()); | ||||
|             break; | ||||
|         case JANET_EV_TCTAG_INTEGER: | ||||
|             janet_schedule(return_value.fiber, janet_wrap_integer(return_value.argi)); | ||||
|             break; | ||||
|         case JANET_EV_TCTAG_STRING: | ||||
|         case JANET_EV_TCTAG_STRINGF: | ||||
|             janet_schedule(return_value.fiber, janet_cstringv((const char *) return_value.argp)); | ||||
|             if (return_value.tag == JANET_EV_TCTAG_STRINGF) janet_free(return_value.argp); | ||||
|             break; | ||||
|         case JANET_EV_TCTAG_KEYWORD: | ||||
|             janet_schedule(return_value.fiber, janet_ckeywordv((const char *) return_value.argp)); | ||||
|             break; | ||||
|         case JANET_EV_TCTAG_ERR_STRING: | ||||
|         case JANET_EV_TCTAG_ERR_STRINGF: | ||||
|             janet_cancel(return_value.fiber, janet_cstringv((const char *) return_value.argp)); | ||||
|             if (return_value.tag == JANET_EV_TCTAG_STRINGF) janet_free(return_value.argp); | ||||
|             break; | ||||
|         case JANET_EV_TCTAG_ERR_KEYWORD: | ||||
|             janet_cancel(return_value.fiber, janet_ckeywordv((const char *) return_value.argp)); | ||||
|             break; | ||||
|         case JANET_EV_TCTAG_BOOLEAN: | ||||
|             janet_schedule(return_value.fiber, janet_wrap_boolean(return_value.argi)); | ||||
|             break; | ||||
|     if (janet_fiber_can_resume(return_value.fiber)) { | ||||
|         switch (return_value.tag) { | ||||
|             default: | ||||
|             case JANET_EV_TCTAG_NIL: | ||||
|                 janet_schedule(return_value.fiber, janet_wrap_nil()); | ||||
|                 break; | ||||
|             case JANET_EV_TCTAG_INTEGER: | ||||
|                 janet_schedule(return_value.fiber, janet_wrap_integer(return_value.argi)); | ||||
|                 break; | ||||
|             case JANET_EV_TCTAG_STRING: | ||||
|             case JANET_EV_TCTAG_STRINGF: | ||||
|                 janet_schedule(return_value.fiber, janet_cstringv((const char *) return_value.argp)); | ||||
|                 if (return_value.tag == JANET_EV_TCTAG_STRINGF) janet_free(return_value.argp); | ||||
|                 break; | ||||
|             case JANET_EV_TCTAG_KEYWORD: | ||||
|                 janet_schedule(return_value.fiber, janet_ckeywordv((const char *) return_value.argp)); | ||||
|                 break; | ||||
|             case JANET_EV_TCTAG_ERR_STRING: | ||||
|             case JANET_EV_TCTAG_ERR_STRINGF: | ||||
|                 janet_cancel(return_value.fiber, janet_cstringv((const char *) return_value.argp)); | ||||
|                 if (return_value.tag == JANET_EV_TCTAG_STRINGF) janet_free(return_value.argp); | ||||
|                 break; | ||||
|             case JANET_EV_TCTAG_ERR_KEYWORD: | ||||
|                 janet_cancel(return_value.fiber, janet_ckeywordv((const char *) return_value.argp)); | ||||
|                 break; | ||||
|             case JANET_EV_TCTAG_BOOLEAN: | ||||
|                 janet_schedule(return_value.fiber, janet_wrap_boolean(return_value.argi)); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|     janet_gcunroot(janet_wrap_fiber(return_value.fiber)); | ||||
| } | ||||
| @@ -2120,7 +2234,7 @@ Janet janet_ev_lasterr(void) { | ||||
| } | ||||
| #else | ||||
| Janet janet_ev_lasterr(void) { | ||||
|     return janet_cstringv(strerror(errno)); | ||||
|     return janet_cstringv(janet_strerror(errno)); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @@ -2199,7 +2313,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|         } | ||||
|  | ||||
|         /* fallthrough */ | ||||
|         case JANET_ASYNC_EVENT_USER: { | ||||
|         case JANET_ASYNC_EVENT_INIT: { | ||||
|             int32_t chunk_size = state->bytes_left > JANET_EV_CHUNKSIZE ? JANET_EV_CHUNKSIZE : state->bytes_left; | ||||
|             memset(&(state->overlapped), 0, sizeof(OVERLAPPED)); | ||||
|             int status; | ||||
| @@ -2237,7 +2351,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             fiber->flags |= JANET_FIBER_EV_FLAG_IN_FLIGHT; | ||||
|             janet_async_in_flight(fiber); | ||||
|         } | ||||
|         break; | ||||
| #else | ||||
| @@ -2253,7 +2367,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|  | ||||
|     read_more: | ||||
|         case JANET_ASYNC_EVENT_HUP: | ||||
|         case JANET_ASYNC_EVENT_USER: | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|         case JANET_ASYNC_EVENT_READ: { | ||||
|             JanetBuffer *buffer = state->buf; | ||||
|             int32_t bytes_left = state->bytes_left; | ||||
| @@ -2332,9 +2446,8 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_ev_read_generic(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int is_chunked, JanetReadMode mode, int flags) { | ||||
|     JanetFiber *f = janet_vm.root_fiber; | ||||
|     StateRead *state = (StateRead *) janet_async_start(f, stream, JANET_ASYNC_LISTEN_READ, ev_callback_read, sizeof(StateRead)); | ||||
| static JANET_NO_RETURN void janet_ev_read_generic(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int is_chunked, JanetReadMode mode, int flags) { | ||||
|     StateRead *state = janet_malloc(sizeof(StateRead)); | ||||
|     state->is_chunk = is_chunked; | ||||
|     state->buf = buf; | ||||
|     state->bytes_left = nbytes; | ||||
| @@ -2345,23 +2458,23 @@ static void janet_ev_read_generic(JanetStream *stream, JanetBuffer *buf, int32_t | ||||
| #else | ||||
|     state->flags = flags; | ||||
| #endif | ||||
|     ev_callback_read(f, JANET_ASYNC_EVENT_USER); | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_READ, ev_callback_read, state); | ||||
| } | ||||
|  | ||||
| void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes) { | ||||
| JANET_NO_RETURN void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes) { | ||||
|     janet_ev_read_generic(stream, buf, nbytes, 0, JANET_ASYNC_READMODE_READ, 0); | ||||
| } | ||||
| void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes) { | ||||
| JANET_NO_RETURN void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes) { | ||||
|     janet_ev_read_generic(stream, buf, nbytes, 1, JANET_ASYNC_READMODE_READ, 0); | ||||
| } | ||||
| #ifdef JANET_NET | ||||
| void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags) { | ||||
|     janet_ev_read_generic(stream, buf, nbytes, 0, JANET_ASYNC_READMODE_RECV, flags); | ||||
| } | ||||
| void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags) { | ||||
|     janet_ev_read_generic(stream, buf, nbytes, 1, JANET_ASYNC_READMODE_RECV, flags); | ||||
| } | ||||
| void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags) { | ||||
|     janet_ev_read_generic(stream, buf, nbytes, 0, JANET_ASYNC_READMODE_RECVFROM, flags); | ||||
| } | ||||
| #endif | ||||
| @@ -2431,7 +2544,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|             return; | ||||
|         } | ||||
|         break; | ||||
|         case JANET_ASYNC_EVENT_USER: { | ||||
|         case JANET_ASYNC_EVENT_INIT: { | ||||
|             /* Begin write */ | ||||
|             int32_t len; | ||||
|             const uint8_t *bytes; | ||||
| @@ -2461,7 +2574,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|                 status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped, NULL); | ||||
|                 if (status) { | ||||
|                     if (WSA_IO_PENDING == WSAGetLastError()) { | ||||
|                         fiber->flags |= JANET_FIBER_EV_FLAG_IN_FLIGHT; | ||||
|                         janet_async_in_flight(fiber); | ||||
|                     } else { | ||||
|                         janet_cancel(fiber, janet_ev_lasterr()); | ||||
|                         janet_async_end(fiber); | ||||
| @@ -2486,7 +2599,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|                 status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped); | ||||
|                 if (!status) { | ||||
|                     if (ERROR_IO_PENDING == GetLastError()) { | ||||
|                         fiber->flags |= JANET_FIBER_EV_FLAG_IN_FLIGHT; | ||||
|                         janet_async_in_flight(fiber); | ||||
|                     } else { | ||||
|                         janet_cancel(fiber, janet_ev_lasterr()); | ||||
|                         janet_async_end(fiber); | ||||
| @@ -2505,7 +2618,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|             janet_cancel(fiber, janet_cstringv("stream hup")); | ||||
|             janet_async_end(fiber); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_USER: | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|         case JANET_ASYNC_EVENT_WRITE: { | ||||
|             int32_t start, len; | ||||
|             const uint8_t *bytes; | ||||
| @@ -2570,10 +2683,8 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_ev_write_generic(JanetStream *stream, void *buf, void *dest_abst, JanetWriteMode mode, int is_buffer, int flags) { | ||||
|     JanetFiber *f = janet_vm.root_fiber; | ||||
|     StateWrite *state = (StateWrite *) janet_async_start(f, stream, JANET_ASYNC_LISTEN_WRITE, | ||||
|                         ev_callback_write, sizeof(StateWrite)); | ||||
| static JANET_NO_RETURN void janet_ev_write_generic(JanetStream *stream, void *buf, void *dest_abst, JanetWriteMode mode, int is_buffer, int flags) { | ||||
|     StateWrite *state = janet_malloc(sizeof(StateWrite)); | ||||
|     state->is_buffer = is_buffer; | ||||
|     state->src.buf = buf; | ||||
|     state->dest_abst = dest_abst; | ||||
| @@ -2584,31 +2695,31 @@ static void janet_ev_write_generic(JanetStream *stream, void *buf, void *dest_ab | ||||
|     state->flags = flags; | ||||
|     state->start = 0; | ||||
| #endif | ||||
|     ev_callback_write(f, JANET_ASYNC_EVENT_USER); | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, ev_callback_write, state); | ||||
| } | ||||
|  | ||||
| void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf) { | ||||
| JANET_NO_RETURN void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf) { | ||||
|     janet_ev_write_generic(stream, buf, NULL, JANET_ASYNC_WRITEMODE_WRITE, 1, 0); | ||||
| } | ||||
|  | ||||
| void janet_ev_write_string(JanetStream *stream, JanetString str) { | ||||
| JANET_NO_RETURN void janet_ev_write_string(JanetStream *stream, JanetString str) { | ||||
|     janet_ev_write_generic(stream, (void *) str, NULL, JANET_ASYNC_WRITEMODE_WRITE, 0, 0); | ||||
| } | ||||
|  | ||||
| #ifdef JANET_NET | ||||
| void janet_ev_send_buffer(JanetStream *stream, JanetBuffer *buf, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_send_buffer(JanetStream *stream, JanetBuffer *buf, int flags) { | ||||
|     janet_ev_write_generic(stream, buf, NULL, JANET_ASYNC_WRITEMODE_SEND, 1, flags); | ||||
| } | ||||
|  | ||||
| void janet_ev_send_string(JanetStream *stream, JanetString str, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_send_string(JanetStream *stream, JanetString str, int flags) { | ||||
|     janet_ev_write_generic(stream, (void *) str, NULL, JANET_ASYNC_WRITEMODE_SEND, 0, flags); | ||||
| } | ||||
|  | ||||
| void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, int flags) { | ||||
|     janet_ev_write_generic(stream, buf, dest, JANET_ASYNC_WRITEMODE_SENDTO, 1, flags); | ||||
| } | ||||
|  | ||||
| void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags) { | ||||
| JANET_NO_RETURN void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags) { | ||||
|     janet_ev_write_generic(stream, (void *) str, dest, JANET_ASYNC_WRITEMODE_SENDTO, 0, flags); | ||||
| } | ||||
| #endif | ||||
| @@ -2622,6 +2733,7 @@ static volatile long PipeSerialNumber; | ||||
|  * mode = 0: both sides non-blocking. | ||||
|  * mode = 1: only read side non-blocking: write side sent to subprocess | ||||
|  * mode = 2: only write side non-blocking: read side sent to subprocess | ||||
|  * mode = 3: both sides blocking - for use in two subprocesses (making pipeline from external processes) | ||||
|  */ | ||||
| int janet_make_pipe(JanetHandle handles[2], int mode) { | ||||
| #ifdef JANET_WINDOWS | ||||
| @@ -2635,6 +2747,11 @@ int janet_make_pipe(JanetHandle handles[2], int mode) { | ||||
|     memset(&saAttr, 0, sizeof(saAttr)); | ||||
|     saAttr.nLength = sizeof(saAttr); | ||||
|     saAttr.bInheritHandle = TRUE; | ||||
|     if (mode == 3) { | ||||
|         /* No overlapped IO involved, just call CreatePipe */ | ||||
|         if (!CreatePipe(handles, handles + 1, &saAttr, 0)) return -1; | ||||
|         return 0; | ||||
|     } | ||||
|     sprintf(PipeNameBuffer, | ||||
|             "\\\\.\\Pipe\\JanetPipeFile.%08x.%08x", | ||||
|             (unsigned int) GetCurrentProcessId(), | ||||
| @@ -2680,8 +2797,8 @@ int janet_make_pipe(JanetHandle handles[2], int mode) { | ||||
|     if (pipe(handles)) return -1; | ||||
|     if (mode != 2 && fcntl(handles[0], F_SETFD, FD_CLOEXEC)) goto error; | ||||
|     if (mode != 1 && fcntl(handles[1], F_SETFD, FD_CLOEXEC)) goto error; | ||||
|     if (mode != 2 && fcntl(handles[0], F_SETFL, O_NONBLOCK)) goto error; | ||||
|     if (mode != 1 && fcntl(handles[1], F_SETFL, O_NONBLOCK)) goto error; | ||||
|     if (mode != 2 && mode != 3 && fcntl(handles[0], F_SETFL, O_NONBLOCK)) goto error; | ||||
|     if (mode != 1 && mode != 3 && fcntl(handles[1], F_SETFL, O_NONBLOCK)) goto error; | ||||
|     return 0; | ||||
| error: | ||||
|     close(handles[0]); | ||||
| @@ -2755,7 +2872,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) { | ||||
|             janet_gcroot(janet_wrap_table(janet_vm.abstract_registry)); | ||||
|         } | ||||
|  | ||||
|         /* Get supervsior */ | ||||
|         /* Get supervisor */ | ||||
|         if (flags & JANET_THREAD_SUPERVISOR_FLAG) { | ||||
|             Janet sup = | ||||
|                 janet_unmarshal(nextbytes, endbytes - nextbytes, | ||||
| @@ -2941,10 +3058,15 @@ JANET_CORE_FN(cfun_ev_sleep, | ||||
|  | ||||
| JANET_CORE_FN(cfun_ev_deadline, | ||||
|               "(ev/deadline sec &opt tocancel tocheck)", | ||||
|               "Set a deadline for a fiber `tocheck`. If `tocheck` is not finished after `sec` seconds, " | ||||
|               "`tocancel` will be canceled as with `ev/cancel`. " | ||||
|               "If `tocancel` and `tocheck` are not given, they default to `(fiber/root)` and " | ||||
|               "`(fiber/current)` respectively. Returns `tocancel`.") { | ||||
|               "Schedules the event loop to try to cancel the `tocancel` " | ||||
|               "task as with `ev/cancel`. After `sec` seconds, the event " | ||||
|               "loop will attempt cancellation of `tocancel` if the " | ||||
|               "`tocheck` fiber is resumable. `sec` is a number that can " | ||||
|               "have a fractional part. `tocancel` defaults to " | ||||
|               "`(fiber/root)`, but if specified, must be a task (root " | ||||
|               "fiber). `tocheck` defaults to `(fiber/current)`, but if " | ||||
|               "specified, should be a fiber. Returns `tocancel` " | ||||
|               "immediately.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     double sec = janet_getnumber(argv, 0); | ||||
|     JanetFiber *tocancel = janet_optfiber(argv, argc, 1, janet_vm.root_fiber); | ||||
| @@ -2999,7 +3121,6 @@ JANET_CORE_FN(janet_cfun_stream_read, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_read(stream, buffer, n); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_stream_chunk, | ||||
| @@ -3014,7 +3135,6 @@ JANET_CORE_FN(janet_cfun_stream_chunk, | ||||
|     double to = janet_optnumber(argv, argc, 3, INFINITY); | ||||
|     if (to != INFINITY) janet_addtimeout(to); | ||||
|     janet_ev_readchunk(stream, buffer, n); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_stream_write, | ||||
| @@ -3034,7 +3154,6 @@ JANET_CORE_FN(janet_cfun_stream_write, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_write_string(stream, bytes.bytes); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| static int mutexgc(void *p, size_t size) { | ||||
| @@ -3190,6 +3309,8 @@ void janet_lib_ev(JanetTable *env) { | ||||
|     janet_register_abstract_type(&janet_channel_type); | ||||
|     janet_register_abstract_type(&janet_mutex_type); | ||||
|     janet_register_abstract_type(&janet_rwlock_type); | ||||
|  | ||||
|     janet_lib_filewatch(env); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -76,4 +76,6 @@ | ||||
| #define __BSD_VISIBLE 1 | ||||
| #endif | ||||
|  | ||||
| #define _FILE_OFFSET_BITS 64 | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										310
									
								
								src/core/ffi.c
									
									
									
									
									
								
							
							
						
						
									
										310
									
								
								src/core/ffi.c
									
									
									
									
									
								
							| @@ -56,6 +56,9 @@ | ||||
| #if (defined(__x86_64__) || defined(_M_X64)) && !defined(JANET_WINDOWS) | ||||
| #define JANET_FFI_SYSV64_ENABLED | ||||
| #endif | ||||
| #if (defined(__aarch64__) || defined(_M_ARM64)) && !defined(JANET_WINDOWS) | ||||
| #define JANET_FFI_AAPCS64_ENABLED | ||||
| #endif | ||||
|  | ||||
| typedef struct JanetFFIType JanetFFIType; | ||||
| typedef struct JanetFFIStruct JanetFFIStruct; | ||||
| @@ -140,7 +143,13 @@ typedef enum { | ||||
|     JANET_WIN64_REGISTER, | ||||
|     JANET_WIN64_STACK, | ||||
|     JANET_WIN64_REGISTER_REF, | ||||
|     JANET_WIN64_STACK_REF | ||||
|     JANET_WIN64_STACK_REF, | ||||
|     JANET_AAPCS64_GENERAL, | ||||
|     JANET_AAPCS64_SSE, | ||||
|     JANET_AAPCS64_GENERAL_REF, | ||||
|     JANET_AAPCS64_STACK, | ||||
|     JANET_AAPCS64_STACK_REF, | ||||
|     JANET_AAPCS64_NONE | ||||
| } JanetFFIWordSpec; | ||||
|  | ||||
| /* Describe how each Janet argument is interpreted in terms of machine words | ||||
| @@ -155,13 +164,16 @@ typedef struct { | ||||
| typedef enum { | ||||
|     JANET_FFI_CC_NONE, | ||||
|     JANET_FFI_CC_SYSV_64, | ||||
|     JANET_FFI_CC_WIN_64 | ||||
|     JANET_FFI_CC_WIN_64, | ||||
|     JANET_FFI_CC_AAPCS64 | ||||
| } JanetFFICallingConvention; | ||||
|  | ||||
| #ifdef JANET_FFI_WIN64_ENABLED | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_WIN_64 | ||||
| #elif defined(JANET_FFI_SYSV64_ENABLED) | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_SYSV_64 | ||||
| #elif defined(JANET_FFI_AAPCS64_ENABLED) | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_AAPCS64 | ||||
| #else | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_NONE | ||||
| #endif | ||||
| @@ -301,6 +313,9 @@ static JanetFFICallingConvention decode_ffi_cc(const uint8_t *name) { | ||||
| #endif | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|     if (!janet_cstrcmp(name, "sysv64")) return JANET_FFI_CC_SYSV_64; | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|     if (!janet_cstrcmp(name, "aapcs64")) return JANET_FFI_CC_AAPCS64; | ||||
| #endif | ||||
|     if (!janet_cstrcmp(name, "default")) return JANET_FFI_CC_DEFAULT; | ||||
|     janet_panicf("unknown calling convention %s", name); | ||||
| @@ -475,7 +490,7 @@ JANET_CORE_FN(cfun_ffi_align, | ||||
| static void *janet_ffi_getpointer(const Janet *argv, int32_t n) { | ||||
|     switch (janet_type(argv[n])) { | ||||
|         default: | ||||
|             janet_panicf("bad slot #%d, expected ffi pointer convertable type, got %v", n, argv[n]); | ||||
|             janet_panicf("bad slot #%d, expected ffi pointer convertible type, got %v", n, argv[n]); | ||||
|         case JANET_POINTER: | ||||
|         case JANET_STRING: | ||||
|         case JANET_KEYWORD: | ||||
| @@ -763,6 +778,101 @@ static JanetFFIWordSpec sysv64_classify(JanetFFIType type) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
| /* Procedure Call Standard for the Arm® 64-bit Architecture (AArch64) 2023Q3 – October 6, 2023 | ||||
|  * See section 6.8.2 Parameter passing rules. | ||||
|  * https://github.com/ARM-software/abi-aa/releases/download/2023Q3/aapcs64.pdf | ||||
|  * | ||||
|  * Additional documentation needed for Apple platforms. | ||||
|  * https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms */ | ||||
|  | ||||
| #define JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ptr, alignment) (ptr = ((ptr) + ((alignment) - 1)) & ~((alignment) - 1)) | ||||
| #if !defined(JANET_APPLE) | ||||
| #define JANET_FFI_AAPCS64_STACK_ALIGN(ptr, alignment) ((void) alignment, JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ptr, 8)) | ||||
| #else | ||||
| #define JANET_FFI_AAPCS64_STACK_ALIGN(ptr, alignment) JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ptr, alignment) | ||||
| #endif | ||||
|  | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
| } Aapcs64Variant1ReturnGeneral; | ||||
|  | ||||
| typedef struct { | ||||
|     double a; | ||||
|     double b; | ||||
|     double c; | ||||
|     double d; | ||||
| } Aapcs64Variant2ReturnSse; | ||||
|  | ||||
| /* Workaround for passing a return value pointer through x8. | ||||
|  * Limits struct returns to 128 bytes. */ | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
|     uint64_t c; | ||||
|     uint64_t d; | ||||
|     uint64_t e; | ||||
|     uint64_t f; | ||||
|     uint64_t g; | ||||
|     uint64_t h; | ||||
|     uint64_t i; | ||||
|     uint64_t j; | ||||
|     uint64_t k; | ||||
|     uint64_t l; | ||||
|     uint64_t m; | ||||
|     uint64_t n; | ||||
|     uint64_t o; | ||||
|     uint64_t p; | ||||
| } Aapcs64Variant3ReturnPointer; | ||||
|  | ||||
| static JanetFFIWordSpec aapcs64_classify(JanetFFIType type) { | ||||
|     switch (type.prim) { | ||||
|         case JANET_FFI_TYPE_PTR: | ||||
|         case JANET_FFI_TYPE_STRING: | ||||
|         case JANET_FFI_TYPE_BOOL: | ||||
|         case JANET_FFI_TYPE_INT8: | ||||
|         case JANET_FFI_TYPE_INT16: | ||||
|         case JANET_FFI_TYPE_INT32: | ||||
|         case JANET_FFI_TYPE_INT64: | ||||
|         case JANET_FFI_TYPE_UINT8: | ||||
|         case JANET_FFI_TYPE_UINT16: | ||||
|         case JANET_FFI_TYPE_UINT32: | ||||
|         case JANET_FFI_TYPE_UINT64: | ||||
|             return JANET_AAPCS64_GENERAL; | ||||
|         case JANET_FFI_TYPE_DOUBLE: | ||||
|         case JANET_FFI_TYPE_FLOAT: | ||||
|             return JANET_AAPCS64_SSE; | ||||
|         case JANET_FFI_TYPE_STRUCT: { | ||||
|             JanetFFIStruct *st = type.st; | ||||
|             if (st->field_count <= 4 && aapcs64_classify(st->fields[0].type) == JANET_AAPCS64_SSE) { | ||||
|                 bool is_hfa = true; | ||||
|                 for (uint32_t i = 1; i < st->field_count; i++) { | ||||
|                     if (st->fields[0].type.prim != st->fields[i].type.prim) { | ||||
|                         is_hfa = false; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (is_hfa) { | ||||
|                     return JANET_AAPCS64_SSE; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (type_size(type) > 16) { | ||||
|                 return JANET_AAPCS64_GENERAL_REF; | ||||
|             } | ||||
|  | ||||
|             return JANET_AAPCS64_GENERAL; | ||||
|         } | ||||
|         case JANET_FFI_TYPE_VOID: | ||||
|             return JANET_AAPCS64_NONE; | ||||
|         default: | ||||
|             janet_panic("nyi"); | ||||
|             return JANET_AAPCS64_NONE; | ||||
|     } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(cfun_ffi_signature, | ||||
|               "(ffi/signature calling-convention ret-type & arg-types)", | ||||
|               "Create a function signature object that can be used to make calls " | ||||
| @@ -960,6 +1070,96 @@ JANET_CORE_FN(cfun_ffi_signature, | ||||
|         } | ||||
|         break; | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|         case JANET_FFI_CC_AAPCS64: { | ||||
|             uint32_t next_general_reg = 0; | ||||
|             uint32_t next_fp_reg = 0; | ||||
|             uint32_t stack_offset = 0; | ||||
|             uint32_t ref_stack_offset = 0; | ||||
|  | ||||
|             JanetFFIWordSpec ret_spec = aapcs64_classify(ret_type); | ||||
|             ret.spec = ret_spec; | ||||
|             if (ret_spec == JANET_AAPCS64_SSE) { | ||||
|                 variant = 1; | ||||
|             } else if (ret_spec == JANET_AAPCS64_GENERAL_REF) { | ||||
|                 if (type_size(ret_type) > sizeof(Aapcs64Variant3ReturnPointer)) { | ||||
|                     janet_panic("return value bigger than supported"); | ||||
|                 } | ||||
|                 variant = 2; | ||||
|             } else { | ||||
|                 variant = 0; | ||||
|             } | ||||
|  | ||||
|             for (uint32_t i = 0; i < arg_count; i++) { | ||||
|                 mappings[i].type = decode_ffi_type(argv[i + 2]); | ||||
|                 mappings[i].spec = aapcs64_classify(mappings[i].type); | ||||
|                 size_t arg_size = type_size(mappings[i].type); | ||||
|  | ||||
|                 switch (mappings[i].spec) { | ||||
|                     case JANET_AAPCS64_GENERAL: { | ||||
|                         bool arg_is_struct = mappings[i].type.prim == JANET_FFI_TYPE_STRUCT; | ||||
|                         uint32_t needed_registers = (arg_size + 7) / 8; | ||||
|                         if (next_general_reg + needed_registers <= 8) { | ||||
|                             mappings[i].offset = next_general_reg; | ||||
|                             next_general_reg += needed_registers; | ||||
|                         } else { | ||||
|                             size_t arg_align = arg_is_struct ? 8 : type_align(mappings[i].type); | ||||
|                             mappings[i].spec = JANET_AAPCS64_STACK; | ||||
|                             mappings[i].offset = JANET_FFI_AAPCS64_STACK_ALIGN(stack_offset, arg_align); | ||||
| #if !defined(JANET_APPLE) | ||||
|                             stack_offset += arg_size > 8 ? arg_size : 8; | ||||
| #else | ||||
|                             stack_offset += arg_size; | ||||
| #endif | ||||
|                             next_general_reg = 8; | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                     case JANET_AAPCS64_GENERAL_REF: | ||||
|                         if (next_general_reg < 8) { | ||||
|                             mappings[i].offset = next_general_reg++; | ||||
|                         } else { | ||||
|                             mappings[i].spec = JANET_AAPCS64_STACK_REF; | ||||
|                             mappings[i].offset = JANET_FFI_AAPCS64_STACK_ALIGN(stack_offset, 8); | ||||
|                             stack_offset += 8; | ||||
|                         } | ||||
|                         mappings[i].offset2 = JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ref_stack_offset, 8); | ||||
|                         ref_stack_offset += arg_size; | ||||
|                         break; | ||||
|                     case JANET_AAPCS64_SSE: { | ||||
|                         uint32_t needed_registers = (arg_size + 7) / 8; | ||||
|                         if (next_fp_reg + needed_registers <= 8) { | ||||
|                             mappings[i].offset = next_fp_reg; | ||||
|                             next_fp_reg += needed_registers; | ||||
|                         } else { | ||||
|                             mappings[i].spec = JANET_AAPCS64_STACK; | ||||
|                             mappings[i].offset = JANET_FFI_AAPCS64_STACK_ALIGN(stack_offset, 8); | ||||
| #if !defined(JANET_APPLE) | ||||
|                             stack_offset += 8; | ||||
| #else | ||||
|                             stack_offset += arg_size; | ||||
| #endif | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                     default: | ||||
|                         janet_panic("nyi"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             stack_offset = (stack_offset + 15) & ~0xFUL; | ||||
|             ref_stack_offset = (ref_stack_offset + 15) & ~0xFUL; | ||||
|             stack_count = stack_offset + ref_stack_offset; | ||||
|  | ||||
|             for (uint32_t i = 0; i < arg_count; i++) { | ||||
|                 if (mappings[i].spec == JANET_AAPCS64_GENERAL_REF || mappings[i].spec == JANET_AAPCS64_STACK_REF) { | ||||
|                     mappings[i].offset2 = stack_offset + mappings[i].offset2; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     /* Create signature abstract value */ | ||||
| @@ -1294,6 +1494,99 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|  | ||||
| static void janet_ffi_aapcs64_standard_callback(void *ctx, void *userdata) { | ||||
|     janet_ffi_trampoline(ctx, userdata); | ||||
| } | ||||
|  | ||||
| typedef Aapcs64Variant1ReturnGeneral janet_aapcs64_variant_1(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7, | ||||
|         double v0, double v1, double v2, double v3, double v4, double v5, double v6, double v7); | ||||
| typedef Aapcs64Variant2ReturnSse janet_aapcs64_variant_2(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7, | ||||
|         double v0, double v1, double v2, double v3, double v4, double v5, double v6, double v7); | ||||
| typedef Aapcs64Variant3ReturnPointer janet_aapcs64_variant_3(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7, | ||||
|         double v0, double v1, double v2, double v3, double v4, double v5, double v6, double v7); | ||||
|  | ||||
|  | ||||
| static Janet janet_ffi_aapcs64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) { | ||||
|     union { | ||||
|         Aapcs64Variant1ReturnGeneral general_return; | ||||
|         Aapcs64Variant2ReturnSse sse_return; | ||||
|         Aapcs64Variant3ReturnPointer pointer_return; | ||||
|     } retu; | ||||
|     uint64_t regs[8]; | ||||
|     double fp_regs[8]; | ||||
|     void *ret_mem = &retu.general_return; | ||||
|  | ||||
|     /* Apple's stack values do not need to be 8-byte aligned, | ||||
|      * thus all stack offsets refer to actual byte positions. */ | ||||
|     uint8_t *stack = alloca(signature->stack_count); | ||||
| #if defined(JANET_APPLE) | ||||
|     /* Values must be zero-extended by the caller instead of the callee. */ | ||||
|     memset(stack, 0, signature->stack_count); | ||||
| #endif | ||||
|     for (uint32_t i = 0; i < signature->arg_count; i++) { | ||||
|         int32_t n = i + 2; | ||||
|         JanetFFIMapping arg = signature->args[i]; | ||||
|         void *to = NULL; | ||||
|  | ||||
|         switch (arg.spec) { | ||||
|             case JANET_AAPCS64_GENERAL: | ||||
|                 to = regs + arg.offset; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_GENERAL_REF: | ||||
|                 to = stack + arg.offset2; | ||||
|                 regs[arg.offset] = (uint64_t) to; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_SSE: | ||||
|                 to = fp_regs + arg.offset; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_STACK: | ||||
|                 to = stack + arg.offset; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_STACK_REF: | ||||
|                 to = stack + arg.offset2; | ||||
|                 uint64_t *ptr = (uint64_t *) stack + arg.offset; | ||||
|                 *ptr = (uint64_t) to; | ||||
|                 break; | ||||
|             default: | ||||
|                 janet_panic("nyi"); | ||||
|         } | ||||
|  | ||||
|         if (to) { | ||||
|             janet_ffi_write_one(to, argv, n, arg.type, JANET_FFI_MAX_RECUR); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     switch (signature->variant) { | ||||
|         case 0: | ||||
|             retu.general_return = ((janet_aapcs64_variant_1 *)(function_pointer))( | ||||
|                                       regs[0], regs[1], regs[2], regs[3], | ||||
|                                       regs[4], regs[5], regs[6], regs[7], | ||||
|                                       fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], | ||||
|                                       fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); | ||||
|             break; | ||||
|         case 1: | ||||
|             retu.sse_return = ((janet_aapcs64_variant_2 *)(function_pointer))( | ||||
|                                   regs[0], regs[1], regs[2], regs[3], | ||||
|                                   regs[4], regs[5], regs[6], regs[7], | ||||
|                                   fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], | ||||
|                                   fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); | ||||
|             break; | ||||
|         case 2: { | ||||
|             retu.pointer_return = ((janet_aapcs64_variant_3 *)(function_pointer))( | ||||
|                                       regs[0], regs[1], regs[2], regs[3], | ||||
|                                       regs[4], regs[5], regs[6], regs[7], | ||||
|                                       fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], | ||||
|                                       fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* Allocate executable memory chunks in sizes of a page. Ideally we would keep | ||||
|  * an allocator around so that multiple JIT allocations would point to the same | ||||
|  * region but it isn't really worth it. */ | ||||
| @@ -1373,6 +1666,10 @@ JANET_CORE_FN(cfun_ffi_call, | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|         case JANET_FFI_CC_SYSV_64: | ||||
|             return janet_ffi_sysv64(signature, function_pointer, argv); | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|         case JANET_FFI_CC_AAPCS64: | ||||
|             return janet_ffi_aapcs64(signature, function_pointer, argv); | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| @@ -1442,6 +1739,10 @@ JANET_CORE_FN(cfun_ffi_get_callback_trampoline, | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|         case JANET_FFI_CC_SYSV_64: | ||||
|             return janet_wrap_pointer(janet_ffi_sysv64_standard_callback); | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|         case JANET_FFI_CC_AAPCS64: | ||||
|             return janet_wrap_pointer(janet_ffi_aapcs64_standard_callback); | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| @@ -1561,6 +1862,9 @@ JANET_CORE_FN(cfun_ffi_supported_calling_conventions, | ||||
| #endif | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|     janet_array_push(array, janet_ckeywordv("sysv64")); | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|     janet_array_push(array, janet_ckeywordv("aapcs64")); | ||||
| #endif | ||||
|     janet_array_push(array, janet_ckeywordv("none")); | ||||
|     return janet_wrap_array(array); | ||||
|   | ||||
| @@ -239,8 +239,8 @@ int janet_fiber_funcframe(JanetFiber *fiber, JanetFunction *func) { | ||||
|                                          fiber->data + tuplehead, | ||||
|                                          oldtop - tuplehead) | ||||
|                                      : janet_wrap_tuple(janet_tuple_n( | ||||
|                                                  fiber->data + tuplehead, | ||||
|                                                  oldtop - tuplehead)); | ||||
|                                              fiber->data + tuplehead, | ||||
|                                              oldtop - tuplehead)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -370,8 +370,8 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { | ||||
|                                          fiber->data + tuplehead, | ||||
|                                          fiber->stacktop - tuplehead) | ||||
|                                      : janet_wrap_tuple(janet_tuple_n( | ||||
|                                                  fiber->data + tuplehead, | ||||
|                                                  fiber->stacktop - tuplehead)); | ||||
|                                              fiber->data + tuplehead, | ||||
|                                              fiber->stacktop - tuplehead)); | ||||
|         } | ||||
|         stacksize = tuplehead - fiber->stackstart + 1; | ||||
|     } else { | ||||
| @@ -662,7 +662,7 @@ JANET_CORE_FN(cfun_fiber_can_resume, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_fiber_last_value, | ||||
|               "(fiber/last-value)", | ||||
|               "(fiber/last-value fiber)", | ||||
|               "Get the last value returned or signaled from the fiber.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
|   | ||||
							
								
								
									
										623
									
								
								src/core/filewatch.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										623
									
								
								src/core/filewatch.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,623 @@ | ||||
| /* | ||||
| * Copyright (c) 2024 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 | ||||
| * deal in the Software without restriction, including without limitation the | ||||
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||||
| * sell copies of the Software, and to permit persons to whom the Software is | ||||
| * furnished to do so, subject to the following conditions: | ||||
| * | ||||
| * The above copyright notice and this permission notice shall be included in | ||||
| * all copies or substantial portions of the Software. | ||||
| * | ||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||
| * IN THE SOFTWARE. | ||||
| */ | ||||
|  | ||||
| #ifndef JANET_AMALG | ||||
| #include "features.h" | ||||
| #include <janet.h> | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| #ifdef JANET_FILEWATCH | ||||
|  | ||||
| #ifdef JANET_LINUX | ||||
| #include <sys/inotify.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| typedef struct { | ||||
|     const char *name; | ||||
|     uint32_t flag; | ||||
| } JanetWatchFlagName; | ||||
|  | ||||
| typedef struct { | ||||
| #ifndef JANET_WINDOWS | ||||
|     JanetStream *stream; | ||||
| #endif | ||||
|     JanetTable* watch_descriptors; | ||||
|     JanetChannel *channel; | ||||
|     uint32_t default_flags; | ||||
|     int is_watching; | ||||
| } JanetWatcher; | ||||
|  | ||||
| #ifdef JANET_LINUX | ||||
|  | ||||
| #include <sys/inotify.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| static const JanetWatchFlagName watcher_flags_linux[] = { | ||||
|     {"access", IN_ACCESS}, | ||||
|     {"all", IN_ALL_EVENTS}, | ||||
|     {"attrib", IN_ATTRIB}, | ||||
|     {"close-nowrite", IN_CLOSE_NOWRITE}, | ||||
|     {"close-write", IN_CLOSE_WRITE}, | ||||
|     {"create", IN_CREATE}, | ||||
|     {"delete", IN_DELETE}, | ||||
|     {"delete-self", IN_DELETE_SELF}, | ||||
|     {"ignored", IN_IGNORED}, | ||||
|     {"modify", IN_MODIFY}, | ||||
|     {"move-self", IN_MOVE_SELF}, | ||||
|     {"moved-from", IN_MOVED_FROM}, | ||||
|     {"moved-to", IN_MOVED_TO}, | ||||
|     {"open", IN_OPEN}, | ||||
|     {"q-overflow", IN_Q_OVERFLOW}, | ||||
|     {"unmount", IN_UNMOUNT}, | ||||
| }; | ||||
|  | ||||
| static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|     uint32_t flags = 0; | ||||
|     for (int32_t i = 0; i < n; i++) { | ||||
|         if (!(janet_checktype(options[i], JANET_KEYWORD))) { | ||||
|             janet_panicf("expected keyword, got %v", options[i]); | ||||
|         } | ||||
|         JanetKeyword keyw = janet_unwrap_keyword(options[i]); | ||||
|         const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_linux, | ||||
|                 sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName), | ||||
|                 sizeof(JanetWatchFlagName), | ||||
|                 keyw); | ||||
|         if (!result) { | ||||
|             janet_panicf("unknown inotify flag %v", options[i]); | ||||
|         } | ||||
|         flags |= result->flag; | ||||
|     } | ||||
|     return flags; | ||||
| } | ||||
|  | ||||
| static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { | ||||
|     int fd; | ||||
|     do { | ||||
|         fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); | ||||
|     } while (fd == -1 && errno == EINTR); | ||||
|     if (fd == -1) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
|     watcher->watch_descriptors = janet_table(0); | ||||
|     watcher->channel = channel; | ||||
|     watcher->default_flags = default_flags; | ||||
|     watcher->is_watching = 0; | ||||
|     watcher->stream = janet_stream(fd, JANET_STREAM_READABLE, NULL); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { | ||||
|     if (watcher->stream == NULL) janet_panic("watcher closed"); | ||||
|     int result; | ||||
|     do { | ||||
|         result = inotify_add_watch(watcher->stream->handle, path, flags); | ||||
|     } while (result == -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
|     Janet name = janet_cstringv(path); | ||||
|     Janet wd = janet_wrap_integer(result); | ||||
|     janet_table_put(watcher->watch_descriptors, name, wd); | ||||
|     janet_table_put(watcher->watch_descriptors, wd, name); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { | ||||
|     if (watcher->stream == NULL) janet_panic("watcher closed"); | ||||
|     Janet check = janet_table_get(watcher->watch_descriptors, janet_cstringv(path)); | ||||
|     janet_assert(janet_checktype(check, JANET_NUMBER), "bad watch descriptor"); | ||||
|     int watch_handle = janet_unwrap_integer(check); | ||||
|     int result; | ||||
|     do { | ||||
|         result = inotify_rm_watch(watcher->stream->handle, watch_handle); | ||||
|     } while (result != -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     JanetStream *stream = fiber->ev_stream; | ||||
|     JanetWatcher *watcher = *((JanetWatcher **) fiber->ev_state); | ||||
|     char buf[1024]; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_MARK: | ||||
|             janet_mark(janet_wrap_abstract(watcher)); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             break; | ||||
|         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; | ||||
|                 } | ||||
|                 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); | ||||
|                     janet_struct_put(event, janet_ckeywordv("mask"), janet_wrap_integer(inevent.mask)); | ||||
|                     janet_struct_put(event, janet_ckeywordv("path"), 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)); | ||||
|  | ||||
|                     janet_channel_give(watcher->channel, eventv); | ||||
|                 } | ||||
|  | ||||
|                 /* Read some more if possible */ | ||||
|                 goto read_more; | ||||
|             } | ||||
|             break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_watcher_listen(JanetWatcher *watcher) { | ||||
|     if (watcher->is_watching) janet_panic("already watching"); | ||||
|     watcher->is_watching = 1; | ||||
|     JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil()); | ||||
|     JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL); | ||||
|     JanetWatcher **state = janet_malloc(sizeof(JanetWatcher *)); /* Gross */ | ||||
|     *state = watcher; | ||||
|     janet_async_start_fiber(fiber, watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, state); | ||||
|     janet_gcroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
|     if (!watcher->is_watching) return; | ||||
|     watcher->is_watching = 0; | ||||
|     janet_stream_close(watcher->stream); | ||||
|     janet_gcunroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| #elif JANET_WINDOWS | ||||
|  | ||||
| #define WATCHFLAG_RECURSIVE 0x100000u | ||||
|  | ||||
| static const JanetWatchFlagName watcher_flags_windows[] = { | ||||
|     {"all",  | ||||
|         FILE_NOTIFY_CHANGE_ATTRIBUTES | | ||||
|         FILE_NOTIFY_CHANGE_CREATION | | ||||
|         FILE_NOTIFY_CHANGE_DIR_NAME | | ||||
|         FILE_NOTIFY_CHANGE_FILE_NAME | | ||||
|         FILE_NOTIFY_CHANGE_LAST_ACCESS | | ||||
|         FILE_NOTIFY_CHANGE_LAST_WRITE | | ||||
|         FILE_NOTIFY_CHANGE_SECURITY | | ||||
|         FILE_NOTIFY_CHANGE_SIZE | | ||||
|         WATCHFLAG_RECURSIVE}, | ||||
|     {"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES}, | ||||
|     {"creation", FILE_NOTIFY_CHANGE_CREATION}, | ||||
|     {"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME}, | ||||
|     {"file-name", FILE_NOTIFY_CHANGE_FILE_NAME}, | ||||
|     {"last-access", FILE_NOTIFY_CHANGE_LAST_ACCESS}, | ||||
|     {"last-write", FILE_NOTIFY_CHANGE_LAST_WRITE}, | ||||
|     {"recursive", WATCHFLAG_RECURSIVE}, | ||||
|     {"security", FILE_NOTIFY_CHANGE_SECURITY}, | ||||
|     {"size", FILE_NOTIFY_CHANGE_SIZE}, | ||||
| }; | ||||
|  | ||||
| static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|     uint32_t flags = 0; | ||||
|     for (int32_t i = 0; i < n; i++) { | ||||
|         if (!(janet_checktype(options[i], JANET_KEYWORD))) { | ||||
|             janet_panicf("expected keyword, got %v", options[i]); | ||||
|         } | ||||
|         JanetKeyword keyw = janet_unwrap_keyword(options[i]); | ||||
|         const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows, | ||||
|                 sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), | ||||
|                 sizeof(JanetWatchFlagName), | ||||
|                 keyw); | ||||
|         if (!result) { | ||||
|             janet_panicf("unknown windows filewatch flag %v", options[i]); | ||||
|         } | ||||
|         flags |= result->flag; | ||||
|     } | ||||
|     return flags; | ||||
| } | ||||
|  | ||||
| static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { | ||||
|     watcher->watch_descriptors = janet_table(0); | ||||
|     watcher->channel = channel; | ||||
|     watcher->default_flags = default_flags; | ||||
|     watcher->is_watching = 0; | ||||
| } | ||||
|  | ||||
| /* Since the file info padding includes embedded file names, we want to include more space for data. | ||||
|  * We also need to handle manually calculating changes if path names are too long, but ideally just avoid | ||||
|  * that scenario as much as possible */ | ||||
| #define FILE_INFO_PADDING (4096 * 4) | ||||
|  | ||||
| typedef struct { | ||||
|     OVERLAPPED overlapped; | ||||
|     JanetStream *stream; | ||||
|     JanetWatcher *watcher; | ||||
|     JanetFiber *fiber; | ||||
|     JanetString dir_path; | ||||
|     uint32_t flags; | ||||
|     uint64_t buf[FILE_INFO_PADDING / sizeof(uint64_t)]; /* Ensure alignment */ | ||||
| } OverlappedWatch; | ||||
|  | ||||
| #define NotifyChange FILE_NOTIFY_INFORMATION | ||||
|  | ||||
| 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); | ||||
|     if (!result) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static const char* watcher_actions_windows[] = { | ||||
|     "unknown", | ||||
|     "added", | ||||
|     "removed", | ||||
|     "modified", | ||||
|     "renamed-old", | ||||
|     "renamed-new", | ||||
| }; | ||||
|  | ||||
| static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     OverlappedWatch *ow = (OverlappedWatch *) fiber->ev_state; | ||||
|     JanetWatcher *watcher = ow->watcher; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|             janet_async_in_flight(fiber); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_MARK: | ||||
|             janet_mark(janet_wrap_abstract(ow->stream)); | ||||
|             janet_mark(janet_wrap_fiber(ow->fiber)); | ||||
|             janet_mark(janet_wrap_abstract(watcher)); | ||||
|             janet_mark(janet_wrap_string(ow->dir_path)); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_table_remove(ow->watcher->watch_descriptors, janet_wrap_string(ow->dir_path)); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_ERR: | ||||
|         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"), 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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void start_listening_ow(OverlappedWatch *ow) { | ||||
|     read_dir_changes(ow); | ||||
|     JanetStream *stream = ow->stream; | ||||
|     JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil()); | ||||
|     JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL); | ||||
|     fiber->supervisor_channel = janet_root_fiber()->supervisor_channel; | ||||
|     ow->fiber = fiber; | ||||
|     janet_async_start_fiber(fiber, stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, 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); | ||||
|     if (handle == INVALID_HANDLE_VALUE) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
|     JanetStream *stream = janet_stream(handle, JANET_STREAM_READABLE, NULL); | ||||
|     OverlappedWatch *ow = janet_malloc(sizeof(OverlappedWatch)); | ||||
|     memset(ow, 0, sizeof(OverlappedWatch)); | ||||
|     ow->stream = stream; | ||||
|     ow->dir_path = janet_cstring(path); | ||||
|     ow->fiber = NULL; | ||||
|     Janet pathv = janet_wrap_string(ow->dir_path); | ||||
|     ow->flags = flags | watcher->default_flags; | ||||
|     ow->watcher = watcher; | ||||
|     ow->overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */ | ||||
|     Janet streamv = janet_wrap_pointer(ow); | ||||
|     janet_table_put(watcher->watch_descriptors, pathv, streamv); | ||||
|     if (watcher->is_watching) { | ||||
|         start_listening_ow(ow); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { | ||||
|     Janet pathv = janet_cstringv(path); | ||||
|     Janet streamv = janet_table_get(watcher->watch_descriptors, pathv); | ||||
|     if (janet_checktype(streamv, JANET_NIL)) { | ||||
|         janet_panicf("path %v is not being watched", pathv); | ||||
|     } | ||||
|     janet_table_remove(watcher->watch_descriptors, pathv); | ||||
|     OverlappedWatch *ow = janet_unwrap_pointer(streamv); | ||||
|     janet_stream_close(ow->stream); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_listen(JanetWatcher *watcher) { | ||||
|     if (watcher->is_watching) janet_panic("already watching"); | ||||
|     watcher->is_watching = 1; | ||||
|     for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { | ||||
|         const JanetKV *kv = watcher->watch_descriptors->data + i; | ||||
|         if (!janet_checktype(kv->value, JANET_POINTER)) continue; | ||||
|         OverlappedWatch *ow = janet_unwrap_pointer(kv->value); | ||||
|         start_listening_ow(ow); | ||||
|     } | ||||
|     janet_gcroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
|     if (!watcher->is_watching) return; | ||||
|     watcher->is_watching = 0; | ||||
|     for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { | ||||
|         const JanetKV *kv = watcher->watch_descriptors->data + i; | ||||
|         if (!janet_checktype(kv->value, JANET_POINTER)) continue; | ||||
|         OverlappedWatch *ow = janet_unwrap_pointer(kv->value); | ||||
|         janet_stream_close(ow->stream); | ||||
|     } | ||||
|     janet_table_clear(watcher->watch_descriptors); | ||||
|     janet_gcunroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| #else | ||||
|  | ||||
| /* Default implementation */ | ||||
|  | ||||
| static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|     (void) options; | ||||
|     (void) n; | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { | ||||
|     (void) watcher; | ||||
|     (void) channel; | ||||
|     (void) default_flags; | ||||
|     janet_panic("filewatch not supported on this platform"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { | ||||
|     (void) watcher; | ||||
|     (void) flags; | ||||
|     (void) path; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { | ||||
|     (void) watcher; | ||||
|     (void) path; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_listen(JanetWatcher *watcher) { | ||||
|     (void) watcher; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
|     (void) watcher; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* C Functions */ | ||||
|  | ||||
| static int janet_filewatch_mark(void *p, size_t s) { | ||||
|     JanetWatcher *watcher = (JanetWatcher *) p; | ||||
|     (void) s; | ||||
|     if (watcher->channel == NULL) return 0; /* Incomplete initialization */ | ||||
| #ifdef JANET_WINDOWS | ||||
|     for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { | ||||
|         const JanetKV *kv = watcher->watch_descriptors->data + i; | ||||
|         if (!janet_checktype(kv->value, JANET_POINTER)) continue; | ||||
|         OverlappedWatch *ow = janet_unwrap_pointer(kv->value); | ||||
|         janet_mark(janet_wrap_fiber(ow->fiber)); | ||||
|         janet_mark(janet_wrap_abstract(ow->stream)); | ||||
|         janet_mark(janet_wrap_string(ow->dir_path)); | ||||
|     } | ||||
| #else | ||||
|     janet_mark(janet_wrap_abstract(watcher->stream)); | ||||
| #endif | ||||
|     janet_mark(janet_wrap_abstract(watcher->channel)); | ||||
|     janet_mark(janet_wrap_table(watcher->watch_descriptors)); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static const JanetAbstractType janet_filewatch_at = { | ||||
|     "filewatch/watcher", | ||||
|     NULL, | ||||
|     janet_filewatch_mark, | ||||
|     JANET_ATEND_GCMARK | ||||
| }; | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_make, | ||||
|         "(filewatch/new channel &opt default-flags)", | ||||
|         "Create a new filewatcher that will give events to a channel channel.") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetChannel *channel = janet_getchannel(argv, 0); | ||||
|     JanetWatcher *watcher = janet_abstract(&janet_filewatch_at, sizeof(JanetWatcher)); | ||||
|     uint32_t default_flags = decode_watch_flags(argv + 1, argc - 1); | ||||
|     janet_watcher_init(watcher, channel, default_flags); | ||||
|     return janet_wrap_abstract(watcher); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_add, | ||||
|         "(filewatch/add watcher path &opt flags)", | ||||
|         "Add a path to the watcher.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     const char *path = janet_getcstring(argv, 1); | ||||
|     uint32_t flags = watcher->default_flags | decode_watch_flags(argv + 2, argc - 2); | ||||
|     janet_watcher_add(watcher, path, flags); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_remove, | ||||
|         "(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); | ||||
|     janet_watcher_remove(watcher, path); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_listen, | ||||
|         "(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); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_unlisten, | ||||
|         "(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); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| /* Module entry point */ | ||||
| void janet_lib_filewatch(JanetTable *env) { | ||||
|     JanetRegExt cfuns[] = { | ||||
|         JANET_CORE_REG("filewatch/new", cfun_filewatch_make), | ||||
|         JANET_CORE_REG("filewatch/add", cfun_filewatch_add), | ||||
|         JANET_CORE_REG("filewatch/remove", cfun_filewatch_remove), | ||||
|         JANET_CORE_REG("filewatch/listen", cfun_filewatch_listen), | ||||
|         JANET_CORE_REG("filewatch/unlisten", cfun_filewatch_unlisten), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, cfuns); | ||||
| } | ||||
|  | ||||
| #endif | ||||
| #endif | ||||
| @@ -64,7 +64,7 @@ enum JanetMemoryType { | ||||
| }; | ||||
|  | ||||
| /* To allocate collectable memory, one must call janet_alloc, initialize the memory, | ||||
|  * and then call when janet_enablegc when it is initailize and reachable by the gc (on the JANET stack) */ | ||||
|  * and then call when janet_enablegc when it is initialized and reachable by the gc (on the JANET stack) */ | ||||
| void *janet_gcalloc(enum JanetMemoryType type, size_t size); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -73,13 +73,13 @@ static void *int64_unmarshal(JanetMarshalContext *ctx) { | ||||
|  | ||||
| static void it_s64_tostring(void *p, JanetBuffer *buffer) { | ||||
|     char str[32]; | ||||
|     sprintf(str, "%" PRId64, *((int64_t *)p)); | ||||
|     snprintf(str, sizeof(str), "%" PRId64, *((int64_t *)p)); | ||||
|     janet_buffer_push_cstring(buffer, str); | ||||
| } | ||||
|  | ||||
| static void it_u64_tostring(void *p, JanetBuffer *buffer) { | ||||
|     char str[32]; | ||||
|     sprintf(str, "%" PRIu64, *((uint64_t *)p)); | ||||
|     snprintf(str, sizeof(str), "%" PRIu64, *((uint64_t *)p)); | ||||
|     janet_buffer_push_cstring(buffer, str); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -41,6 +41,11 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx); | ||||
| static void *io_file_unmarshal(JanetMarshalContext *ctx); | ||||
| static Janet io_file_next(void *p, Janet key); | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #define ftell _ftelli64 | ||||
| #define fseek _fseeki64 | ||||
| #endif | ||||
|  | ||||
| const JanetAbstractType janet_file_type = { | ||||
|     "core/file", | ||||
|     cfun_io_gc, | ||||
| @@ -126,7 +131,7 @@ JANET_CORE_FN(cfun_io_temp, | ||||
|     // XXX use mkostemp when we can to avoid CLOEXEC race. | ||||
|     FILE *tmp = tmpfile(); | ||||
|     if (!tmp) | ||||
|         janet_panicf("unable to create temporary file - %s", strerror(errno)); | ||||
|         janet_panicf("unable to create temporary file - %s", janet_strerror(errno)); | ||||
|     return janet_makefile(tmp, JANET_FILE_WRITE | JANET_FILE_READ | JANET_FILE_BINARY); | ||||
| } | ||||
|  | ||||
| @@ -168,7 +173,7 @@ JANET_CORE_FN(cfun_io_fopen, | ||||
|         } | ||||
|     } | ||||
|     return f ? janet_makefile(f, flags) | ||||
|            : (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, strerror(errno)), janet_wrap_nil()) | ||||
|            : (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil()) | ||||
|            : janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -289,7 +294,7 @@ int janet_file_close(JanetFile *file) { | ||||
|     if (!(file->flags & (JANET_FILE_NOT_CLOSEABLE | JANET_FILE_CLOSED))) { | ||||
|         ret = fclose(file->file); | ||||
|         file->flags |= JANET_FILE_CLOSED; | ||||
|         file->file = NULL; /* NULL derefence is easier to debug then other problems */ | ||||
|         file->file = NULL; /* NULL dereference is easier to debug then other problems */ | ||||
|         return ret; | ||||
|     } | ||||
|     return 0; | ||||
| @@ -337,7 +342,7 @@ JANET_CORE_FN(cfun_io_fseek, | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     long int offset = 0; | ||||
|     int64_t offset = 0; | ||||
|     int whence = SEEK_CUR; | ||||
|     if (argc >= 2) { | ||||
|         const uint8_t *whence_sym = janet_getkeyword(argv, 1); | ||||
| @@ -351,7 +356,7 @@ JANET_CORE_FN(cfun_io_fseek, | ||||
|             janet_panicf("expected one of :cur, :set, :end, got %v", argv[1]); | ||||
|         } | ||||
|         if (argc == 3) { | ||||
|             offset = (long) janet_getinteger64(argv, 2); | ||||
|             offset = (int64_t) janet_getinteger64(argv, 2); | ||||
|         } | ||||
|     } | ||||
|     if (fseek(iof->file, offset, whence)) janet_panic("error seeking file"); | ||||
| @@ -365,7 +370,7 @@ JANET_CORE_FN(cfun_io_ftell, | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     long pos = ftell(iof->file); | ||||
|     int64_t pos = ftell(iof->file); | ||||
|     if (pos == -1) janet_panic("error getting position in file"); | ||||
|     return janet_wrap_number((double)pos); | ||||
| } | ||||
|   | ||||
| @@ -185,6 +185,19 @@ static void marshal_one_env(MarshalState *st, JanetFuncEnv *env, int flags); | ||||
| /* Prevent stack overflows */ | ||||
| #define MARSH_STACKCHECK if ((flags & 0xFFFF) > JANET_RECURSION_GUARD) janet_panic("stack overflow") | ||||
|  | ||||
| /* Quick check if a fiber cannot be marshalled. This is will | ||||
|  * have no false positives, but may have false negatives. */ | ||||
| static int fiber_cannot_be_marshalled(JanetFiber *fiber) { | ||||
|     if (janet_fiber_status(fiber) == JANET_STATUS_ALIVE) return 1; | ||||
|     int32_t i = fiber->frame; | ||||
|     while (i > 0) { | ||||
|         JanetStackFrame *frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE); | ||||
|         if (!frame->func) return 1; /* has cfunction on stack */ | ||||
|         i = frame->prevframe; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* Marshal a function env */ | ||||
| static void marshal_one_env(MarshalState *st, JanetFuncEnv *env, int flags) { | ||||
|     MARSH_STACKCHECK; | ||||
| @@ -197,7 +210,9 @@ static void marshal_one_env(MarshalState *st, JanetFuncEnv *env, int flags) { | ||||
|     } | ||||
|     janet_env_valid(env); | ||||
|     janet_v_push(st->seen_envs, env); | ||||
|     if (env->offset > 0 && (JANET_STATUS_ALIVE == janet_fiber_status(env->as.fiber))) { | ||||
|  | ||||
|     /* Special case for early detachment */ | ||||
|     if (env->offset > 0 && fiber_cannot_be_marshalled(env->as.fiber)) { | ||||
|         pushint(st, 0); | ||||
|         pushint(st, env->length); | ||||
|         Janet *values = env->as.fiber->data + env->offset; | ||||
| @@ -328,7 +343,7 @@ static void marshal_one_fiber(MarshalState *st, JanetFiber *fiber, int flags) { | ||||
|     while (i > 0) { | ||||
|         JanetStackFrame *frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE); | ||||
|         if (frame->env) frame->flags |= JANET_STACKFRAME_HASENV; | ||||
|         if (!frame->func) janet_panic("cannot marshal fiber with c stackframe"); | ||||
|         if (!frame->func) janet_panicf("cannot marshal fiber with c stackframe (%v)", janet_wrap_cfunction((JanetCFunction) frame->pc)); | ||||
|         pushint(st, frame->flags); | ||||
|         pushint(st, frame->prevframe); | ||||
|         int32_t pcdiff = (int32_t)(frame->pc - frame->func->def->bytecode); | ||||
|   | ||||
| @@ -85,10 +85,10 @@ void janet_rng_longseed(JanetRNG *rng, const uint8_t *bytes, int32_t len) { | ||||
|     uint8_t state[16] = {0}; | ||||
|     for (int32_t i = 0; i < len; i++) | ||||
|         state[i & 0xF] ^= bytes[i]; | ||||
|     rng->a = state[0] + (state[1] << 8) + (state[2] << 16) + (state[3] << 24); | ||||
|     rng->b = state[4] + (state[5] << 8) + (state[6] << 16) + (state[7] << 24); | ||||
|     rng->c = state[8] + (state[9] << 8) + (state[10] << 16) + (state[11] << 24); | ||||
|     rng->d = state[12] + (state[13] << 8) + (state[14] << 16) + (state[15] << 24); | ||||
|     rng->a = state[0] + ((uint32_t) state[1] << 8) + ((uint32_t) state[2] << 16) + ((uint32_t) state[3] << 24); | ||||
|     rng->b = state[4] + ((uint32_t) state[5] << 8) + ((uint32_t) state[6] << 16) + ((uint32_t) state[7] << 24); | ||||
|     rng->c = state[8] + ((uint32_t) state[9] << 8) + ((uint32_t) state[10] << 16) + ((uint32_t) state[11] << 24); | ||||
|     rng->d = state[12] + ((uint32_t) state[13] << 8) + ((uint32_t) state[14] << 16) + ((uint32_t) state[15] << 24); | ||||
|     rng->counter = 0u; | ||||
|     /* a, b, c, d can't all be 0 */ | ||||
|     if (rng->a == 0) rng->a = 1u; | ||||
| @@ -349,6 +349,26 @@ JANET_CORE_FN(janet_cfun_lcm, "(math/lcm x y)", | ||||
|     return janet_wrap_number(janet_lcm(x, y)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_frexp, "(math/frexp x)", | ||||
|               "Returns a tuple of (mantissa, exponent) from number.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     double x = janet_getnumber(argv, 0); | ||||
|     int exp; | ||||
|     x = frexp(x, &exp); | ||||
|     Janet *result = janet_tuple_begin(2); | ||||
|     result[0] = janet_wrap_number(x); | ||||
|     result[1] = janet_wrap_number((double) exp); | ||||
|     return janet_wrap_tuple(janet_tuple_end(result)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_ldexp, "(math/ldexp m e)", | ||||
|               "Creates a new number from a mantissa and an exponent.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     double x = janet_getnumber(argv, 0); | ||||
|     int32_t y = janet_getinteger(argv, 1); | ||||
|     return janet_wrap_number(ldexp(x, y)); | ||||
| } | ||||
|  | ||||
| /* Module entry point */ | ||||
| void janet_lib_math(JanetTable *env) { | ||||
|     JanetRegExt math_cfuns[] = { | ||||
| @@ -395,6 +415,8 @@ void janet_lib_math(JanetTable *env) { | ||||
|         JANET_CORE_REG("math/next", janet_nextafter), | ||||
|         JANET_CORE_REG("math/gcd", janet_cfun_gcd), | ||||
|         JANET_CORE_REG("math/lcm", janet_cfun_lcm), | ||||
|         JANET_CORE_REG("math/frexp", janet_cfun_frexp), | ||||
|         JANET_CORE_REG("math/ldexp", janet_cfun_ldexp), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, math_cfuns); | ||||
|   | ||||
| @@ -79,12 +79,20 @@ const JanetAbstractType janet_address_type = { | ||||
|  | ||||
| /* maximum number of bytes in a socket address host (post name resolution) */ | ||||
| #ifdef JANET_WINDOWS | ||||
| #ifdef JANET_NO_IPV6 | ||||
| #define SA_ADDRSTRLEN (INET_ADDRSTRLEN + 1) | ||||
| #else | ||||
| #define SA_ADDRSTRLEN (INET6_ADDRSTRLEN + 1) | ||||
| #endif | ||||
| typedef unsigned short in_port_t; | ||||
| #else | ||||
| #define JANET_SA_MAX(a, b) (((a) > (b))? (a) : (b)) | ||||
| #ifdef JANET_NO_IPV6 | ||||
| #define SA_ADDRSTRLEN JANET_SA_MAX(INET_ADDRSTRLEN + 1, (sizeof ((struct sockaddr_un *)0)->sun_path) + 1) | ||||
| #else | ||||
| #define SA_ADDRSTRLEN JANET_SA_MAX(INET6_ADDRSTRLEN + 1, (sizeof ((struct sockaddr_un *)0)->sun_path) + 1) | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| static JanetStream *make_stream(JSock handle, uint32_t flags); | ||||
|  | ||||
| @@ -114,16 +122,18 @@ static void janet_net_socknoblock(JSock s) { | ||||
|  | ||||
| /* State machine for async connect */ | ||||
|  | ||||
| typedef struct { | ||||
|     int did_connect; | ||||
| } NetStateConnect; | ||||
|  | ||||
| void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     JanetStream *stream = fiber->ev_stream; | ||||
|     NetStateConnect *state = (NetStateConnect *)fiber->ev_state; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
| #ifndef JANET_WINDOWS | ||||
|         /* Wait until we have an actual event before checking. | ||||
|          * Windows doesn't support async connect with this, just try immediately.*/ | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
| #endif | ||||
|         case JANET_ASYNC_EVENT_DEINIT: | ||||
|             return; | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_cancel(fiber, janet_cstringv("stream closed")); | ||||
|             janet_async_end(fiber); | ||||
| @@ -140,10 +150,9 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
| #endif | ||||
|     if (r == 0) { | ||||
|         if (res == 0) { | ||||
|             state->did_connect = 1; | ||||
|             janet_schedule(fiber, janet_wrap_abstract(stream)); | ||||
|         } else { | ||||
|             janet_cancel(fiber, janet_cstringv(strerror(res))); | ||||
|             janet_cancel(fiber, janet_cstringv(janet_strerror(res))); | ||||
|             stream->flags |= JANET_STREAM_TOCLOSE; | ||||
|         } | ||||
|     } else { | ||||
| @@ -153,13 +162,8 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     janet_async_end(fiber); | ||||
| } | ||||
|  | ||||
| static void net_sched_connect(JanetStream *stream) { | ||||
|     JanetFiber *f = janet_vm.root_fiber; | ||||
|     NetStateConnect *state = (NetStateConnect *) janet_async_start(f, stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, sizeof(NetStateConnect)); | ||||
|     state->did_connect = 0; | ||||
| #ifdef JANET_WINDOWS | ||||
|     net_callback_connect(f, JANET_ASYNC_EVENT_USER); | ||||
| #endif | ||||
| static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) { | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, NULL); | ||||
| } | ||||
|  | ||||
| /* State machine for accepting connections. */ | ||||
| @@ -229,14 +233,16 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|  | ||||
| JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) { | ||||
|     Janet err; | ||||
|     JanetFiber *f = janet_vm.root_fiber; | ||||
|     NetStateAccept *state = (NetStateAccept *) janet_async_start(f, stream, JANET_ASYNC_LISTEN_READ, net_callback_accept, sizeof(NetStateAccept)); | ||||
|     NetStateAccept *state = janet_malloc(sizeof(NetStateAccept)); | ||||
|     memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED)); | ||||
|     memset(&state->buf, 0, 1024); | ||||
|     state->function = fun; | ||||
|     state->lstream = stream; | ||||
|     if (net_sched_accept_impl(state, f, &err)) janet_panicv(err); | ||||
|     janet_await(); | ||||
|     if (net_sched_accept_impl(state, janet_root_fiber(), &err)) { | ||||
|         janet_free(state); | ||||
|         janet_panicv(err); | ||||
|     } | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_READ, net_callback_accept, state); | ||||
| } | ||||
|  | ||||
| static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet *err) { | ||||
| @@ -253,7 +259,7 @@ static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet | ||||
|         int code = WSAGetLastError(); | ||||
|         if (code == WSA_IO_PENDING) { | ||||
|             /* indicates io is happening async */ | ||||
|             fiber->flags |= JANET_FIBER_EV_FLAG_IN_FLIGHT; | ||||
|             janet_async_in_flight(fiber); | ||||
|             return 0; | ||||
|         } | ||||
|         *err = janet_ev_lasterr(); | ||||
| @@ -282,7 +288,7 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             return; | ||||
|         case JANET_ASYNC_EVENT_USER: | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|         case JANET_ASYNC_EVENT_READ: { | ||||
| #if defined(JANET_LINUX) | ||||
|             JSock connfd = accept4(stream->handle, NULL, NULL, SOCK_CLOEXEC); | ||||
| @@ -310,16 +316,16 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
| } | ||||
|  | ||||
| JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) { | ||||
|     JanetFiber *f = janet_vm.root_fiber; | ||||
|     NetStateAccept *state = (NetStateAccept *) janet_async_start(f, stream, JANET_ASYNC_LISTEN_READ, net_callback_accept, sizeof(NetStateAccept)); | ||||
|     NetStateAccept *state = janet_malloc(sizeof(NetStateAccept)); | ||||
|     memset(state, 0, sizeof(NetStateAccept)); | ||||
|     state->function = fun; | ||||
|     net_callback_accept(f, JANET_ASYNC_EVENT_USER); | ||||
|     janet_await(); | ||||
|     if (fun) janet_stream_level_triggered(stream); | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_READ, net_callback_accept, state); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* Adress info */ | ||||
| /* Address info */ | ||||
|  | ||||
| static int janet_get_sockettype(Janet *argv, int32_t argc, int32_t n) { | ||||
|     JanetKeyword stype = janet_optkeyword(argv, argc, n, NULL); | ||||
| @@ -570,7 +576,6 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     } | ||||
|  | ||||
|     net_sched_connect(stream); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| static const char *serverify_socket(JSock sfd) { | ||||
| @@ -741,6 +746,7 @@ static Janet janet_so_getname(const void *sa_any) { | ||||
|             Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai->sin_port))}; | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, 2)); | ||||
|         } | ||||
| #ifndef JANET_NO_IPV6 | ||||
|         case AF_INET6: { | ||||
|             const struct sockaddr_in6 *sai6 = sa_any; | ||||
|             if (!inet_ntop(AF_INET6, &(sai6->sin6_addr), buffer, sizeof(buffer))) { | ||||
| @@ -749,6 +755,7 @@ static Janet janet_so_getname(const void *sa_any) { | ||||
|             Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai6->sin6_port))}; | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, 2)); | ||||
|         } | ||||
| #endif | ||||
| #ifndef JANET_WINDOWS | ||||
|         case AF_UNIX: { | ||||
|             const struct sockaddr_un *sun = sa_any; | ||||
| @@ -815,6 +822,7 @@ JANET_CORE_FN(cfun_stream_accept_loop, | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_ACCEPTABLE | JANET_STREAM_SOCKET); | ||||
|     JanetFunction *fun = janet_getfunction(argv, 1); | ||||
|     if (fun->def->min_arity < 1) janet_panic("handler function must take at least 1 argument"); | ||||
|     janet_sched_accept(stream, fun); | ||||
| } | ||||
|  | ||||
| @@ -851,7 +859,6 @@ JANET_CORE_FN(cfun_stream_read, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_recv(stream, buffer, n, MSG_NOSIGNAL); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_chunk, | ||||
| @@ -866,7 +873,6 @@ JANET_CORE_FN(cfun_stream_chunk, | ||||
|     double to = janet_optnumber(argv, argc, 3, INFINITY); | ||||
|     if (to != INFINITY) janet_addtimeout(to); | ||||
|     janet_ev_recvchunk(stream, buffer, n, MSG_NOSIGNAL); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_recv_from, | ||||
| @@ -881,7 +887,6 @@ JANET_CORE_FN(cfun_stream_recv_from, | ||||
|     double to = janet_optnumber(argv, argc, 3, INFINITY); | ||||
|     if (to != INFINITY) janet_addtimeout(to); | ||||
|     janet_ev_recvfrom(stream, buffer, n, MSG_NOSIGNAL); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_write, | ||||
| @@ -901,7 +906,6 @@ JANET_CORE_FN(cfun_stream_write, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_send_string(stream, bytes.bytes, MSG_NOSIGNAL); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_send_to, | ||||
| @@ -922,7 +926,6 @@ JANET_CORE_FN(cfun_stream_send_to, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_sendto_string(stream, bytes.bytes, dest, MSG_NOSIGNAL); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_flush, | ||||
| @@ -956,8 +959,10 @@ static const struct sockopt_type sockopt_type_list[] = { | ||||
|     { "ip-multicast-ttl", IPPROTO_IP, IP_MULTICAST_TTL, JANET_NUMBER }, | ||||
|     { "ip-add-membership", IPPROTO_IP, IP_ADD_MEMBERSHIP, JANET_POINTER }, | ||||
|     { "ip-drop-membership", IPPROTO_IP, IP_DROP_MEMBERSHIP, JANET_POINTER }, | ||||
| #ifndef JANET_NO_IPV6 | ||||
|     { "ipv6-join-group", IPPROTO_IPV6, IPV6_JOIN_GROUP, JANET_POINTER }, | ||||
|     { "ipv6-leave-group", IPPROTO_IPV6, IPV6_LEAVE_GROUP, JANET_POINTER }, | ||||
| #endif | ||||
|     { NULL, 0, 0, JANET_POINTER } | ||||
| }; | ||||
|  | ||||
| @@ -994,7 +999,9 @@ JANET_CORE_FN(cfun_net_setsockopt, | ||||
|     union { | ||||
|         int v_int; | ||||
|         struct ip_mreq v_mreq; | ||||
| #ifndef JANET_NO_IPV6 | ||||
|         struct ipv6_mreq v_mreq6; | ||||
| #endif | ||||
|     } val; | ||||
|  | ||||
|     void *optval = (void *)&val; | ||||
| @@ -1012,12 +1019,14 @@ JANET_CORE_FN(cfun_net_setsockopt, | ||||
|         val.v_mreq.imr_interface.s_addr = htonl(INADDR_ANY); | ||||
|         inet_pton(AF_INET, addr, &val.v_mreq.imr_multiaddr.s_addr); | ||||
|         optlen = sizeof(val.v_mreq); | ||||
| #ifndef JANET_NO_IPV6 | ||||
|     } else if (st->optname == IPV6_JOIN_GROUP || st->optname == IPV6_LEAVE_GROUP) { | ||||
|         const char *addr = janet_getcstring(argv, 2); | ||||
|         memset(&val.v_mreq6, 0, sizeof val.v_mreq6); | ||||
|         val.v_mreq6.ipv6mr_interface = 0; | ||||
|         inet_pton(AF_INET6, addr, &val.v_mreq6.ipv6mr_multiaddr); | ||||
|         optlen = sizeof(val.v_mreq6); | ||||
| #endif | ||||
|     } else { | ||||
|         janet_panicf("invalid socket option type"); | ||||
|     } | ||||
| @@ -1026,7 +1035,7 @@ JANET_CORE_FN(cfun_net_setsockopt, | ||||
|  | ||||
|     int r = setsockopt((JSock) stream->handle, st->level, st->optname, optval, optlen); | ||||
|     if (r == -1) { | ||||
|         janet_panicf("setsockopt(%q): %s", argv[1], strerror(errno)); | ||||
|         janet_panicf("setsockopt(%q): %s", argv[1], janet_strerror(errno)); | ||||
|     } | ||||
|  | ||||
|     return janet_wrap_nil(); | ||||
|   | ||||
							
								
								
									
										334
									
								
								src/core/os.c
									
									
									
									
									
								
							
							
						
						
									
										334
									
								
								src/core/os.c
									
									
									
									
									
								
							| @@ -38,6 +38,7 @@ | ||||
| #include <string.h> | ||||
| #include <sys/stat.h> | ||||
| #include <signal.h> | ||||
| #include <locale.h> | ||||
|  | ||||
| #ifdef JANET_BSD | ||||
| #include <sys/sysctl.h> | ||||
| @@ -229,10 +230,11 @@ JANET_CORE_FN(os_compiler, | ||||
| #undef janet_stringify | ||||
|  | ||||
| JANET_CORE_FN(os_exit, | ||||
|               "(os/exit &opt x)", | ||||
|               "(os/exit &opt x force)", | ||||
|               "Exit from janet with an exit code equal to x. If x is not an integer, " | ||||
|               "the exit with status equal the hash of x.") { | ||||
|     janet_arity(argc, 0, 1); | ||||
|               "the exit with status equal the hash of x. If `force` is truthy will exit immediately and " | ||||
|               "skip cleanup code.") { | ||||
|     janet_arity(argc, 0, 2); | ||||
|     int status; | ||||
|     if (argc == 0) { | ||||
|         status = EXIT_SUCCESS; | ||||
| @@ -242,7 +244,11 @@ JANET_CORE_FN(os_exit, | ||||
|         status = EXIT_FAILURE; | ||||
|     } | ||||
|     janet_deinit(); | ||||
|     exit(status); | ||||
|     if (argc >= 2 && janet_truthy(argv[1])) { | ||||
|         _exit(status); | ||||
|     } else { | ||||
|         exit(status); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -500,8 +506,11 @@ static int proc_get_status(JanetProc *proc) { | ||||
|         status = WEXITSTATUS(status); | ||||
|     } else if (WIFSTOPPED(status)) { | ||||
|         status = WSTOPSIG(status) + 128; | ||||
|     } else { | ||||
|     } else if (WIFSIGNALED(status)) { | ||||
|         status = WTERMSIG(status) + 128; | ||||
|     } else { | ||||
|         /* Could possibly return -1 but for now, just panic */ | ||||
|         janet_panicf("Undefined status code for process termination, %d.", status); | ||||
|     } | ||||
|     return status; | ||||
| } | ||||
| @@ -529,7 +538,9 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) { | ||||
|             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)); | ||||
|             if (janet_fiber_can_resume(args.fiber)) { | ||||
|                 janet_schedule(args.fiber, janet_wrap_integer(status)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -611,7 +622,11 @@ os_proc_wait_impl(JanetProc *proc) { | ||||
|  | ||||
| JANET_CORE_FN(os_proc_wait, | ||||
|               "(os/proc-wait proc)", | ||||
|               "Block until the subprocess completes. Returns the subprocess return code.") { | ||||
|               "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.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
| #ifdef JANET_EV | ||||
| @@ -640,7 +655,7 @@ static const struct keyword_signal signal_keywords[] = { | ||||
| #ifdef SIGTERM | ||||
|     {"term", SIGTERM}, | ||||
| #endif | ||||
| #ifdef SIGARLM | ||||
| #ifdef SIGALRM | ||||
|     {"alrm", SIGALRM}, | ||||
| #endif | ||||
| #ifdef SIGHUP | ||||
| @@ -722,10 +737,11 @@ 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 `wait` is truthy, will wait for the process to finish and " | ||||
|               "returns the exit code. 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.") { | ||||
|               "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.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
|     if (proc->flags & JANET_PROC_WAITED) { | ||||
| @@ -746,7 +762,7 @@ JANET_CORE_FN(os_proc_kill, | ||||
|     } | ||||
|     int status = kill(proc->pid, signal == -1 ? SIGKILL : signal); | ||||
|     if (status) { | ||||
|         janet_panic(strerror(errno)); | ||||
|         janet_panic(janet_strerror(errno)); | ||||
|     } | ||||
| #endif | ||||
|     /* After killing process we wait on it. */ | ||||
| @@ -764,8 +780,9 @@ JANET_CORE_FN(os_proc_kill, | ||||
|  | ||||
| JANET_CORE_FN(os_proc_close, | ||||
|               "(os/proc-close proc)", | ||||
|               "Wait on a process if it has not been waited on, and close pipes created by `os/spawn` " | ||||
|               "if they have not been closed. Returns nil.") { | ||||
|               "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.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
| #ifdef JANET_EV | ||||
| @@ -875,8 +892,9 @@ JANET_CORE_FN(os_sigaction, | ||||
|     } | ||||
|     struct sigaction action; | ||||
|     sigset_t mask; | ||||
|     sigfillset(&mask); | ||||
|     sigaddset(&mask, sig); | ||||
|     memset(&action, 0, sizeof(action)); | ||||
|     action.sa_flags |= SA_RESTART; | ||||
|     if (can_interrupt) { | ||||
| #ifdef JANET_NO_INTERPRETER_INTERRUPT | ||||
|         janet_panic("interpreter interrupt not enabled"); | ||||
| @@ -1081,11 +1099,18 @@ static JanetFile *get_stdio_for_handle(JanetHandle handle, void *orig, int iswri | ||||
| } | ||||
| #endif | ||||
|  | ||||
| static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
| typedef enum { | ||||
|     JANET_EXECUTE_EXECUTE, | ||||
|     JANET_EXECUTE_SPAWN, | ||||
|     JANET_EXECUTE_EXEC | ||||
| } JanetExecuteMode; | ||||
|  | ||||
| static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); | ||||
|     janet_arity(argc, 1, 3); | ||||
|  | ||||
|     /* Get flags */ | ||||
|     int is_spawn = mode == JANET_EXECUTE_SPAWN; | ||||
|     uint64_t flags = 0; | ||||
|     if (argc > 1) { | ||||
|         flags = janet_getflags(argv, 1, "epxd"); | ||||
| @@ -1109,7 +1134,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|     int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0; | ||||
|  | ||||
|     /* Get optional redirections */ | ||||
|     if (argc > 2) { | ||||
|     if (argc > 2 && (mode != JANET_EXECUTE_EXEC)) { | ||||
|         JanetDictView tab = janet_getdictionary(argv, 2); | ||||
|         Janet maybe_stdin = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("in")); | ||||
|         Janet maybe_stdout = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("out")); | ||||
| @@ -1230,12 +1255,32 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|      * of posix_spawn would modify the argv array passed in. */ | ||||
|     char *const *cargv = (char *const *)child_argv; | ||||
|  | ||||
|     /* Use posix_spawn to spawn new process */ | ||||
|  | ||||
|     if (use_environ) { | ||||
|         janet_lock_environ(); | ||||
|     } | ||||
|  | ||||
|     /* exec mode */ | ||||
|     if (mode == JANET_EXECUTE_EXEC) { | ||||
| #ifdef JANET_WINDOWS | ||||
|         janet_panic("not supported on windows"); | ||||
| #else | ||||
|         int status; | ||||
|         if (!use_environ) { | ||||
|             environ = envp; | ||||
|         } | ||||
|         do { | ||||
|             if (janet_flag_at(flags, 1)) { | ||||
|                 status = execvp(cargv[0], cargv); | ||||
|             } else { | ||||
|                 status = execv(cargv[0], cargv); | ||||
|             } | ||||
|         } while (status == -1 && errno == EINTR); | ||||
|         janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT)); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     /* Use posix_spawn to spawn new process */ | ||||
|  | ||||
|     /* Posix spawn setup */ | ||||
|     posix_spawn_file_actions_t actions; | ||||
|     posix_spawn_file_actions_init(&actions); | ||||
| @@ -1287,7 +1332,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|     os_execute_cleanup(envp, child_argv); | ||||
|     if (status) { | ||||
|         /* correct for macos bug where errno is not set */ | ||||
|         janet_panicf("%p: %s", argv[0], strerror(errno ? errno : ENOENT)); | ||||
|         janet_panicf("%p: %s", argv[0], janet_strerror(errno ? errno : ENOENT)); | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| @@ -1342,22 +1387,63 @@ JANET_CORE_FN(os_execute, | ||||
|               "* :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. " | ||||
|               "These arguments should be core/file values. " | ||||
|               "Returns the exit status of the program.") { | ||||
|     return os_execute_impl(argc, argv, 0); | ||||
|               ":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.") { | ||||
|     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 to 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, :return-code, and " | ||||
|               "the additional field :pid on unix-like platforms. Use `(os/proc-wait proc)` to rejoin the " | ||||
|               "subprocess or `(os/proc-kill proc)`.") { | ||||
|     return os_execute_impl(argc, argv, 1); | ||||
|               "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.") { | ||||
|     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 " | ||||
|               "does not allow redirection of stdio.") { | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_posix_fork, | ||||
|               "(os/posix-fork)", | ||||
|               "Make a `fork` system call and create a new process. Return nil if in the new process, otherwise a core/process object (as returned by os/spawn). " | ||||
|               "Not supported on all systems (POSIX only).") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
| #ifdef JANET_WINDOWS | ||||
|     janet_panic("not supported"); | ||||
| #else | ||||
|     pid_t result; | ||||
|     do { | ||||
|         result = fork(); | ||||
|     } while (result == -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panic(janet_strerror(errno)); | ||||
|     } | ||||
|     if (result) { | ||||
|         JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc)); | ||||
|         memset(proc, 0, sizeof(JanetProc)); | ||||
|         proc->pid = result; | ||||
|         proc->flags = JANET_PROC_ALLOW_ZOMBIE; | ||||
|         return janet_wrap_abstract(proc); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| @@ -1479,34 +1565,51 @@ JANET_CORE_FN(os_time, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_clock, | ||||
|               "(os/clock &opt source)", | ||||
|               "Return the number of whole + fractional seconds of the requested clock source.\n\n" | ||||
|               "(os/clock &opt source format)", | ||||
|               "Return the current time of the requested clock source.\n\n" | ||||
|               "The `source` argument selects the clock source to use, when not specified the default " | ||||
|               "is `:realtime`:\n" | ||||
|               "- :realtime: Return the real (i.e., wall-clock) time. This clock is affected by discontinuous " | ||||
|               "  jumps in the system time\n" | ||||
|               "- :monotonic: Return the number of whole + fractional seconds since some fixed point in " | ||||
|               "  time. The clock is guaranteed to be non-decreasing in real time.\n" | ||||
|               "- :cputime: Return the CPU time consumed by this process  (i.e. all threads in the process)\n") { | ||||
|               "- :cputime: Return the CPU time consumed by this process  (i.e. all threads in the process)\n" | ||||
|               "The `format` argument selects the type of output, when not specified the default is `:double`:\n" | ||||
|               "- :double: Return the number of seconds + fractional seconds as a double\n" | ||||
|               "- :int: Return the number of seconds as an integer\n" | ||||
|               "- :tuple: Return a 2 integer tuple [seconds, nanoseconds]\n") { | ||||
|     enum JanetTimeSource source; | ||||
|     janet_sandbox_assert(JANET_SANDBOX_HRTIME); | ||||
|     janet_arity(argc, 0, 1); | ||||
|     enum JanetTimeSource source = JANET_TIME_REALTIME; | ||||
|     if (argc == 1) { | ||||
|         JanetKeyword sourcestr = janet_getkeyword(argv, 0); | ||||
|         if (janet_cstrcmp(sourcestr, "realtime") == 0) { | ||||
|             source = JANET_TIME_REALTIME; | ||||
|         } else if (janet_cstrcmp(sourcestr, "monotonic") == 0) { | ||||
|             source = JANET_TIME_MONOTONIC; | ||||
|         } else if (janet_cstrcmp(sourcestr, "cputime") == 0) { | ||||
|             source = JANET_TIME_CPUTIME; | ||||
|         } else { | ||||
|             janet_panicf("expected :realtime, :monotonic, or :cputime, got %v", argv[0]); | ||||
|         } | ||||
|     janet_arity(argc, 0, 2); | ||||
|  | ||||
|     JanetKeyword sourcestr = janet_optkeyword(argv, argc, 0, NULL); | ||||
|     if (sourcestr == NULL || janet_cstrcmp(sourcestr, "realtime") == 0) { | ||||
|         source = JANET_TIME_REALTIME; | ||||
|     } else if (janet_cstrcmp(sourcestr, "monotonic") == 0) { | ||||
|         source = JANET_TIME_MONOTONIC; | ||||
|     } else if (janet_cstrcmp(sourcestr, "cputime") == 0) { | ||||
|         source = JANET_TIME_CPUTIME; | ||||
|     } else { | ||||
|         janet_panicf("expected :realtime, :monotonic, or :cputime, got %v", argv[0]); | ||||
|     } | ||||
|  | ||||
|     struct timespec tv; | ||||
|     if (janet_gettime(&tv, source)) janet_panic("could not get time"); | ||||
|     double dtime = tv.tv_sec + (tv.tv_nsec / 1E9); | ||||
|     return janet_wrap_number(dtime); | ||||
|  | ||||
|     JanetKeyword formatstr = janet_optkeyword(argv, argc, 1, NULL); | ||||
|     if (formatstr == NULL || janet_cstrcmp(formatstr, "double") == 0) { | ||||
|         double dtime = (double)(tv.tv_sec + (tv.tv_nsec / 1E9)); | ||||
|         return janet_wrap_number(dtime); | ||||
|     } else if (janet_cstrcmp(formatstr, "int") == 0) { | ||||
|         return janet_wrap_number((double)(tv.tv_sec)); | ||||
|     } else if (janet_cstrcmp(formatstr, "tuple") == 0) { | ||||
|         Janet tup[2] = {janet_wrap_number((double)tv.tv_sec), | ||||
|                         janet_wrap_number((double)tv.tv_nsec) | ||||
|                        }; | ||||
|         return janet_wrap_tuple(janet_tuple_n(tup, 2)); | ||||
|     } else { | ||||
|         janet_panicf("expected :double, :int, or :tuple, got %v", argv[1]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_sleep, | ||||
| @@ -1542,7 +1645,7 @@ JANET_CORE_FN(os_isatty, | ||||
|     return janet_wrap_boolean(_isatty(fd)); | ||||
| #else | ||||
|     int fd = fileno(f); | ||||
|     if (fd == -1) janet_panic(strerror(errno)); | ||||
|     if (fd == -1) janet_panic(janet_strerror(errno)); | ||||
|     return janet_wrap_boolean(isatty(fd)); | ||||
| #endif | ||||
| } | ||||
| @@ -1777,7 +1880,7 @@ JANET_CORE_FN(os_mktime, | ||||
|     } | ||||
|  | ||||
|     if (t == (time_t) -1) { | ||||
|         janet_panicf("%s", strerror(errno)); | ||||
|         janet_panicf("%s", janet_strerror(errno)); | ||||
|     } | ||||
|  | ||||
|     return janet_wrap_number((double)t); | ||||
| @@ -1789,6 +1892,43 @@ JANET_CORE_FN(os_mktime, | ||||
| #define j_symlink symlink | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(os_setlocale, | ||||
|               "(os/setlocale &opt locale category)", | ||||
|               "Set the system locale, which affects how dates and numbers are formatted. " | ||||
|               "Passing nil to locale will return the current locale. Category can be one of:\n\n" | ||||
|               " * :all (default)\n" | ||||
|               " * :collate\n" | ||||
|               " * :ctype\n" | ||||
|               " * :monetary\n" | ||||
|               " * :numeric\n" | ||||
|               " * :time\n\n" | ||||
|               "Returns the new locale if set successfully, otherwise nil. Note that this will affect " | ||||
|               "other functions such as `os/strftime` and even `printf`.") { | ||||
|     janet_arity(argc, 0, 2); | ||||
|     const char *locale_name = janet_optcstring(argv, argc, 0, NULL); | ||||
|     int category_int = LC_ALL; | ||||
|     if (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) { | ||||
|         if (janet_keyeq(argv[1], "all")) { | ||||
|             category_int = LC_ALL; | ||||
|         } else if (janet_keyeq(argv[1], "collate")) { | ||||
|             category_int = LC_COLLATE; | ||||
|         } else if (janet_keyeq(argv[1], "ctype")) { | ||||
|             category_int = LC_CTYPE; | ||||
|         } else if (janet_keyeq(argv[1], "monetary")) { | ||||
|             category_int = LC_MONETARY; | ||||
|         } else if (janet_keyeq(argv[1], "numeric")) { | ||||
|             category_int = LC_NUMERIC; | ||||
|         } else if (janet_keyeq(argv[1], "time")) { | ||||
|             category_int = LC_TIME; | ||||
|         } else { | ||||
|             janet_panicf("expected one of :all, :collate, :ctype, :monetary, :numeric, or :time, got %v", argv[1]); | ||||
|         } | ||||
|     } | ||||
|     const char *old = setlocale(category_int, locale_name); | ||||
|     if (old == NULL) return janet_wrap_nil(); | ||||
|     return janet_cstringv(old); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_link, | ||||
|               "(os/link oldpath newpath &opt symlink)", | ||||
|               "Create a link at newpath that points to oldpath and returns nil. " | ||||
| @@ -1806,7 +1946,7 @@ JANET_CORE_FN(os_link, | ||||
|     const char *oldpath = janet_getcstring(argv, 0); | ||||
|     const char *newpath = janet_getcstring(argv, 1); | ||||
|     int res = ((argc == 3 && janet_truthy(argv[2])) ? j_symlink : link)(oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", strerror(errno), oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", janet_strerror(errno), oldpath, newpath); | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
| @@ -1825,7 +1965,7 @@ JANET_CORE_FN(os_symlink, | ||||
|     const char *oldpath = janet_getcstring(argv, 0); | ||||
|     const char *newpath = janet_getcstring(argv, 1); | ||||
|     int res = j_symlink(oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", strerror(errno), oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", janet_strerror(errno), oldpath, newpath); | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
| @@ -1847,7 +1987,7 @@ JANET_CORE_FN(os_mkdir, | ||||
| #endif | ||||
|     if (res == 0) return janet_wrap_true(); | ||||
|     if (errno == EEXIST) return janet_wrap_false(); | ||||
|     janet_panicf("%s: %s", strerror(errno), path); | ||||
|     janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_rmdir, | ||||
| @@ -1861,7 +2001,7 @@ JANET_CORE_FN(os_rmdir, | ||||
| #else | ||||
|     int res = rmdir(path); | ||||
| #endif | ||||
|     if (-1 == res) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1876,7 +2016,7 @@ JANET_CORE_FN(os_cd, | ||||
| #else | ||||
|     int res = chdir(path); | ||||
| #endif | ||||
|     if (-1 == res) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1900,7 +2040,7 @@ JANET_CORE_FN(os_touch, | ||||
|         bufp = NULL; | ||||
|     } | ||||
|     int res = utime(path, bufp); | ||||
|     if (-1 == res) janet_panic(strerror(errno)); | ||||
|     if (-1 == res) janet_panic(janet_strerror(errno)); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1910,7 +2050,7 @@ JANET_CORE_FN(os_remove, | ||||
|     janet_fixarity(argc, 1); | ||||
|     const char *path = janet_getcstring(argv, 0); | ||||
|     int status = remove(path); | ||||
|     if (-1 == status) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == status) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1929,7 +2069,7 @@ JANET_CORE_FN(os_readlink, | ||||
|     const char *path = janet_getcstring(argv, 0); | ||||
|     ssize_t len = readlink(path, buffer, sizeof buffer); | ||||
|     if (len < 0 || (size_t)len >= sizeof buffer) | ||||
|         janet_panicf("%s: %s", strerror(errno), path); | ||||
|         janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_stringv((const uint8_t *)buffer, len); | ||||
| #endif | ||||
| } | ||||
| @@ -2224,7 +2364,7 @@ JANET_CORE_FN(os_chmod, | ||||
| #else | ||||
|     int res = chmod(path, os_getmode(argv, 1)); | ||||
| #endif | ||||
|     if (-1 == res) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -2260,7 +2400,7 @@ JANET_CORE_FN(os_dir, | ||||
|         janet_panicf("path too long: %s", dir); | ||||
|     sprintf(pattern, "%s/*", dir); | ||||
|     intptr_t res = _findfirst(pattern, &afile); | ||||
|     if (-1 == res) janet_panicv(janet_cstringv(strerror(errno))); | ||||
|     if (-1 == res) janet_panicv(janet_cstringv(janet_strerror(errno))); | ||||
|     do { | ||||
|         if (strcmp(".", afile.name) && strcmp("..", afile.name)) { | ||||
|             janet_array_push(paths, janet_cstringv(afile.name)); | ||||
| @@ -2271,8 +2411,18 @@ JANET_CORE_FN(os_dir, | ||||
|     /* Read directory items with opendir / readdir / closedir */ | ||||
|     struct dirent *dp; | ||||
|     DIR *dfd = opendir(dir); | ||||
|     if (dfd == NULL) janet_panicf("cannot open directory %s", dir); | ||||
|     while ((dp = readdir(dfd)) != NULL) { | ||||
|     if (dfd == NULL) janet_panicf("cannot open directory %s: %s", dir, janet_strerror(errno)); | ||||
|     for (;;) { | ||||
|         errno = 0; | ||||
|         dp = readdir(dfd); | ||||
|         if (dp == NULL) { | ||||
|             if (errno) { | ||||
|                 int olderr = errno; | ||||
|                 closedir(dfd); | ||||
|                 janet_panicf("failed to read directory %s: %s", dir, janet_strerror(olderr)); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) { | ||||
|             continue; | ||||
|         } | ||||
| @@ -2292,7 +2442,7 @@ JANET_CORE_FN(os_rename, | ||||
|     const char *dest = janet_getcstring(argv, 1); | ||||
|     int status = rename(src, dest); | ||||
|     if (status) { | ||||
|         janet_panic(strerror(errno)); | ||||
|         janet_panic(janet_strerror(errno)); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
| @@ -2312,7 +2462,7 @@ JANET_CORE_FN(os_realpath, | ||||
| #else | ||||
|     char *dest = realpath(src, NULL); | ||||
| #endif | ||||
|     if (NULL == dest) janet_panicf("%s: %s", strerror(errno), src); | ||||
|     if (NULL == dest) janet_panicf("%s: %s", janet_strerror(errno), src); | ||||
|     Janet ret = janet_cstringv(dest); | ||||
|     janet_free(dest); | ||||
|     return ret; | ||||
| @@ -2336,34 +2486,6 @@ JANET_CORE_FN(os_permission_int, | ||||
|     return janet_wrap_integer(os_get_unix_mode(argv, 0)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_posix_fork, | ||||
|               "(os/posix-fork)", | ||||
|               "Make a `fork` system call and create a new process. Return nil if in the new process, otherwise a core/process object (as returned by os/spawn). " | ||||
|               "Not supported on all systems (POSIX only).") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
| #ifdef JANET_WINDOWS | ||||
|     janet_panic("not supported"); | ||||
| #else | ||||
|     pid_t result; | ||||
|     do { | ||||
|         result = fork(); | ||||
|     } while (result == -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panic(strerror(errno)); | ||||
|     } | ||||
|     if (result) { | ||||
|         JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc)); | ||||
|         memset(proc, 0, sizeof(JanetProc)); | ||||
|         proc->pid = result; | ||||
|         proc->flags = JANET_PROC_ALLOW_ZOMBIE; | ||||
|         return janet_wrap_abstract(proc); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef JANET_EV | ||||
|  | ||||
| /* | ||||
| @@ -2546,7 +2668,7 @@ JANET_CORE_FN(os_open, | ||||
|     } else if (write_flag && !read_flag) { | ||||
|         open_flags |= O_WRONLY; | ||||
|     } else { | ||||
|         open_flags = O_RDWR; | ||||
|         open_flags |= O_RDWR; | ||||
|     } | ||||
|  | ||||
|     do { | ||||
| @@ -2558,16 +2680,24 @@ JANET_CORE_FN(os_open, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_pipe, | ||||
|               "(os/pipe)", | ||||
|               "(os/pipe &opt flags)", | ||||
|               "Create a readable stream and a writable stream that are connected. Returns a two-element " | ||||
|               "tuple where the first element is a readable stream and the second element is the writable " | ||||
|               "stream.") { | ||||
|               "stream. `flags` is a keyword set of flags to disable non-blocking settings on the ends of the pipe. " | ||||
|               "This may be desired if passing the pipe to a subprocess with `os/spawn`.\n\n" | ||||
|               "* :W - sets the writable end of the pipe to a blocking stream.\n" | ||||
|               "* :R - sets the readable end of the pipe to a blocking stream.\n\n" | ||||
|               "By default, both ends of the pipe are non-blocking for use with the `ev` module.") { | ||||
|     (void) argv; | ||||
|     janet_fixarity(argc, 0); | ||||
|     janet_arity(argc, 0, 1); | ||||
|     JanetHandle fds[2]; | ||||
|     if (janet_make_pipe(fds, 0)) janet_panicv(janet_ev_lasterr()); | ||||
|     JanetStream *reader = janet_stream(fds[0], JANET_STREAM_READABLE, NULL); | ||||
|     JanetStream *writer = janet_stream(fds[1], JANET_STREAM_WRITABLE, NULL); | ||||
|     int flags = 0; | ||||
|     if (argc > 0 && !janet_checktype(argv[0], JANET_NIL)) { | ||||
|         flags = (int) janet_getflags(argv, 0, "WR"); | ||||
|     } | ||||
|     if (janet_make_pipe(fds, flags)) janet_panicv(janet_ev_lasterr()); | ||||
|     JanetStream *reader = janet_stream(fds[0], (flags & 2) ? 0 : JANET_STREAM_READABLE, NULL); | ||||
|     JanetStream *writer = janet_stream(fds[1], (flags & 1) ? 0 : JANET_STREAM_WRITABLE, NULL); | ||||
|     Janet tup[2] = {janet_wrap_abstract(reader), janet_wrap_abstract(writer)}; | ||||
|     return janet_wrap_tuple(janet_tuple_n(tup, 2)); | ||||
| } | ||||
| @@ -2614,6 +2744,7 @@ void janet_lib_os(JanetTable *env) { | ||||
|         JANET_CORE_REG("os/strftime", os_strftime), | ||||
|         JANET_CORE_REG("os/sleep", os_sleep), | ||||
|         JANET_CORE_REG("os/isatty", os_isatty), | ||||
|         JANET_CORE_REG("os/setlocale", os_setlocale), | ||||
|  | ||||
|         /* env functions */ | ||||
|         JANET_CORE_REG("os/environ", os_environ), | ||||
| @@ -2651,6 +2782,7 @@ void janet_lib_os(JanetTable *env) { | ||||
|         JANET_CORE_REG("os/spawn", os_spawn), | ||||
|         JANET_CORE_REG("os/shell", os_shell), | ||||
|         JANET_CORE_REG("os/posix-fork", os_posix_fork), | ||||
|         JANET_CORE_REG("os/posix-exec", os_posix_exec), | ||||
|         /* no need to sandbox process management if you can't create processes | ||||
|          * (allows for limited functionality if use exposes C-functions to create specific processes) */ | ||||
|         JANET_CORE_REG("os/proc-wait", os_proc_wait), | ||||
|   | ||||
| @@ -467,8 +467,13 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|             return 0; | ||||
|         } | ||||
|         ret = janet_keywordv(p->buf + 1, blen - 1); | ||||
| #ifdef JANET_INT_TYPES | ||||
|     } else if (start_num && !janet_scan_numeric(p->buf, blen, &ret)) { | ||||
|         (void) numval; | ||||
| #else | ||||
|     } else if (start_num && !janet_scan_number(p->buf, blen, &numval)) { | ||||
|         ret = janet_wrap_number(numval); | ||||
| #endif | ||||
|     } else if (!check_str_const("nil", p->buf, blen)) { | ||||
|         ret = janet_wrap_nil(); | ||||
|     } else if (!check_str_const("false", p->buf, blen)) { | ||||
|   | ||||
							
								
								
									
										116
									
								
								src/core/peg.c
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								src/core/peg.c
									
									
									
									
									
								
							| @@ -39,6 +39,10 @@ | ||||
| typedef struct { | ||||
|     const uint8_t *text_start; | ||||
|     const uint8_t *text_end; | ||||
|     /* text_end can be restricted by some rules, but | ||||
|        outer_text_end will always contain the real end of | ||||
|        input, which we need to generate a line mapping */ | ||||
|     const uint8_t *outer_text_end; | ||||
|     const uint32_t *bytecode; | ||||
|     const Janet *constants; | ||||
|     JanetArray *captures; | ||||
| @@ -114,12 +118,12 @@ static LineCol get_linecol_from_position(PegState *s, int32_t position) { | ||||
|     /* Generate if not made yet */ | ||||
|     if (s->linemaplen < 0) { | ||||
|         int32_t newline_count = 0; | ||||
|         for (const uint8_t *c = s->text_start; c < s->text_end; c++) { | ||||
|         for (const uint8_t *c = s->text_start; c < s->outer_text_end; c++) { | ||||
|             if (*c == '\n') newline_count++; | ||||
|         } | ||||
|         int32_t *mem = janet_smalloc(sizeof(int32_t) * newline_count); | ||||
|         size_t index = 0; | ||||
|         for (const uint8_t *c = s->text_start; c < s->text_end; c++) { | ||||
|         for (const uint8_t *c = s->text_start; c < s->outer_text_end; c++) { | ||||
|             if (*c == '\n') mem[index++] = (int32_t)(c - s->text_start); | ||||
|         } | ||||
|         s->linemaplen = newline_count; | ||||
| @@ -130,7 +134,7 @@ static LineCol get_linecol_from_position(PegState *s, int32_t position) { | ||||
|      *   a newline character is consider to be on the same line as the character before | ||||
|      *   (\n is line terminator, not line separator). | ||||
|      * - in the not-found case, we still want to find the greatest-indexed newline that | ||||
|      *   is before position. we use that to calcuate the line and column. | ||||
|      *   is before position. we use that to calculate the line and column. | ||||
|      * - in the case that lo = 0 and s->linemap[0] is still greater than position, we | ||||
|      *   are on the first line and our column is position + 1. */ | ||||
|     int32_t hi = s->linemaplen; /* hi is greater than the actual line */ | ||||
| @@ -179,7 +183,7 @@ static const uint8_t *peg_rule( | ||||
|     const uint32_t *rule, | ||||
|     const uint8_t *text) { | ||||
| tail: | ||||
|     switch (*rule & 0x1F) { | ||||
|     switch (*rule) { | ||||
|         default: | ||||
|             janet_panic("unexpected opcode"); | ||||
|             return NULL; | ||||
| @@ -482,6 +486,68 @@ tail: | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_SUB: { | ||||
|             const uint8_t *text_start = text; | ||||
|             const uint32_t *rule_window = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_subpattern = s->bytecode + rule[2]; | ||||
|             down1(s); | ||||
|             const uint8_t *window_end = peg_rule(s, rule_window, text); | ||||
|             up1(s); | ||||
|             if (!window_end) { | ||||
|                 return NULL; | ||||
|             } | ||||
|             const uint8_t *saved_end = s->text_end; | ||||
|             s->text_end = window_end; | ||||
|             down1(s); | ||||
|             const uint8_t *next_text = peg_rule(s, rule_subpattern, text_start); | ||||
|             up1(s); | ||||
|             s->text_end = saved_end; | ||||
|  | ||||
|             if (!next_text) { | ||||
|                 return NULL; | ||||
|             } | ||||
|  | ||||
|             return window_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; | ||||
|                 CapState cs = cap_save(s); | ||||
|                 down1(s); | ||||
|                 while (text <= s->text_end) { | ||||
|                     separator_end = peg_rule(s, rule_separator, text); | ||||
|                     cap_load(s, cs); | ||||
|                     if (separator_end) { | ||||
|                         break; | ||||
|                     } | ||||
|                     text++; | ||||
|                 } | ||||
|                 up1(s); | ||||
|  | ||||
|                 if (separator_end) { | ||||
|                     s->text_end = text; | ||||
|                     text = separator_end; | ||||
|                 } | ||||
|  | ||||
|                 down1(s); | ||||
|                 const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, text_start); | ||||
|                 up1(s); | ||||
|                 s->text_end = saved_end; | ||||
|  | ||||
|                 if (!subpattern_end) { | ||||
|                     return NULL; | ||||
|                 } | ||||
|             } while (separator_end); | ||||
|  | ||||
|             return s->text_end; | ||||
|         } | ||||
|  | ||||
|         case RULE_REPLACE: | ||||
|         case RULE_MATCHTIME: { | ||||
|             uint32_t tag = rule[3]; | ||||
| @@ -601,11 +667,11 @@ tail: | ||||
|         case RULE_READINT: { | ||||
|             uint32_t tag = rule[2]; | ||||
|             uint32_t signedness = rule[1] & 0x10; | ||||
|             uint32_t endianess = rule[1] & 0x20; | ||||
|             uint32_t endianness = rule[1] & 0x20; | ||||
|             int width = (int)(rule[1] & 0xF); | ||||
|             if (text + width > s->text_end) return NULL; | ||||
|             uint64_t accum = 0; | ||||
|             if (endianess) { | ||||
|             if (endianness) { | ||||
|                 /* BE */ | ||||
|                 for (int i = 0; i < width; i++) accum = (accum << 8) | text[i]; | ||||
|             } else { | ||||
| @@ -1107,6 +1173,22 @@ static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     emit_3(r, RULE_MATCHTIME, subrule, cindex, tag); | ||||
| } | ||||
|  | ||||
| static void spec_sub(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_SUB, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| static void spec_split(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_SPLIT, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| #ifdef JANET_INT_TYPES | ||||
| #define JANET_MAX_READINT_WIDTH 8 | ||||
| #else | ||||
| @@ -1190,6 +1272,8 @@ static const SpecialPair peg_specials[] = { | ||||
|     {"sequence", spec_sequence}, | ||||
|     {"set", spec_set}, | ||||
|     {"some", spec_some}, | ||||
|     {"split", spec_split}, | ||||
|     {"sub", spec_sub}, | ||||
|     {"thru", spec_thru}, | ||||
|     {"to", spec_to}, | ||||
|     {"uint", spec_uint_le}, | ||||
| @@ -1431,7 +1515,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|         uint32_t instr = bytecode[i]; | ||||
|         uint32_t *rule = bytecode + i; | ||||
|         op_flags[i] |= 0x02; | ||||
|         switch (instr & 0x1F) { | ||||
|         switch (instr) { | ||||
|             case RULE_LITERAL: | ||||
|                 i += 2 + ((rule[1] + 3) >> 2); | ||||
|                 break; | ||||
| @@ -1524,6 +1608,15 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 op_flags[rule[1]] |= 0x01; | ||||
|                 i += 4; | ||||
|                 break; | ||||
|             case RULE_SUB: | ||||
|             case RULE_SPLIT: | ||||
|                 /* [rule, rule] */ | ||||
|                 if (rule[1] >= blen) goto bad; | ||||
|                 if (rule[2] >= blen) goto bad; | ||||
|                 op_flags[rule[1]] |= 0x01; | ||||
|                 op_flags[rule[2]] |= 0x01; | ||||
|                 i += 3; | ||||
|                 break; | ||||
|             case RULE_ERROR: | ||||
|             case RULE_DROP: | ||||
|             case RULE_NOT: | ||||
| @@ -1535,7 +1628,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 i += 2; | ||||
|                 break; | ||||
|             case RULE_READINT: | ||||
|                 /* [ width | (endianess << 5) | (signedness << 6), tag ] */ | ||||
|                 /* [ width | (endianness << 5) | (signedness << 6), tag ] */ | ||||
|                 if (rule[1] > JANET_MAX_READINT_WIDTH) goto bad; | ||||
|                 i += 3; | ||||
|                 break; | ||||
| @@ -1632,7 +1725,7 @@ static JanetPeg *compile_peg(Janet x) { | ||||
| JANET_CORE_FN(cfun_peg_compile, | ||||
|               "(peg/compile peg)", | ||||
|               "Compiles a peg source data structure into a <core/peg>. This will speed up matching " | ||||
|               "if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to suppliment " | ||||
|               "if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to supplement " | ||||
|               "the grammar of the peg for otherwise undefined peg keywords.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetPeg *peg = compile_peg(argv[0]); | ||||
| @@ -1652,7 +1745,7 @@ typedef struct { | ||||
| static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) { | ||||
|     PegCall ret; | ||||
|     int32_t min = get_replace ? 3 : 2; | ||||
|     janet_arity(argc, get_replace, -1); | ||||
|     janet_arity(argc, min, -1); | ||||
|     if (janet_checktype(argv[0], JANET_ABSTRACT) && | ||||
|             janet_abstract_type(janet_unwrap_abstract(argv[0])) == &janet_peg_type) { | ||||
|         ret.peg = janet_unwrap_abstract(argv[0]); | ||||
| @@ -1677,6 +1770,7 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) { | ||||
|     ret.s.mode = PEG_MODE_NORMAL; | ||||
|     ret.s.text_start = ret.bytes.bytes; | ||||
|     ret.s.text_end = ret.bytes.bytes + ret.bytes.len; | ||||
|     ret.s.outer_text_end = ret.s.text_end; | ||||
|     ret.s.depth = JANET_RECURSION_GUARD; | ||||
|     ret.s.captures = janet_array(0); | ||||
|     ret.s.tagged_captures = janet_array(0); | ||||
| @@ -1771,7 +1865,7 @@ JANET_CORE_FN(cfun_peg_replace_all, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_peg_replace, | ||||
|               "(peg/replace peg repl text &opt start & args)", | ||||
|               "(peg/replace peg subst text &opt start & args)", | ||||
|               "Replace first match of `peg` in `text` with `subst`, returning a new buffer. " | ||||
|               "The peg does not need to make captures to do replacement. " | ||||
|               "If `subst` is a function, it will be called with the " | ||||
|   | ||||
| @@ -31,6 +31,7 @@ | ||||
| #include <string.h> | ||||
| #include <ctype.h> | ||||
| #include <inttypes.h> | ||||
| #include <float.h> | ||||
|  | ||||
| /* Implements a pretty printer for Janet. The pretty printer | ||||
|  * is simple and not that flexible, but fast. */ | ||||
| @@ -38,11 +39,15 @@ | ||||
| /* Temporary buffer size */ | ||||
| #define BUFSIZE 64 | ||||
|  | ||||
| /* Preprocessor hacks */ | ||||
| #define STR_HELPER(x) #x | ||||
| #define STR(x) STR_HELPER(x) | ||||
|  | ||||
| static void number_to_string_b(JanetBuffer *buffer, double x) { | ||||
|     janet_buffer_ensure(buffer, buffer->count + BUFSIZE, 2); | ||||
|     const char *fmt = (x == floor(x) && | ||||
|                        x <= JANET_INTMAX_DOUBLE && | ||||
|                        x >= JANET_INTMIN_DOUBLE) ? "%.0f" : "%g"; | ||||
|                        x >= JANET_INTMIN_DOUBLE) ? "%.0f" : ("%." STR(DBL_DIG) "g"); | ||||
|     int count; | ||||
|     if (x == 0.0) { | ||||
|         /* Prevent printing of '-0' */ | ||||
| @@ -374,8 +379,10 @@ static int print_jdn_one(struct pretty *S, Janet x, int depth) { | ||||
|             break; | ||||
|         case JANET_NUMBER: | ||||
|             janet_buffer_ensure(S->buffer, S->buffer->count + BUFSIZE, 2); | ||||
|             int count = snprintf((char *) S->buffer->data + S->buffer->count, BUFSIZE, "%.17g", janet_unwrap_number(x)); | ||||
|             S->buffer->count += count; | ||||
|             double num = janet_unwrap_number(x); | ||||
|             if (isnan(num)) return 1; | ||||
|             if (isinf(num)) return 1; | ||||
|             janet_buffer_dtostr(S->buffer, num); | ||||
|             break; | ||||
|         case JANET_SYMBOL: | ||||
|         case JANET_KEYWORD: | ||||
| @@ -772,6 +779,8 @@ struct FmtMapping { | ||||
| /* Janet uses fixed width integer types for most things, so map | ||||
|  * format specifiers to these fixed sizes */ | ||||
| static const struct FmtMapping format_mappings[] = { | ||||
|     {'D', PRId64}, | ||||
|     {'I', PRIi64}, | ||||
|     {'d', PRId64}, | ||||
|     {'i', PRIi64}, | ||||
|     {'o', PRIo64}, | ||||
| @@ -823,7 +832,7 @@ static const char *scanformat( | ||||
|         if (loc != NULL && *loc != '\0') { | ||||
|             const char *mapping = get_fmt_mapping(*p2++); | ||||
|             size_t len = strlen(mapping); | ||||
|             strcpy(form, mapping); | ||||
|             memcpy(form, mapping, len); | ||||
|             form += len; | ||||
|         } else { | ||||
|             *(form++) = *(p2++); | ||||
| @@ -850,13 +859,19 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { | ||||
|             c = scanformat(c, form, width, precision); | ||||
|             switch (*c++) { | ||||
|                 case 'c': { | ||||
|                     int n = va_arg(args, long); | ||||
|                     int n = va_arg(args, int); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'd': | ||||
|                 case 'i': { | ||||
|                     int64_t n = va_arg(args, int); | ||||
|                     int64_t n = (int64_t) va_arg(args, int32_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'D': | ||||
|                 case 'I': { | ||||
|                     int64_t n = va_arg(args, int64_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -864,7 +879,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { | ||||
|                 case 'X': | ||||
|                 case 'o': | ||||
|                 case 'u': { | ||||
|                     uint64_t n = va_arg(args, unsigned int); | ||||
|                     uint64_t n = va_arg(args, uint64_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -908,7 +923,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { | ||||
|                     janet_buffer_push_cstring(b, typestr(va_arg(args, Janet))); | ||||
|                     break; | ||||
|                 case 'T': { | ||||
|                     int types = va_arg(args, long); | ||||
|                     int types = va_arg(args, int); | ||||
|                     pushtypes(b, types); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -1017,6 +1032,8 @@ void janet_buffer_format( | ||||
|                                   janet_getinteger(argv, arg)); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'D': | ||||
|                 case 'I': | ||||
|                 case 'd': | ||||
|                 case 'i': { | ||||
|                     int64_t n = janet_getinteger64(argv, arg); | ||||
|   | ||||
| @@ -32,6 +32,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|     int errflags = 0, done = 0; | ||||
|     int32_t index = 0; | ||||
|     Janet ret = janet_wrap_nil(); | ||||
|     JanetFiber *fiber = NULL; | ||||
|     const uint8_t *where = sourcePath ? janet_cstring(sourcePath) : NULL; | ||||
|  | ||||
|     if (where) janet_gcroot(janet_wrap_string(where)); | ||||
| @@ -47,7 +48,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|             JanetCompileResult cres = janet_compile(form, env, where); | ||||
|             if (cres.status == JANET_COMPILE_OK) { | ||||
|                 JanetFunction *f = janet_thunk(cres.funcdef); | ||||
|                 JanetFiber *fiber = janet_fiber(f, 64, 0, NULL); | ||||
|                 fiber = janet_fiber(f, 64, 0, NULL); | ||||
|                 fiber->env = env; | ||||
|                 JanetSignal status = janet_continue(fiber, janet_wrap_nil(), &ret); | ||||
|                 if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) { | ||||
| @@ -112,9 +113,14 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
| #ifdef JANET_EV | ||||
|     /* Enter the event loop if we are not already in it */ | ||||
|     if (janet_vm.stackn == 0) { | ||||
|         janet_gcroot(ret); | ||||
|         if (fiber) { | ||||
|             janet_gcroot(janet_wrap_fiber(fiber)); | ||||
|         } | ||||
|         janet_loop(); | ||||
|         janet_gcunroot(ret); | ||||
|         if (fiber) { | ||||
|             janet_gcunroot(janet_wrap_fiber(fiber)); | ||||
|             ret = fiber->last_value; | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
|     if (out) *out = ret; | ||||
|   | ||||
| @@ -149,7 +149,7 @@ static int destructure(JanetCompiler *c, | ||||
|                        JanetTable *attr) { | ||||
|     switch (janet_type(left)) { | ||||
|         default: | ||||
|             janetc_error(c, janet_formatc("unexpected type in destruction, got %v", left)); | ||||
|             janetc_error(c, janet_formatc("unexpected type in destructuring, got %v", left)); | ||||
|             return 1; | ||||
|         case JANET_SYMBOL: | ||||
|             /* Leaf, assign right to left */ | ||||
| @@ -531,17 +531,11 @@ static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
| } | ||||
|  | ||||
| /* Check if a form matches the pattern (= nil _) or (not= nil _) */ | ||||
| static int janetc_check_nil_form(JanetFopts opts, Janet x, Janet *capture, uint32_t fun_tag) { | ||||
| static int janetc_check_nil_form(Janet x, Janet *capture, uint32_t fun_tag) { | ||||
|     if (!janet_checktype(x, JANET_TUPLE)) return 0; | ||||
|     JanetTuple tup = janet_unwrap_tuple(x); | ||||
|     if (3 != janet_tuple_length(tup)) return 0; | ||||
|     Janet op1 = tup[0]; | ||||
|     if (janet_checktype(op1, JANET_SYMBOL)) { | ||||
|         Janet entry = janet_table_get(opts.compiler->env, op1); | ||||
|         if (janet_checktype(entry, JANET_TABLE)) { | ||||
|             op1 = janet_table_get(janet_unwrap_table(entry), janet_ckeywordv("value")); | ||||
|         } | ||||
|     } | ||||
|     if (!janet_checktype(op1, JANET_FUNCTION)) return 0; | ||||
|     JanetFunction *fun = janet_unwrap_function(op1); | ||||
|     uint32_t tag = fun->def->flags & JANET_FUNCDEF_FLAG_TAG; | ||||
| @@ -601,10 +595,9 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     janetc_scope(&condscope, c, 0, "if"); | ||||
|  | ||||
|     Janet condform = argv[0]; | ||||
|     if (janetc_check_nil_form(opts, condform, &condform, JANET_FUN_EQ)) { | ||||
|     if (janetc_check_nil_form(condform, &condform, JANET_FUN_EQ)) { | ||||
|         ifnjmp = JOP_JUMP_IF_NOT_NIL; | ||||
|     } | ||||
|     if (janetc_check_nil_form(opts, condform, &condform, JANET_FUN_NEQ)) { | ||||
|     } else if (janetc_check_nil_form(condform, &condform, JANET_FUN_NEQ)) { | ||||
|         ifnjmp = JOP_JUMP_IF_NIL; | ||||
|     } | ||||
|  | ||||
| @@ -613,7 +606,11 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     /* Check constant condition. */ | ||||
|     /* TODO: Use type info for more short circuits */ | ||||
|     if (cond.flags & JANET_SLOT_CONSTANT) { | ||||
|         if (!janet_truthy(cond.constant)) { | ||||
|         int swap_condition = 0; | ||||
|         if (ifnjmp == JOP_JUMP_IF_NOT && !janet_truthy(cond.constant)) swap_condition = 1; | ||||
|         if (ifnjmp == JOP_JUMP_IF_NIL && janet_checktype(cond.constant, JANET_NIL)) swap_condition = 1; | ||||
|         if (ifnjmp == JOP_JUMP_IF_NOT_NIL && !janet_checktype(cond.constant, JANET_NIL)) swap_condition = 1; | ||||
|         if (swap_condition) { | ||||
|             /* Swap the true and false bodies */ | ||||
|             Janet temp = falsebody; | ||||
|             falsebody = truebody; | ||||
| @@ -808,12 +805,12 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|      * jmpnl or jmpnn instructions. This let's us implement `(each ...)` | ||||
|      * more efficiently. */ | ||||
|     Janet condform = argv[0]; | ||||
|     if (janetc_check_nil_form(opts, condform, &condform, JANET_FUN_EQ)) { | ||||
|     if (janetc_check_nil_form(condform, &condform, JANET_FUN_EQ)) { | ||||
|         is_nil_form = 1; | ||||
|         ifjmp = JOP_JUMP_IF_NIL; | ||||
|         ifnjmp = JOP_JUMP_IF_NOT_NIL; | ||||
|     } | ||||
|     if (janetc_check_nil_form(opts, condform, &condform, JANET_FUN_NEQ)) { | ||||
|     if (janetc_check_nil_form(condform, &condform, JANET_FUN_NEQ)) { | ||||
|         is_notnil_form = 1; | ||||
|         ifjmp = JOP_JUMP_IF_NOT_NIL; | ||||
|         ifnjmp = JOP_JUMP_IF_NIL; | ||||
| @@ -928,6 +925,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     int structarg = 0; | ||||
|     int allow_extra = 0; | ||||
|     int selfref = 0; | ||||
|     int hasname = 0; | ||||
|     int seenamp = 0; | ||||
|     int seenopt = 0; | ||||
|     int namedargs = 0; | ||||
| @@ -946,6 +944,10 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     head = argv[0]; | ||||
|     if (janet_checktype(head, JANET_SYMBOL)) { | ||||
|         selfref = 1; | ||||
|         hasname = 1; | ||||
|         parami = 1; | ||||
|     } else if (janet_checktype(head, JANET_KEYWORD)) { | ||||
|         hasname = 1; | ||||
|         parami = 1; | ||||
|     } | ||||
|     if (parami >= argn || !janet_checktype(argv[parami], JANET_TUPLE)) { | ||||
| @@ -1106,7 +1108,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     if (vararg) def->flags |= JANET_FUNCDEF_FLAG_VARARG; | ||||
|     if (structarg) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG; | ||||
|  | ||||
|     if (selfref) def->name = janet_unwrap_symbol(head); | ||||
|     if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */ | ||||
|     janet_def_addflags(def); | ||||
|     defindex = janetc_addfuncdef(c, def); | ||||
|  | ||||
|   | ||||
| @@ -58,7 +58,7 @@ void janet_vm_load(JanetVM *from) { | ||||
| } | ||||
|  | ||||
| /* Trigger suspension of the Janet vm by trying to | ||||
|  * exit the interpeter loop when convenient. You can optionally | ||||
|  * exit the interpreter loop when convenient. You can optionally | ||||
|  * use NULL to interrupt the current VM when convenient */ | ||||
| void janet_interpreter_interrupt(JanetVM *vm) { | ||||
|     vm = vm ? vm : &janet_vm; | ||||
|   | ||||
| @@ -149,6 +149,11 @@ struct JanetVM { | ||||
|     JanetTraversalNode *traversal_top; | ||||
|     JanetTraversalNode *traversal_base; | ||||
|  | ||||
|     /* Thread safe strerror error buffer - for janet_strerror */ | ||||
| #ifndef JANET_WINDOWS | ||||
|     char strerror_buf[256]; | ||||
| #endif | ||||
|  | ||||
|     /* Event loop and scheduler globals */ | ||||
| #ifdef JANET_EV | ||||
|     size_t tq_count; | ||||
|   | ||||
| @@ -549,8 +549,8 @@ JANET_CORE_FN(cfun_string_format, | ||||
|               "- `a`, `A`: floating point number, formatted as a hexadecimal number.\n" | ||||
|               "- `s`: formatted as a string, precision indicates padding and maximum length.\n" | ||||
|               "- `t`: emit the type of the given value.\n" | ||||
|               "- `v`: format with (describe x)" | ||||
|               "- `V`: format with (string x)" | ||||
|               "- `v`: format with (describe x)\n" | ||||
|               "- `V`: format with (string x)\n" | ||||
|               "- `j`: format to jdn (Janet data notation).\n" | ||||
|               "\n" | ||||
|               "The following conversion specifiers are used for \"pretty-printing\", where the upper-case " | ||||
|   | ||||
| @@ -34,9 +34,9 @@ | ||||
|  * because E is a valid digit in bases 15 or greater. For bases greater than | ||||
|  * 10, the letters are used as digits. A through Z correspond to the digits 10 | ||||
|  * through 35, and the lowercase letters have the same values. The radix number | ||||
|  * is always in base 10. For example, a hexidecimal number could be written | ||||
|  * is always in base 10. For example, a hexadecimal number could be written | ||||
|  * '16rdeadbeef'. janet_scan_number also supports some c style syntax for | ||||
|  * hexidecimal literals. The previous number could also be written | ||||
|  * hexadecimal literals. The previous number could also be written | ||||
|  * '0xdeadbeef'. | ||||
|  */ | ||||
|  | ||||
| @@ -489,4 +489,53 @@ int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out) { | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* Similar to janet_scan_number but allows for | ||||
|  * more numeric types with a given suffix. */ | ||||
| int janet_scan_numeric( | ||||
|     const uint8_t *str, | ||||
|     int32_t len, | ||||
|     Janet *out) { | ||||
|     int result; | ||||
|     double num; | ||||
|     int64_t i64; | ||||
|     uint64_t u64; | ||||
|     if (len < 2 || str[len - 2] != ':') { | ||||
|         result = janet_scan_number_base(str, len, 0, &num); | ||||
|         *out = janet_wrap_number(num); | ||||
|         return result; | ||||
|     } | ||||
|     switch (str[len - 1]) { | ||||
|         default: | ||||
|             return 1; | ||||
|         case 'n': | ||||
|             result = janet_scan_number_base(str, len - 2, 0, &num); | ||||
|             *out = janet_wrap_number(num); | ||||
|             return result; | ||||
|         /* Condition is inverted janet_scan_int64 and janet_scan_uint64 */ | ||||
|         case 's': | ||||
|             result = !janet_scan_int64(str, len - 2, &i64); | ||||
|             *out = janet_wrap_s64(i64); | ||||
|             return result; | ||||
|         case 'u': | ||||
|             result = !janet_scan_uint64(str, len - 2, &u64); | ||||
|             *out = janet_wrap_u64(u64); | ||||
|             return result; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| void janet_buffer_dtostr(JanetBuffer *buffer, double x) { | ||||
| #define BUFSIZE 32 | ||||
|     janet_buffer_extra(buffer, BUFSIZE); | ||||
|     int count = snprintf((char *) buffer->data + buffer->count, BUFSIZE, "%.17g", x); | ||||
| #undef BUFSIZE | ||||
|     /* fix locale issues with commas */ | ||||
|     for (int i = 0; i < count; i++) { | ||||
|         char c = buffer->data[buffer->count + i]; | ||||
|         if (c == ',') { | ||||
|             buffer->data[buffer->count + i] = '.'; | ||||
|         } | ||||
|     } | ||||
|     buffer->count += count; | ||||
| } | ||||
|   | ||||
| @@ -234,6 +234,7 @@ const uint8_t *janet_symbol_gen(void) { | ||||
|     head->hash = hash; | ||||
|     sym = (uint8_t *)(head->data); | ||||
|     memcpy(sym, janet_vm.gensym_counter, sizeof(janet_vm.gensym_counter)); | ||||
|     sym[head->length] = 0; | ||||
|     janet_symcache_put((const uint8_t *)sym, bucket); | ||||
|     return (const uint8_t *)sym; | ||||
| } | ||||
|   | ||||
| @@ -67,7 +67,7 @@ static JanetTable *janet_table_init_impl(JanetTable *table, int32_t capacity, in | ||||
|     return table; | ||||
| } | ||||
|  | ||||
| /* Initialize a table (for use withs scratch memory) */ | ||||
| /* Initialize a table (for use with scratch memory) */ | ||||
| JanetTable *janet_table_init(JanetTable *table, int32_t capacity) { | ||||
|     return janet_table_init_impl(table, capacity, 1); | ||||
| } | ||||
| @@ -319,13 +319,6 @@ JANET_CORE_FN(cfun_table_new, | ||||
|     int32_t cap = janet_getnat(argv, 0); | ||||
|     return janet_wrap_table(janet_table(cap)); | ||||
| } | ||||
| /* | ||||
|     uint32_t flags = janet_getflags(argv, 1, "kv"); | ||||
|     if (flags == 0) return janet_wrap_table(janet_table(cap)); | ||||
|     if (flags == 1) return janet_wrap_table(janet_table_weakk(cap)); | ||||
|     if (flags == 2) return janet_wrap_table(janet_table_weakv(cap)); | ||||
|     return janet_wrap_table(janet_table_weakkv(cap)); | ||||
|     */ | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_weak, | ||||
|               "(table/weak capacity)", | ||||
|   | ||||
| @@ -116,6 +116,34 @@ JANET_CORE_FN(cfun_tuple_setmap, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_tuple_join, | ||||
|               "(tuple/join & parts)", | ||||
|               "Create a tuple by joining together other tuples and arrays.") { | ||||
|     janet_arity(argc, 0, -1); | ||||
|     int32_t total_len = 0; | ||||
|     for (int32_t i = 0; i < argc; i++) { | ||||
|         int32_t len = 0; | ||||
|         const Janet *vals = NULL; | ||||
|         if (!janet_indexed_view(argv[i], &vals, &len)) { | ||||
|             janet_panicf("expected indexed type for argument %d, got %v", i, argv[i]); | ||||
|         } | ||||
|         if (INT32_MAX - total_len < len) { | ||||
|             janet_panic("tuple too large"); | ||||
|         } | ||||
|         total_len += len; | ||||
|     } | ||||
|     Janet *tup = janet_tuple_begin(total_len); | ||||
|     Janet *tup_cursor = tup; | ||||
|     for (int32_t i = 0; i < argc; i++) { | ||||
|         int32_t len = 0; | ||||
|         const Janet *vals = NULL; | ||||
|         janet_indexed_view(argv[i], &vals, &len); | ||||
|         memcpy(tup_cursor, vals, len * sizeof(Janet)); | ||||
|         tup_cursor += len; | ||||
|     } | ||||
|     return janet_wrap_tuple(janet_tuple_end(tup)); | ||||
| } | ||||
|  | ||||
| /* Load the tuple module */ | ||||
| void janet_lib_tuple(JanetTable *env) { | ||||
|     JanetRegExt tuple_cfuns[] = { | ||||
| @@ -124,6 +152,7 @@ void janet_lib_tuple(JanetTable *env) { | ||||
|         JANET_CORE_REG("tuple/type", cfun_tuple_type), | ||||
|         JANET_CORE_REG("tuple/sourcemap", cfun_tuple_sourcemap), | ||||
|         JANET_CORE_REG("tuple/setmap", cfun_tuple_setmap), | ||||
|         JANET_CORE_REG("tuple/join", cfun_tuple_join), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, tuple_cfuns); | ||||
|   | ||||
| @@ -826,6 +826,20 @@ int janet_checkuint64(Janet x) { | ||||
|     return janet_checkuint64range(dval); | ||||
| } | ||||
|  | ||||
| int janet_checkint16(Janet x) { | ||||
|     if (!janet_checktype(x, JANET_NUMBER)) | ||||
|         return 0; | ||||
|     double dval = janet_unwrap_number(x); | ||||
|     return janet_checkint16range(dval); | ||||
| } | ||||
|  | ||||
| int janet_checkuint16(Janet x) { | ||||
|     if (!janet_checktype(x, JANET_NUMBER)) | ||||
|         return 0; | ||||
|     double dval = janet_unwrap_number(x); | ||||
|     return janet_checkuint16range(dval); | ||||
| } | ||||
|  | ||||
| int janet_checksize(Janet x) { | ||||
|     if (!janet_checktype(x, JANET_NUMBER)) | ||||
|         return 0; | ||||
| @@ -953,6 +967,20 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* Better strerror (thread-safe if available) */ | ||||
| const char *janet_strerror(int e) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     /* Microsoft strerror seems sane here and is thread safe by default */ | ||||
|     return strerror(e); | ||||
| #elif defined(__GLIBC__) | ||||
|     /* See https://linux.die.net/man/3/strerror_r */ | ||||
|     return strerror_r(e, janet_vm.strerror_buf, sizeof(janet_vm.strerror_buf)); | ||||
| #else | ||||
|     strerror_r(e, janet_vm.strerror_buf, sizeof(janet_vm.strerror_buf)); | ||||
|     return janet_vm.strerror_buf; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| /* Setting C99 standard makes this not available, but it should | ||||
|  * work/link properly if we detect a BSD */ | ||||
| #if defined(JANET_BSD) || defined(MAC_OS_X_VERSION_10_7) | ||||
| @@ -960,6 +988,7 @@ void arc4random_buf(void *buf, size_t nbytes); | ||||
| #endif | ||||
|  | ||||
| int janet_cryptorand(uint8_t *out, size_t n) { | ||||
| #ifndef JANET_NO_CRYPTORAND | ||||
| #ifdef JANET_WINDOWS | ||||
|     for (size_t i = 0; i < n; i += sizeof(unsigned int)) { | ||||
|         unsigned int v; | ||||
| @@ -971,7 +1000,10 @@ int janet_cryptorand(uint8_t *out, size_t n) { | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| #elif defined(JANET_LINUX) || defined(JANET_CYGWIN) || ( defined(JANET_APPLE) && !defined(MAC_OS_X_VERSION_10_7) ) | ||||
| #elif defined(JANET_BSD) || defined(MAC_OS_X_VERSION_10_7) | ||||
|     arc4random_buf(out, n); | ||||
|     return 0; | ||||
| #else | ||||
|     /* We should be able to call getrandom on linux, but it doesn't seem | ||||
|        to be uniformly supported on linux distros. | ||||
|        On Mac, arc4random_buf wasn't available on until 10.7. | ||||
| @@ -993,12 +1025,10 @@ int janet_cryptorand(uint8_t *out, size_t n) { | ||||
|     } | ||||
|     RETRY_EINTR(rc, close(randfd)); | ||||
|     return 0; | ||||
| #elif defined(JANET_BSD) || defined(MAC_OS_X_VERSION_10_7) | ||||
|     arc4random_buf(out, n); | ||||
|     return 0; | ||||
| #endif | ||||
| #else | ||||
|     (void) n; | ||||
|     (void) out; | ||||
|     (void) n; | ||||
|     return -1; | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -33,6 +33,7 @@ | ||||
| #include <errno.h> | ||||
| #include <stddef.h> | ||||
| #include <stdbool.h> | ||||
| #include <math.h> | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| #ifndef JANET_WINDOWS | ||||
| @@ -49,11 +50,11 @@ | ||||
| #ifndef JANET_EXIT | ||||
| #include <stdio.h> | ||||
| #define JANET_EXIT(m) do { \ | ||||
|     fprintf(stderr, "janet interpreter runtime error at line %d in file %s: %s\n",\ | ||||
|     fprintf(stderr, "janet internal error at line %d in file %s: %s\n",\ | ||||
|         __LINE__,\ | ||||
|         __FILE__,\ | ||||
|         (m));\ | ||||
|     exit(1);\ | ||||
|     abort();\ | ||||
| } while (0) | ||||
| #endif | ||||
|  | ||||
| @@ -80,6 +81,8 @@ void janet_memempty(JanetKV *mem, int32_t count); | ||||
| void *janet_memalloc_empty(int32_t count); | ||||
| JanetTable *janet_get_core_table(const char *name); | ||||
| void janet_def_addflags(JanetFuncDef *def); | ||||
| void janet_buffer_dtostr(JanetBuffer *buffer, double x); | ||||
| const char *janet_strerror(int e); | ||||
| const void *janet_strbinsearch( | ||||
|     const void *tab, | ||||
|     size_t tabcount, | ||||
| @@ -139,7 +142,7 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source); | ||||
| #define strdup(x) _strdup(x) | ||||
| #endif | ||||
|  | ||||
| /* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries | ||||
| /* Use LoadLibrary on windows or dlopen on posix to load dynamic libraries | ||||
|  * with native code. */ | ||||
| #if defined(JANET_NO_DYNAMIC_MODULES) | ||||
| typedef int Clib; | ||||
| @@ -187,9 +190,6 @@ void janet_lib_debug(JanetTable *env); | ||||
| #ifdef JANET_PEG | ||||
| void janet_lib_peg(JanetTable *env); | ||||
| #endif | ||||
| #ifdef JANET_TYPED_ARRAY | ||||
| void janet_lib_typed_array(JanetTable *env); | ||||
| #endif | ||||
| #ifdef JANET_INT_TYPES | ||||
| void janet_lib_inttypes(JanetTable *env); | ||||
| #endif | ||||
| @@ -200,10 +200,14 @@ extern const JanetAbstractType janet_address_type; | ||||
| #ifdef JANET_EV | ||||
| void janet_lib_ev(JanetTable *env); | ||||
| void janet_ev_mark(void); | ||||
| void janet_async_start_fiber(JanetFiber *fiber, JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state); | ||||
| int janet_make_pipe(JanetHandle handles[2], int mode); | ||||
| #ifdef JANET_FILEWATCH | ||||
| void janet_lib_filewatch(JanetTable *env); | ||||
| #endif | ||||
| #ifdef JANET_FFI | ||||
| void janet_lib_ffi(JanetTable *env); | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -318,7 +318,7 @@ static Janet janet_binop_call(const char *lmethod, const char *rmethod, Janet lh | ||||
|         Janet lr = janet_method_lookup(rhs, rmethod); | ||||
|         Janet argv[2] = { rhs, lhs }; | ||||
|         if (janet_checktype(lr, JANET_NIL)) { | ||||
|             janet_panicf("could not find method :%s for %v, or :%s for %v", | ||||
|             janet_panicf("could not find method :%s for %v or :%s for %v", | ||||
|                          lmethod, lhs, | ||||
|                          rmethod, rhs); | ||||
|         } | ||||
| @@ -1268,7 +1268,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
| /* | ||||
|  * Execute a single instruction in the fiber. Does this by inspecting | ||||
|  * the fiber, setting a breakpoint at the next instruction, executing, and | ||||
|  * reseting breakpoints to how they were prior. Yes, it's a bit hacky. | ||||
|  * resetting breakpoints to how they were prior. Yes, it's a bit hacky. | ||||
|  */ | ||||
| JanetSignal janet_step(JanetFiber *fiber, Janet in, Janet *out) { | ||||
|     /* No finished or currently alive fibers. */ | ||||
| @@ -1613,7 +1613,7 @@ int janet_init(void) { | ||||
|     janet_vm.registry_count = 0; | ||||
|     janet_vm.registry_dirty = 0; | ||||
|  | ||||
|     /* Intialize abstract registry */ | ||||
|     /* Initialize abstract registry */ | ||||
|     janet_vm.abstract_registry = janet_table(0); | ||||
|     janet_gcroot(janet_wrap_table(janet_vm.abstract_registry)); | ||||
|  | ||||
|   | ||||
| @@ -46,7 +46,7 @@ extern "C" { | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * Detect OS and endianess. | ||||
|  * Detect OS and endianness. | ||||
|  * From webkit source. There is likely some extreneous | ||||
|  * detection for unsupported platforms | ||||
|  */ | ||||
| @@ -112,7 +112,8 @@ extern "C" { | ||||
|     || defined(__s390x__) /* S390 64-bit (BE) */ \ | ||||
|     || (defined(__ppc64__) || defined(__PPC64__)) \ | ||||
|     || defined(__aarch64__) /* ARM 64-bit */ \ | ||||
|     || (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ | ||||
|     || (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ \ | ||||
|     || defined(__loongarch64) /* LoongArch64 64-bit */ | ||||
| #define JANET_64 1 | ||||
| #else | ||||
| #define JANET_32 1 | ||||
| @@ -209,6 +210,11 @@ extern "C" { | ||||
| #define JANET_EV | ||||
| #endif | ||||
|  | ||||
| /* Enable or disable the filewatch/ module */ | ||||
| #if !defined(JANET_NO_FILEWATCH) | ||||
| #define JANET_FILEWATCH | ||||
| #endif | ||||
|  | ||||
| /* Enable or disable networking */ | ||||
| #if defined(JANET_EV) && !defined(JANET_NO_NET) && !defined(__EMSCRIPTEN__) | ||||
| #define JANET_NET | ||||
| @@ -261,7 +267,7 @@ extern "C" { | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* Tell complier some functions don't return */ | ||||
| /* Tell compiler some functions don't return */ | ||||
| #ifndef JANET_NO_RETURN | ||||
| #ifdef JANET_WINDOWS | ||||
| #define JANET_NO_RETURN __declspec(noreturn) | ||||
| @@ -271,7 +277,7 @@ extern "C" { | ||||
| #endif | ||||
|  | ||||
| /* Prevent some recursive functions from recursing too deeply | ||||
|  * ands crashing (the parser). Instead, error out. */ | ||||
|  * and crashing (the parser). Instead, error out. */ | ||||
| #define JANET_RECURSION_GUARD 1024 | ||||
|  | ||||
| /* Maximum depth to follow table prototypes before giving up and returning nil. */ | ||||
| @@ -353,6 +359,7 @@ typedef struct { | ||||
| #ifdef JANET_EV | ||||
| typedef struct JanetOSMutex JanetOSMutex; | ||||
| typedef struct JanetOSRWLock JanetOSRWLock; | ||||
| typedef struct JanetChannel JanetChannel; | ||||
| #endif | ||||
|  | ||||
| /***** END SECTION CONFIG *****/ | ||||
| @@ -596,8 +603,7 @@ typedef enum { | ||||
|     JANET_ASYNC_EVENT_READ = 6, | ||||
|     JANET_ASYNC_EVENT_WRITE = 7, | ||||
|     JANET_ASYNC_EVENT_COMPLETE = 8, /* Used on windows for IOCP */ | ||||
|     JANET_ASYNC_EVENT_FAILED = 9, /* Used on windows for IOCP */ | ||||
|     JANET_ASYNC_EVENT_USER = 10 | ||||
|     JANET_ASYNC_EVENT_FAILED = 9 /* Used on windows for IOCP */ | ||||
| } JanetAsyncEvent; | ||||
|  | ||||
| typedef enum { | ||||
| @@ -606,9 +612,7 @@ typedef enum { | ||||
|     JANET_ASYNC_LISTEN_BOTH | ||||
| } JanetAsyncMode; | ||||
|  | ||||
| /* Typedefs */ | ||||
| typedef struct JanetStream JanetStream; | ||||
| typedef void (*JanetEVCallback)(JanetFiber *fiber, JanetAsyncEvent event); | ||||
|  | ||||
| /* Wrapper around file descriptors and HANDLEs that can be polled. */ | ||||
| struct JanetStream { | ||||
| @@ -620,9 +624,32 @@ struct JanetStream { | ||||
|     const void *methods; /* Methods for this stream */ | ||||
| }; | ||||
|  | ||||
| typedef void (*JanetEVCallback)(JanetFiber *fiber, JanetAsyncEvent event); | ||||
|  | ||||
| /* Start listening for events from a stream on the current root fiber. After | ||||
|  * calling this, users should call janet_await() before returning from the | ||||
|  * current C Function. This also will call janet_await. | ||||
|  * mode is which events to listen for, and callback is the function pointer to | ||||
|  * call when ever an event is sent from the event loop. state is an optional (can be NULL) | ||||
|  * pointer to data allocated with janet_malloc. This pointer will be passed to callback as | ||||
|  * fiber->ev_state. It will also be freed for you by the runtime when the event loop determines | ||||
|  * it can no longer be referenced. On windows, the contents of state MUST contained an OVERLAPPED struct at the 0 offset. */ | ||||
|  | ||||
| JANET_API void janet_async_start_fiber(JanetFiber *fiber, JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state); | ||||
| JANET_API JANET_NO_RETURN void janet_async_start(JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state); | ||||
|  | ||||
| /* Do not send any more events to the given callback. Call this after scheduling fiber to be resume | ||||
|  * or canceled. */ | ||||
| JANET_API void janet_async_end(JanetFiber *fiber); | ||||
| JANET_API void *janet_async_start(JanetFiber *fiber, JanetStream *stream, | ||||
|                                   JanetAsyncMode mode, JanetEVCallback callback, size_t data_size); | ||||
|  | ||||
| /* Needed for windows to mark a fiber as waiting for an IOCP completion event. Noop on other platforms. */ | ||||
| JANET_API void janet_async_in_flight(JanetFiber *fiber); | ||||
|  | ||||
| /* On some platforms, it is important to be able to control if a stream is edge-trigger or level triggered. | ||||
|  * For example, a server that is accepting connections might want to be level triggered or edge-triggered | ||||
|  * depending on expected service. */ | ||||
| JANET_API void janet_stream_edge_triggered(JanetStream *stream); | ||||
| JANET_API void janet_stream_level_triggered(JanetStream *stream); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| @@ -635,6 +662,7 @@ typedef int32_t JanetAtomicInt; | ||||
| #endif | ||||
| JANET_API JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x); | ||||
| JANET_API JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x); | ||||
| JANET_API JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x); | ||||
|  | ||||
| /* We provide three possible implementations of Janets. The preferred | ||||
|  * nanboxing approach, for 32 or 64 bits, and the standard C version. Code in the rest of the | ||||
| @@ -877,12 +905,16 @@ JANET_API Janet janet_nanbox32_from_tagp(uint32_t tag, void *pointer); | ||||
| /* End of tagged union implementation */ | ||||
| #endif | ||||
|  | ||||
| JANET_API int janet_checkint16(Janet x); | ||||
| JANET_API int janet_checkuint16(Janet x); | ||||
| JANET_API int janet_checkint(Janet x); | ||||
| JANET_API int janet_checkuint(Janet x); | ||||
| JANET_API int janet_checkint64(Janet x); | ||||
| JANET_API int janet_checkuint64(Janet x); | ||||
| JANET_API int janet_checksize(Janet x); | ||||
| JANET_API JanetAbstract janet_checkabstract(Janet x, const JanetAbstractType *at); | ||||
| #define janet_checkint16range(x) ((x) >= INT16_MIN && (x) <= INT16_MAX && (x) == (int16_t)(x)) | ||||
| #define janet_checkuint16range(x) ((x) >= 0 && (x) <= UINT16_MAX && (x) == (uint16_t)(x)) | ||||
| #define janet_checkintrange(x) ((x) >= INT32_MIN && (x) <= INT32_MAX && (x) == (int32_t)(x)) | ||||
| #define janet_checkuintrange(x) ((x) >= 0 && (x) <= UINT32_MAX && (x) == (uint32_t)(x)) | ||||
| #define janet_checkint64range(x) ((x) >= JANET_INTMIN_DOUBLE && (x) <= JANET_INTMAX_DOUBLE && (x) == (int64_t)(x)) | ||||
| @@ -1389,6 +1421,7 @@ JANET_API void janet_loop1_interrupt(JanetVM *vm); | ||||
|  | ||||
| /* Wrapper around streams */ | ||||
| JANET_API JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods); | ||||
| JANET_API JanetStream *janet_stream_ext(JanetHandle handle, uint32_t flags, const JanetMethod *methods, size_t size); /* Allow for type punning streams */ | ||||
| JANET_API void janet_stream_close(JanetStream *stream); | ||||
| JANET_API Janet janet_cfun_stream_close(int32_t argc, Janet *argv); | ||||
| JANET_API Janet janet_cfun_stream_read(int32_t argc, Janet *argv); | ||||
| @@ -1419,6 +1452,14 @@ JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t s | ||||
| JANET_API int32_t janet_abstract_incref(void *abst); | ||||
| JANET_API int32_t janet_abstract_decref(void *abst); | ||||
|  | ||||
| /* Expose channel utilities */ | ||||
| JanetChannel *janet_channel_make(uint32_t limit); | ||||
| JanetChannel *janet_channel_make_threaded(uint32_t limit); | ||||
| JanetChannel *janet_getchannel(const Janet *argv, int32_t n); | ||||
| JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, JanetChannel *dflt); | ||||
| JANET_API int janet_channel_give(JanetChannel *channel, Janet x); | ||||
| JANET_API int janet_channel_take(JanetChannel *channel, Janet *out); | ||||
|  | ||||
| /* Expose some OS sync primitives */ | ||||
| JANET_API size_t janet_os_mutex_size(void); | ||||
| JANET_API size_t janet_os_rwlock_size(void); | ||||
| @@ -1488,22 +1529,22 @@ JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGeneric | ||||
| JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value); | ||||
|  | ||||
| /* Read async from a stream */ | ||||
| JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| #ifdef JANET_NET | ||||
| JANET_API void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| #endif | ||||
|  | ||||
| /* Write async to a stream */ | ||||
| JANET_API void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf); | ||||
| JANET_API void janet_ev_write_string(JanetStream *stream, JanetString str); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_write_string(JanetStream *stream, JanetString str); | ||||
| #ifdef JANET_NET | ||||
| JANET_API void janet_ev_send_buffer(JanetStream *stream, JanetBuffer *buf, int flags); | ||||
| JANET_API void janet_ev_send_string(JanetStream *stream, JanetString str, int flags); | ||||
| JANET_API void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, int flags); | ||||
| JANET_API void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_send_buffer(JanetStream *stream, JanetBuffer *buf, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_send_string(JanetStream *stream, JanetString str, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags); | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
| @@ -1574,6 +1615,9 @@ JANET_API int janet_scan_number(const uint8_t *str, int32_t len, double *out); | ||||
| JANET_API int janet_scan_number_base(const uint8_t *str, int32_t len, int32_t base, double *out); | ||||
| JANET_API int janet_scan_int64(const uint8_t *str, int32_t len, int64_t *out); | ||||
| JANET_API int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out); | ||||
| #ifdef JANET_INT_TYPES | ||||
| JANET_API int janet_scan_numeric(const uint8_t *str, int32_t len, Janet *out); | ||||
| #endif | ||||
|  | ||||
| /* Debugging */ | ||||
| JANET_API void janet_debug_break(JanetFuncDef *def, int32_t pc); | ||||
| @@ -1761,6 +1805,7 @@ JANET_API void janet_gcpressure(size_t s); | ||||
| /* Functions */ | ||||
| JANET_API JanetFuncDef *janet_funcdef_alloc(void); | ||||
| JANET_API JanetFunction *janet_thunk(JanetFuncDef *def); | ||||
| JANET_API JanetFunction *janet_thunk_delay(Janet x); | ||||
| JANET_API int janet_verify(JanetFuncDef *def); | ||||
|  | ||||
| /* Pretty printing */ | ||||
| @@ -2000,7 +2045,10 @@ JANET_API void *janet_getpointer(const Janet *argv, int32_t n); | ||||
|  | ||||
| JANET_API int32_t janet_getnat(const Janet *argv, int32_t n); | ||||
| JANET_API int32_t janet_getinteger(const Janet *argv, int32_t n); | ||||
| JANET_API int16_t janet_getinteger16(const Janet *argv, int32_t n); | ||||
| JANET_API int64_t janet_getinteger64(const Janet *argv, int32_t n); | ||||
| JANET_API uint32_t janet_getuinteger(const Janet *argv, int32_t n); | ||||
| JANET_API uint16_t janet_getuinteger16(const Janet *argv, int32_t n); | ||||
| JANET_API uint64_t janet_getuinteger64(const Janet *argv, int32_t n); | ||||
| JANET_API size_t janet_getsize(const Janet *argv, int32_t n); | ||||
| JANET_API JanetView janet_getindexed(const Janet *argv, int32_t n); | ||||
| @@ -2123,11 +2171,13 @@ typedef enum { | ||||
|     RULE_TO,           /* [rule] */ | ||||
|     RULE_THRU,         /* [rule] */ | ||||
|     RULE_LENPREFIX,    /* [rule_a, rule_b (repeat rule_b rule_a times)] */ | ||||
|     RULE_READINT,      /* [(signedness << 4) | (endianess << 5) | bytewidth, tag] */ | ||||
|     RULE_READINT,      /* [(signedness << 4) | (endianness << 5) | bytewidth, tag] */ | ||||
|     RULE_LINE,         /* [tag] */ | ||||
|     RULE_COLUMN,       /* [tag] */ | ||||
|     RULE_UNREF,        /* [rule, tag] */ | ||||
|     RULE_CAPTURE_NUM   /* [rule, tag] */ | ||||
|     RULE_CAPTURE_NUM,  /* [rule, tag] */ | ||||
|     RULE_SUB,          /* [rule, rule] */ | ||||
|     RULE_SPLIT         /* [rule, rule] */ | ||||
| } JanetPegOpcod; | ||||
|  | ||||
| typedef struct { | ||||
|   | ||||
| @@ -502,10 +502,10 @@ static void kright(void) { | ||||
| } | ||||
|  | ||||
| static void krightw(void) { | ||||
|     while (gbl_pos != gbl_len && !isspace(gbl_buf[gbl_pos])) { | ||||
|     while (gbl_pos != gbl_len && isspace(gbl_buf[gbl_pos])) { | ||||
|         gbl_pos++; | ||||
|     } | ||||
|     while (gbl_pos != gbl_len && isspace(gbl_buf[gbl_pos])) { | ||||
|     while (gbl_pos != gbl_len && !isspace(gbl_buf[gbl_pos])) { | ||||
|         gbl_pos++; | ||||
|     } | ||||
|     refresh(); | ||||
| @@ -867,7 +867,7 @@ static int line() { | ||||
|     if (write_console((char *) gbl_prompt, gbl_plen) == -1) return -1; | ||||
|     for (;;) { | ||||
|         char c; | ||||
|         char seq[3]; | ||||
|         char seq[5]; | ||||
|  | ||||
|         int rc; | ||||
|         do { | ||||
| @@ -991,6 +991,20 @@ static int line() { | ||||
|                                 default: | ||||
|                                     break; | ||||
|                             } | ||||
|                         } else if (seq[2] == ';') { | ||||
|                             if (read_console(seq + 3, 2) == -1) break; | ||||
|                             if (seq[3] == '5') { | ||||
|                                 switch (seq[4]) { | ||||
|                                     case 'C': /* ctrl-right */ | ||||
|                                         krightw(); | ||||
|                                         break; | ||||
|                                     case 'D': /* ctrl-left */ | ||||
|                                         kleftw(); | ||||
|                                         break; | ||||
|                                     default: | ||||
|                                         break; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } else if (seq[0] == 'O') { | ||||
|                         if (read_console(seq + 1, 1) == -1) break; | ||||
| @@ -1163,6 +1177,7 @@ int main(int argc, char **argv) { | ||||
|     janet_resolve(env, janet_csymbol("cli-main"), &mainfun); | ||||
|     Janet mainargs[1] = { janet_wrap_array(args) }; | ||||
|     JanetFiber *fiber = janet_fiber(janet_unwrap_function(mainfun), 64, 1, mainargs); | ||||
|     janet_gcroot(janet_wrap_fiber(fiber)); | ||||
|     fiber->env = env; | ||||
|  | ||||
|     /* Run the fiber in an event loop */ | ||||
|   | ||||
| @@ -7,21 +7,31 @@ | ||||
|  | ||||
| (def is-verbose (os/getenv "VERBOSE")) | ||||
|  | ||||
| (defn assert | ||||
| (defn- assert-no-tail | ||||
|   "Override's the default assert with some nice error handling." | ||||
|   [x &opt e] | ||||
|   (default e "assert error") | ||||
|   (++ num-tests-run) | ||||
|   (when x (++ num-tests-passed)) | ||||
|   (def str (string e)) | ||||
|   (def frame (last (debug/stack (fiber/current)))) | ||||
|   (def stack (debug/stack (fiber/current))) | ||||
|   (def frame (last stack)) | ||||
|   (def line-info (string/format "%s:%d" | ||||
|                               (frame :source) (frame :source-line))) | ||||
|   (if x | ||||
|     (when is-verbose (eprintf "\e[32m✔\e[0m %s: %s: %v" line-info (describe e) x)) | ||||
|     (do (eprintf "\e[31m✘\e[0m %s: %s: %v" line-info (describe e) x) (eflush))) | ||||
|     (do | ||||
|       (eprintf "\e[31m✘\e[0m %s: %s: %v" line-info (describe e) x) (eflush))) | ||||
|   x) | ||||
|  | ||||
| (defmacro assert | ||||
|   [x &opt e] | ||||
|   (def xx (gensym)) | ||||
|   ~(do | ||||
|      (def ,xx ,x) | ||||
|      (,assert-no-tail ,xx ,e) | ||||
|      ,xx)) | ||||
|  | ||||
| (defmacro assert-error | ||||
|   [msg & forms] | ||||
|   (def errsym (keyword (gensym))) | ||||
| @@ -54,3 +64,20 @@ | ||||
|   (eprinf "Finished suite %s in %.3f seconds - " suite-name delta) | ||||
|   (eprint num-tests-passed " of " num-tests-run " tests passed.") | ||||
|   (if (not= num-tests-passed num-tests-run) (os/exit 1))) | ||||
|  | ||||
| (defn rmrf | ||||
|   "rm -rf in janet" | ||||
|   [x] | ||||
|   (case (os/lstat x :mode) | ||||
|     nil nil | ||||
|     :directory (do | ||||
|                  (each y (os/dir x) | ||||
|                    (rmrf (string x "/" y))) | ||||
|                  (os/rmdir x)) | ||||
|     (os/rm x)) | ||||
|   nil) | ||||
|  | ||||
| (defn randdir | ||||
|   "Get a random directory name" | ||||
|   [] | ||||
|   (string "tmp_dir_" (slice (string (math/random) ".tmp") 2))) | ||||
|   | ||||
| @@ -76,6 +76,16 @@ | ||||
| (array/trim a) | ||||
| (array/ensure @[1 1] 6 2) | ||||
|  | ||||
| # array/join | ||||
| (assert (deep= @[1 2 3] (array/join @[] [1] [2] [3])) "array/join 1") | ||||
| (assert (deep= @[] (array/join @[])) "array/join 2") | ||||
| (assert (deep= @[1 :a :b :c] (array/join @[1] @[:a :b] [] [:c])) "array/join 3") | ||||
| (assert (deep= @[:x :y :z "abc123" "def456"] (array/join @[:x :y :z] ["abc123" "def456"])) "array/join 4") | ||||
| (assert-error "array/join error 1" (array/join)) | ||||
| (assert-error "array/join error 2" (array/join [])) | ||||
| (assert-error "array/join error 3" (array/join [] "abc123")) | ||||
| (assert-error "array/join error 4" (array/join @[] "abc123")) | ||||
| (assert-error "array/join error 5" (array/join @[] "abc123")) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
| @@ -51,5 +51,13 @@ | ||||
| (def f (asm (disasm (fn [x] (fn [y] (+ x y)))))) | ||||
| (assert (= ((f 10) 37) 47) "asm environment tables") | ||||
|  | ||||
| # issue #1424 | ||||
| (assert-no-error "arity > used slots (issue #1424)" | ||||
|                  (asm | ||||
|                    (disasm | ||||
|                      (fn [] | ||||
|                        (def foo (fn [one two] one)) | ||||
|                        (foo 100 200))))) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
| @@ -241,6 +241,16 @@ | ||||
|   (assert (pos? (% x 4)) "generate in loop")) | ||||
| (assert (= gencount 75) "generate loop count") | ||||
|  | ||||
| # more loop checks | ||||
| (assert (deep= (seq [i :range [0 10]] i) @[0 1 2 3 4 5 6 7 8 9]) "seq 1") | ||||
| (assert (deep= (seq [i :range [0 10 2]] i) @[0 2 4 6 8]) "seq 2") | ||||
| (assert (deep= (seq [i :range [10]] i) @[0 1 2 3 4 5 6 7 8 9]) "seq 3") | ||||
| (assert (deep= (seq [i :range-to [10]] i) @[0 1 2 3 4 5 6 7 8 9 10]) "seq 4") | ||||
| (def gen (generate [x :range-to [0 nil 2]] x)) | ||||
| (assert (deep= (take 5 gen) @[0 2 4 6 8]) "generate nil limit") | ||||
| (def gen (generate [x :range [0 nil 2]] x)) | ||||
| (assert (deep= (take 5 gen) @[0 2 4 6 8]) "generate nil limit 2") | ||||
|  | ||||
| # Even and odd | ||||
| # ff163a5ae | ||||
| (assert (odd? 9) "odd? 1") | ||||
| @@ -354,7 +364,7 @@ | ||||
|         "sort 5") | ||||
| (assert (<= ;(sort (map (fn [x] (math/random)) (range 1000)))) "sort 6") | ||||
|  | ||||
| # #1283   | ||||
| # #1283 | ||||
| (assert (deep= | ||||
|           (partition 2 (generate [ i :in [:a :b :c :d :e]] i)) | ||||
|           '@[(:a :b) (:c :d) (:e)])) | ||||
| @@ -744,7 +754,7 @@ | ||||
|     (default name (string "has-key? " (++ test-has-key-auto))) | ||||
|     (assert (= expected (has-key? col key)) name) | ||||
|     (if | ||||
|       # guarenteed by `has-key?` to never fail | ||||
|       # guaranteed by `has-key?` to never fail | ||||
|       expected (in col key) | ||||
|       # if `has-key?` is false, then `in` should fail (for indexed types) | ||||
|       # | ||||
| @@ -945,10 +955,28 @@ | ||||
| (defn case-4 [&] | ||||
|   (def x (break (break (break))))) | ||||
| (bytecode-roundtrip case-4) | ||||
| (defn case-5 [] | ||||
|   (def foo (fn [one two] one)) | ||||
|   (foo 100 200)) | ||||
| (bytecode-roundtrip case-5) | ||||
|  | ||||
| # Debug bytecode of these functions | ||||
| # (pp (disasm case-1)) | ||||
| # (pp (disasm case-2)) | ||||
| # (pp (disasm case-3)) | ||||
|  | ||||
| # Regression #1330 | ||||
| (defn regress-1330 [&] | ||||
|   (def a [1 2 3]) | ||||
|   (def b [;a]) | ||||
|   (identity a)) | ||||
| (assert (= [1 2 3] (regress-1330)) "regression 1330") | ||||
|  | ||||
| # Issue 1341 | ||||
| (assert (= () '() (macex '())) "macex ()") | ||||
| (assert (= '[] (macex '[])) "macex []") | ||||
|  | ||||
| (assert (= :a (with-env @{:b :a} (dyn :b))) "with-env dyn") | ||||
| (assert-error "unknown symbol +" (with-env @{} (eval '(+ 1 2)))) | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 Calvin Rose | ||||
| # Copyright (c) 2024 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 | ||||
| @@ -77,6 +77,48 @@ | ||||
| (buffer/push-string b5 "456" @"789") | ||||
| (assert (= "123456789" (string b5)) "buffer/push-buffer 2") | ||||
|  | ||||
| (def buffer-uint16-be @"") | ||||
| (buffer/push-uint16 buffer-uint16-be :be 0x0102) | ||||
| (assert (= "\x01\x02" (string buffer-uint16-be)) "buffer/push-uint16 big endian") | ||||
|  | ||||
| (def buffer-uint16-le @"") | ||||
| (buffer/push-uint16 buffer-uint16-le :le 0x0102) | ||||
| (assert (= "\x02\x01" (string buffer-uint16-le)) "buffer/push-uint16 little endian") | ||||
|  | ||||
| (def buffer-uint16-max @"") | ||||
| (buffer/push-uint16 buffer-uint16-max :be 0xFFFF) | ||||
| (assert (= "\xff\xff" (string buffer-uint16-max)) "buffer/push-uint16 max") | ||||
| (assert-error "too large" (buffer/push-uint16 @"" 0x1FFFF)) | ||||
| (assert-error "too small" (buffer/push-uint16 @"" -0x1)) | ||||
|  | ||||
| (def buffer-uint32-be @"") | ||||
| (buffer/push-uint32 buffer-uint32-be :be 0x01020304) | ||||
| (assert (= "\x01\x02\x03\x04" (string buffer-uint32-be)) "buffer/push-uint32 big endian") | ||||
|  | ||||
| (def buffer-uint32-le @"") | ||||
| (buffer/push-uint32 buffer-uint32-le :le 0x01020304) | ||||
| (assert (= "\x04\x03\x02\x01" (string buffer-uint32-le)) "buffer/push-uint32 little endian") | ||||
|  | ||||
| (def buffer-uint32-max @"") | ||||
| (buffer/push-uint32 buffer-uint32-max :be 0xFFFFFFFF) | ||||
| (assert (= "\xff\xff\xff\xff" (string buffer-uint32-max)) "buffer/push-uint32 max") | ||||
|  | ||||
| (def buffer-float32-be @"") | ||||
| (buffer/push-float32 buffer-float32-be :be 1.234) | ||||
| (assert (= "\x3f\x9d\xf3\xb6" (string buffer-float32-be)) "buffer/push-float32 big endian") | ||||
|  | ||||
| (def buffer-float32-le @"") | ||||
| (buffer/push-float32 buffer-float32-le :le 1.234) | ||||
| (assert (= "\xb6\xf3\x9d\x3f" (string buffer-float32-le)) "buffer/push-float32 little endian") | ||||
|  | ||||
| (def buffer-float64-be @"") | ||||
| (buffer/push-float64 buffer-float64-be :be 1.234) | ||||
| (assert (= "\x3f\xf3\xbe\x76\xc8\xb4\x39\x58" (string buffer-float64-be)) "buffer/push-float64 big endian") | ||||
|  | ||||
| (def buffer-float64-le @"") | ||||
| (buffer/push-float64 buffer-float64-le :le 1.234) | ||||
| (assert (= "\x58\x39\xb4\xc8\x76\xbe\xf3\x3f" (string buffer-float64-le)) "buffer/push-float64 little endian") | ||||
|  | ||||
| # Buffer from bytes | ||||
| (assert (deep= @"" (buffer/from-bytes)) "buffer/from-bytes 1") | ||||
| (assert (deep= @"ABC" (buffer/from-bytes 65 66 67)) "buffer/from-bytes 2") | ||||
| @@ -122,5 +164,20 @@ | ||||
| (assert (deep= @"abc423" (buffer/push-at @"abc123" 3 "4")) | ||||
|         "buffer/push-at 3") | ||||
|  | ||||
| # buffer/format-at | ||||
| (def start-buf (buffer/new-filled 100 (chr "x"))) | ||||
| (buffer/format-at start-buf 50 "aa%dbb" 32) | ||||
| (assert (= (string start-buf) (string (string/repeat "x" 50) "aa32bb"  (string/repeat "x" 44))) | ||||
|         "buffer/format-at 1") | ||||
| (assert | ||||
|   (deep= | ||||
|     (buffer/format @"" "%j" [1 2 3 :a :b :c]) | ||||
|     (buffer/format-at @"" 0 "%j" [1 2 3 :a :b :c])) | ||||
|   "buffer/format-at empty buffer") | ||||
| (def buf @"xxxyyy") | ||||
| (buffer/format-at buf -4 "xxx") | ||||
| (assert (= (string buf) "xxxxxx") "buffer/format-at negative index") | ||||
| (assert-error "expected index at to be in range [0, 0), got 1" (buffer/format-at @"" 1 "abc")) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
							
								
								
									
										122
									
								
								test/suite-bundle.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								test/suite-bundle.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| # Copyright (c) 2024 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 | ||||
| # deal in the Software without restriction, including without limitation the | ||||
| # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||||
| # sell copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||
| # IN THE SOFTWARE. | ||||
|  | ||||
| (import ./helper :prefix "" :exit true) | ||||
| (start-suite) | ||||
|  | ||||
| (assert true) # smoke test | ||||
|  | ||||
| # Testing here is stateful since we are manipulating the filesystem. | ||||
|  | ||||
| # Copy since not exposed in boot.janet | ||||
| (defn- bundle-rpath | ||||
|   [path] | ||||
|   (string/replace-all "\\" "/" (os/realpath path))) | ||||
|  | ||||
| # Test mkdir -> rmdir | ||||
| (assert (os/mkdir "tempdir123")) | ||||
| (rmrf "tempdir123") | ||||
|  | ||||
| # Setup a temporary syspath for manipultation | ||||
| (math/seedrandom (os/cryptorand 16)) | ||||
| (def syspath (randdir)) | ||||
| (rmrf syspath) | ||||
| (assert (os/mkdir syspath)) | ||||
| (put root-env *syspath* (bundle-rpath syspath)) | ||||
| (unless (os/getenv "VERBOSE") | ||||
|   (setdyn *out* @"")) | ||||
| (assert (empty? (bundle/list)) "initial bundle/list") | ||||
| (assert (empty? (bundle/topolist)) "initial bundle/topolist") | ||||
|  | ||||
| # Try (and fail) to install sample-bundle (missing deps) | ||||
| (assert-error "missing dependencies sample-dep1, sample-dep2" | ||||
|               (bundle/install "./examples/sample-bundle")) | ||||
| (assert (empty? (bundle/list))) | ||||
|  | ||||
| # Install deps (dep1 as :auto-remove) | ||||
| (assert-no-error "sample-dep2" | ||||
|                  (bundle/install "./examples/sample-dep2")) | ||||
| (assert (= 1 (length (bundle/list)))) | ||||
| (assert-no-error "sample-dep1" (bundle/install "./examples/sample-dep1")) | ||||
| (assert (= 2 (length (bundle/list)))) | ||||
|  | ||||
| (assert-no-error "sample-dep2 reinstall" (bundle/reinstall "sample-dep2")) | ||||
| (assert-no-error "sample-dep1 reinstall" (bundle/reinstall "sample-dep1" :auto-remove true)) | ||||
|  | ||||
| (assert (= 2 (length (bundle/list))) "bundles are listed correctly 1") | ||||
| (assert (= 2 (length (bundle/topolist))) "bundles are listed correctly 2") | ||||
|  | ||||
| # Now install sample-bundle | ||||
| (assert-no-error "sample-bundle install" (bundle/install "./examples/sample-bundle")) | ||||
|  | ||||
| (assert-error "" (bundle/install "./examples/sample-dep11111")) | ||||
|  | ||||
| (assert (= 3 (length (bundle/list))) "bundles are listed correctly 3") | ||||
| (assert (= 3 (length (bundle/topolist))) "bundles are listed correctly 4") | ||||
|  | ||||
| # Check topolist has not bad order | ||||
| (def tlist (bundle/topolist)) | ||||
| (assert (> (index-of "sample-bundle" tlist) (index-of "sample-dep2" tlist)) "topolist 1") | ||||
| (assert (> (index-of "sample-bundle" tlist) (index-of "sample-dep1" tlist)) "topolist 2") | ||||
| (assert (> (index-of "sample-dep1" tlist) (index-of "sample-dep2" tlist)) "topolist 3") | ||||
|  | ||||
| # Prune should do nothing | ||||
| (assert-no-error "first prune" (bundle/prune)) | ||||
| (assert (= 3 (length (bundle/list))) "bundles are listed correctly 3") | ||||
| (assert (= 3 (length (bundle/topolist))) "bundles are listed correctly 4") | ||||
|  | ||||
| # Check that we can import the main dependency | ||||
| (import mymod) | ||||
| (assert (= 288 (mymod/myfn 12)) "using sample-bundle") | ||||
|  | ||||
| # Manual uninstall of dep1 and dep2 shouldn't work either since that would break dependencies | ||||
| (assert-error "cannot uninstall sample-dep1, breaks dependent bundles @[\"sample-bundle\"]" | ||||
|               (bundle/uninstall "sample-dep1")) | ||||
|  | ||||
| # Check bundle file aliases | ||||
| (assert-no-error "sample-bundle-aliases install" (bundle/install "./examples/sample-bundle-aliases")) | ||||
| (assert (= 4 (length (bundle/list))) "bundles are listed correctly 5") | ||||
| (assert-no-error "import aliases" (import aliases-mod)) | ||||
| (assert (deep= (range 12) (aliases-mod/fun 12)) "using sample-bundle-aliases") | ||||
| (assert-no-error "aliases uninstall" (bundle/uninstall "sample-bundle-aliases")) | ||||
|  | ||||
| # Now re-install sample-bundle as auto-remove | ||||
| (assert-no-error "sample-bundle install" (bundle/reinstall "sample-bundle" :auto-remove true)) | ||||
|  | ||||
| # Reinstallation should also work without being concerned about breaking dependencies | ||||
| (assert-no-error "reinstall dep" (bundle/reinstall "sample-dep2")) | ||||
|  | ||||
| # Now prune should get rid of everything except sample-dep2 | ||||
| (assert-no-error "second prune" (bundle/prune)) | ||||
|  | ||||
| # Now check that we exactly one package left, which is dep2 | ||||
| (assert (= 1 (length (bundle/list))) "bundles are listed correctly 5") | ||||
| (assert (= 1 (length (bundle/topolist))) "bundles are listed correctly 6") | ||||
|  | ||||
| # Which we can uninstall manually | ||||
| (assert-no-error "uninstall dep2" (bundle/uninstall "sample-dep2")) | ||||
|  | ||||
| # Now check bundle listing is again empty | ||||
| (assert (= 0 (length (bundle/list))) "bundles are listed correctly 7") | ||||
| (assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8") | ||||
|  | ||||
| (rmrf syspath) | ||||
|  | ||||
| (end-suite) | ||||
| @@ -69,6 +69,13 @@ | ||||
|                (seq [n :range [0 10]] (% n 5 3)) | ||||
|                [0 1 2 0 1 0 1 2 0 1]) "variadic mod") | ||||
|  | ||||
| # linspace range | ||||
| (assert (deep= @[0 1 2 3] (range 4)) "range 1") | ||||
| (assert (deep= @[0 1 2 3] (range 3.01)) "range 2") | ||||
| (assert (deep= @[0 1 2 3] (range 3.999)) "range 3") | ||||
| (assert (deep= @[0.8 1.8 2.8 3.8] (range 0.8 3.999)) "range 4") | ||||
| (assert (deep= @[0.8 1.8 2.8 3.8] (range 0.8 3.999)) "range 5") | ||||
|  | ||||
| (assert (< 1.0 nil false true | ||||
|            (fiber/new (fn [] 1)) | ||||
|            "hi" | ||||
|   | ||||
| @@ -21,6 +21,9 @@ | ||||
| (import ./helper :prefix "" :exit true) | ||||
| (start-suite) | ||||
|  | ||||
| (def test-port (os/getenv "JANET_TEST_PORT" "8761")) | ||||
| (def test-host (os/getenv "JANET_TEST_HOST" "127.0.0.1")) | ||||
|  | ||||
| # Subprocess | ||||
| # 5e1a8c86f | ||||
| (def janet (dyn *executable*)) | ||||
| @@ -192,11 +195,11 @@ | ||||
|       (net/write stream b) | ||||
|       (buffer/clear b))) | ||||
|  | ||||
|   (def s (net/server "127.0.0.1" "8000" handler)) | ||||
|   (def s (net/server test-host test-port handler)) | ||||
|   (assert s "made server 1") | ||||
|  | ||||
|   (defn test-echo [msg] | ||||
|     (with [conn (net/connect "127.0.0.1" "8000")] | ||||
|     (with [conn (net/connect test-host test-port)] | ||||
|       (net/write conn msg) | ||||
|       (def res (net/read conn 1024)) | ||||
|       (assert (= (string res) msg) (string "echo " msg)))) | ||||
| @@ -216,18 +219,18 @@ | ||||
|     # prevent immediate close | ||||
|     (ev/read stream 1) | ||||
|     (def [host port] (net/localname stream)) | ||||
|     (assert (= host "127.0.0.1") "localname host server") | ||||
|     (assert (= port 8000) "localname port server"))) | ||||
|     (assert (= host test-host) "localname host server") | ||||
|     (assert (= port (scan-number test-port)) "localname port server"))) | ||||
|  | ||||
| # Test localname and peername | ||||
| # 077bf5eba | ||||
| (repeat 10 | ||||
|   (with [s (net/server "127.0.0.1" "8000" names-handler)] | ||||
|   (with [s (net/server test-host test-port names-handler)] | ||||
|     (repeat 10 | ||||
|       (with [conn (net/connect "127.0.0.1" "8000")] | ||||
|       (with [conn (net/connect test-host test-port)] | ||||
|         (def [host port] (net/peername conn)) | ||||
|         (assert (= host "127.0.0.1") "peername host client ") | ||||
|         (assert (= port 8000) "peername port client") | ||||
|         (assert (= host test-host) "peername host client ") | ||||
|         (assert (= port (scan-number test-port)) "peername port client") | ||||
|         # let server close | ||||
|         (ev/write conn " ")))) | ||||
|   (gccollect)) | ||||
| @@ -366,4 +369,10 @@ | ||||
|                (exec-slurp ;run janet "-e" "(print :hi)"))) | ||||
|           "exec-slurp 1")) | ||||
|  | ||||
| # valgrind-able check for #1337 | ||||
| (def superv (ev/chan 10)) | ||||
| (def f (ev/go |(ev/sleep 1e9) nil superv)) | ||||
| (ev/cancel f (gensym)) | ||||
| (ev/take superv) | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
| @@ -21,7 +21,6 @@ | ||||
| (import ./helper :prefix "" :exit true) | ||||
| (start-suite) | ||||
|  | ||||
| # We should get ARM support... | ||||
| (def has-ffi (dyn 'ffi/native)) | ||||
| (def has-full-ffi | ||||
|   (and has-ffi | ||||
|   | ||||
							
								
								
									
										191
									
								
								test/suite-filewatch.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								test/suite-filewatch.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| # Copyright (c) 2024 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 | ||||
| # deal in the Software without restriction, including without limitation the | ||||
| # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||||
| # sell copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||
| # IN THE SOFTWARE. | ||||
|  | ||||
| (import ./helper :prefix "" :exit true) | ||||
| (start-suite) | ||||
|  | ||||
| (assert true) | ||||
|  | ||||
| (def chan (ev/chan 1000)) | ||||
| (def is-win (or (= :mingw (os/which)) (= :windows (os/which)))) | ||||
| (def is-linux (= :linux (os/which))) | ||||
|  | ||||
| # If not supported, exit early | ||||
| (def [supported msg] (protect (filewatch/new chan))) | ||||
| (when (and (not supported) (string/find "filewatch not supported" msg)) | ||||
|   (end-suite) | ||||
|   (quit)) | ||||
|  | ||||
| # Test GC | ||||
| (assert-no-error "filewatch/new" (filewatch/new chan)) | ||||
| (gccollect) | ||||
|  | ||||
| (defn- expect | ||||
|   [key value] | ||||
|   (ev/with-deadline | ||||
|     1 | ||||
|     (def event (ev/take chan)) | ||||
|     (when is-verbose (pp event)) | ||||
|     (assert event "check event") | ||||
|     (assert (= value (get event key)) (string/format "got %p, expected %p" (get event key) value)))) | ||||
|  | ||||
| (defn- expect-empty | ||||
|   [] | ||||
|   (assert (zero? (ev/count chan)) "channel check empty") | ||||
|   (ev/sleep 0) # turn the event loop | ||||
|   (assert (zero? (ev/count chan)) "channel check empty") | ||||
|   # Drain if not empty, help with failures after this | ||||
|   (while (pos? (ev/count chan)) (printf "extra: %p" (ev/take chan)))) | ||||
|  | ||||
| (defn- expect-maybe | ||||
|   "On wine + mingw, we get an extra event. This is a wine peculiarity." | ||||
|   [key value] | ||||
|   (ev/with-deadline | ||||
|     1 | ||||
|     (ev/sleep 0) | ||||
|     (when (pos? (ev/count chan)) | ||||
|       (def event (ev/take chan)) | ||||
|       (when is-verbose (pp event)) | ||||
|       (assert event "check event") | ||||
|       (assert (= value (get event key)) (string/format "got %p, expected %p" (get event key) value))))) | ||||
|  | ||||
| (defn spit-file | ||||
|   [dir name] | ||||
|   (def path (string dir "/" name)) | ||||
|   (spit path "test text")) | ||||
|  | ||||
| # Different operating systems report events differently. While it would be nice to | ||||
| # normalize this, each system has very large limitations in what can be reported when | ||||
| # compared with other systems. As such, the maximum subset of common functionality here | ||||
| # is quite small. Instead, test the capabilities of each system. | ||||
|  | ||||
| # Create a file watcher on two test directories | ||||
| (def fw (filewatch/new chan)) | ||||
| (def td1 (randdir)) | ||||
| (def td2 (randdir)) | ||||
| (rmrf td1) | ||||
| (rmrf td2) | ||||
| (os/mkdir td1) | ||||
| (os/mkdir td2) | ||||
| (when is-win | ||||
|   (filewatch/add fw td1 :last-write :last-access :file-name :dir-name :size :attributes :recursive) | ||||
|   (filewatch/add fw td2 :last-write :last-access :file-name :dir-name :size :attributes)) | ||||
| (when is-linux | ||||
|   (filewatch/add fw td1 :close-write :create :delete) | ||||
|   (filewatch/add fw td2 :close-write :create :delete :ignored)) | ||||
| (assert-no-error "filewatch/listen no error" (filewatch/listen fw)) | ||||
|  | ||||
| # | ||||
| # Windows file writing | ||||
| # | ||||
|  | ||||
| (when is-win | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :added) | ||||
|   (expect :type :modified) | ||||
|   (expect-maybe :type :modified) # for mingw + wine | ||||
|   (gccollect) | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :modified) | ||||
|   (expect :type :modified) | ||||
|   (expect-empty) | ||||
|   (gccollect) | ||||
|  | ||||
|   # Check td2 | ||||
|   (spit-file td2 "file2.txt") | ||||
|   (expect :type :added) | ||||
|   (expect :type :modified) | ||||
|   (expect-maybe :type :modified) | ||||
|  | ||||
|   # Remove a file, then wait for remove event | ||||
|   (rmrf (string td1 "/file1.txt")) | ||||
|   (expect :type :removed) | ||||
|   (expect-empty) | ||||
|  | ||||
|   # Unlisten to some events | ||||
|   (filewatch/remove fw td2) | ||||
|  | ||||
|   # Check that we don't get anymore events from test directory 2 | ||||
|   (spit-file td2 "file2.txt") | ||||
|   (expect-empty) | ||||
|  | ||||
|   # Repeat and things should still work with test directory 1 | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :added) | ||||
|   (expect :type :modified) | ||||
|   (expect-maybe :type :modified) | ||||
|   (gccollect) | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :modified) | ||||
|   (expect :type :modified) | ||||
|   (expect-maybe :type :modified) | ||||
|   (gccollect)) | ||||
|  | ||||
| # | ||||
| # Linux file writing | ||||
| # | ||||
|  | ||||
| (when is-linux | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :create) | ||||
|   (expect :type :close-write) | ||||
|   (expect-empty) | ||||
|   (gccollect) | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :close-write) | ||||
|   (expect-empty) | ||||
|   (gccollect) | ||||
|  | ||||
|   # Check td2 | ||||
|   (spit-file td2 "file2.txt") | ||||
|   (expect :type :create) | ||||
|   (expect :type :close-write) | ||||
|   (expect-empty) | ||||
|  | ||||
|   # Remove a file, then wait for remove event | ||||
|   (rmrf (string td1 "/file1.txt")) | ||||
|   (expect :type :delete) | ||||
|   (expect-empty) | ||||
|  | ||||
|   # Unlisten to some events | ||||
|   (filewatch/remove fw td2) | ||||
|   (expect :type :ignored) | ||||
|   (expect-empty) | ||||
|  | ||||
|   # Check that we don't get anymore events from test directory 2 | ||||
|   (spit-file td2 "file2.txt") | ||||
|   (expect-empty) | ||||
|  | ||||
|   # Repeat and things should still work with test directory 1 | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :create) | ||||
|   (expect :type :close-write) | ||||
|   (expect-empty) | ||||
|   (gccollect) | ||||
|   (spit-file td1 "file1.txt") | ||||
|   (expect :type :close-write) | ||||
|   (expect-empty) | ||||
|   (gccollect)) | ||||
|  | ||||
| (assert-no-error "filewatch/unlisten no error" (filewatch/unlisten fw)) | ||||
| (assert-no-error "cleanup 1" (rmrf td1)) | ||||
| (assert-no-error "cleanup 2" (rmrf td2)) | ||||
|  | ||||
| (end-suite) | ||||
| @@ -47,6 +47,14 @@ | ||||
| (assert (= (int/to-number (i64 9007199254740991)) 9007199254740991)) | ||||
| (assert (= (int/to-number (i64 -9007199254740991)) -9007199254740991)) | ||||
|  | ||||
| # New parser | ||||
| (assert (= (u64 "123") 123:u) "u64 parsing") | ||||
| (assert (= (u64 "0") 0:u) "u64 parsing") | ||||
| (assert (= (u64 "0xFFFF_FFFF_FFFF_FFFF") 0xFFFF_FFFF_FFFF_FFFF:u) "u64 parsing") | ||||
| (assert (= (i64 "123") 123:s) "s64 parsing") | ||||
| (assert (= (i64 "-123") -123:s) "s64 parsing") | ||||
| (assert (= (i64 "0") 0:s) "s64 parsing") | ||||
|  | ||||
| (assert-error | ||||
|   "u64 out of bounds for safe integer" | ||||
|   (int/to-number (u64 "9007199254740993")) | ||||
|   | ||||
| @@ -96,11 +96,23 @@ | ||||
|   (assert (= (in buf 0) 0) "cryptorand doesn't overwrite buffer") | ||||
|   (assert (= (length buf) 2) "cryptorand appends to buffer")) | ||||
|  | ||||
| (assert-no-error "realtime clock" (os/clock)) | ||||
| (assert-no-error "realtime clock" (os/clock nil)) | ||||
| (assert-no-error "realtime clock" (os/clock nil nil)) | ||||
|  | ||||
| # 80db68210 | ||||
| (assert-no-error "realtime clock" (os/clock :realtime)) | ||||
| (assert-no-error "cputime clock" (os/clock :cputime)) | ||||
| (assert-no-error "monotonic clock" (os/clock :monotonic)) | ||||
|  | ||||
| (assert-no-error "realtime clock double output" (os/clock nil :double)) | ||||
| (assert-no-error "realtime clock int output" (os/clock nil :int)) | ||||
| (assert-no-error "realtime clock tuple output" (os/clock nil :tuple)) | ||||
|  | ||||
| (assert-error "invalid clock" (os/clock :a)) | ||||
| (assert-error "invalid output" (os/clock :realtime :b)) | ||||
| (assert-error "invalid clock and output" (os/clock :a :b)) | ||||
|  | ||||
| (def before (os/clock :monotonic)) | ||||
| (def after (os/clock :monotonic)) | ||||
| (assert (>= after before) "monotonic clock is monotonic") | ||||
| @@ -119,6 +131,12 @@ | ||||
| (assert (= (os/perm-string 8r755) "rwxr-xr-x") "perm 8") | ||||
| (assert (= (os/perm-string 8r644) "rw-r--r--") "perm 9") | ||||
|  | ||||
| # Pipes | ||||
| (assert-no-error (os/pipe)) | ||||
| (assert-no-error (os/pipe :RW)) | ||||
| (assert-no-error (os/pipe :R)) | ||||
| (assert-no-error (os/pipe :W)) | ||||
|  | ||||
| # os/execute with environment variables | ||||
| # issue #636 - 7e2c433ab | ||||
| (assert (= 0 (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe | ||||
| @@ -148,4 +166,3 @@ | ||||
|                                {:out dn :err dn}))) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
| @@ -263,6 +263,9 @@ | ||||
| (marshpeg '(if-not "abcdf" 123)) | ||||
| (marshpeg ~(cmt "abcdf" ,identity)) | ||||
| (marshpeg '(group "abc")) | ||||
| (marshpeg '(sub "abcdf" "abc")) | ||||
| (marshpeg '(* (sub 1 1))) | ||||
| (marshpeg '(split "," (+ "a" "b" "c"))) | ||||
|  | ||||
| # Peg swallowing errors | ||||
| # 159651117 | ||||
| @@ -489,7 +492,7 @@ | ||||
|       # header, followed by body, and drop the :header-len capture | ||||
|       :packet (/ (* :packet-header :packet-body) ,|$1) | ||||
|  | ||||
|       # any exact seqence of packets (no extra characters) | ||||
|       # any exact sequence of packets (no extra characters) | ||||
|       :main (* (any :packet) -1)})) | ||||
|  | ||||
| (assert (deep= @["a" "bb" "ccc"] (peg/match peg2 "1:a2:bb3:ccc")) | ||||
| @@ -660,5 +663,98 @@ | ||||
|   (peg/match '(if (not (* (constant 7) "a")) "hello") "hello") | ||||
|   @[]) "peg if not") | ||||
|  | ||||
| (defn test [name peg input expected] | ||||
|   (assert (deep= (peg/match peg input) expected) name)) | ||||
|  | ||||
| (test "sub: matches the same input twice" | ||||
|   ~(sub "abcd" "abc") | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "sub: second pattern cannot match more than the first pattern" | ||||
|   ~(sub "abcd" "abcde") | ||||
|   "abcdef" | ||||
|   nil) | ||||
|  | ||||
| (test "sub: fails if first pattern fails" | ||||
|   ~(sub "x" "abc") | ||||
|   "abcdef" | ||||
|   nil) | ||||
|  | ||||
| (test "sub: fails if second pattern fails" | ||||
|   ~(sub "abc" "x") | ||||
|   "abcdef" | ||||
|   nil) | ||||
|  | ||||
| (test "sub: keeps captures from both patterns" | ||||
|   ~(sub '"abcd" '"abc") | ||||
|   "abcdef" | ||||
|   @["abcd" "abc"]) | ||||
|  | ||||
| (test "sub: second pattern can reference captures from first" | ||||
|   ~(* (constant 5 :tag) (sub (capture "abc" :tag) (backref :tag))) | ||||
|   "abcdef" | ||||
|   @[5 "abc" "abc"]) | ||||
|  | ||||
| (test "sub: second pattern can't see past what the first pattern matches" | ||||
|   ~(sub "abc" (* "abc" -1)) | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "sub: positions inside second match are still relative to the entire input" | ||||
|   ~(* "one\ntw" (sub "o" (* ($) (line) (column)))) | ||||
|   "one\ntwo\nthree\n" | ||||
|   @[6 2 3]) | ||||
|  | ||||
| (test "sub: advances to the end of the first pattern's match" | ||||
|   ~(* (sub "abc" "ab") "d") | ||||
|   "abcdef" | ||||
|   @[]) | ||||
|  | ||||
| (test "split: basic functionality" | ||||
|   ~(split "," '1) | ||||
|   "a,b,c" | ||||
|   @["a" "b" "c"]) | ||||
|  | ||||
| (test "split: drops captures from separator pattern" | ||||
|   ~(split '"," '1) | ||||
|   "a,b,c" | ||||
|   @["a" "b" "c"]) | ||||
|  | ||||
| (test "split: can match empty subpatterns" | ||||
|   ~(split "," ':w*) | ||||
|   ",a,,bar,,,c,," | ||||
|   @["" "a" "" "bar" "" "" "c" "" ""]) | ||||
|  | ||||
| (test "split: subpattern is limited to only text before the separator" | ||||
|   ~(split "," '(to -1)) | ||||
|   "a,,bar,c" | ||||
|   @["a" "" "bar" "c"]) | ||||
|  | ||||
| (test "split: fails if any subpattern fails" | ||||
|   ~(split "," '"a") | ||||
|   "a,a,b" | ||||
|   nil) | ||||
|  | ||||
| (test "split: separator does not have to match anything" | ||||
|   ~(split "x" '(to -1)) | ||||
|   "a,a,b" | ||||
|   @["a,a,b"]) | ||||
|  | ||||
| (test "split: always consumes entire input" | ||||
|   ~(split 1 '"") | ||||
|   "abc" | ||||
|   @["" "" "" ""]) | ||||
|  | ||||
| (test "split: separator can be an arbitrary PEG" | ||||
|   ~(split :s+ '(to -1)) | ||||
|   "a   b      c" | ||||
|   @["a" "b" "c"]) | ||||
|  | ||||
| (test "split: does not advance past the end of the input" | ||||
|   ~(* (split "," ':w+) 0) | ||||
|   "a,b,c" | ||||
|   @["a" "b" "c"]) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
| @@ -198,5 +198,9 @@ | ||||
|  | ||||
| (assert (= (test) '(1 ())) "issue #919") | ||||
|  | ||||
| (end-suite) | ||||
| # Regression #1327 | ||||
| (def x "A") | ||||
| (def x (if (= nil x) "B" x)) | ||||
| (assert (= x "A")) | ||||
|  | ||||
| (end-suite) | ||||
|   | ||||
							
								
								
									
										30
									
								
								test/suite-tuple.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								test/suite-tuple.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # Copyright (c) 2023 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 | ||||
| # deal in the Software without restriction, including without limitation the | ||||
| # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||||
| # sell copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||
| # IN THE SOFTWARE. | ||||
|  | ||||
| (import ./helper :prefix "" :exit true) | ||||
| (start-suite) | ||||
|  | ||||
| (assert (= [1 2 3] (tuple/join [1] [2] [3])) "tuple/join 1") | ||||
| (assert (= [] (tuple/join)) "tuple/join 2") | ||||
| (assert (= [:a :b :c] (tuple/join @[:a :b] [] [:c])) "tuple/join 3") | ||||
| (assert (= ["abc123" "def456"] (tuple/join ["abc123" "def456"])) "tuple/join 4") | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
| @@ -42,7 +42,7 @@ | ||||
|  | ||||
| (defn buffer-factory | ||||
|   [] | ||||
|   @"im am a buffer") | ||||
|   @"i am a buffer") | ||||
|  | ||||
| (assert (not= (buffer-factory) (buffer-factory)) "buffer instantiation") | ||||
|  | ||||
|   | ||||
| @@ -12,4 +12,4 @@ true | ||||
| @[1 "hello"] | ||||
| nil | ||||
| (foo 2 3) | ||||
| ([{} @{:k ([""])}]) | ||||
| ([{} @{:k ([""])}]) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user