mirror of
https://github.com/janet-lang/janet
synced 2026-04-02 13:01:28 +00:00
Compare commits
475 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a0453ff7f | ||
|
|
8f849cec55 | ||
|
|
7df23e8070 | ||
|
|
aa63adccb4 | ||
|
|
7fc12ff167 | ||
|
|
39f8cf207c | ||
|
|
95f2e233c5 | ||
|
|
e8f9c12935 | ||
|
|
32d75c9e49 | ||
|
|
5fec2aa9df | ||
|
|
54fbd7607f | ||
|
|
019829fdf9 | ||
|
|
2602bec017 | ||
|
|
403b2c704a | ||
|
|
ca9ffaa5bb | ||
|
|
e61194a8d9 | ||
|
|
08e4030487 | ||
|
|
56b5998553 | ||
|
|
ea997d585b | ||
|
|
fc725e2511 | ||
|
|
d636502c32 | ||
|
|
0fea20c821 | ||
|
|
91cc499e77 | ||
|
|
68850a0a05 | ||
|
|
d3d7c675a8 | ||
|
|
b2c9fc123c | ||
|
|
fa0c039cd3 | ||
|
|
78ef9d1733 | ||
|
|
b6676f350c | ||
|
|
0299620a2d | ||
|
|
739d9d9fe3 | ||
|
|
1557f9da78 | ||
|
|
529d8c9e4a | ||
|
|
2df16e5a48 | ||
|
|
b0db2b22d6 | ||
|
|
8b6d56edae | ||
|
|
a2a7e9f01e | ||
|
|
4b078e7a45 | ||
|
|
5c0bb4b385 | ||
|
|
2aaa7dfa10 | ||
|
|
10bb17199c | ||
|
|
0aa7dfeb9a | ||
|
|
8f7c32e5cb | ||
|
|
abd7bb1110 | ||
|
|
d81512723b | ||
|
|
2a54154976 | ||
|
|
306ce892ea | ||
|
|
c7c3821aa6 | ||
|
|
d2685594f9 | ||
|
|
ca5c617fba | ||
|
|
16b449a137 | ||
|
|
2e8dd90a51 | ||
|
|
196f27af3d | ||
|
|
42c0096ce7 | ||
|
|
0194115412 | ||
|
|
f33697d6a0 | ||
|
|
b2bf70eace | ||
|
|
855d1f2940 | ||
|
|
416bba9bd9 | ||
|
|
517e40a17b | ||
|
|
4f9a2af357 | ||
|
|
a37752708e | ||
|
|
5042ad6d4b | ||
|
|
643c0b4976 | ||
|
|
ecb72c9c9a | ||
|
|
a95546ff16 | ||
|
|
d47f82713b | ||
|
|
497e363401 | ||
|
|
8481da18d0 | ||
|
|
8f8382eead | ||
|
|
8e2ec997f0 | ||
|
|
ea271b6d6c | ||
|
|
e1897e1865 | ||
|
|
0c1585fdfe | ||
|
|
a5c4e929e8 | ||
|
|
4c21dc3c06 | ||
|
|
d67b462023 | ||
|
|
24ca108288 | ||
|
|
7366fbed1f | ||
|
|
797643716b | ||
|
|
eda2e11d31 | ||
|
|
ae0afe6198 | ||
|
|
33f5a0b319 | ||
|
|
3ecc9bc543 | ||
|
|
339b0751c8 | ||
|
|
87b1bf1a66 | ||
|
|
41354ada96 | ||
|
|
ee8d816738 | ||
|
|
0f285855f0 | ||
|
|
c43e06672c | ||
|
|
2fabc80151 | ||
|
|
4dd08a4cde | ||
|
|
883dde4fa5 | ||
|
|
6111291ede | ||
|
|
53b8bf2684 | ||
|
|
0c402cf3d6 | ||
|
|
606a1fc11a | ||
|
|
a2db57b9dc | ||
|
|
f021bb2839 | ||
|
|
979233dee5 | ||
|
|
78a785175a | ||
|
|
268864b072 | ||
|
|
06f099d7f9 | ||
|
|
6549903c51 | ||
|
|
c1dff351d9 | ||
|
|
4aa5615a37 | ||
|
|
67932bbaed | ||
|
|
4575cefb7e | ||
|
|
d5a014baff | ||
|
|
eb825772bb | ||
|
|
ee2985f5e3 | ||
|
|
5819408715 | ||
|
|
8fe284b5eb | ||
|
|
19b5502f50 | ||
|
|
0a5ff208a8 | ||
|
|
d35f189446 | ||
|
|
216c9799f5 | ||
|
|
5966017232 | ||
|
|
f80690e4c9 | ||
|
|
9bc308532f | ||
|
|
7a8d8444fe | ||
|
|
15cea60589 | ||
|
|
10954fe0d7 | ||
|
|
70fb13eb48 | ||
|
|
cb355815ee | ||
|
|
ddc7cc5ae4 | ||
|
|
84ced08810 | ||
|
|
7b0b9f57e8 | ||
|
|
80da028aeb | ||
|
|
f881f4dba9 | ||
|
|
6e4b0b9259 | ||
|
|
d5a56caa33 | ||
|
|
7d672f43fc | ||
|
|
7810724ed2 | ||
|
|
1e73fa7ec7 | ||
|
|
9b1194b08a | ||
|
|
d3f5b541ee | ||
|
|
3a3c0dec7a | ||
|
|
bb3cf7d83b | ||
|
|
9bc47e766e | ||
|
|
978c4e8b69 | ||
|
|
2544c4ae1a | ||
|
|
7f9d92a73b | ||
|
|
898e93bc4a | ||
|
|
715dd413cc | ||
|
|
1971d7df0a | ||
|
|
0d7afc9419 | ||
|
|
7e2a57687a | ||
|
|
2de6d26a46 | ||
|
|
4a9c9ebefc | ||
|
|
8887075a8c | ||
|
|
01ac5c32f3 | ||
|
|
b980c2ae17 | ||
|
|
7d06faca66 | ||
|
|
352935596a | ||
|
|
dc52242d36 | ||
|
|
dde1a67b77 | ||
|
|
7c31110195 | ||
|
|
d41eeaf7fa | ||
|
|
53fd2185fc | ||
|
|
2937125633 | ||
|
|
8284b1f5f2 | ||
|
|
04c96296b5 | ||
|
|
d888b1c530 | ||
|
|
7777a017e2 | ||
|
|
d8a074731e | ||
|
|
b723c00b98 | ||
|
|
98bbe9f474 | ||
|
|
2f69678f2f | ||
|
|
efe66f207f | ||
|
|
d6803f7a17 | ||
|
|
6e883b8972 | ||
|
|
c2cbfa4d5d | ||
|
|
a85689312a | ||
|
|
b9bc89a38c | ||
|
|
f79e4d6249 | ||
|
|
238d6c2e13 | ||
|
|
be2a1ddf96 | ||
|
|
d9105299f1 | ||
|
|
8efeeaec95 | ||
|
|
b92992c862 | ||
|
|
996ad64d01 | ||
|
|
bd1641b936 | ||
|
|
a9e6766958 | ||
|
|
ee46315e1d | ||
|
|
7c1db67749 | ||
|
|
4fa1a6947c | ||
|
|
1449ad8b31 | ||
|
|
c44592c84d | ||
|
|
37e084969a | ||
|
|
b389f01005 | ||
|
|
a886a93d2f | ||
|
|
f7d0d065c8 | ||
|
|
8c2a517cd7 | ||
|
|
7e0c692d4e | ||
|
|
732fe0ad03 | ||
|
|
0c8622c803 | ||
|
|
94f2494f8d | ||
|
|
0f9ecc2da5 | ||
|
|
83f5da3b8f | ||
|
|
9b9f2a1713 | ||
|
|
8df4d47ede | ||
|
|
1c372fbf32 | ||
|
|
8ace580498 | ||
|
|
8241d9cbb4 | ||
|
|
6bd02bb5b6 | ||
|
|
2a3308005d | ||
|
|
0c34033b72 | ||
|
|
f1ec0cc48b | ||
|
|
98265f0637 | ||
|
|
1018cb9cca | ||
|
|
2204209133 | ||
|
|
95abff31d9 | ||
|
|
a776466423 | ||
|
|
511c1f4b0a | ||
|
|
c29195596e | ||
|
|
56027227fb | ||
|
|
c057e14b20 | ||
|
|
db7f741dad | ||
|
|
c901edbfb9 | ||
|
|
8fd1672963 | ||
|
|
9b99fc44b9 | ||
|
|
f393531335 | ||
|
|
6b8e5249ca | ||
|
|
6a96b615f0 | ||
|
|
8ec465d308 | ||
|
|
07bfd34c2f | ||
|
|
5f7878c00f | ||
|
|
aaf8ac2217 | ||
|
|
73b1cf547e | ||
|
|
ed2ae562c6 | ||
|
|
dd59d84b51 | ||
|
|
06873fbf0b | ||
|
|
1ff26d702a | ||
|
|
4da568254a | ||
|
|
357f1f94ca | ||
|
|
015e49c806 | ||
|
|
6b06ab5f9c | ||
|
|
fe6c6e15a6 | ||
|
|
b4eb52ca45 | ||
|
|
aca5428846 | ||
|
|
3dab9737e2 | ||
|
|
e601e8faab | ||
|
|
07cf63622f | ||
|
|
8e7b1e9ce0 | ||
|
|
355c514f0e | ||
|
|
976329abc1 | ||
|
|
ab3e843433 | ||
|
|
148e108864 | ||
|
|
c90c737345 | ||
|
|
13b9976382 | ||
|
|
095a81286a | ||
|
|
82416e4e4e | ||
|
|
ae51434a05 | ||
|
|
bb6ac423a7 | ||
|
|
c5ba3c0513 | ||
|
|
e9c6678614 | ||
|
|
800457c1bf | ||
|
|
2a85781616 | ||
|
|
7c15e7f7dc | ||
|
|
896c28b0c8 | ||
|
|
e7bb0dd58e | ||
|
|
4e02f27eb9 | ||
|
|
fd234461d7 | ||
|
|
eabb215391 | ||
|
|
deede6bae0 | ||
|
|
697fdcff6d | ||
|
|
ad8a5cb6c7 | ||
|
|
99abada2c2 | ||
|
|
0624936711 | ||
|
|
f764788b36 | ||
|
|
4701bc6543 | ||
|
|
156fb0c999 | ||
|
|
bf34340737 | ||
|
|
20535e8626 | ||
|
|
1ead670e33 | ||
|
|
3ad86108f2 | ||
|
|
0aee7765cf | ||
|
|
4894a4673a | ||
|
|
f00d3199c3 | ||
|
|
e34a8545e6 | ||
|
|
f974c0667b | ||
|
|
ddc122958b | ||
|
|
2e363bf29c | ||
|
|
312f9faae8 | ||
|
|
8c9cd63cb1 | ||
|
|
2af3f21d69 | ||
|
|
c4e3fa03fa | ||
|
|
91b7bcad3d | ||
|
|
8d2a9c1148 | ||
|
|
f1d47bd05a | ||
|
|
58b1491592 | ||
|
|
21a6ed3bd3 | ||
|
|
e815c91e85 | ||
|
|
d96e584869 | ||
|
|
f4ecb5a90f | ||
|
|
f181948aa9 | ||
|
|
bbe6b90331 | ||
|
|
27f01e2664 | ||
|
|
877967966a | ||
|
|
56c5a0ca09 | ||
|
|
f3ad13c2d4 | ||
|
|
8ac4eec370 | ||
|
|
92e91259c3 | ||
|
|
e355cb07e0 | ||
|
|
5bbfcdacd5 | ||
|
|
790a4f2636 | ||
|
|
84bb84b0b7 | ||
|
|
29f2b5c345 | ||
|
|
4643c8fa35 | ||
|
|
a8e2c8e5b8 | ||
|
|
3d3e880f52 | ||
|
|
bef8ba5e06 | ||
|
|
523639362c | ||
|
|
4b6d5e5671 | ||
|
|
a695454dae | ||
|
|
f2eaa5dee8 | ||
|
|
b27c830d90 | ||
|
|
92a852f2df | ||
|
|
647e218bed | ||
|
|
5ebe945ffd | ||
|
|
6254fffad0 | ||
|
|
5705b2f6c7 | ||
|
|
90a33bc88a | ||
|
|
1ba077c87d | ||
|
|
34629ae314 | ||
|
|
3edc4f35b2 | ||
|
|
06d01c099f | ||
|
|
d493eaf485 | ||
|
|
332f123abe | ||
|
|
38e841fc5c | ||
|
|
e8187fdee5 | ||
|
|
2fedb67cb3 | ||
|
|
bdab93c999 | ||
|
|
a9ff8b388f | ||
|
|
b12dfd784e | ||
|
|
e2cc8f2965 | ||
|
|
17524d2ed3 | ||
|
|
d2ee4aa074 | ||
|
|
363e32d455 | ||
|
|
31920e574d | ||
|
|
cf714ed591 | ||
|
|
b458404b41 | ||
|
|
707463a645 | ||
|
|
eac37ab869 | ||
|
|
a24e5b1eaa | ||
|
|
09ac85b1b9 | ||
|
|
87c1eab7d4 | ||
|
|
5a29a28c11 | ||
|
|
2ed186664f | ||
|
|
73334f3485 | ||
|
|
a5b8da8d67 | ||
|
|
e8cccfced5 | ||
|
|
88984f7ffb | ||
|
|
182170b3be | ||
|
|
f92412841b | ||
|
|
18c00e89da | ||
|
|
7c38a55a9a | ||
|
|
a15916ec9c | ||
|
|
3583d4c92f | ||
|
|
a456c67a7b | ||
|
|
6e226e4073 | ||
|
|
d23e6614b1 | ||
|
|
ab6afa72fd | ||
|
|
9538b8a77c | ||
|
|
36d3804dd2 | ||
|
|
a34b8ea68f | ||
|
|
55c10f98bb | ||
|
|
4b5a2a14c0 | ||
|
|
665705d06d | ||
|
|
cc8cd4bace | ||
|
|
8f8b6ed001 | ||
|
|
aa9efee868 | ||
|
|
e0a0e2ed42 | ||
|
|
39f5c539d7 | ||
|
|
1b6437a4f8 | ||
|
|
c62c1a58f0 | ||
|
|
3441bcbd69 | ||
|
|
2e6001316a | ||
|
|
53bcc15207 | ||
|
|
dd609bb1bb | ||
|
|
5c56c7fa91 | ||
|
|
9a892363a3 | ||
|
|
5f550ea5d4 | ||
|
|
1b6cc023a5 | ||
|
|
410f8d69bc | ||
|
|
6da44bdb6a | ||
|
|
d30fd27575 | ||
|
|
1b278fc657 | ||
|
|
eecffe01a5 | ||
|
|
f63a33884f | ||
|
|
fa75a395cb | ||
|
|
1f34ec9902 | ||
|
|
f75c08a78e | ||
|
|
5e93f0e34b | ||
|
|
49f151e265 | ||
|
|
2b73a15ad8 | ||
|
|
06d581dde3 | ||
|
|
2b49903c82 | ||
|
|
a17ae977a5 | ||
|
|
8a6b44cb4e | ||
|
|
60d9f97750 | ||
|
|
f252933f62 | ||
|
|
6dbd7b476c | ||
|
|
a47eb847fb | ||
|
|
ba5990ef21 | ||
|
|
753911fe2d | ||
|
|
746ced5501 | ||
|
|
1b49934e4f | ||
|
|
682f0f584f | ||
|
|
611b2a6c3a | ||
|
|
8043caf581 | ||
|
|
b2d2690eb9 | ||
|
|
7f745a34c3 | ||
|
|
b16cf17246 | ||
|
|
67e8518ba6 | ||
|
|
e94e8dc484 | ||
|
|
1a24d4fc86 | ||
|
|
6ee05785d1 | ||
|
|
268ff666d2 | ||
|
|
91bb34c3bf | ||
|
|
17d5fb3210 | ||
|
|
687b987f7e | ||
|
|
4daecc9a41 | ||
|
|
a85eacadda | ||
|
|
ed63987fd1 | ||
|
|
ff173047f4 | ||
|
|
83e8aab289 | ||
|
|
85cb35e68f | ||
|
|
952906279c | ||
|
|
5b79b48ae0 | ||
|
|
7c44127bcb | ||
|
|
9338312103 | ||
|
|
a0eeb630e7 | ||
|
|
6535d72bd4 | ||
|
|
a7d424bc81 | ||
|
|
2bceba4a7a | ||
|
|
e3159bb0f5 | ||
|
|
5d1bd8a932 | ||
|
|
bafa6bfff0 | ||
|
|
e2eb7ab4b2 | ||
|
|
9f4497a5ae | ||
|
|
70de8bf092 | ||
|
|
e52575e23a | ||
|
|
10994cbc6a | ||
|
|
abad9d7db9 | ||
|
|
5e443cd29d | ||
|
|
7bf3a9d24c | ||
|
|
d80a7094ae | ||
|
|
ad77bc391c | ||
|
|
2b84fb14b4 | ||
|
|
07155ce657 | ||
|
|
046d28662d | ||
|
|
84dd3db620 | ||
|
|
282f2671ea | ||
|
|
3fc2be3e6e | ||
|
|
d10c1fe759 | ||
|
|
d18472b07d | ||
|
|
43a68dcd2a | ||
|
|
3d93028088 | ||
|
|
99f0af92bd | ||
|
|
71d81b14a2 | ||
|
|
3894f4021a | ||
|
|
72c659d1ee | ||
|
|
8f879b4adc | ||
|
|
18f2847dc1 | ||
|
|
89b7ff9daf | ||
|
|
26c263d6be | ||
|
|
2570e0f7a0 | ||
|
|
8084e4c728 | ||
|
|
ee90f9df62 | ||
|
|
906a982ace | ||
|
|
88e60c309c | ||
|
|
9694aee819 | ||
|
|
2697b0e425 |
@@ -10,3 +10,9 @@ tasks:
|
||||
gmake test
|
||||
sudo gmake install
|
||||
sudo gmake uninstall
|
||||
- build-sanitizers: |
|
||||
cd janet
|
||||
CFLAGS="-g -O2 -fsanitize=address,undefined" gmake
|
||||
gmake test
|
||||
sudo gmake install
|
||||
sudo gmake uninstall
|
||||
|
||||
@@ -19,3 +19,8 @@ tasks:
|
||||
ninja
|
||||
ninja test
|
||||
sudo ninja install
|
||||
- meson_min: |
|
||||
cd janet
|
||||
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
|
||||
cd build_meson_min
|
||||
ninja
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
image: openbsd/7.4
|
||||
image: openbsd/7.7
|
||||
sources:
|
||||
- https://git.sr.ht/~bakpakin/janet
|
||||
packages:
|
||||
@@ -10,13 +10,17 @@ tasks:
|
||||
gmake
|
||||
gmake test
|
||||
doas gmake install
|
||||
gmake test-install
|
||||
doas gmake uninstall
|
||||
- meson_min: |
|
||||
cd janet
|
||||
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
|
||||
cd build_meson_min
|
||||
ninja
|
||||
- meson_reduced: |
|
||||
cd janet
|
||||
meson setup build_meson_reduced --buildtype=release -Dreduced_os=true
|
||||
cd build_meson_reduced
|
||||
ninja
|
||||
- meson_prf: |
|
||||
cd janet
|
||||
meson setup build_meson_prf --buildtype=release -Dprf=true
|
||||
|
||||
8
.github/cosmo/setup
vendored
8
.github/cosmo/setup
vendored
@@ -1,19 +1,19 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
sudo apt update
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates libssl-dev\
|
||||
qemu qemu-utils qemu-user-static\
|
||||
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
|
||||
wget https://github.com/jart/cosmopolitan/releases/download/4.0.2/cosmocc-4.0.2.zip
|
||||
mkdir -p cosmocc
|
||||
cd cosmocc
|
||||
unzip ../cosmocc-3.3.3.zip
|
||||
unzip ../cosmocc-4.0.2.zip
|
||||
|
||||
# register
|
||||
cd /sc/cosmocc
|
||||
|
||||
7
.github/workflows/codeql.yml
vendored
7
.github/workflows/codeql.yml
vendored
@@ -27,15 +27,16 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-and-quality
|
||||
tools: linked
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest ]
|
||||
os: [ ubuntu-latest ]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
@@ -39,6 +39,35 @@ jobs:
|
||||
build/c/janet.c
|
||||
build/c/shell.c
|
||||
|
||||
release-arm:
|
||||
permissions:
|
||||
contents: write # for softprops/action-gh-release to create GitHub release
|
||||
name: Build release binaries
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-latest, macos-15-intel ]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
- name: Set the version
|
||||
run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||
- name: Set the platform
|
||||
run: echo "platform=$(tr '[A-Z]' '[a-z]' <<< $RUNNER_OS)" >> $GITHUB_ENV
|
||||
- name: Compile the project
|
||||
run: make clean && make
|
||||
- name: Build the artifact
|
||||
run: JANET_DIST_DIR=janet-${{ env.version }}-${{ env.platform }} make build/janet-${{ env.version }}-${{ env.platform }}-aarch64.tar.gz
|
||||
- name: Draft the release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
build/*.gz
|
||||
build/janet.h
|
||||
build/c/janet.c
|
||||
build/c/shell.c
|
||||
|
||||
release-windows:
|
||||
permissions:
|
||||
contents: write # for softprops/action-gh-release to create GitHub release
|
||||
|
||||
112
.github/workflows/test.yml
vendored
112
.github/workflows/test.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest ]
|
||||
os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
@@ -21,9 +21,26 @@ jobs:
|
||||
- name: Test the project
|
||||
run: make test
|
||||
|
||||
test-posix-sanitizers:
|
||||
name: Build and test on POSIX systems with sanitizers turned on
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
- name: Compile the project
|
||||
run: make clean && CFLAGS="-g -O2 -fsanitize=address,undefined" make
|
||||
- name: Test the project
|
||||
run: make test
|
||||
|
||||
test-windows:
|
||||
name: Build and test on Windows
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-latest, windows-2022 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
@@ -35,28 +52,82 @@ jobs:
|
||||
- name: Test the project
|
||||
shell: cmd
|
||||
run: build_win test
|
||||
- name: Test installer build
|
||||
shell: cmd
|
||||
run: build_win dist
|
||||
|
||||
test-windows-sanitizers:
|
||||
name: Build and test on Windows with sanitizers
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-latest ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Build the project
|
||||
shell: cmd
|
||||
run: set SANITIZE=1 & build_win
|
||||
- name: Test the project
|
||||
shell: cmd
|
||||
run: set VERBOSE=1 & build_win test
|
||||
- name: Test installer build
|
||||
shell: cmd
|
||||
run: build_win dist
|
||||
|
||||
test-windows-min:
|
||||
name: Build and test on Windows Minimal build
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-2022 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Python Dependencies
|
||||
run: pip install meson ninja
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
|
||||
cd build_meson_min
|
||||
ninja
|
||||
|
||||
test-mingw:
|
||||
name: Build on Windows with Mingw (no test yet)
|
||||
name: Build on Windows with Mingw
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
strategy:
|
||||
matrix:
|
||||
msystem: [ UCRT64, CLANG64 ]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
- name: Setup Mingw
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
gcc
|
||||
- name: Build the project
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: make -j4 CC=gcc JANET_NO_AMALG=1
|
||||
run: make -j4 CC=gcc
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: make -j4 CC=gcc test
|
||||
|
||||
test-mingw-linux:
|
||||
name: Build and test with Mingw on Linux + Wine
|
||||
@@ -86,7 +157,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-linux-gnueabi qemu-user
|
||||
- name: Compile the project
|
||||
run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc
|
||||
run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc
|
||||
- name: Test the project
|
||||
run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" SUBRUN="qemu-arm -L /usr/arm-linux-gnueabi/" test VERBOSE=1
|
||||
|
||||
@@ -96,7 +167,26 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@master
|
||||
- name: Do Qemu build and test
|
||||
run: |
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
docker run --rm -v .:/janet s390x/ubuntu bash -c "apt-get -y update && apt-get -y install git build-essential && cd /janet && make -j3 && make test"
|
||||
- name: Enable qemu
|
||||
run: docker run --privileged --rm tonistiigi/binfmt --install s390x
|
||||
- name: Build and run on emulated architecture
|
||||
run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test"
|
||||
|
||||
test-cosmo:
|
||||
name: Test build 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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@ janet
|
||||
/src/include/generated/*.h
|
||||
janet-*.tar.gz
|
||||
dist
|
||||
/tmp
|
||||
|
||||
# jpm lockfile
|
||||
lockfile.janet
|
||||
|
||||
93
CHANGELOG.md
93
CHANGELOG.md
@@ -1,6 +1,99 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## Unreleased - ???
|
||||
- Add `file/sync` as a wrapper around fsync.
|
||||
- Documentation fixes
|
||||
- ev/thread-chan deadlock bug fixed
|
||||
- Re-add removed support for non-blocking net/connect on windows.
|
||||
|
||||
## 1.41.2 - 2026-02-18
|
||||
- Fix regressions in `put` for arrays and buffers.
|
||||
- Add `module/add-file-extension`
|
||||
- Add `module/add-syspath`
|
||||
- Fix issue with possible stack corrpution with abstract types that modify the current fiber.
|
||||
- Allow use of the interpreter and garbage collection inside module entry for native modules.
|
||||
|
||||
## 1.41.1 - 2026-02-15
|
||||
- Revert to blocking behaior of `net/connect` on windows to fix spurious errors.
|
||||
- Allow overriding the loader when doing imports with the `:loader` argument.
|
||||
- Allow importing modules with a path extension to do what one would expect.
|
||||
- Add `find-all` argument to `module/find`
|
||||
- Add :threads, :unmarshal, :compiler, and :asm sandbox flags.
|
||||
- Add support for persistent REPL history with the environment variable `JANET_HISTFILE`
|
||||
- Fix a number of fuzzer-found compiler bugs
|
||||
- Fix windows processes launching bug with empty environment table that caused process-launch failures.
|
||||
- Add `:I`, `:V`, and `:N` flags to `os/open` for more control when creating streams.
|
||||
- Add `ev/go-gather` for a dynamic `ev/gather`.
|
||||
- Use color in script output if color is being used in REPL output.
|
||||
- Fix `varfn` macros handling of extra metadata.
|
||||
- Disallow certain degenerate uses of fibers with the ev/ module.
|
||||
- Add linting for unused bindings.
|
||||
- Add linting for extra or wrong parameters to &named functions.
|
||||
- Add `janet_optuinteger` and `janet_optuinteger64` to the C API.
|
||||
- Add `cms` combinator to PEGs.
|
||||
- Add `thaw-keep-keys` as a variant of thaw
|
||||
- The `repl` function now respects the `*repl-prompt*` dynamic binding by default.
|
||||
- Allow matching exact lengths of datastructures with the `match` macro using a dollar suffix.
|
||||
- Add initial support for Plan 9. Some modules (e.g. ev) are not yet enabled.
|
||||
- Allow specifying `:flycheck` for individual defines to annotate that they are safe to evaluate for flychecking.
|
||||
|
||||
## 1.40.1 - 2025-11-16
|
||||
- Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`.
|
||||
- Code formatting
|
||||
|
||||
## 1.40.0 - 2025-11-15
|
||||
- Add `os/posix-chroot`
|
||||
- Fix `ev/deadline` with interrupt race condition bug on Windows.
|
||||
- Improve `flycheck` by allowing functions and macros to define their own flycheck behavior via the metadata `:flycheck`.
|
||||
- Add `*flychecking*` dynamic binding to check if inside flycheck evalutation
|
||||
- Add `gcperthread` callback for abstract types. This lets threaded abstracts have a finalizer that is called per thread, as well as a global finalizer.
|
||||
- Add `JANET_DO_ERROR_*` flags to describe the return value of `janet_dobytes` and `janet_dostring`.
|
||||
|
||||
## 1.39.1 - 2025-08-30
|
||||
- Add support for chdir in os/spawn on older macOS versions
|
||||
- Expose channels properly in C API
|
||||
|
||||
## 1.39.0 - 2025-08-24
|
||||
- Various bug fixes
|
||||
- Add `net/socket`
|
||||
- Add support for illumos OS
|
||||
- Raise helpful errors for incorrect arguments to `import`.
|
||||
- Allow configuring `JANET_THREAD_LOCAL` during builds to allow multi-threading on unknown compilers.
|
||||
- Make `ffi/write` append to a buffer instead of insert at 0 by default.
|
||||
- Add `os/getpid` to get the current process id.
|
||||
- Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes.
|
||||
Add `interrupt?` argument to `ev/deadline` to use VM interruptions.
|
||||
|
||||
## 1.38.0 - 2025-03-18
|
||||
- Add `bundle/replace`
|
||||
- Add CLI flags for the `bundle/` module to install and manage bundles.
|
||||
- Improve `?` peg special termination behavior
|
||||
- Add IEEE hex floats to grammar.
|
||||
- Add buffer peg literal support
|
||||
- Improve `split` peg special edge case behavior
|
||||
- Add Arm64 .msi support
|
||||
- Add `no-reuse` argument to `net/listen` to disable reusing server sockets
|
||||
- Add `struct/rawget`
|
||||
- Fix `deep=` and `deep-not=` to better handle degenerate cases with mutable table keys
|
||||
- Long strings will now dedent on `\r\n` instead of just `\n`.
|
||||
- Add `ev/to-file` for synchronous resource operations
|
||||
- Improve `file/open` error message by including path
|
||||
|
||||
## 1.37.1 - 2024-12-05
|
||||
- Fix meson cross compilation
|
||||
- Update timeout documentation for networking APIs: timeouts raise errors and do not return nil.
|
||||
- Add `janet_addtimeout_nil(double sec);` to the C API.
|
||||
- Change string hashing.
|
||||
- Fix string equality bug.
|
||||
- Add `assertf`
|
||||
- Change how JANET_PROFILE is loaded to allow more easily customizing the environment.
|
||||
- Add `*repl-prompt*` dynamic binding to allow customizing the built in repl.
|
||||
- Add multiple path support in the `JANET_PATH` environment variables. This lets
|
||||
user more easily import modules from many directories.
|
||||
- Add `nth` and `only-tags` PEG specials to select from sub-captures while
|
||||
dropping the rest.
|
||||
|
||||
## 1.36.0 - 2024-09-07
|
||||
- Improve error messages in `bundle/add*` functions.
|
||||
- Add CI testing and verify tests pass on the s390x architecture.
|
||||
|
||||
@@ -61,7 +61,7 @@ ensure a consistent code style for C.
|
||||
|
||||
## Janet style
|
||||
|
||||
All janet code in the project should be formatted similar to the code in core.janet.
|
||||
All janet code in the project should be formatted similar to the code in src/boot/boot.janet.
|
||||
The auto formatting from janet.vim will work well.
|
||||
|
||||
## Typo Fixing and One-Line changes
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2023 Calvin Rose and contributors
|
||||
Copyright (c) 2026 Calvin Rose and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
75
Makefile
75
Makefile
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 Calvin Rose
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
@@ -43,9 +43,11 @@ JANET_DIST_DIR?=janet-dist
|
||||
JANET_BOOT_FLAGS:=. JANET_PATH '$(JANET_PATH)'
|
||||
JANET_TARGET_OBJECTS=build/janet.o build/shell.o
|
||||
JPM_TAG?=master
|
||||
SPORK_TAG?=master
|
||||
HAS_SHARED?=1
|
||||
DEBUGGER=gdb
|
||||
SONAME_SETTER=-Wl,-soname,
|
||||
STRIPFLAGS=-x -S
|
||||
|
||||
# For cross compilation
|
||||
HOSTCC?=$(CC)
|
||||
@@ -53,7 +55,7 @@ HOSTAR?=$(AR)
|
||||
# Symbols are (optionally) removed later, keep -g as default!
|
||||
CFLAGS?=-O2 -g
|
||||
LDFLAGS?=-rdynamic
|
||||
LIBJANET_LDFLAGS?=$(LD_FLAGS)
|
||||
LIBJANET_LDFLAGS?=$(LDFLAGS)
|
||||
RUN:=$(RUN)
|
||||
|
||||
COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC
|
||||
@@ -78,6 +80,12 @@ ifeq ($(UNAME), Darwin)
|
||||
LDCONFIG:=true
|
||||
else ifeq ($(UNAME), Linux)
|
||||
CLIBS:=$(CLIBS) -lrt -ldl
|
||||
else ifeq ($(UNAME), SunOS)
|
||||
BUILD_CFLAGS+=-D__EXTENSIONS__ -DJANET_NO_NANBOX
|
||||
BOOT_CFLAGS+=-D__EXTENSIONS__ -DJANET_NO_NANBOX
|
||||
CLIBS:=-lsocket -lm
|
||||
STRIPFLAGS=-x
|
||||
LDCONFIG:=false
|
||||
endif
|
||||
|
||||
# For other unix likes, add flags here!
|
||||
@@ -93,12 +101,18 @@ endif
|
||||
endif
|
||||
|
||||
# Mingw
|
||||
MINGW_COMPILER=
|
||||
ifeq ($(findstring MINGW,$(UNAME)), MINGW)
|
||||
MINGW_COMPILER=gcc
|
||||
CLIBS:=-lws2_32 -lpsapi -lwsock32
|
||||
LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB)
|
||||
LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB)
|
||||
JANET_TARGET:=$(JANET_TARGET).exe
|
||||
JANET_BOOT:=$(JANET_BOOT).exe
|
||||
COMPILER_VERSION:=$(shell $(CC) --version)
|
||||
ifeq ($(findstring clang,$(COMPILER_VERSION)), clang)
|
||||
MINGW_COMPILER=clang
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
@@ -205,9 +219,14 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile
|
||||
########################
|
||||
|
||||
ifeq ($(UNAME), Darwin)
|
||||
SONAME=libjanet.1.36.dylib
|
||||
SONAME=libjanet.1.41.dylib
|
||||
else
|
||||
SONAME=libjanet.so.1.36
|
||||
SONAME=libjanet.so.1.41
|
||||
endif
|
||||
|
||||
ifeq ($(MINGW_COMPILER), clang)
|
||||
SONAME=
|
||||
SONAME_SETTER=
|
||||
endif
|
||||
|
||||
build/c/shell.c: src/mainclient/shell.c
|
||||
@@ -241,6 +260,7 @@ $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS)
|
||||
# Testing assumes HOSTCC=CC
|
||||
|
||||
TEST_SCRIPTS=$(wildcard test/suite*.janet)
|
||||
EXAMPLE_SCRIPTS=$(wildcard examples/*.janet)
|
||||
|
||||
repl: $(JANET_TARGET)
|
||||
$(RUN) ./$(JANET_TARGET)
|
||||
@@ -248,21 +268,26 @@ repl: $(JANET_TARGET)
|
||||
debug: $(JANET_TARGET)
|
||||
$(DEBUGGER) ./$(JANET_TARGET)
|
||||
|
||||
VALGRIND_COMMAND=valgrind --leak-check=full --quiet
|
||||
VALGRIND_COMMAND=$(RUN) valgrind --leak-check=full --quiet
|
||||
CALLGRIND_COMMAND=$(RUN) valgrind --tool=callgrind
|
||||
|
||||
valgrind: $(JANET_TARGET)
|
||||
$(VALGRIND_COMMAND) ./$(JANET_TARGET)
|
||||
|
||||
test: $(JANET_TARGET) $(TEST_PROGRAMS)
|
||||
test: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
|
||||
for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done
|
||||
for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done
|
||||
|
||||
valtest: $(JANET_TARGET) $(TEST_PROGRAMS)
|
||||
valtest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
|
||||
for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
|
||||
for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done
|
||||
for f in examples/*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done
|
||||
|
||||
callgrind: $(JANET_TARGET)
|
||||
for f in test/suite*.janet; do valgrind --tool=callgrind ./$(JANET_TARGET) "$$f" || exit; done
|
||||
$(CALLGRIND_COMMAND) ./$(JANET_TARGET)
|
||||
|
||||
calltest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
|
||||
for f in test/suite*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
|
||||
for f in examples/*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done
|
||||
|
||||
########################
|
||||
##### Distribution #####
|
||||
@@ -276,7 +301,7 @@ build/janet-%.tar.gz: $(JANET_TARGET) \
|
||||
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/
|
||||
strip -x -S 'build/$(JANET_DIST_DIR)/bin/janet'
|
||||
strip $(STRIPFLAGS) 'build/$(JANET_DIST_DIR)/bin/janet'
|
||||
mkdir -p build/$(JANET_DIST_DIR)/include
|
||||
cp build/janet.h build/$(JANET_DIST_DIR)/include/
|
||||
mkdir -p build/$(JANET_DIST_DIR)/lib/
|
||||
@@ -321,22 +346,23 @@ build/janet.pc: $(JANET_TARGET)
|
||||
echo 'Libs.private: $(CLIBS)' >> $@
|
||||
|
||||
install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h
|
||||
$(eval JANET_VERSION := $(shell $(JANET_TARGET) -e '(print janet/version)'))
|
||||
mkdir -p '$(DESTDIR)$(BINDIR)'
|
||||
cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet'
|
||||
strip -x -S '$(DESTDIR)$(BINDIR)/janet'
|
||||
strip $(STRIPFLAGS) '$(DESTDIR)$(BINDIR)/janet'
|
||||
mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet'
|
||||
cp -r build/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet'
|
||||
ln -sf ./janet/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet.h'
|
||||
mkdir -p '$(DESTDIR)$(JANET_PATH)'
|
||||
mkdir -p '$(DESTDIR)$(LIBDIR)'
|
||||
if test $(UNAME) = Darwin ; then \
|
||||
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib' ; \
|
||||
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(JANET_VERSION).dylib' ; \
|
||||
ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.dylib' ; \
|
||||
ln -sf libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
|
||||
ln -sf libjanet.$(JANET_VERSION).dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
|
||||
else \
|
||||
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)')' ; \
|
||||
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(JANET_VERSION)' ; \
|
||||
ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.so' ; \
|
||||
ln -sf libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)') $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
|
||||
ln -sf libjanet.so.$(JANET_VERSION) $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
|
||||
fi
|
||||
cp $(JANET_STATIC_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.a'
|
||||
mkdir -p '$(DESTDIR)$(JANET_MANPATH)'
|
||||
@@ -359,6 +385,12 @@ install-jpm-git: $(JANET_TARGET)
|
||||
JANET_LIBPATH='$(LIBDIR)' \
|
||||
$(RUN) ../../$(JANET_TARGET) ./bootstrap.janet
|
||||
|
||||
install-spork-git: $(JANET_TARGET)
|
||||
mkdir -p build
|
||||
rm -rf build/spork
|
||||
git clone --depth=1 --branch='$(SPORK_TAG)' https://github.com/janet-lang/spork.git build/spork
|
||||
$(JANET_TARGET) -e '(bundle/install "build/spork")'
|
||||
|
||||
uninstall:
|
||||
-rm '$(DESTDIR)$(BINDIR)/janet'
|
||||
-rm -rf '$(DESTDIR)$(INCLUDEDIR)/janet'
|
||||
@@ -387,9 +419,6 @@ clean:
|
||||
-rm -rf build vgcore.* callgrind.*
|
||||
-rm -rf test/install/build test/install/modpath
|
||||
|
||||
test-install:
|
||||
echo "JPM has been removed from default install."
|
||||
|
||||
help:
|
||||
@echo
|
||||
@echo 'Janet: A Dynamic Language & Bytecode VM'
|
||||
@@ -401,7 +430,8 @@ help:
|
||||
@echo ' make test Test a built Janet'
|
||||
@echo ' make valgrind Assess Janet with Valgrind'
|
||||
@echo ' make callgrind Assess Janet with Valgrind, using Callgrind'
|
||||
@echo ' make valtest Run the test suite with Valgrind to check for memory leaks'
|
||||
@echo ' make valtest Run the test suite and examples with Valgrind to check for memory leaks'
|
||||
@echo ' make calltest Run the test suite and examples with Callgrind'
|
||||
@echo ' make dist Create a distribution tarball'
|
||||
@echo ' make docs Generate documentation'
|
||||
@echo ' make debug Run janet with GDB or LLDB'
|
||||
@@ -411,6 +441,9 @@ help:
|
||||
@echo " make format Format Janet's own source files"
|
||||
@echo ' make grammar Generate a TextMate language grammar'
|
||||
@echo
|
||||
@echo ' make install-jpm-git Install jpm into the current filesystem'
|
||||
@echo ' make install-spork-git Install spork into the current filesystem'
|
||||
@echo
|
||||
|
||||
.PHONY: clean install repl debug valgrind test \
|
||||
valtest dist uninstall docs grammar format help compile-commands
|
||||
.PHONY: clean install install-jpm-git install-spork-git repl debug valgrind test \
|
||||
valtest callgrind callgrind-test dist uninstall docs grammar format help compile-commands
|
||||
|
||||
84
README.md
84
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://gitter.im/janet-language/community)
|
||||
[](https://janet.zulipchat.com)
|
||||
|
||||
[](https://builds.sr.ht/~bakpakin/janet/commits/master/freebsd.yml?)
|
||||
[](https://builds.sr.ht/~bakpakin/janet/commits/master/openbsd.yml?)
|
||||
@@ -18,9 +18,6 @@ to run script files. This client program is separate from the core runtime, so
|
||||
Janet can be embedded in other programs. Try Janet in your browser at
|
||||
<https://janet-lang.org>.
|
||||
|
||||
If you'd like to financially support the ongoing development of Janet, consider
|
||||
[sponsoring its primary author](https://github.com/sponsors/bakpakin) through GitHub.
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
@@ -151,8 +148,40 @@ You can get the source on [GitHub](https://github.com/janet-lang/janet) or
|
||||
[SourceHut](https://git.sr.ht/~bakpakin/janet). While the GitHub repo is the official repo,
|
||||
the SourceHut mirror is actively maintained.
|
||||
|
||||
## Spork and JPM
|
||||
|
||||
Spork and JPM are two companion projects to Janet. They are optional, especially in an embedding use case.
|
||||
|
||||
Spork is a collection of common utility modules, and several packaged scripts
|
||||
like `janet-format` for code formatting, `janet-netrepl` for a socket-based
|
||||
REPL, and `janet-pm` for a comprehensive Janet project manager tool. The
|
||||
modules in `spork` are less stable than the interfaces in core Janet, although
|
||||
we try to prevent breaking changes to existing modules, with a preference to
|
||||
add new modules and functions. Spork requires a C compiler to build and install
|
||||
various extenstion components such as miniz and JSON utilities. Many spork
|
||||
sub-modules, for example spork/path, are independent and can be manually
|
||||
vendored in programmer projects without fully installing spork.
|
||||
|
||||
When install Spork, scripts will be installed to $JANET_PATH/bin/ on POSIX systems by default.
|
||||
This likely needs to be added to the path to use these scripts.
|
||||
|
||||
JPM is the older, more opinionated, project manager tool, which has it's pros
|
||||
and cons. It does not require a C compiler to build and install, but is less
|
||||
flexible and is not receiving many changes and improvements going forward. It
|
||||
may also be harder to configure correctly on new systems. In that sense, it may
|
||||
be more stable.
|
||||
|
||||
JPM will install to /usr/local/bin/ on posix systems by default, which may or
|
||||
may not be on your PATH.
|
||||
|
||||
## Building
|
||||
|
||||
When building from source, for stability, please use the latest tagged release. For
|
||||
example, run `git checkout $(git describe --tags --abbrev=0)` after cloning but
|
||||
before building. For the latest development, build directly on the master
|
||||
branch. The master branch is not-necessarily stable as most Janet development
|
||||
happens directly on the master branch.
|
||||
|
||||
### macOS and Unix-like
|
||||
|
||||
The Makefile is non-portable and requires GNU-flavored make.
|
||||
@@ -163,11 +192,34 @@ make
|
||||
make test
|
||||
make repl
|
||||
make install
|
||||
make install-jpm-git
|
||||
make install-spork-git # optional
|
||||
make install-jpm-git # optional
|
||||
```
|
||||
|
||||
Find out more about the available make targets by running `make help`.
|
||||
|
||||
### Alpine Linux
|
||||
|
||||
To build a statically-linked build of Janet, Alpine Linux + MUSL is a good
|
||||
combination. Janet can also be built inside a docker container or similar in
|
||||
this manner. This is a great way to try Janet without committing to a full
|
||||
install or needing to customize the default install.
|
||||
|
||||
```sh
|
||||
docker run -it --rm alpine /bin/ash
|
||||
$ apk add make gcc musl-dev git
|
||||
$ git clone https://github.com/janet-lang/janet.git
|
||||
$ cd janet
|
||||
$ make -j10
|
||||
$ make test
|
||||
$ make install
|
||||
$ make install-spork-git # optional
|
||||
$ make install-jpm-git # optional
|
||||
```
|
||||
|
||||
Note that for a true statically-linked binary with MUSL, one needs to add `-static` to the Makefile flags. This
|
||||
will also disable runtime loading of native modules (plugins) as well as the FFI.
|
||||
|
||||
### 32-bit Haiku
|
||||
|
||||
32-bit Haiku build instructions are the same as the UNIX-like build instructions,
|
||||
@@ -179,7 +231,8 @@ make CC=gcc-x86
|
||||
make test
|
||||
make repl
|
||||
make install
|
||||
make install-jpm-git
|
||||
make install-spork-git # optional
|
||||
make install-jpm-git # optional
|
||||
```
|
||||
|
||||
### FreeBSD
|
||||
@@ -193,7 +246,8 @@ gmake
|
||||
gmake test
|
||||
gmake repl
|
||||
gmake install
|
||||
gmake install-jpm-git
|
||||
gmake install-spork-git # optional
|
||||
gmake install-jpm-git # optional
|
||||
```
|
||||
|
||||
### NetBSD
|
||||
@@ -201,6 +255,10 @@ gmake install-jpm-git
|
||||
NetBSD build instructions are the same as the FreeBSD build instructions.
|
||||
Alternatively, install the package directly with `pkgin install janet`.
|
||||
|
||||
### illumos
|
||||
|
||||
Building on illumos is exactly the same as building on FreeBSD.
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15#) or [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15#).
|
||||
@@ -210,7 +268,7 @@ Alternatively, install the package directly with `pkgin install janet`.
|
||||
|
||||
To build an `.msi` installer executable, in addition to the above steps, you will have to:
|
||||
|
||||
5. Install, or otherwise add to your PATH the [WiX 3.11 Toolset](https://github.com/wixtoolset/wix3/releases).
|
||||
5. Install, or otherwise add to your PATH the [WiX 3.14 Toolset](https://github.com/wixtoolset/wix3/releases).
|
||||
6. Run `build_win dist`.
|
||||
|
||||
Now you should have an `.msi`. You can run `build_win install` to install the `.msi`, or execute the file itself.
|
||||
@@ -253,8 +311,10 @@ Emacs, and Atom each have syntax packages for the Janet language, though.
|
||||
|
||||
## Installation
|
||||
|
||||
See the [Introduction](https://janet-lang.org/docs/index.html) for more details. If you just want
|
||||
to try out the language, you don't need to install anything. You can also move the `janet` executable wherever you want on your system and run it.
|
||||
If you just want to try out the language, you don't need to install anything.
|
||||
In this case you can also move the `janet` executable wherever you want on
|
||||
your system and run it. However, for a fuller setup, please see the
|
||||
[Introduction](https://janet-lang.org/docs/index.html) for more details.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -302,8 +362,8 @@ If installed, you can also run `man janet` to get usage information.
|
||||
## Embedding
|
||||
|
||||
Janet can be embedded in a host program very easily. The normal build
|
||||
will create a file `build/janet.c`, which is a single C file
|
||||
that contains all the source to Janet. This file, along with
|
||||
will create a file `build/c/janet.c`, a C source code file that
|
||||
that contains the amalgamated source to Janet. This file, along with
|
||||
`src/include/janet.h` and `src/conf/janetconf.h`, can be dragged into any C
|
||||
project and compiled into it. Janet should be compiled with `-std=c99`
|
||||
on most compilers, and will need to be linked to the math library, `-lm`, and
|
||||
|
||||
@@ -23,10 +23,22 @@
|
||||
@rem set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD /fsanitize=address /Zi
|
||||
@rem set JANET_LINK=link /nologo clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
|
||||
|
||||
@set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD
|
||||
@set JANET_LINK=link /nologo
|
||||
if DEFINED CLANG (
|
||||
@set COMPILER=clang-cl.exe
|
||||
) else (
|
||||
@set COMPILER=cl.exe
|
||||
)
|
||||
if DEFINED SANITIZE (
|
||||
@set "SANITIZERS=/fsanitize=address /Zi"
|
||||
@set "LINK_SAN=/DEBUG"
|
||||
) else (
|
||||
@set "SANITIZERS="
|
||||
@set "LINK_SAN=/DEBUG"
|
||||
)
|
||||
@set JANET_COMPILE=%COMPILER% /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD %SANITIZERS%
|
||||
@set JANET_LINK=link /nologo %LINK_SAN%
|
||||
|
||||
@set JANET_LINK_STATIC=lib /nologo
|
||||
@set JANET_LINK_STATIC=lib /nologo %LINK_SAN%
|
||||
|
||||
@rem Add janet build tag
|
||||
if not "%JANET_BUILD%" == "" (
|
||||
@@ -41,34 +53,35 @@ if not exist build\boot mkdir build\boot
|
||||
@rem Build the bootstrap interpreter
|
||||
for %%f in (src\core\*.c) do (
|
||||
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
)
|
||||
for %%f in (src\boot\*.c) do (
|
||||
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
)
|
||||
%JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
@rem note that there is no default syspath being baked in
|
||||
build\janet_boot . > build\c\janet.c
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
|
||||
@rem Build the sources
|
||||
%JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
%JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
|
||||
@rem Build the resources
|
||||
rc /nologo /fobuild\janet_win.res janet_win.rc
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
|
||||
@rem Link everything to main client
|
||||
%JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
|
||||
@rem Build static library (libjanet.lib)
|
||||
%JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj
|
||||
@if not errorlevel 0 goto :BUILDFAIL
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
|
||||
echo === Successfully built janet.exe for Windows ===
|
||||
echo === Run 'build_win test' to run tests. ==
|
||||
@@ -91,7 +104,7 @@ exit /b 0
|
||||
|
||||
@rem Clean build artifacts
|
||||
:CLEAN
|
||||
del *.exe *.lib *.exp
|
||||
del *.exe *.lib *.exp *.msi *.wixpdb
|
||||
rd /s /q build
|
||||
if exist dist (
|
||||
rd /s /q dist
|
||||
@@ -102,7 +115,7 @@ exit /b 0
|
||||
:TEST
|
||||
for %%f in (test/suite*.janet) do (
|
||||
janet.exe test\%%f
|
||||
@if not errorlevel 0 goto TESTFAIL
|
||||
@if errorlevel 1 goto TESTFAIL
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
@@ -138,11 +151,18 @@ if defined APPVEYOR_REPO_TAG_NAME (
|
||||
set RELEASE_VERSION=%JANET_VERSION%
|
||||
)
|
||||
if defined CI (
|
||||
set WIXBIN="c:\Program Files (x86)\WiX Toolset v3.11\bin\"
|
||||
set WIXBIN="%WIX%bin\"
|
||||
echo WIXBIN = %WIXBIN%
|
||||
) else (
|
||||
set WIXBIN=
|
||||
)
|
||||
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %BUILDARCH% -out build\
|
||||
|
||||
set WIXARCH=%BUILDARCH%
|
||||
if "%WIXARCH%"=="aarch64" (
|
||||
set WIXARCH=arm64
|
||||
)
|
||||
|
||||
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %WIXARCH% -out build\
|
||||
%WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi
|
||||
exit /b 0
|
||||
|
||||
|
||||
6
examples/abstract-unix-socket.janet
Normal file
6
examples/abstract-unix-socket.janet
Normal file
@@ -0,0 +1,6 @@
|
||||
# Linux only - uses abstract unix domain sockets
|
||||
(ev/spawn (net/server :unix "@abc123" (fn [conn] (print (:read conn 1024)) (:close conn))))
|
||||
(ev/sleep 1)
|
||||
(def s (net/connect :unix "@abc123" :stream))
|
||||
(:write s "hello")
|
||||
(:close s)
|
||||
@@ -26,7 +26,7 @@
|
||||
(broadcast name (string msg)))
|
||||
(print name " disconnected")))))
|
||||
|
||||
(defn main [& args]
|
||||
(defn main [&]
|
||||
(printf "STARTING SERVER...")
|
||||
(flush)
|
||||
(def my-server (net/listen "127.0.0.1" "8000"))
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
"Go to the next breakpoint."
|
||||
[&opt n]
|
||||
(var res nil)
|
||||
(for i 0 (or n 1)
|
||||
(repeat (or n 1)
|
||||
(set res (resume (.fiber))))
|
||||
res)
|
||||
|
||||
@@ -146,6 +146,6 @@
|
||||
"Execute the next n instructions."
|
||||
[&opt n]
|
||||
(var res nil)
|
||||
(for i 0 (or n 1)
|
||||
(repeat (or n 1)
|
||||
(set res (debug/step (.fiber))))
|
||||
res)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
(def counts (seq [_ :range [0 100]] 0))
|
||||
|
||||
(for i 0 1000000
|
||||
(repeat 1000000
|
||||
(let [x (math/random)
|
||||
intrange (math/floor (* 100 x))
|
||||
oldcount (counts intrange)]
|
||||
|
||||
3
examples/sample-bad-bundle1/info.jdn
Normal file
3
examples/sample-bad-bundle1/info.jdn
Normal file
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
:name "sample-bad-bundle1"
|
||||
}
|
||||
1
examples/sample-bad-bundle2/badmod.janet
Normal file
1
examples/sample-bad-bundle2/badmod.janet
Normal file
@@ -0,0 +1 @@
|
||||
(def abc 123)
|
||||
7
examples/sample-bad-bundle2/bundle.janet
Normal file
7
examples/sample-bad-bundle2/bundle.janet
Normal file
@@ -0,0 +1,7 @@
|
||||
(defn install
|
||||
[manifest &]
|
||||
(bundle/add-file manifest "badmod.janet"))
|
||||
|
||||
(defn check
|
||||
[&]
|
||||
(error "Check failed!"))
|
||||
3
examples/sample-bad-bundle2/info.jdn
Normal file
3
examples/sample-bad-bundle2/info.jdn
Normal file
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
:name "sample-bad-bundle2"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@{
|
||||
:name "sample-dep1"
|
||||
:dependencies ["sample-dep2"]
|
||||
:dependencies [{:name "sample-dep2"}]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(ev/give chan (math/random))
|
||||
(ev/give chan (math/random))
|
||||
(ev/sleep 0.5)
|
||||
(for i 0 10
|
||||
(repeat 10
|
||||
(print "giving to channel...")
|
||||
(ev/give chan (math/random))
|
||||
(ev/sleep 1))
|
||||
|
||||
12
janet.1
12
janet.1
@@ -156,7 +156,7 @@ Shows the version text and exits immediately.
|
||||
|
||||
.TP
|
||||
.BR \-s
|
||||
Read raw input from stdin and forgo prompt history and other readline-like features.
|
||||
Read raw input from stdin and forgo fancy input, which includes prompt history and other readline-like features.
|
||||
|
||||
.TP
|
||||
.BR \-e\ code
|
||||
@@ -214,7 +214,7 @@ Don't execute a script, only compile it to check for errors. Useful for linting
|
||||
.BR \-m\ syspath
|
||||
Set the dynamic binding :syspath to the string syspath so that Janet will load system modules
|
||||
from a directory different than the default. The default is set when Janet is built, and defaults to
|
||||
/usr/local/lib/janet on Linux/Posix, and C:/Janet/Library on Windows. This option supersedes JANET_PATH.
|
||||
/usr/local/lib/janet on Linux/Posix. On Windows, there is no default value. This option supersedes JANET_PATH.
|
||||
|
||||
.TP
|
||||
.BR \-c\ source\ output
|
||||
@@ -255,7 +255,7 @@ and then arguments to the script.
|
||||
.RS
|
||||
The location to look for Janet libraries. This is the only environment variable Janet needs to
|
||||
find native and source code modules. If no JANET_PATH is set, Janet will look in
|
||||
the default location set at compile time.
|
||||
the default location set at compile time. This should be a colon-separated list of directory names on Linux/Posix, and a semicolon-separated list on Windows. Note that a typical setup (i.e. not NixOS / Guix) will only use a single directory.
|
||||
.RE
|
||||
|
||||
.B JANET_PROFILE
|
||||
@@ -272,6 +272,12 @@ This variable does nothing in the default configuration of Janet, as PRF is disa
|
||||
cannot be defined for this variable to have an effect.
|
||||
.RE
|
||||
|
||||
.B JANET_HISTFILE
|
||||
.RS
|
||||
A file location to use for the default shell's REPL history when using fancy input. This relative path is where commands are persisted between sessions.
|
||||
If unset, no repl history well be used. Does not work with the -s flag where fancy input is disabled.
|
||||
.RE
|
||||
|
||||
.B NO_COLOR
|
||||
.RS
|
||||
Turn off color by default in the repl and in the error handler of scripts. This can be changed at runtime
|
||||
|
||||
45
meson.build
45
meson.build
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2024 Calvin Rose and contributors
|
||||
# Copyright (c) 2026 Calvin Rose and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
@@ -20,16 +20,34 @@
|
||||
|
||||
project('janet', 'c',
|
||||
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
|
||||
version : '1.36.0')
|
||||
version : '1.41.3')
|
||||
|
||||
# Global settings
|
||||
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
|
||||
header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet')
|
||||
|
||||
# Link math library on all systems
|
||||
# Compilers
|
||||
cc = meson.get_compiler('c')
|
||||
native_cc = meson.get_compiler('c', native : true)
|
||||
|
||||
# Native deps
|
||||
native_m_dep = native_cc.find_library('m', required : false)
|
||||
native_dl_dep = native_cc.find_library('dl', required : false)
|
||||
native_android_spawn_dep = native_cc.find_library('android-spawn', required : false)
|
||||
native_thread_dep = dependency('threads', native : true)
|
||||
|
||||
# Deps
|
||||
m_dep = cc.find_library('m', required : false)
|
||||
dl_dep = cc.find_library('dl', required : false)
|
||||
|
||||
# for MINGW/MSYS2
|
||||
native_ws2_dep = native_cc.find_library('ws2_32', required: false)
|
||||
native_psapi_dep = native_cc.find_library('psapi', required: false)
|
||||
native_wsock_dep = native_cc.find_library('wsock32', required: false)
|
||||
ws2_dep = cc.find_library('ws2_32', required: false)
|
||||
psapi_dep = cc.find_library('psapi', required: false)
|
||||
wsock_dep = cc.find_library('wsock32', required: false)
|
||||
|
||||
android_spawn_dep = cc.find_library('android-spawn', required : false)
|
||||
thread_dep = dependency('threads')
|
||||
|
||||
@@ -87,6 +105,9 @@ endif
|
||||
if get_option('arch_name') != ''
|
||||
conf.set('JANET_ARCH_NAME', get_option('arch_name'))
|
||||
endif
|
||||
if get_option('thread_local_prefix') != ''
|
||||
conf.set('JANET_THREAD_LOCAL', get_option('thread_local_prefix'))
|
||||
endif
|
||||
jconf = configure_file(output : 'janetconf.h',
|
||||
configuration : conf)
|
||||
|
||||
@@ -164,11 +185,18 @@ mainclient_src = [
|
||||
'src/mainclient/shell.c'
|
||||
]
|
||||
|
||||
janet_dependencies = [m_dep, dl_dep, android_spawn_dep, ws2_dep, psapi_dep, wsock_dep]
|
||||
janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep, native_ws2_dep, native_psapi_dep, native_wsock_dep]
|
||||
if not get_option('single_threaded')
|
||||
janet_dependencies += thread_dep
|
||||
janet_native_dependencies += native_thread_dep
|
||||
endif
|
||||
|
||||
# Build boot binary
|
||||
janet_boot = executable('janet-boot', core_src, boot_src,
|
||||
include_directories : incdir,
|
||||
c_args : '-DJANET_BOOTSTRAP',
|
||||
dependencies : [m_dep, dl_dep, thread_dep, android_spawn_dep],
|
||||
dependencies : janet_native_dependencies,
|
||||
native : true)
|
||||
|
||||
# Build janet.c
|
||||
@@ -181,11 +209,6 @@ janetc = custom_target('janetc',
|
||||
'JANET_PATH', janet_path
|
||||
])
|
||||
|
||||
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
|
||||
if not get_option('single_threaded')
|
||||
janet_dependencies += thread_dep
|
||||
endif
|
||||
|
||||
# Allow building with no shared library
|
||||
if cc.has_argument('-fvisibility=hidden')
|
||||
lib_cflags = ['-fvisibility=hidden']
|
||||
@@ -231,7 +254,7 @@ if meson.is_cross_build()
|
||||
endif
|
||||
janet_nativeclient = executable('janet-native', janetc, mainclient_src,
|
||||
include_directories : incdir,
|
||||
dependencies : janet_dependencies,
|
||||
dependencies : janet_native_dependencies,
|
||||
c_args : extra_native_cflags,
|
||||
native : true)
|
||||
else
|
||||
@@ -258,12 +281,14 @@ test_files = [
|
||||
'test/suite-corelib.janet',
|
||||
'test/suite-debug.janet',
|
||||
'test/suite-ev.janet',
|
||||
'test/suite-ev2.janet',
|
||||
'test/suite-ffi.janet',
|
||||
'test/suite-filewatch.janet',
|
||||
'test/suite-inttypes.janet',
|
||||
'test/suite-io.janet',
|
||||
'test/suite-marsh.janet',
|
||||
'test/suite-math.janet',
|
||||
'test/suite-net.janet',
|
||||
'test/suite-os.janet',
|
||||
'test/suite-parse.janet',
|
||||
'test/suite-peg.janet',
|
||||
|
||||
@@ -30,6 +30,7 @@ option('max_macro_expand', type : 'integer', min : 1, max : 8000, value : 200)
|
||||
option('stack_max', type : 'integer', min : 8096, max : 0x7fffffff, value : 0x7fffffff)
|
||||
|
||||
option('arch_name', type : 'string', value: '')
|
||||
option('thread_local_prefix', type : 'string', value: '')
|
||||
option('os_name', type : 'string', value: '')
|
||||
option('shared', type : 'boolean', value: true)
|
||||
option('cryptorand', type : 'boolean', value: true)
|
||||
|
||||
49
plan9.mk
Executable file
49
plan9.mk
Executable file
@@ -0,0 +1,49 @@
|
||||
</$objtype/mkfile
|
||||
|
||||
TARG=janet
|
||||
HFILES=src/include/janet.h src/conf/janetconf.h
|
||||
JANET_PATH=/sys/lib/janet
|
||||
BIN=/$objtype/bin/
|
||||
DISABLED=EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK SPAWN
|
||||
JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE `{echo JANET_NO_^$DISABLED} PLAN9_`{echo -n $objtype}
|
||||
CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG}
|
||||
BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP
|
||||
|
||||
JANET_CORE_HEADERS=`{ls src/core/*.h}
|
||||
JANET_CORE_SOURCES=`{ls src/core/*.c}
|
||||
|
||||
JANET_BOOT_SOURCES=src/boot/array_test.c \
|
||||
src/boot/boot.c \
|
||||
src/boot/buffer_test.c \
|
||||
src/boot/number_test.c \
|
||||
src/boot/system_test.c \
|
||||
src/boot/table_test.c
|
||||
JANET_BOOT_HEADERS=src/boot/tests.h
|
||||
JANET_BOOT_OBJECTS=`{echo $JANET_CORE_SOURCES $JANET_BOOT_SOURCES | sed -e 's/\.c/.boot.'$O'/g'}
|
||||
|
||||
OFILES=janet.$O src/mainclient/shell.$O
|
||||
|
||||
default:V:all
|
||||
|
||||
src/%.boot.$O: src/%.c $JANET_HEADERS $JANET_CORE_HEADERS $JANET_BOOT_HEADERS
|
||||
$CC $BOOT_CFLAGS -o $target $prereq(1)
|
||||
|
||||
src/mainclient/shell.$O: src/mainclient/shell.c
|
||||
$CC $BOOT_CFLAGS -o $target $prereq(1)
|
||||
|
||||
$O.janetboot: $JANET_BOOT_OBJECTS
|
||||
$LD $LDFLAGS -o $target $prereq
|
||||
|
||||
janet.c: $O.janetboot src/boot/boot.janet
|
||||
$prereq(1) . JANET_PATH $JANET_PATH >$target
|
||||
|
||||
build/janet.$O: build/c/janet.c src/conf/janetconf.h src/include/janet.h
|
||||
$CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1)
|
||||
|
||||
build/shell.$O: src/mainclient/shell.c src/conf/janetconf.h src/include/janet.h
|
||||
$CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1)
|
||||
|
||||
</sys/src/cmd/mkone
|
||||
|
||||
clean:V:
|
||||
rm -f src/core/*.[$OS] src/boot/*.[$OS] *.a[$OS] y.tab.? lex.yy.c y.debug y.output [$OS].??* $TARG janet.[$OS] janet.c src/mainclient/shell.[$OS]
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -44,7 +44,9 @@ static void test_valid_str(const char *str) {
|
||||
}
|
||||
|
||||
int number_test() {
|
||||
|
||||
#ifdef JANET_PLAN9
|
||||
return 0;
|
||||
#endif
|
||||
test_valid_str("1.0");
|
||||
test_valid_str("1");
|
||||
test_valid_str("2.1");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -37,7 +37,7 @@ int system_test() {
|
||||
|
||||
/* 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);
|
||||
snprintf(version_combined, sizeof(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 */
|
||||
@@ -51,6 +51,10 @@ int system_test() {
|
||||
assert(janet_equals(janet_wrap_number(1.4), janet_wrap_number(1.4)));
|
||||
assert(janet_equals(janet_wrap_number(3.14159265), janet_wrap_number(3.14159265)));
|
||||
#ifdef NAN
|
||||
#ifdef JANET_PLAN9
|
||||
// Plan 9 traps NaNs by default; disable that.
|
||||
setfcr(0);
|
||||
#endif
|
||||
assert(janet_checktype(janet_wrap_number(NAN), JANET_NUMBER));
|
||||
#else
|
||||
assert(janet_checktype(janet_wrap_number(0.0 / 0.0), JANET_NUMBER));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
#define JANETCONF_H
|
||||
|
||||
#define JANET_VERSION_MAJOR 1
|
||||
#define JANET_VERSION_MINOR 36
|
||||
#define JANET_VERSION_PATCH 0
|
||||
#define JANET_VERSION_EXTRA ""
|
||||
#define JANET_VERSION "1.36.0"
|
||||
#define JANET_VERSION_MINOR 41
|
||||
#define JANET_VERSION_PATCH 3
|
||||
#define JANET_VERSION_EXTRA "-dev"
|
||||
#define JANET_VERSION "1.41.3-dev"
|
||||
|
||||
/* #define JANET_BUILD "local" */
|
||||
|
||||
/* These settings all affect linking, so use cautiously. */
|
||||
/* #define JANET_SINGLE_THREADED */
|
||||
/* #define JANET_THREAD_LOCAL _Thread_local */
|
||||
/* #define JANET_NO_DYNAMIC_MODULES */
|
||||
/* #define JANET_NO_NANBOX */
|
||||
/* #define JANET_API __attribute__((visibility ("default"))) */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -201,4 +201,17 @@ int32_t janet_abstract_decref(void *abst) {
|
||||
return janet_atomic_dec(&janet_abstract_head(abst)->gc.data.refcount);
|
||||
}
|
||||
|
||||
int32_t janet_abstract_decref_maybe_free(void *abst) {
|
||||
int32_t result = janet_abstract_decref(abst);
|
||||
if (0 == result) {
|
||||
JanetAbstractHead *head = janet_abstract_head(abst);
|
||||
if (head->type->gc) {
|
||||
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
|
||||
}
|
||||
/* Free memory */
|
||||
janet_free(head);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -567,6 +567,13 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
|
||||
x = janet_get1(s, janet_ckeywordv("structarg"));
|
||||
if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG;
|
||||
|
||||
/* Check namedarg */
|
||||
x = janet_get1(s, janet_ckeywordv("namedargs"));
|
||||
if (janet_checkint(x)) {
|
||||
def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
|
||||
def->named_args_count = janet_unwrap_integer(x);
|
||||
}
|
||||
|
||||
/* Check source */
|
||||
x = janet_get1(s, janet_ckeywordv("source"));
|
||||
if (janet_checktype(x, JANET_STRING)) def->source = janet_unwrap_string(x);
|
||||
@@ -982,6 +989,14 @@ static Janet janet_disasm_structarg(JanetFuncDef *def) {
|
||||
return janet_wrap_boolean(def->flags & JANET_FUNCDEF_FLAG_STRUCTARG);
|
||||
}
|
||||
|
||||
static Janet janet_disasm_namedargs(JanetFuncDef *def) {
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS) {
|
||||
return janet_wrap_integer(def->named_args_count);
|
||||
} else {
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
}
|
||||
|
||||
static Janet janet_disasm_constants(JanetFuncDef *def) {
|
||||
JanetArray *constants = janet_array(def->constants_length);
|
||||
for (int32_t i = 0; i < def->constants_length; i++) {
|
||||
@@ -1032,6 +1047,7 @@ Janet janet_disasm(JanetFuncDef *def) {
|
||||
janet_table_put(ret, janet_ckeywordv("source"), janet_disasm_source(def));
|
||||
janet_table_put(ret, janet_ckeywordv("vararg"), janet_disasm_vararg(def));
|
||||
janet_table_put(ret, janet_ckeywordv("structarg"), janet_disasm_structarg(def));
|
||||
janet_table_put(ret, janet_ckeywordv("namedargs"), janet_disasm_namedargs(def));
|
||||
janet_table_put(ret, janet_ckeywordv("name"), janet_disasm_name(def));
|
||||
janet_table_put(ret, janet_ckeywordv("slotcount"), janet_disasm_slotcount(def));
|
||||
janet_table_put(ret, janet_ckeywordv("symbolmap"), janet_disasm_symbolslots(def));
|
||||
@@ -1048,6 +1064,7 @@ JANET_CORE_FN(cfun_asm,
|
||||
"The syntax for the assembly can be found on the Janet website, and should correspond\n"
|
||||
"to the return value of disasm. Will throw an\n"
|
||||
"error on invalid assembly.") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_ASM);
|
||||
janet_fixarity(argc, 1);
|
||||
JanetAssembleResult res;
|
||||
res = janet_asm(argv[0], 0);
|
||||
@@ -1067,6 +1084,8 @@ JANET_CORE_FN(cfun_disasm,
|
||||
"* :min-arity - minimum number of arguments function can be called with.\n"
|
||||
"* :max-arity - maximum number of arguments function can be called with.\n"
|
||||
"* :vararg - true if function can take a variable number of arguments.\n"
|
||||
"* :structarg - true if function can take a variable number of arguments using the &keys option.\n"
|
||||
"* :namedargs - if function can take a variable number of arguments using the &named option, this will be the number of named arguments.\n"
|
||||
"* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n"
|
||||
"* :source - name of source file that this function was compiled from.\n"
|
||||
"* :name - name of function.\n"
|
||||
@@ -1076,6 +1095,7 @@ JANET_CORE_FN(cfun_disasm,
|
||||
"* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n"
|
||||
"* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n"
|
||||
"* :defs - other function definitions that this function may instantiate.\n") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_ASM);
|
||||
janet_arity(argc, 1, 2);
|
||||
JanetFunction *f = janet_getfunction(argv, 0);
|
||||
if (argc == 2) {
|
||||
@@ -1088,6 +1108,7 @@ JANET_CORE_FN(cfun_disasm,
|
||||
if (!janet_cstrcmp(kw, "name")) return janet_disasm_name(f->def);
|
||||
if (!janet_cstrcmp(kw, "vararg")) return janet_disasm_vararg(f->def);
|
||||
if (!janet_cstrcmp(kw, "structarg")) return janet_disasm_structarg(f->def);
|
||||
if (!janet_cstrcmp(kw, "namedargs")) return janet_disasm_namedargs(f->def);
|
||||
if (!janet_cstrcmp(kw, "slotcount")) return janet_disasm_slotcount(f->def);
|
||||
if (!janet_cstrcmp(kw, "constants")) return janet_disasm_constants(f->def);
|
||||
if (!janet_cstrcmp(kw, "sourcemap")) return janet_disasm_sourcemap(f->def);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -74,6 +74,7 @@ JanetBuffer *janet_pointer_buffer_unsafe(void *memory, int32_t capacity, int32_t
|
||||
void janet_buffer_deinit(JanetBuffer *buffer) {
|
||||
if (!(buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) {
|
||||
janet_free(buffer->data);
|
||||
buffer->data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -522,6 +522,7 @@ JanetFuncDef *janet_funcdef_alloc(void) {
|
||||
def->bytecode_length = 0;
|
||||
def->environments_length = 0;
|
||||
def->symbolmap_length = 0;
|
||||
def->named_args_count = 0;
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -31,11 +31,13 @@
|
||||
#ifndef JANET_SINGLE_THREADED
|
||||
#ifndef JANET_WINDOWS
|
||||
#include <pthread.h>
|
||||
#else
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef JANET_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef JANET_USE_STDATOMIC
|
||||
#include <stdatomic.h>
|
||||
/* We don't need stdatomic on most compilers since we use compiler builtins for atomic operations.
|
||||
@@ -60,6 +62,18 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) {
|
||||
|
||||
void janet_signalv(JanetSignal sig, Janet message) {
|
||||
if (janet_vm.return_reg != NULL) {
|
||||
/* Should match logic in janet_call for coercing everything not ok to an error (no awaits, yields, etc.) */
|
||||
if (janet_vm.coerce_error && sig != JANET_SIGNAL_OK) {
|
||||
#ifdef JANET_EV
|
||||
if (NULL != janet_vm.root_fiber && sig == JANET_SIGNAL_EVENT) {
|
||||
janet_vm.root_fiber->sched_id++;
|
||||
}
|
||||
#endif
|
||||
if (sig != JANET_SIGNAL_ERROR) {
|
||||
message = janet_wrap_string(janet_formatc("%v coerced from %s to error", message, janet_signal_names[sig]));
|
||||
}
|
||||
sig = JANET_SIGNAL_ERROR;
|
||||
}
|
||||
*janet_vm.return_reg = message;
|
||||
if (NULL != janet_vm.fiber) {
|
||||
janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP;
|
||||
@@ -446,7 +460,7 @@ Janet janet_dyn(const char *name) {
|
||||
return janet_table_get(janet_vm.top_dyns, janet_ckeywordv(name));
|
||||
}
|
||||
if (janet_vm.fiber->env) {
|
||||
return janet_table_get(janet_vm.fiber->env, janet_ckeywordv(name));
|
||||
return janet_table_get_keyword(janet_vm.fiber->env, name);
|
||||
} else {
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
@@ -543,31 +557,49 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA
|
||||
return janet_getabstract(argv, n, at);
|
||||
}
|
||||
|
||||
uint32_t janet_optuinteger(const Janet *argv, int32_t argc, int32_t n, uint32_t dflt) {
|
||||
if (argc <= n) return dflt;
|
||||
if (janet_checktype(argv[n], JANET_NIL)) return dflt;
|
||||
return janet_getuinteger(argv, n);
|
||||
}
|
||||
|
||||
uint64_t janet_optuinteger64(const Janet *argv, int32_t argc, int32_t n, uint64_t dflt) {
|
||||
if (argc <= n) return dflt;
|
||||
if (janet_checktype(argv[n], JANET_NIL)) return dflt;
|
||||
return janet_getuinteger64(argv, n);
|
||||
}
|
||||
|
||||
/* Atomic refcounts */
|
||||
|
||||
JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
|
||||
#ifdef JANET_WINDOWS
|
||||
return InterlockedIncrement(x);
|
||||
#ifdef _MSC_VER
|
||||
return _InterlockedIncrement(x);
|
||||
#elif defined(JANET_USE_STDATOMIC)
|
||||
return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1;
|
||||
#elif defined(JANET_PLAN9)
|
||||
return aincl((void*)x, 1);
|
||||
#else
|
||||
return __atomic_add_fetch(x, 1, __ATOMIC_RELAXED);
|
||||
#endif
|
||||
}
|
||||
|
||||
JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
|
||||
#ifdef JANET_WINDOWS
|
||||
return InterlockedDecrement(x);
|
||||
#ifdef _MSC_VER
|
||||
return _InterlockedDecrement(x);
|
||||
#elif defined(JANET_USE_STDATOMIC)
|
||||
return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1;
|
||||
#elif defined(JANET_PLAN9)
|
||||
return aincl((void*)x, -1);
|
||||
#else
|
||||
return __atomic_add_fetch(x, -1, __ATOMIC_ACQ_REL);
|
||||
#endif
|
||||
}
|
||||
|
||||
JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
|
||||
#ifdef JANET_WINDOWS
|
||||
return InterlockedOr(x, 0);
|
||||
#ifdef _MSC_VER
|
||||
return _InterlockedOr(x, 0);
|
||||
#elif defined(JANET_PLAN9)
|
||||
return agetl((void*)x);
|
||||
#elif defined(JANET_USE_STDATOMIC)
|
||||
return atomic_load_explicit(x, memory_order_acquire);
|
||||
#else
|
||||
@@ -575,6 +607,18 @@ JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
|
||||
#endif
|
||||
}
|
||||
|
||||
JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x) {
|
||||
#ifdef _MSC_VER
|
||||
return _InterlockedOr(x, 0);
|
||||
#elif defined(JANET_PLAN9)
|
||||
return agetl((void*)x);
|
||||
#elif defined(JANET_USE_STDATOMIC)
|
||||
return atomic_load_explicit(x, memory_order_relaxed);
|
||||
#else
|
||||
return __atomic_load_n(x, __ATOMIC_RELAXED);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Some definitions for function-like macros */
|
||||
|
||||
JANET_API JanetStructHead *(janet_struct_head)(JanetStruct st) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -201,14 +201,29 @@ static JanetSlot do_cmp(JanetFopts opts, JanetSlot *args) {
|
||||
return opreduce(opts, args, JOP_COMPARE, 0, janet_wrap_nil(), janet_wrap_nil());
|
||||
}
|
||||
static JanetSlot do_put(JanetFopts opts, JanetSlot *args) {
|
||||
if (opts.flags & JANET_FOPTS_DROP) {
|
||||
janetc_emit_sss(opts.compiler, JOP_PUT, args[0], args[1], args[2], 0);
|
||||
return janetc_cslot(janet_wrap_nil());
|
||||
int8_t inline_index = 0;
|
||||
if (can_slot_be_imm(args[1], &inline_index)) {
|
||||
/* Use JOP_PUT_INDEX */
|
||||
if (opts.flags & JANET_FOPTS_DROP) {
|
||||
janetc_emit_ssi(opts.compiler, JOP_PUT_INDEX, args[0], args[2], inline_index, 0);
|
||||
return janetc_cslot(janet_wrap_nil());
|
||||
} else {
|
||||
JanetSlot t = janetc_gettarget(opts);
|
||||
janetc_copy(opts.compiler, t, args[0]);
|
||||
janetc_emit_ssi(opts.compiler, JOP_PUT_INDEX, t, args[2], inline_index, 0);
|
||||
return t;
|
||||
}
|
||||
} else {
|
||||
JanetSlot t = janetc_gettarget(opts);
|
||||
janetc_copy(opts.compiler, t, args[0]);
|
||||
janetc_emit_sss(opts.compiler, JOP_PUT, t, args[1], args[2], 0);
|
||||
return t;
|
||||
/* Use JOP_PUT */
|
||||
if (opts.flags & JANET_FOPTS_DROP) {
|
||||
janetc_emit_sss(opts.compiler, JOP_PUT, args[0], args[1], args[2], 0);
|
||||
return janetc_cslot(janet_wrap_nil());
|
||||
} else {
|
||||
JanetSlot t = janetc_gettarget(opts);
|
||||
janetc_copy(opts.compiler, t, args[0]);
|
||||
janetc_emit_sss(opts.compiler, JOP_PUT, t, args[1], args[2], 0);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
static JanetSlot do_length(JanetFopts opts, JanetSlot *args) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -98,6 +98,22 @@ void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s) {
|
||||
sp.sym2 = sym;
|
||||
sp.slot = s;
|
||||
sp.keep = 0;
|
||||
sp.referenced = sym[0] == '_'; /* Fake ref if symbol is _ to avoid lints */
|
||||
sp.slot.flags |= JANET_SLOT_NAMED;
|
||||
sp.birth_pc = cnt ? cnt - 1 : 0;
|
||||
sp.death_pc = UINT32_MAX;
|
||||
janet_v_push(c->scope->syms, sp);
|
||||
}
|
||||
|
||||
/* Same as janetc_nameslot, but don't have a lint for unused bindings. */
|
||||
void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s) {
|
||||
SymPair sp;
|
||||
int32_t cnt = janet_v_count(c->buffer);
|
||||
sp.sym = sym;
|
||||
sp.sym2 = sym;
|
||||
sp.slot = s;
|
||||
sp.keep = 0;
|
||||
sp.referenced = 1;
|
||||
sp.slot.flags |= JANET_SLOT_NAMED;
|
||||
sp.birth_pc = cnt ? cnt - 1 : 0;
|
||||
sp.death_pc = UINT32_MAX;
|
||||
@@ -170,6 +186,10 @@ void janetc_popscope(JanetCompiler *c) {
|
||||
/* Keep upvalue slots and symbols for debugging. */
|
||||
for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) {
|
||||
SymPair pair = oldscope->syms[i];
|
||||
/* Check for unused symbols */
|
||||
if (pair.referenced == 0 && pair.sym) {
|
||||
janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is unused", janet_wrap_symbol(pair.sym));
|
||||
}
|
||||
/* The variable should not be lexically accessible */
|
||||
pair.sym = NULL;
|
||||
if (pair.death_pc == UINT32_MAX) {
|
||||
@@ -262,6 +282,7 @@ JanetSlot janetc_resolve(
|
||||
pair = scope->syms + i;
|
||||
if (pair->sym == sym) {
|
||||
ret = pair->slot;
|
||||
pair->referenced = 1;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
@@ -274,7 +295,7 @@ JanetSlot janetc_resolve(
|
||||
{
|
||||
JanetBinding binding = janet_resolve_ext(c->env, sym);
|
||||
if (binding.type == JANET_BINDING_NONE) {
|
||||
Janet handler = janet_table_get(c->env, janet_ckeywordv("missing-symbol"));
|
||||
Janet handler = janet_table_get_keyword(c->env, "missing-symbol");
|
||||
switch (janet_type(handler)) {
|
||||
case JANET_NIL:
|
||||
break;
|
||||
@@ -346,6 +367,7 @@ found:
|
||||
/* non-local scope needs to expose its environment */
|
||||
JanetScope *original_scope = scope;
|
||||
pair->keep = 1;
|
||||
pair->referenced = 1;
|
||||
while (scope && !(scope->flags & JANET_SCOPE_FUNCTION))
|
||||
scope = scope->parent;
|
||||
janet_assert(scope, "invalid scopes");
|
||||
@@ -437,11 +459,22 @@ JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) {
|
||||
const JanetKV *kvs = NULL;
|
||||
int32_t cap = 0, len = 0;
|
||||
janet_dictionary_view(ds, &kvs, &len, &cap);
|
||||
for (int32_t i = 0; i < cap; i++) {
|
||||
if (janet_checktype(kvs[i].key, JANET_NIL)) continue;
|
||||
janet_v_push(ret, janetc_value(subopts, kvs[i].key));
|
||||
janet_v_push(ret, janetc_value(subopts, kvs[i].value));
|
||||
/* Sort keys for stability of order? */
|
||||
int32_t *index_buf;
|
||||
int32_t index_buf_stack[32];
|
||||
int32_t *index_buf_heap = NULL;
|
||||
if (len < 32) {
|
||||
index_buf = index_buf_stack;
|
||||
} else {
|
||||
index_buf_heap = janet_smalloc(sizeof(int32_t) * len);
|
||||
index_buf = index_buf_heap;
|
||||
}
|
||||
if (len) janet_sorted_keys(kvs, cap, index_buf);
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].key));
|
||||
janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].value));
|
||||
}
|
||||
if (index_buf_heap) janet_sfree(index_buf_heap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -514,9 +547,9 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
|
||||
JanetScope unusedScope;
|
||||
int32_t bufstart = janet_v_count(c->buffer);
|
||||
int32_t mapbufstart = janet_v_count(c->mapbuffer);
|
||||
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unusued");
|
||||
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unused");
|
||||
janetc_value(opts, x);
|
||||
janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.2q", x);
|
||||
janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.4q", x);
|
||||
janetc_popscope(c);
|
||||
if (c->buffer) {
|
||||
janet_v__cnt(c->buffer) = bufstart;
|
||||
@@ -526,7 +559,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
|
||||
}
|
||||
|
||||
/* Compile a call or tailcall instruction */
|
||||
static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
|
||||
static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun, const Janet *form) {
|
||||
JanetSlot retslot;
|
||||
JanetCompiler *c = opts.compiler;
|
||||
int specialized = 0;
|
||||
@@ -552,6 +585,8 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
|
||||
JanetFunction *f = janet_unwrap_function(fun.constant);
|
||||
int32_t min = f->def->min_arity;
|
||||
int32_t max = f->def->max_arity;
|
||||
int structarg = f->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG;
|
||||
int namedarg = f->def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS;
|
||||
if (min_arity < 0) {
|
||||
/* Call has splices */
|
||||
min_arity = -1 - min_arity;
|
||||
@@ -575,6 +610,47 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
|
||||
fun.constant, min, min == 1 ? "" : "s", min_arity);
|
||||
janetc_error(c, es);
|
||||
}
|
||||
if (structarg && (min_arity > f->def->arity) && ((min_arity - f->def->arity) & 1)) {
|
||||
/* If we have an odd number of variadic arguments to a `&keys` function, that is almost certainly wrong. */
|
||||
if (namedarg) {
|
||||
janetc_lintf(c, JANET_C_LINT_NORMAL,
|
||||
"odd number of named arguments to `&named` function %v", fun.constant);
|
||||
} else {
|
||||
janetc_lintf(c, JANET_C_LINT_NORMAL,
|
||||
"odd number of named arguments to `&keys` function %v", fun.constant);
|
||||
}
|
||||
}
|
||||
if (namedarg && f->def->named_args_count > 0) {
|
||||
/* For each argument passed in, check if it is one of the used named arguments
|
||||
* by checking the list defined in the function def. If not, raise a normal compiler
|
||||
* lint. We can also do a strict lint for _missing_ named arguments, although in many
|
||||
* cases those are assumed to have some kind of default, or we have dynamic keys. */
|
||||
int32_t first_arg_key_index = f->def->arity + 1;
|
||||
for (int32_t i = first_arg_key_index; i < janet_tuple_length(form); i += 2) {
|
||||
Janet argkey = form[i];
|
||||
/* Assumption: The first N constants of a function are its named argument keys. This
|
||||
* may change if the compiler changes, but is true for all Janet generated functions. */
|
||||
int found = 0;
|
||||
if (janet_checktype(argkey, JANET_KEYWORD)) {
|
||||
for (int32_t j = 0; j < f->def->named_args_count && j < f->def->constants_length; j++) {
|
||||
if (janet_equals(argkey, f->def->constants[j])) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (janet_checktype(argkey, JANET_TUPLE)) {
|
||||
/* Possible lint : too dynamic, be dumber
|
||||
* (defn f [&named x] [x])
|
||||
* (f (if (coin-flip) :x :w) 10)
|
||||
* A tuple could be a function call the evaluates to a valid key */
|
||||
found = 1;
|
||||
}
|
||||
if (!found) {
|
||||
janetc_lintf(c, JANET_C_LINT_NORMAL,
|
||||
"unused named argument %v to function %v", argkey, fun.constant);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -809,14 +885,16 @@ JanetSlot janetc_value(JanetFopts opts, Janet x) {
|
||||
} else if (janet_tuple_flag(tup) & JANET_TUPLE_FLAG_BRACKETCTOR) { /* [] tuples are not function call */
|
||||
ret = janetc_tuple(opts, x);
|
||||
} else {
|
||||
/* Function calls */
|
||||
JanetSlot head = janetc_value(subopts, tup[0]);
|
||||
subopts.flags = JANET_FUNCTION | JANET_CFUNCTION;
|
||||
ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head);
|
||||
ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head, tup);
|
||||
janetc_freeslot(c, head);
|
||||
}
|
||||
ret.flags &= ~JANET_SLOT_SPLICED;
|
||||
}
|
||||
break;
|
||||
/* Data Constructors */
|
||||
case JANET_SYMBOL:
|
||||
ret = janetc_resolve(c, janet_unwrap_symbol(x));
|
||||
break;
|
||||
@@ -856,19 +934,21 @@ void janet_def_addflags(JanetFuncDef *def) {
|
||||
int32_t set_flags = 0;
|
||||
int32_t unset_flags = 0;
|
||||
/* pos checks */
|
||||
if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME;
|
||||
if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
|
||||
if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
|
||||
if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS;
|
||||
if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
|
||||
if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
|
||||
if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME;
|
||||
if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
|
||||
if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
|
||||
if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS;
|
||||
if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
|
||||
if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
|
||||
if (def->named_args_count) set_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
|
||||
/* negative checks */
|
||||
if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME;
|
||||
if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
|
||||
if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
|
||||
if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS;
|
||||
if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
|
||||
if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
|
||||
if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME;
|
||||
if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
|
||||
if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
|
||||
if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS;
|
||||
if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
|
||||
if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
|
||||
if (!def->named_args_count) unset_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
|
||||
/* Update flags */
|
||||
def->flags |= set_flags;
|
||||
def->flags &= ~unset_flags;
|
||||
@@ -939,8 +1019,9 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
memcpy(chunks, scope->ua.chunks, sizeof(uint32_t) * numchunks);
|
||||
/* fprintf(stderr, "slot chunks: %d, scope->ua.count: %d, numchunks: %d\n", slotchunks, scope->ua.count, numchunks); */
|
||||
/* Register allocator preallocates some registers [240-255, high 16 bits of chunk index 7], we can ignore those. */
|
||||
if (scope->ua.count > 7) chunks[7] &= 0xFFFFU;
|
||||
if (scope->ua.count > 7 && slotchunks > 7) chunks[7] &= 0xFFFFU;
|
||||
def->closure_bitset = chunks;
|
||||
}
|
||||
|
||||
@@ -974,6 +1055,10 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
|
||||
SymPair pair = scope->syms[i];
|
||||
if (pair.sym2) {
|
||||
JanetSymbolMap jsm;
|
||||
/* Check for unused symbols */
|
||||
if (pair.referenced == 0 && pair.sym) {
|
||||
janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is unused", janet_wrap_symbol(pair.sym));
|
||||
}
|
||||
if (pair.death_pc == UINT32_MAX) {
|
||||
jsm.death_pc = def->bytecode_length;
|
||||
} else {
|
||||
@@ -1082,6 +1167,7 @@ JANET_CORE_FN(cfun_compile,
|
||||
"struct with keys :line, :column, and :error if compilation fails. "
|
||||
"If a `lints` array is given, linting messages will be appended to the array. "
|
||||
"Each message will be a tuple of the form `(level line col message)`.") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_COMPILE);
|
||||
janet_arity(argc, 1, 4);
|
||||
JanetTable *env = (argc > 1 && !janet_checktype(argv[1], JANET_NIL))
|
||||
? janet_gettable(argv, 1) : janet_vm.fiber->env;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -114,6 +114,7 @@ typedef struct SymPair {
|
||||
const uint8_t *sym;
|
||||
const uint8_t *sym2;
|
||||
int keep;
|
||||
int referenced; /* Has this value been used */
|
||||
uint32_t birth_pc;
|
||||
uint32_t death_pc;
|
||||
} SymPair;
|
||||
@@ -222,6 +223,7 @@ const JanetSpecial *janetc_special(const uint8_t *name);
|
||||
|
||||
void janetc_freeslot(JanetCompiler *c, JanetSlot s);
|
||||
void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s);
|
||||
void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s);
|
||||
JanetSlot janetc_farslot(JanetCompiler *c);
|
||||
|
||||
/* Throw away some code after checking that it is well formed. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "compile.h"
|
||||
#include "state.h"
|
||||
#include "util.h"
|
||||
#include "fiber.h"
|
||||
#endif
|
||||
|
||||
/* Generated bytes */
|
||||
@@ -66,7 +67,7 @@ JanetModule janet_native(const char *name, const uint8_t **error) {
|
||||
JanetBuildConfig modconf = getter();
|
||||
JanetBuildConfig host = janet_config_current();
|
||||
if (host.major != modconf.major ||
|
||||
host.minor < modconf.minor ||
|
||||
host.minor != modconf.minor ||
|
||||
host.bits != modconf.bits) {
|
||||
char errbuf[128];
|
||||
snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)",
|
||||
@@ -294,6 +295,7 @@ JANET_CORE_FN(janet_core_native,
|
||||
"from the native module.") {
|
||||
JanetModule init;
|
||||
janet_arity(argc, 1, 2);
|
||||
Janet argv0 = argv[0];
|
||||
const uint8_t *path = janet_getstring(argv, 0);
|
||||
const uint8_t *error = NULL;
|
||||
JanetTable *env;
|
||||
@@ -306,8 +308,10 @@ JANET_CORE_FN(janet_core_native,
|
||||
if (!init) {
|
||||
janet_panicf("could not load native %S: %S", path, error);
|
||||
}
|
||||
/* GC root incase garbage collection called inside module entry */
|
||||
janet_fiber_push(janet_vm.fiber, janet_wrap_table(env));
|
||||
init(env);
|
||||
janet_table_put(env, janet_ckeywordv("native"), argv[0]);
|
||||
janet_table_put(env, janet_ckeywordv("native"), argv0);
|
||||
return janet_wrap_table(env);
|
||||
}
|
||||
|
||||
@@ -449,8 +453,9 @@ JANET_CORE_FN(janet_core_range,
|
||||
}
|
||||
count = (count > 0) ? count : 0;
|
||||
int32_t int_count;
|
||||
janet_assert(count >= 0, "bad range code");
|
||||
if (count > (double) INT32_MAX) {
|
||||
int_count = INT32_MAX;
|
||||
janet_panicf("range is too large, %f elements", count);
|
||||
} else {
|
||||
int_count = (int32_t) ceil(count);
|
||||
}
|
||||
@@ -652,22 +657,15 @@ JANET_CORE_FN(janet_core_check_int,
|
||||
"(int? x)",
|
||||
"Check if x can be exactly represented as a 32 bit signed two's complement integer.") {
|
||||
janet_fixarity(argc, 1);
|
||||
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false;
|
||||
double num = janet_unwrap_number(argv[0]);
|
||||
return janet_wrap_boolean(num == (double)((int32_t)num));
|
||||
ret_false:
|
||||
return janet_wrap_false();
|
||||
return janet_wrap_boolean(janet_checkint(argv[0]));
|
||||
}
|
||||
|
||||
JANET_CORE_FN(janet_core_check_nat,
|
||||
"(nat? x)",
|
||||
"Check if x can be exactly represented as a non-negative 32 bit signed two's complement integer.") {
|
||||
janet_fixarity(argc, 1);
|
||||
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false;
|
||||
double num = janet_unwrap_number(argv[0]);
|
||||
return janet_wrap_boolean(num >= 0 && (num == (double)((int32_t)num)));
|
||||
ret_false:
|
||||
return janet_wrap_false();
|
||||
if (!janet_checkint(argv[0])) return janet_wrap_false();
|
||||
return janet_wrap_boolean(janet_unwrap_integer(argv[0]) >= 0);
|
||||
}
|
||||
|
||||
JANET_CORE_FN(janet_core_is_bytes,
|
||||
@@ -700,7 +698,15 @@ JANET_CORE_FN(janet_core_is_lengthable,
|
||||
|
||||
JANET_CORE_FN(janet_core_signal,
|
||||
"(signal what x)",
|
||||
"Raise a signal with payload x. ") {
|
||||
"Raise a signal with payload x. `what` can be an integer\n"
|
||||
"from 0 through 7 indicating user(0-7), or one of:\n\n"
|
||||
"* :ok\n"
|
||||
"* :error\n"
|
||||
"* :debug\n"
|
||||
"* :yield\n"
|
||||
"* :user(0-7)\n"
|
||||
"* :interrupt\n"
|
||||
"* :await") {
|
||||
janet_arity(argc, 1, 2);
|
||||
Janet payload = argc == 2 ? argv[1] : janet_wrap_nil();
|
||||
if (janet_checkint(argv[0])) {
|
||||
@@ -744,6 +750,9 @@ typedef struct SandboxOption {
|
||||
|
||||
static const SandboxOption sandbox_options[] = {
|
||||
{"all", JANET_SANDBOX_ALL},
|
||||
{"asm", JANET_SANDBOX_ASM},
|
||||
{"chroot", JANET_SANDBOX_CHROOT},
|
||||
{"compile", JANET_SANDBOX_COMPILE},
|
||||
{"env", JANET_SANDBOX_ENV},
|
||||
{"ffi", JANET_SANDBOX_FFI},
|
||||
{"ffi-define", JANET_SANDBOX_FFI_DEFINE},
|
||||
@@ -761,6 +770,8 @@ static const SandboxOption sandbox_options[] = {
|
||||
{"sandbox", JANET_SANDBOX_SANDBOX},
|
||||
{"signal", JANET_SANDBOX_SIGNAL},
|
||||
{"subprocess", JANET_SANDBOX_SUBPROCESS},
|
||||
{"threads", JANET_SANDBOX_THREADS},
|
||||
{"unmarshal", JANET_SANDBOX_UNMARSHAL},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
@@ -769,6 +780,9 @@ JANET_CORE_FN(janet_core_sandbox,
|
||||
"Disable feature sets to prevent the interpreter from using certain system resources. "
|
||||
"Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n"
|
||||
"* :all - disallow all (except IO to stdout, stderr, and stdin)\n"
|
||||
"* :asm - disallow calling `asm` and `disasm` functions.\n"
|
||||
"* :chroot - disallow calling `os/posix-chroot`\n"
|
||||
"* :compile - disallow calling `compile`. This will disable a lot of functionality, such as `eval`.\n"
|
||||
"* :env - disallow reading and write env variables\n"
|
||||
"* :ffi - disallow FFI (recommended if disabling anything else)\n"
|
||||
"* :ffi-define - disallow loading new FFI modules and binding new functions\n"
|
||||
@@ -785,7 +799,9 @@ JANET_CORE_FN(janet_core_sandbox,
|
||||
"* :net-listen - disallow accepting inbound network connections\n"
|
||||
"* :sandbox - disallow calling this function\n"
|
||||
"* :signal - disallow adding or removing signal handlers\n"
|
||||
"* :subprocess - disallow running subprocesses") {
|
||||
"* :subprocess - disallow running subprocesses\n"
|
||||
"* :threads - disallow spawning threads with `ev/thread`. Certain helper threads may still be spawned.\n"
|
||||
"* :unmarshal - disallow calling the unmarshal function.\n") {
|
||||
uint32_t flags = 0;
|
||||
for (int32_t i = 0; i < argc; i++) {
|
||||
JanetKeyword kw = janet_getkeyword(argv, i);
|
||||
@@ -993,12 +1009,11 @@ static void make_apply(JanetTable *env) {
|
||||
janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG,
|
||||
"apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm),
|
||||
JDOC("(apply f & args)\n\n"
|
||||
"Applies a function to a variable number of arguments. Each element in args "
|
||||
"is used as an argument to f, except the last element in args, which is expected to "
|
||||
"be an array-like. Each element in this last argument is then also pushed as an argument to "
|
||||
"f. For example:\n\n"
|
||||
"\t(apply + 1000 (range 10))\n\n"
|
||||
"sums the first 10 integers and 1000."));
|
||||
"Applies a function f to a variable number of arguments. Each "
|
||||
"element in args is used as an argument to f, except the last "
|
||||
"element in args, which is expected to be an array or a tuple. "
|
||||
"Each element in this last argument is then also pushed as an "
|
||||
"argument to f."));
|
||||
}
|
||||
|
||||
static const uint32_t error_asm[] = {
|
||||
@@ -1235,7 +1250,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
|
||||
/* Variadic ops */
|
||||
templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD,
|
||||
JDOC("(+ & xs)\n\n"
|
||||
"Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0."));
|
||||
"Returns the sum of all xs. If xs is empty, return 0."));
|
||||
templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT,
|
||||
JDOC("(- & xs)\n\n"
|
||||
"Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the "
|
||||
@@ -1269,7 +1284,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
|
||||
"Returns the bit-wise or of all values in xs. Each x in xs must be an integer."));
|
||||
templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR,
|
||||
JDOC("(bxor & xs)\n\n"
|
||||
"Returns the bit-wise xor of all values in xs. Each in xs must be an integer."));
|
||||
"Returns the bit-wise xor of all values in xs. Each x in xs must be an integer."));
|
||||
templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT,
|
||||
JDOC("(blshift x & shifts)\n\n"
|
||||
"Returns the value of x bit shifted left by the sum of all values in shifts. x "
|
||||
@@ -1351,12 +1366,16 @@ JanetTable *janet_core_env(JanetTable *replacements) {
|
||||
lidv = midv = janet_wrap_nil();
|
||||
janet_resolve(env, janet_csymbol("load-image-dict"), &lidv);
|
||||
janet_resolve(env, janet_csymbol("make-image-dict"), &midv);
|
||||
JanetTable *lid = janet_unwrap_table(lidv);
|
||||
JanetTable *mid = janet_unwrap_table(midv);
|
||||
for (int32_t i = 0; i < lid->capacity; i++) {
|
||||
const JanetKV *kv = lid->data + i;
|
||||
if (!janet_checktype(kv->key, JANET_NIL)) {
|
||||
janet_table_put(mid, kv->value, kv->key);
|
||||
|
||||
/* Check that we actually got tables - if we are using a smaller corelib, may not exist */
|
||||
if (janet_checktype(lidv, JANET_TABLE) && janet_checktype(midv, JANET_TABLE)) {
|
||||
JanetTable *lid = janet_unwrap_table(lidv);
|
||||
JanetTable *mid = janet_unwrap_table(midv);
|
||||
for (int32_t i = 0; i < lid->capacity; i++) {
|
||||
const JanetKV *kv = lid->data + i;
|
||||
if (!janet_checktype(kv->key, JANET_NIL)) {
|
||||
janet_table_put(mid, kv->value, kv->key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
|
||||
501
src/core/ev.c
501
src/core/ev.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -32,9 +32,11 @@
|
||||
#ifdef JANET_EV
|
||||
|
||||
#include <math.h>
|
||||
#include <fcntl.h>
|
||||
#ifdef JANET_WINDOWS
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <limits.h>
|
||||
@@ -43,7 +45,6 @@
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
@@ -111,6 +112,16 @@ typedef struct {
|
||||
JanetHandle write_pipe;
|
||||
} JanetEVThreadInit;
|
||||
|
||||
/* Structure used to initialize threads that run timeouts */
|
||||
typedef struct {
|
||||
double sec;
|
||||
JanetVM *vm;
|
||||
JanetFiber *fiber;
|
||||
#ifdef JANET_WINDOWS
|
||||
HANDLE cancel_event;
|
||||
#endif
|
||||
} JanetThreadedTimeout;
|
||||
|
||||
#define JANET_MAX_Q_CAPACITY 0x7FFFFFF
|
||||
|
||||
static void janet_q_init(JanetQueue *q) {
|
||||
@@ -345,21 +356,22 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod
|
||||
|
||||
static void janet_stream_close_impl(JanetStream *stream) {
|
||||
stream->flags |= JANET_STREAM_CLOSED;
|
||||
int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE);
|
||||
#ifdef JANET_WINDOWS
|
||||
if (stream->handle != INVALID_HANDLE_VALUE) {
|
||||
#ifdef JANET_NET
|
||||
if (stream->flags & JANET_STREAM_SOCKET) {
|
||||
closesocket((SOCKET) stream->handle);
|
||||
if (canclose) closesocket((SOCKET) stream->handle);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
CloseHandle(stream->handle);
|
||||
if (canclose) CloseHandle(stream->handle);
|
||||
}
|
||||
stream->handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
#else
|
||||
if (stream->handle != -1) {
|
||||
close(stream->handle);
|
||||
if (canclose) close(stream->handle);
|
||||
stream->handle = -1;
|
||||
#ifdef JANET_EV_POLL
|
||||
uint32_t i = stream->index;
|
||||
@@ -512,9 +524,9 @@ static void janet_schedule_general(JanetFiber *fiber, Janet value, JanetSignal s
|
||||
fiber->gc.flags |= JANET_FIBER_FLAG_ROOT;
|
||||
if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_FLAG_CANCELED;
|
||||
if (soon) {
|
||||
janet_q_push_head(&janet_vm.spawn, &t, sizeof(t));
|
||||
janet_assert(!janet_q_push_head(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow");
|
||||
} else {
|
||||
janet_q_push(&janet_vm.spawn, &t, sizeof(t));
|
||||
janet_assert(!janet_q_push(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,6 +539,9 @@ void janet_schedule_soon(JanetFiber *fiber, Janet value, JanetSignal sig) {
|
||||
}
|
||||
|
||||
void janet_cancel(JanetFiber *fiber, Janet value) {
|
||||
if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) {
|
||||
janet_panic("cannot cancel non-task fiber");
|
||||
}
|
||||
janet_schedule_signal(fiber, value, JANET_SIGNAL_ERROR);
|
||||
}
|
||||
|
||||
@@ -595,8 +610,43 @@ void janet_ev_init_common(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JANET_ANDROID
|
||||
static void janet_timeout_stop(int sig_num) {
|
||||
if (sig_num == SIGUSR1) {
|
||||
pthread_exit(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void handle_timeout_worker(JanetTimeout to, int cancel) {
|
||||
if (!to.has_worker) return;
|
||||
#ifdef JANET_WINDOWS
|
||||
if (cancel && to.worker_event) {
|
||||
SetEvent(to.worker_event);
|
||||
}
|
||||
WaitForSingleObject(to.worker, INFINITE);
|
||||
CloseHandle(to.worker);
|
||||
if (to.worker_event) {
|
||||
CloseHandle(to.worker_event);
|
||||
}
|
||||
#else
|
||||
#ifdef JANET_ANDROID
|
||||
if (cancel) janet_assert(!pthread_kill(to.worker, SIGUSR1), "pthread_kill");
|
||||
#else
|
||||
if (cancel) janet_assert(!pthread_cancel(to.worker), "pthread_cancel");
|
||||
#endif
|
||||
void *res = NULL;
|
||||
janet_assert(!pthread_join(to.worker, &res), "pthread_join");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Common deinit code */
|
||||
void janet_ev_deinit_common(void) {
|
||||
JanetTimeout to;
|
||||
while (peek_timeout(&to)) {
|
||||
handle_timeout_worker(to, 1);
|
||||
pop_timeout(0);
|
||||
}
|
||||
janet_q_deinit(&janet_vm.spawn);
|
||||
janet_free(janet_vm.tq);
|
||||
janet_table_deinit(&janet_vm.threaded_abstracts);
|
||||
@@ -622,9 +672,74 @@ void janet_addtimeout(double sec) {
|
||||
to.curr_fiber = NULL;
|
||||
to.sched_id = fiber->sched_id;
|
||||
to.is_error = 1;
|
||||
to.has_worker = 0;
|
||||
add_timeout(to);
|
||||
}
|
||||
|
||||
/* Set timeout for the current root fiber but resume with nil instead of raising an error */
|
||||
void janet_addtimeout_nil(double sec) {
|
||||
JanetFiber *fiber = janet_vm.root_fiber;
|
||||
JanetTimeout to;
|
||||
to.when = ts_delta(ts_now(), sec);
|
||||
to.fiber = fiber;
|
||||
to.curr_fiber = NULL;
|
||||
to.sched_id = fiber->sched_id;
|
||||
to.is_error = 0;
|
||||
to.has_worker = 0;
|
||||
add_timeout(to);
|
||||
}
|
||||
|
||||
static void janet_timeout_cb(JanetEVGenericMessage msg) {
|
||||
(void) msg;
|
||||
janet_interpreter_interrupt_handled(&janet_vm);
|
||||
}
|
||||
|
||||
#ifdef JANET_WINDOWS
|
||||
static DWORD WINAPI janet_timeout_body(LPVOID ptr) {
|
||||
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
|
||||
janet_free(ptr);
|
||||
JanetTimestamp wait_begin = ts_now();
|
||||
DWORD duration = (DWORD)round(tto.sec * 1000);
|
||||
DWORD res = WAIT_TIMEOUT;
|
||||
JanetTimestamp wait_end = ts_now();
|
||||
for (DWORD i = 1; res == WAIT_TIMEOUT && (wait_end - wait_begin) < duration; i++) {
|
||||
res = WaitForSingleObject(tto.cancel_event, (duration + i));
|
||||
wait_end = ts_now();
|
||||
}
|
||||
/* only send interrupt message if result is WAIT_TIMEOUT */
|
||||
if (res == WAIT_TIMEOUT) {
|
||||
janet_interpreter_interrupt(tto.vm);
|
||||
JanetEVGenericMessage msg = {0};
|
||||
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static void *janet_timeout_body(void *ptr) {
|
||||
#ifdef JANET_ANDROID
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
action.sa_handler = &janet_timeout_stop;
|
||||
sigaction(SIGUSR1, &action, NULL);
|
||||
#endif
|
||||
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
|
||||
janet_free(ptr);
|
||||
struct timespec ts;
|
||||
ts.tv_sec = (time_t) tto.sec;
|
||||
ts.tv_nsec = (tto.sec <= UINT32_MAX)
|
||||
? (long)((tto.sec - ((uint32_t)tto.sec)) * 1000000000)
|
||||
: 0;
|
||||
nanosleep(&ts, &ts);
|
||||
janet_interpreter_interrupt(tto.vm);
|
||||
JanetEVGenericMessage msg = {0};
|
||||
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void janet_ev_inc_refcount(void) {
|
||||
janet_atomic_inc(&janet_vm.listener_count);
|
||||
}
|
||||
@@ -739,6 +854,34 @@ static int janet_chanat_gc(void *p, size_t s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void janet_chanat_remove_vmref(JanetQueue *fq) {
|
||||
JanetChannelPending *pending = fq->data;
|
||||
if (fq->head <= fq->tail) {
|
||||
for (int32_t i = fq->head; i < fq->tail; i++) {
|
||||
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
|
||||
}
|
||||
} else {
|
||||
for (int32_t i = fq->head; i < fq->capacity; i++) {
|
||||
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
|
||||
}
|
||||
for (int32_t i = 0; i < fq->tail; i++) {
|
||||
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int janet_chanat_gcperthread(void *p, size_t s) {
|
||||
(void) s;
|
||||
JanetChannel *chan = p;
|
||||
janet_chan_lock(chan);
|
||||
/* Make sure that the internals of the threaded channel no longer reference _this_ thread. Replace
|
||||
* those references with NULL. */
|
||||
janet_chanat_remove_vmref(&chan->read_pending);
|
||||
janet_chanat_remove_vmref(&chan->write_pending);
|
||||
janet_chan_unlock(chan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void janet_chanat_mark_fq(JanetQueue *fq) {
|
||||
JanetChannelPending *pending = fq->data;
|
||||
if (fq->head <= fq->tail) {
|
||||
@@ -816,32 +959,41 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
|
||||
janet_schedule(fiber, janet_wrap_nil());
|
||||
}
|
||||
} else if (mode != JANET_CP_MODE_CLOSE) {
|
||||
/* Fiber has already been cancelled or resumed. */
|
||||
/* Fiber has already been canceled or resumed. */
|
||||
/* Resend event to another waiting thread, depending on mode */
|
||||
int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ);
|
||||
if (is_read) {
|
||||
JanetChannelPending reader;
|
||||
if (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
|
||||
int sent = 0;
|
||||
while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
|
||||
JanetVM *vm = reader.thread;
|
||||
JanetEVGenericMessage msg;
|
||||
if (!vm) continue;
|
||||
JanetEVGenericMessage msg = {0};
|
||||
msg.tag = reader.mode;
|
||||
msg.fiber = reader.fiber;
|
||||
msg.argi = (int32_t) reader.sched_id;
|
||||
msg.argp = channel;
|
||||
msg.argj = x;
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
sent = 1;
|
||||
break;
|
||||
}
|
||||
if (!sent) {
|
||||
janet_chan_unpack(channel, &x, 1);
|
||||
}
|
||||
} else {
|
||||
JanetChannelPending writer;
|
||||
if (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
|
||||
while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
|
||||
JanetVM *vm = writer.thread;
|
||||
JanetEVGenericMessage msg;
|
||||
if (!vm) continue;
|
||||
JanetEVGenericMessage msg = {0};
|
||||
msg.tag = writer.mode;
|
||||
msg.fiber = writer.fiber;
|
||||
msg.argi = (int32_t) writer.sched_id;
|
||||
msg.argp = channel;
|
||||
msg.argj = janet_wrap_nil();
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -854,14 +1006,14 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
|
||||
static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode) {
|
||||
JanetChannelPending reader;
|
||||
int is_empty;
|
||||
if (janet_chan_pack(channel, &x)) {
|
||||
janet_chan_unlock(channel);
|
||||
janet_panicf("failed to pack value for channel: %v", x);
|
||||
}
|
||||
if (channel->closed) {
|
||||
janet_chan_unlock(channel);
|
||||
janet_panic("cannot write to closed channel");
|
||||
}
|
||||
if (janet_chan_pack(channel, &x)) {
|
||||
janet_chan_unlock(channel);
|
||||
janet_panicf("failed to pack value for channel: %v", x);
|
||||
}
|
||||
int is_threaded = janet_chan_is_threaded(channel);
|
||||
if (is_threaded) {
|
||||
/* don't dereference fiber from another thread */
|
||||
@@ -874,6 +1026,7 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
|
||||
if (is_empty) {
|
||||
/* No pending reader */
|
||||
if (janet_q_push(&channel->items, &x, sizeof(Janet))) {
|
||||
janet_chan_unpack(channel, &x, 1);
|
||||
janet_chan_unlock(channel);
|
||||
janet_panicf("channel overflow: %v", x);
|
||||
} else if (janet_q_count(&channel->items) > channel->limit) {
|
||||
@@ -899,13 +1052,18 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
|
||||
/* Pending reader */
|
||||
if (is_threaded) {
|
||||
JanetVM *vm = reader.thread;
|
||||
JanetEVGenericMessage msg;
|
||||
JanetEVGenericMessage msg = {0};
|
||||
msg.tag = reader.mode;
|
||||
msg.fiber = reader.fiber;
|
||||
msg.argi = (int32_t) reader.sched_id;
|
||||
msg.argp = channel;
|
||||
msg.argj = x;
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
if (vm) {
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
} else {
|
||||
/* If no vm to send to, we must clean up (unpack) the packed payload to avoid leak */
|
||||
janet_chan_unpack(channel, &x, 1);
|
||||
}
|
||||
} else {
|
||||
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
|
||||
janet_schedule(reader.fiber, make_read_result(channel, x));
|
||||
@@ -936,7 +1094,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
|
||||
if (is_choice == 2) return 0; /* Skip pending read */
|
||||
JanetChannelPending pending;
|
||||
pending.thread = &janet_vm;
|
||||
pending.fiber = janet_vm.root_fiber,
|
||||
@@ -954,13 +1112,15 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i
|
||||
/* Pending writer */
|
||||
if (is_threaded) {
|
||||
JanetVM *vm = writer.thread;
|
||||
JanetEVGenericMessage msg;
|
||||
JanetEVGenericMessage msg = {0};
|
||||
msg.tag = writer.mode;
|
||||
msg.fiber = writer.fiber;
|
||||
msg.argi = (int32_t) writer.sched_id;
|
||||
msg.argp = channel;
|
||||
msg.argj = janet_wrap_nil();
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
if (vm) {
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
}
|
||||
} else {
|
||||
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
|
||||
janet_schedule(writer.fiber, make_write_result(channel));
|
||||
@@ -1024,6 +1184,9 @@ JANET_CORE_FN(cfun_channel_push,
|
||||
"Returns the channel if the write succeeded, nil otherwise.") {
|
||||
janet_fixarity(argc, 2);
|
||||
JanetChannel *channel = janet_getchannel(argv, 0);
|
||||
if (janet_vm.coerce_error) {
|
||||
janet_panic("cannot give to channel inside janet_call");
|
||||
}
|
||||
if (janet_channel_push(channel, argv[1], 0)) {
|
||||
janet_await();
|
||||
}
|
||||
@@ -1036,26 +1199,15 @@ JANET_CORE_FN(cfun_channel_pop,
|
||||
janet_fixarity(argc, 1);
|
||||
JanetChannel *channel = janet_getchannel(argv, 0);
|
||||
Janet item;
|
||||
if (janet_vm.coerce_error) {
|
||||
janet_panic("cannot take from channel inside janet_call");
|
||||
}
|
||||
if (janet_channel_pop(channel, &item, 0)) {
|
||||
janet_schedule(janet_vm.root_fiber, item);
|
||||
}
|
||||
janet_await();
|
||||
}
|
||||
|
||||
static void chan_unlock_args(const Janet *argv, int32_t n) {
|
||||
for (int32_t i = 0; i < n; i++) {
|
||||
int32_t len;
|
||||
const Janet *data;
|
||||
JanetChannel *chan;
|
||||
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
|
||||
chan = janet_getchannel(data, 0);
|
||||
} else {
|
||||
chan = janet_getchannel(argv, i);
|
||||
}
|
||||
janet_chan_unlock(chan);
|
||||
}
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_channel_choice,
|
||||
"(ev/select & clauses)",
|
||||
"Block until the first of several channel operations occur. Returns a "
|
||||
@@ -1072,6 +1224,10 @@ JANET_CORE_FN(cfun_channel_choice,
|
||||
int32_t len;
|
||||
const Janet *data;
|
||||
|
||||
if (janet_vm.coerce_error) {
|
||||
janet_panic("cannot select from channel inside janet_call");
|
||||
}
|
||||
|
||||
/* Check channels for immediate reads and writes */
|
||||
for (int32_t i = 0; i < argc; i++) {
|
||||
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
|
||||
@@ -1080,29 +1236,27 @@ JANET_CORE_FN(cfun_channel_choice,
|
||||
janet_chan_lock(chan);
|
||||
if (chan->closed) {
|
||||
janet_chan_unlock(chan);
|
||||
chan_unlock_args(argv, i);
|
||||
return make_close_result(chan);
|
||||
}
|
||||
if (janet_q_count(&chan->items) < chan->limit) {
|
||||
janet_channel_push_with_lock(chan, data[1], 1);
|
||||
chan_unlock_args(argv, i);
|
||||
return make_write_result(chan);
|
||||
}
|
||||
janet_chan_unlock(chan);
|
||||
} else {
|
||||
/* Read */
|
||||
JanetChannel *chan = janet_getchannel(argv, i);
|
||||
janet_chan_lock(chan);
|
||||
if (chan->closed) {
|
||||
janet_chan_unlock(chan);
|
||||
chan_unlock_args(argv, i);
|
||||
return make_close_result(chan);
|
||||
}
|
||||
if (chan->items.head != chan->items.tail) {
|
||||
Janet item;
|
||||
janet_channel_pop_with_lock(chan, &item, 1);
|
||||
chan_unlock_args(argv, i);
|
||||
return make_read_result(chan, item);
|
||||
}
|
||||
janet_chan_unlock(chan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,11 +1265,13 @@ JANET_CORE_FN(cfun_channel_choice,
|
||||
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
|
||||
/* Write */
|
||||
JanetChannel *chan = janet_getchannel(data, 0);
|
||||
janet_chan_lock(chan);
|
||||
janet_channel_push_with_lock(chan, data[1], 1);
|
||||
} else {
|
||||
/* Read */
|
||||
Janet item;
|
||||
JanetChannel *chan = janet_getchannel(argv, i);
|
||||
janet_chan_lock(chan);
|
||||
janet_channel_pop_with_lock(chan, &item, 1);
|
||||
}
|
||||
}
|
||||
@@ -1208,15 +1364,17 @@ JANET_CORE_FN(cfun_channel_close,
|
||||
while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
|
||||
if (writer.thread != &janet_vm) {
|
||||
JanetVM *vm = writer.thread;
|
||||
JanetEVGenericMessage msg;
|
||||
JanetEVGenericMessage msg = {0};
|
||||
msg.fiber = writer.fiber;
|
||||
msg.argp = channel;
|
||||
msg.tag = JANET_CP_MODE_CLOSE;
|
||||
msg.argi = (int32_t) writer.sched_id;
|
||||
msg.argj = janet_wrap_nil();
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
if (vm) {
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
}
|
||||
} else {
|
||||
if (janet_fiber_can_resume(writer.fiber)) {
|
||||
if (janet_fiber_can_resume(writer.fiber) && writer.sched_id == writer.fiber->sched_id) {
|
||||
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
|
||||
janet_schedule(writer.fiber, make_close_result(channel));
|
||||
} else {
|
||||
@@ -1229,15 +1387,17 @@ JANET_CORE_FN(cfun_channel_close,
|
||||
while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
|
||||
if (reader.thread != &janet_vm) {
|
||||
JanetVM *vm = reader.thread;
|
||||
JanetEVGenericMessage msg;
|
||||
JanetEVGenericMessage msg = {0};
|
||||
msg.fiber = reader.fiber;
|
||||
msg.argp = channel;
|
||||
msg.tag = JANET_CP_MODE_CLOSE;
|
||||
msg.argi = (int32_t) reader.sched_id;
|
||||
msg.argj = janet_wrap_nil();
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
if (vm) {
|
||||
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
|
||||
}
|
||||
} else {
|
||||
if (janet_fiber_can_resume(reader.fiber)) {
|
||||
if (janet_fiber_can_resume(reader.fiber) && reader.sched_id == reader.fiber->sched_id) {
|
||||
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
|
||||
janet_schedule(reader.fiber, make_close_result(channel));
|
||||
} else {
|
||||
@@ -1307,11 +1467,12 @@ static void *janet_chanat_unmarshal(JanetMarshalContext *ctx) {
|
||||
int32_t limit = janet_unmarshal_int(ctx);
|
||||
int32_t count = janet_unmarshal_int(ctx);
|
||||
if (count < 0) janet_panic("invalid negative channel count");
|
||||
if (count > limit) janet_panic("invalid channel count");
|
||||
janet_chan_init(abst, limit, 0);
|
||||
abst->closed = !!is_closed;
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
Janet item = janet_unmarshal_janet(ctx);
|
||||
janet_q_push(&abst->items, &item, sizeof(item));
|
||||
janet_assert(!janet_q_push(&abst->items, &item, sizeof(item)), "bad unmarshal channel");
|
||||
}
|
||||
return abst;
|
||||
}
|
||||
@@ -1328,7 +1489,10 @@ const JanetAbstractType janet_channel_type = {
|
||||
NULL, /* compare */
|
||||
NULL, /* hash */
|
||||
janet_chanat_next,
|
||||
JANET_ATEND_NEXT
|
||||
NULL, /* call */
|
||||
NULL, /* length */
|
||||
NULL, /* bytes */
|
||||
janet_chanat_gcperthread
|
||||
};
|
||||
|
||||
/* Main event loop */
|
||||
@@ -1361,12 +1525,13 @@ JanetFiber *janet_loop1(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
handle_timeout_worker(to, 0);
|
||||
}
|
||||
|
||||
/* Run scheduled fibers unless interrupts need to be handled. */
|
||||
while (janet_vm.spawn.head != janet_vm.spawn.tail) {
|
||||
/* Don't run until all interrupts have been marked as handled by calling janet_interpreter_interrupt_handled */
|
||||
if (janet_vm.auto_suspend) break;
|
||||
if (janet_atomic_load_relaxed(&janet_vm.auto_suspend)) break;
|
||||
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0};
|
||||
janet_q_pop(&janet_vm.spawn, &task, sizeof(task));
|
||||
if (task.fiber->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) janet_ev_dec_refcount();
|
||||
@@ -1408,12 +1573,14 @@ JanetFiber *janet_loop1(void) {
|
||||
while ((has_timeout = peek_timeout(&to))) {
|
||||
if (to.curr_fiber != NULL) {
|
||||
if (!janet_fiber_can_resume(to.curr_fiber)) {
|
||||
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
|
||||
pop_timeout(0);
|
||||
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
|
||||
handle_timeout_worker(to, 1);
|
||||
continue;
|
||||
}
|
||||
} else if (to.fiber->sched_id != to.sched_id) {
|
||||
pop_timeout(0);
|
||||
handle_timeout_worker(to, 1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
@@ -1545,20 +1712,20 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) {
|
||||
janet_free(response);
|
||||
} else {
|
||||
/* Normal event */
|
||||
JanetOverlapped *jo = (JanetOverlapped *) overlapped;
|
||||
JanetStream *stream = (JanetStream *) completionKey;
|
||||
JanetFiber *fiber = NULL;
|
||||
if (stream->read_fiber && stream->read_fiber->ev_state == overlapped) {
|
||||
if (stream->read_fiber && stream->read_fiber->ev_state == jo) {
|
||||
fiber = stream->read_fiber;
|
||||
} else if (stream->write_fiber && stream->write_fiber->ev_state == overlapped) {
|
||||
} else if (stream->write_fiber && stream->write_fiber->ev_state == jo) {
|
||||
fiber = stream->write_fiber;
|
||||
}
|
||||
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_transferred;
|
||||
jo->bytes_transferred = (ULONG_PTR) num_bytes_transferred;
|
||||
fiber->ev_callback(fiber, result ? JANET_ASYNC_EVENT_COMPLETE : JANET_ASYNC_EVENT_FAILED);
|
||||
} else {
|
||||
janet_free((void *) overlapped);
|
||||
janet_free((void *) jo);
|
||||
janet_ev_dec_refcount();
|
||||
}
|
||||
janet_stream_checktoclose(stream);
|
||||
@@ -1578,7 +1745,7 @@ void janet_stream_level_triggered(JanetStream *stream) {
|
||||
|
||||
static JanetTimestamp ts_now(void) {
|
||||
struct timespec now;
|
||||
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time");
|
||||
janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
|
||||
uint64_t res = 1000 * now.tv_sec;
|
||||
res += now.tv_nsec / 1000000;
|
||||
return res;
|
||||
@@ -1736,7 +1903,7 @@ JanetTimestamp to_interval(const JanetTimestamp ts) {
|
||||
|
||||
static JanetTimestamp ts_now(void) {
|
||||
struct timespec now;
|
||||
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time");
|
||||
janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
|
||||
uint64_t res = 1000 * now.tv_sec;
|
||||
res += now.tv_nsec / 1000000;
|
||||
return res;
|
||||
@@ -1776,6 +1943,22 @@ void janet_stream_edge_triggered(JanetStream *stream) {
|
||||
}
|
||||
|
||||
void janet_stream_level_triggered(JanetStream *stream) {
|
||||
/* On macos, we seem to need to delete any registered events before re-registering without
|
||||
* EV_CLEAR, otherwise the new event will still have EV_CLEAR set erroneously. This could be a
|
||||
* kernel bug, but unfortunately the specification is vague here, esp. in regards to where and when
|
||||
* EV_CLEAR is set automatically. */
|
||||
struct kevent kevs[2];
|
||||
int length = 0;
|
||||
if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) {
|
||||
EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_DELETE, 0, 0, stream);
|
||||
}
|
||||
if (stream->flags & JANET_STREAM_WRITABLE) {
|
||||
EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_DELETE, 0, 0, stream);
|
||||
}
|
||||
int status;
|
||||
do {
|
||||
status = kevent(janet_vm.kq, kevs, length, NULL, 0, NULL);
|
||||
} while (status == -1 && errno == EINTR);
|
||||
janet_register_stream_impl(stream, 0);
|
||||
}
|
||||
|
||||
@@ -1874,7 +2057,7 @@ void janet_ev_deinit(void) {
|
||||
|
||||
static JanetTimestamp ts_now(void) {
|
||||
struct timespec now;
|
||||
janet_assert(-1 != clock_gettime(CLOCK_REALTIME, &now), "failed to get time");
|
||||
janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
|
||||
uint64_t res = 1000 * now.tv_sec;
|
||||
res += now.tv_nsec / 1000000;
|
||||
return res;
|
||||
@@ -2046,7 +2229,7 @@ void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGenericMessage ms
|
||||
event.cb = cb;
|
||||
int fd = vm->selfpipe[1];
|
||||
/* handle a bit of back pressure before giving up. */
|
||||
int tries = 4;
|
||||
int tries = 20;
|
||||
while (tries > 0) {
|
||||
int status;
|
||||
do {
|
||||
@@ -2074,11 +2257,14 @@ static DWORD WINAPI janet_thread_body(LPVOID ptr) {
|
||||
/* Reuse memory from thread init for returning data */
|
||||
init->msg = subr(msg);
|
||||
init->cb = cb;
|
||||
janet_assert(PostQueuedCompletionStatus(iocp,
|
||||
BOOL result = PostQueuedCompletionStatus(iocp,
|
||||
sizeof(JanetSelfPipeEvent),
|
||||
0,
|
||||
(LPOVERLAPPED) init),
|
||||
"failed to post completion event");
|
||||
(LPOVERLAPPED) init);
|
||||
if (!result) {
|
||||
JanetString x = janet_formatc("failed to post completion event: %V", janet_ev_lasterr());
|
||||
janet_assert(0, (const char *)x);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
@@ -2180,8 +2366,7 @@ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) {
|
||||
/* Convenience method for common case */
|
||||
JANET_NO_RETURN
|
||||
void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp) {
|
||||
JanetEVGenericMessage arguments;
|
||||
memset(&arguments, 0, sizeof(arguments));
|
||||
JanetEVGenericMessage arguments = {0};
|
||||
arguments.tag = tag;
|
||||
arguments.argi = argi;
|
||||
arguments.argp = argp;
|
||||
@@ -2227,7 +2412,7 @@ Janet janet_ev_lasterr(void) {
|
||||
msgbuf,
|
||||
sizeof(msgbuf),
|
||||
NULL);
|
||||
if (!*msgbuf) sprintf(msgbuf, "%d", code);
|
||||
if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%d", code);
|
||||
char *c = msgbuf;
|
||||
while (*c) {
|
||||
if (*c == '\n' || *c == '\r') {
|
||||
@@ -2254,7 +2439,7 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
#ifdef JANET_WINDOWS
|
||||
OVERLAPPED overlapped;
|
||||
JanetOverlapped overlapped;
|
||||
DWORD flags;
|
||||
#ifdef JANET_NET
|
||||
WSABUF wbuf;
|
||||
@@ -2289,7 +2474,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
case JANET_ASYNC_EVENT_FAILED:
|
||||
case JANET_ASYNC_EVENT_COMPLETE: {
|
||||
/* Called when read finished */
|
||||
uint32_t ev_bytes = (uint32_t) state->overlapped.InternalHigh;
|
||||
uint32_t ev_bytes = (uint32_t) state->overlapped.bytes_transferred;
|
||||
state->bytes_read += ev_bytes;
|
||||
if (state->bytes_read == 0 && (state->mode != JANET_ASYNC_READMODE_RECVFROM)) {
|
||||
janet_schedule(fiber, janet_wrap_nil());
|
||||
@@ -2321,7 +2506,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
/* fallthrough */
|
||||
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));
|
||||
memset(&(state->overlapped), 0, sizeof(JanetOverlapped));
|
||||
int status;
|
||||
#ifdef JANET_NET
|
||||
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
|
||||
@@ -2329,7 +2514,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
state->wbuf.buf = (char *) state->chunk_buf;
|
||||
state->fromlen = sizeof(state->from);
|
||||
status = WSARecvFrom((SOCKET) stream->handle, &state->wbuf, 1,
|
||||
NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped, NULL);
|
||||
NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped.as.wsaoverlapped, NULL);
|
||||
if (status && (WSA_IO_PENDING != WSAGetLastError())) {
|
||||
janet_cancel(fiber, janet_ev_lasterr());
|
||||
janet_async_end(fiber);
|
||||
@@ -2340,9 +2525,9 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
{
|
||||
/* Some handles (not all) read from the offset in lpOverlapped
|
||||
* if its not set before calling `ReadFile` these streams will always read from offset 0 */
|
||||
state->overlapped.Offset = (DWORD) state->bytes_read;
|
||||
state->overlapped.as.overlapped.Offset = (DWORD) state->bytes_read;
|
||||
|
||||
status = ReadFile(stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped);
|
||||
status = ReadFile(stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped.as.overlapped);
|
||||
if (!status && (ERROR_IO_PENDING != GetLastError())) {
|
||||
if (GetLastError() == ERROR_BROKEN_PIPE) {
|
||||
if (state->bytes_read) {
|
||||
@@ -2498,7 +2683,7 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
#ifdef JANET_WINDOWS
|
||||
OVERLAPPED overlapped;
|
||||
JanetOverlapped overlapped;
|
||||
DWORD flags;
|
||||
#ifdef JANET_NET
|
||||
WSABUF wbuf;
|
||||
@@ -2539,7 +2724,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
case JANET_ASYNC_EVENT_FAILED:
|
||||
case JANET_ASYNC_EVENT_COMPLETE: {
|
||||
/* Called when write finished */
|
||||
uint32_t ev_bytes = (uint32_t) state->overlapped.InternalHigh;
|
||||
uint32_t ev_bytes = (uint32_t) state->overlapped.bytes_transferred;
|
||||
if (ev_bytes == 0 && (state->mode != JANET_ASYNC_WRITEMODE_SENDTO)) {
|
||||
janet_cancel(fiber, janet_cstringv("disconnect"));
|
||||
janet_async_end(fiber);
|
||||
@@ -2568,7 +2753,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
bytes = state->src.str;
|
||||
len = janet_string_length(bytes);
|
||||
}
|
||||
memset(&(state->overlapped), 0, sizeof(WSAOVERLAPPED));
|
||||
memset(&(state->overlapped), 0, sizeof(JanetOverlapped));
|
||||
|
||||
int status;
|
||||
#ifdef JANET_NET
|
||||
@@ -2578,7 +2763,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
state->wbuf.len = len;
|
||||
const struct sockaddr *to = state->dest_abst;
|
||||
int tolen = (int) janet_abstract_size((void *) to);
|
||||
status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped, NULL);
|
||||
status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped.as.wsaoverlapped, NULL);
|
||||
if (status) {
|
||||
if (WSA_IO_PENDING == WSAGetLastError()) {
|
||||
janet_async_in_flight(fiber);
|
||||
@@ -2601,9 +2786,9 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
* for more details see the lpOverlapped parameter in
|
||||
* https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
|
||||
*/
|
||||
state->overlapped.Offset = (DWORD) 0xFFFFFFFF;
|
||||
state->overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF;
|
||||
status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped);
|
||||
state->overlapped.as.overlapped.Offset = (DWORD) 0xFFFFFFFF;
|
||||
state->overlapped.as.overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF;
|
||||
status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped.as.overlapped);
|
||||
if (!status) {
|
||||
if (ERROR_IO_PENDING == GetLastError()) {
|
||||
janet_async_in_flight(fiber);
|
||||
@@ -2759,10 +2944,11 @@ int janet_make_pipe(JanetHandle handles[2], int mode) {
|
||||
if (!CreatePipe(handles, handles + 1, &saAttr, 0)) return -1;
|
||||
return 0;
|
||||
}
|
||||
sprintf(PipeNameBuffer,
|
||||
"\\\\.\\Pipe\\JanetPipeFile.%08x.%08x",
|
||||
(unsigned int) GetCurrentProcessId(),
|
||||
(unsigned int) InterlockedIncrement(&PipeSerialNumber));
|
||||
snprintf(PipeNameBuffer,
|
||||
sizeof(PipeNameBuffer),
|
||||
"\\\\.\\Pipe\\JanetPipeFile.%08x.%08x",
|
||||
(unsigned int) GetCurrentProcessId(),
|
||||
(unsigned int) InterlockedIncrement(&PipeSerialNumber));
|
||||
|
||||
/* server handle goes to subprocess */
|
||||
shandle = CreateNamedPipeA(
|
||||
@@ -2818,12 +3004,14 @@ error:
|
||||
|
||||
JANET_CORE_FN(cfun_ev_go,
|
||||
"(ev/go fiber-or-fun &opt value supervisor)",
|
||||
"Put a fiber on the event loop to be resumed later. If a function is used, it is wrapped "
|
||||
"with `fiber/new` first. "
|
||||
"Optionally pass a value to resume with, otherwise resumes with nil. Returns the fiber. "
|
||||
"An optional `core/channel` can be provided as a supervisor. When various "
|
||||
"events occur in the newly scheduled fiber, an event will be pushed to the supervisor. "
|
||||
"If not provided, the new fiber will inherit the current supervisor.") {
|
||||
"Put a fiber on the event loop to be resumed later. If a "
|
||||
"function is used, it is wrapped with `fiber/new` first. "
|
||||
"Returns a task fiber. Optionally pass a value to resume "
|
||||
"with, otherwise resumes with nil. An optional `core/channel` "
|
||||
"can be provided as a supervisor. When various events occur "
|
||||
"in the newly scheduled fiber, an event will be pushed to the "
|
||||
"supervisor. If not provided, the new fiber will inherit the "
|
||||
"current supervisor.") {
|
||||
janet_arity(argc, 1, 3);
|
||||
Janet value = argc >= 2 ? argv[1] : janet_wrap_nil();
|
||||
void *supervisor = janet_optabstract(argv, argc, 2, &janet_channel_type, janet_vm.root_fiber->supervisor_channel);
|
||||
@@ -2849,6 +3037,9 @@ JANET_CORE_FN(cfun_ev_go,
|
||||
fiber->env->proto = janet_vm.fiber->env;
|
||||
} else {
|
||||
fiber = janet_getfiber(argv, 0);
|
||||
if (janet_fiber_status(fiber) != JANET_STATUS_NEW) {
|
||||
janet_panic("can only schedule new fibers where (= (fiber/status f) :new)");
|
||||
}
|
||||
}
|
||||
fiber->supervisor_channel = supervisor;
|
||||
janet_schedule(fiber, value);
|
||||
@@ -2893,7 +3084,8 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
|
||||
uint32_t count1;
|
||||
memcpy(&count1, nextbytes, sizeof(count1));
|
||||
size_t count = (size_t) count1;
|
||||
if (count > (endbytes - nextbytes) * sizeof(JanetCFunRegistry)) {
|
||||
/* Use division to avoid overflowing size_t */
|
||||
if (count > (endbytes - nextbytes - sizeof(count1)) / sizeof(JanetCFunRegistry)) {
|
||||
janet_panic("thread message invalid");
|
||||
}
|
||||
janet_vm.registry_count = count;
|
||||
@@ -2983,6 +3175,7 @@ JANET_CORE_FN(cfun_ev_thread,
|
||||
"* `:t` - set the task-id of the new thread to value. The task-id is passed in messages to the supervisor channel.\n"
|
||||
"* `:a` - don't copy abstract registry to new thread (performance optimization)\n"
|
||||
"* `:c` - don't copy cfunction registry to new thread (performance optimization)") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_THREADS);
|
||||
janet_arity(argc, 1, 4);
|
||||
Janet value = argc >= 2 ? argv[1] : janet_wrap_nil();
|
||||
if (!janet_checktype(argv[0], JANET_FUNCTION)) janet_getfiber(argv, 0);
|
||||
@@ -3015,8 +3208,7 @@ JANET_CORE_FN(cfun_ev_thread,
|
||||
janet_marshal(buffer, value, NULL, JANET_MARSHAL_UNSAFE);
|
||||
if (flags & 0x1) {
|
||||
/* Return immediately */
|
||||
JanetEVGenericMessage arguments;
|
||||
memset(&arguments, 0, sizeof(arguments));
|
||||
JanetEVGenericMessage arguments = {0};
|
||||
arguments.tag = (uint32_t) flags;
|
||||
arguments.argi = (uint32_t) janet_vm.sandbox_flags;
|
||||
arguments.argp = buffer;
|
||||
@@ -3051,6 +3243,7 @@ JANET_NO_RETURN void janet_sleep_await(double sec) {
|
||||
to.is_error = 0;
|
||||
to.sched_id = to.fiber->sched_id;
|
||||
to.curr_fiber = NULL;
|
||||
to.has_worker = 0;
|
||||
add_timeout(to);
|
||||
janet_await();
|
||||
}
|
||||
@@ -3064,33 +3257,74 @@ JANET_CORE_FN(cfun_ev_sleep,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_ev_deadline,
|
||||
"(ev/deadline sec &opt tocancel tocheck)",
|
||||
"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);
|
||||
"(ev/deadline sec &opt tocancel tocheck intr?)",
|
||||
"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, must be a fiber. "
|
||||
"Returns `tocancel` immediately. If `interrupt?` is set to true, will create a "
|
||||
"background thread to try to interrupt the VM if the timeout expires.") {
|
||||
janet_arity(argc, 1, 4);
|
||||
double sec = janet_getnumber(argv, 0);
|
||||
sec = (sec < 0) ? 0 : sec;
|
||||
JanetFiber *tocancel = janet_optfiber(argv, argc, 1, janet_vm.root_fiber);
|
||||
JanetFiber *tocheck = janet_optfiber(argv, argc, 2, janet_vm.fiber);
|
||||
int use_interrupt = janet_optboolean(argv, argc, 3, 0);
|
||||
JanetTimeout to;
|
||||
to.when = ts_delta(ts_now(), sec);
|
||||
to.fiber = tocancel;
|
||||
to.curr_fiber = tocheck;
|
||||
to.is_error = 0;
|
||||
to.sched_id = to.fiber->sched_id;
|
||||
if (use_interrupt) {
|
||||
#ifdef JANET_ANDROID
|
||||
janet_sandbox_assert(JANET_SANDBOX_SIGNAL);
|
||||
#endif
|
||||
JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout));
|
||||
if (NULL == tto) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
tto->sec = sec;
|
||||
tto->vm = &janet_vm;
|
||||
tto->fiber = tocheck;
|
||||
#ifdef JANET_WINDOWS
|
||||
HANDLE cancel_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (NULL == cancel_event) {
|
||||
janet_free(tto);
|
||||
janet_panic("failed to create cancel event");
|
||||
}
|
||||
tto->cancel_event = cancel_event;
|
||||
HANDLE worker = CreateThread(NULL, 0, janet_timeout_body, tto, CREATE_SUSPENDED, NULL);
|
||||
if (NULL == worker) {
|
||||
janet_free(tto);
|
||||
janet_panic("failed to create thread");
|
||||
}
|
||||
#else
|
||||
pthread_t worker;
|
||||
int err = pthread_create(&worker, NULL, janet_timeout_body, tto);
|
||||
if (err) {
|
||||
janet_free(tto);
|
||||
janet_panicf("%s", janet_strerror(err));
|
||||
}
|
||||
#endif
|
||||
to.has_worker = 1;
|
||||
to.worker = worker;
|
||||
#ifdef JANET_WINDOWS
|
||||
to.worker_event = cancel_event;
|
||||
ResumeThread(worker);
|
||||
#endif
|
||||
} else {
|
||||
to.has_worker = 0;
|
||||
}
|
||||
add_timeout(to);
|
||||
return janet_wrap_fiber(tocancel);
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_ev_cancel,
|
||||
"(ev/cancel fiber err)",
|
||||
"Cancel a suspended fiber in the event loop. Differs from cancel in that it returns the canceled fiber immediately.") {
|
||||
"Cancel a suspended task fiber in the event loop. Differs from "
|
||||
"`cancel` in that it returns the canceled fiber immediately.") {
|
||||
janet_fixarity(argc, 2);
|
||||
JanetFiber *fiber = janet_getfiber(argv, 0);
|
||||
Janet err = argv[1];
|
||||
@@ -3263,9 +3497,67 @@ JANET_CORE_FN(janet_cfun_rwlock_write_release,
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
static JanetFile *get_file_for_stream(JanetStream *stream) {
|
||||
int32_t flags = 0;
|
||||
char fmt[4] = {0};
|
||||
int index = 0;
|
||||
if (stream->flags & JANET_STREAM_READABLE) {
|
||||
flags |= JANET_FILE_READ;
|
||||
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
|
||||
fmt[index++] = 'r';
|
||||
}
|
||||
if (stream->flags & JANET_STREAM_WRITABLE) {
|
||||
flags |= JANET_FILE_WRITE;
|
||||
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
|
||||
int currindex = index;
|
||||
fmt[index++] = (currindex == 0) ? 'w' : '+';
|
||||
}
|
||||
if (index == 0) return NULL;
|
||||
/* duplicate handle when converting stream to file */
|
||||
#ifdef JANET_WINDOWS
|
||||
int htype = 0;
|
||||
if (fmt[0] == 'r' && fmt[1] == '+') {
|
||||
htype = _O_RDWR;
|
||||
} else if (fmt[0] == 'r') {
|
||||
htype = _O_RDONLY;
|
||||
} else if (fmt[0] == 'w') {
|
||||
htype = _O_WRONLY;
|
||||
}
|
||||
int fd = _open_osfhandle((intptr_t) stream->handle, htype);
|
||||
if (fd < 0) return NULL;
|
||||
int fd_dup = _dup(fd);
|
||||
if (fd_dup < 0) return NULL;
|
||||
FILE *f = _fdopen(fd_dup, fmt);
|
||||
if (NULL == f) {
|
||||
_close(fd_dup);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
int fd_dup = dup(stream->handle);
|
||||
if (fd_dup < 0) return NULL;
|
||||
FILE *f = fdopen(fd_dup, fmt);
|
||||
if (NULL == f) {
|
||||
close(fd_dup);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
return janet_makejfile(f, flags);
|
||||
}
|
||||
|
||||
JANET_CORE_FN(janet_cfun_to_file,
|
||||
"(ev/to-file)",
|
||||
"Create core/file copy of the stream. This value can be used "
|
||||
"when blocking IO behavior is needed.") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
JanetFile *iof = get_file_for_stream(stream);
|
||||
if (iof == NULL) janet_panic("cannot make file from stream");
|
||||
return janet_wrap_abstract(iof);
|
||||
}
|
||||
|
||||
JANET_CORE_FN(janet_cfun_ev_all_tasks,
|
||||
"(ev/all-tasks)",
|
||||
"Get an array of all active fibers that are being used by the scheduler.") {
|
||||
"Get an array of all active task fibers that are being used by the scheduler.") {
|
||||
janet_fixarity(argc, 0);
|
||||
(void) argv;
|
||||
JanetArray *array = janet_array(janet_vm.active_tasks.count);
|
||||
@@ -3307,6 +3599,7 @@ void janet_lib_ev(JanetTable *env) {
|
||||
JANET_CORE_REG("ev/acquire-wlock", janet_cfun_rwlock_write_lock),
|
||||
JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release),
|
||||
JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release),
|
||||
JANET_CORE_REG("ev/to-file", janet_cfun_to_file),
|
||||
JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks),
|
||||
JANET_REG_END
|
||||
};
|
||||
@@ -3316,8 +3609,6 @@ 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -400,7 +400,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) {
|
||||
|
||||
JanetFFIStruct *st = janet_abstract(&janet_struct_type,
|
||||
sizeof(JanetFFIStruct) + argc * sizeof(JanetFFIStructMember));
|
||||
st->field_count = member_count;
|
||||
st->field_count = 0;
|
||||
st->size = 0;
|
||||
st->align = 1;
|
||||
if (argc == 0) {
|
||||
@@ -418,6 +418,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) {
|
||||
st->fields[i].type = decode_ffi_type(argv[j]);
|
||||
size_t el_size = type_size(st->fields[i].type);
|
||||
size_t el_align = type_align(st->fields[i].type);
|
||||
if (el_align <= 0) janet_panicf("bad field type %V", argv[j]);
|
||||
if (all_packed || pack_one) {
|
||||
if (st->size % el_align != 0) is_aligned = 0;
|
||||
st->fields[i].offset = st->size;
|
||||
@@ -433,6 +434,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) {
|
||||
st->size += (st->align - 1);
|
||||
st->size /= st->align;
|
||||
st->size *= st->align;
|
||||
st->field_count = member_count;
|
||||
return st;
|
||||
}
|
||||
|
||||
@@ -1342,6 +1344,15 @@ typedef double (win64_variant_f_ffif)(double, double, uint64_t, double);
|
||||
typedef double (win64_variant_f_fffi)(double, double, double, uint64_t);
|
||||
typedef double (win64_variant_f_ffff)(double, double, double, double);
|
||||
|
||||
/* MSVC stack frame runtime error checking (/RTCs) prepends alloca() allocations with an _RTC_ALLOCA_NODE
|
||||
* header; misalligning stack-based FFI arguments and causing the memmove() (by stack_shift) to corrupt
|
||||
* the _RTC_ALLOCA_NODE header.
|
||||
*
|
||||
* We turn off the RTC-instrumented alloca() and adding of _RTC_CheckStackVars to function prologue just
|
||||
* for janet_ffi_win64() */
|
||||
#ifdef __MSVC_RUNTIME_CHECKS
|
||||
#pragma runtime_checks( "s", off )
|
||||
#endif
|
||||
static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) {
|
||||
union {
|
||||
uint64_t integer;
|
||||
@@ -1491,6 +1502,10 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
|
||||
|
||||
return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR);
|
||||
}
|
||||
#ifdef __MSVC_RUNTIME_CHECKS
|
||||
// Restore stack frame runtime error checking (/RTCs) if it was enabled.
|
||||
#pragma runtime_checks ( "s", restore )
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1684,7 +1699,7 @@ JANET_CORE_FN(cfun_ffi_buffer_write,
|
||||
JanetFFIType type = decode_ffi_type(argv[0]);
|
||||
uint32_t el_size = (uint32_t) type_size(type);
|
||||
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, el_size);
|
||||
int32_t index = janet_optnat(argv, argc, 3, 0);
|
||||
int32_t index = janet_optnat(argv, argc, 3, buffer->count);
|
||||
int32_t old_count = buffer->count;
|
||||
if (index > old_count) janet_panic("index out of bounds");
|
||||
buffer->count = index;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -592,8 +592,8 @@ JANET_CORE_FN(cfun_fiber_status,
|
||||
"* :user(0-7) - the fiber is suspended by a user signal\n"
|
||||
"* :interrupted - the fiber was interrupted\n"
|
||||
"* :suspended - the fiber is waiting to be resumed by the scheduler\n"
|
||||
"* :alive - the fiber is currently running and cannot be resumed\n"
|
||||
"* :new - the fiber has just been created and not yet run") {
|
||||
"* :new - the fiber has just been created and not yet run\n"
|
||||
"* :alive - the fiber is currently running and cannot be resumed") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetFiber *fiber = janet_getfiber(argv, 0);
|
||||
uint32_t s = janet_fiber_status(fiber);
|
||||
@@ -610,8 +610,9 @@ JANET_CORE_FN(cfun_fiber_current,
|
||||
|
||||
JANET_CORE_FN(cfun_fiber_root,
|
||||
"(fiber/root)",
|
||||
"Returns the current root fiber. The root fiber is the oldest ancestor "
|
||||
"that does not have a parent.") {
|
||||
"Returns the current root fiber. The root fiber is the oldest "
|
||||
"ancestor that does not have a parent. Note that a root fiber "
|
||||
"is also a task fiber.") {
|
||||
(void) argv;
|
||||
janet_fixarity(argc, 0);
|
||||
return janet_wrap_fiber(janet_vm.root_fiber);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -47,7 +47,7 @@ typedef struct {
|
||||
#ifndef JANET_WINDOWS
|
||||
JanetStream *stream;
|
||||
#endif
|
||||
JanetTable* watch_descriptors;
|
||||
JanetTable *watch_descriptors;
|
||||
JanetChannel *channel;
|
||||
uint32_t default_flags;
|
||||
int is_watching;
|
||||
@@ -85,9 +85,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) {
|
||||
}
|
||||
JanetKeyword keyw = janet_unwrap_keyword(options[i]);
|
||||
const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_linux,
|
||||
sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName),
|
||||
sizeof(JanetWatchFlagName),
|
||||
keyw);
|
||||
sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName),
|
||||
sizeof(JanetWatchFlagName),
|
||||
keyw);
|
||||
if (!result) {
|
||||
janet_panicf("unknown inotify flag %v", options[i]);
|
||||
}
|
||||
@@ -154,99 +154,97 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
janet_schedule(fiber, janet_wrap_nil());
|
||||
janet_async_end(fiber);
|
||||
break;
|
||||
case JANET_ASYNC_EVENT_ERR:
|
||||
{
|
||||
janet_schedule(fiber, janet_wrap_nil());
|
||||
case JANET_ASYNC_EVENT_ERR: {
|
||||
janet_schedule(fiber, janet_wrap_nil());
|
||||
janet_async_end(fiber);
|
||||
break;
|
||||
}
|
||||
read_more:
|
||||
case JANET_ASYNC_EVENT_HUP:
|
||||
case JANET_ASYNC_EVENT_INIT:
|
||||
case JANET_ASYNC_EVENT_READ: {
|
||||
Janet name = janet_wrap_nil();
|
||||
|
||||
/* Assumption - read will never return partial events *
|
||||
* From documentation:
|
||||
*
|
||||
* The behavior when the buffer given to read(2) is too small to
|
||||
* return information about the next event depends on the kernel
|
||||
* version: before Linux 2.6.21, read(2) returns 0; since Linux
|
||||
* 2.6.21, read(2) fails with the error EINVAL. Specifying a buffer
|
||||
* of size
|
||||
*
|
||||
* sizeof(struct inotify_event) + NAME_MAX + 1
|
||||
*
|
||||
* will be sufficient to read at least one event. */
|
||||
ssize_t nread;
|
||||
do {
|
||||
nread = read(stream->handle, buf, sizeof(buf));
|
||||
} while (nread == -1 && errno == EINTR);
|
||||
|
||||
/* Check for errors - special case errors that can just be waited on to fix */
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
janet_cancel(fiber, janet_ev_lasterr());
|
||||
fiber->ev_state = NULL;
|
||||
janet_async_end(fiber);
|
||||
break;
|
||||
}
|
||||
read_more:
|
||||
case JANET_ASYNC_EVENT_HUP:
|
||||
case JANET_ASYNC_EVENT_INIT:
|
||||
case JANET_ASYNC_EVENT_READ:
|
||||
{
|
||||
Janet name = janet_wrap_nil();
|
||||
if (nread < (ssize_t) sizeof(struct inotify_event)) break;
|
||||
|
||||
/* Assumption - read will never return partial events *
|
||||
* From documentation:
|
||||
*
|
||||
* The behavior when the buffer given to read(2) is too small to
|
||||
* return information about the next event depends on the kernel
|
||||
* version: before Linux 2.6.21, read(2) returns 0; since Linux
|
||||
* 2.6.21, read(2) fails with the error EINVAL. Specifying a buffer
|
||||
* of size
|
||||
*
|
||||
* sizeof(struct inotify_event) + NAME_MAX + 1
|
||||
*
|
||||
* will be sufficient to read at least one event. */
|
||||
ssize_t nread;
|
||||
do {
|
||||
nread = read(stream->handle, buf, sizeof(buf));
|
||||
} while (nread == -1 && errno == EINTR);
|
||||
|
||||
/* Check for errors - special case errors that can just be waited on to fix */
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
janet_cancel(fiber, janet_ev_lasterr());
|
||||
fiber->ev_state = NULL;
|
||||
janet_async_end(fiber);
|
||||
break;
|
||||
/* Iterate through all events read from the buffer */
|
||||
char *cursor = buf;
|
||||
while (cursor < buf + nread) {
|
||||
struct inotify_event inevent;
|
||||
memcpy(&inevent, cursor, sizeof(inevent));
|
||||
cursor += sizeof(inevent);
|
||||
/* Read path of inevent */
|
||||
if (inevent.len) {
|
||||
name = janet_cstringv(cursor);
|
||||
cursor += inevent.len;
|
||||
}
|
||||
if (nread < (ssize_t) sizeof(struct inotify_event)) break;
|
||||
|
||||
/* Iterate through all events read from the buffer */
|
||||
char *cursor = buf;
|
||||
while (cursor < buf + nread) {
|
||||
struct inotify_event inevent;
|
||||
memcpy(&inevent, cursor, sizeof(inevent));
|
||||
cursor += sizeof(inevent);
|
||||
/* Read path of inevent */
|
||||
if (inevent.len) {
|
||||
name = janet_cstringv(cursor);
|
||||
cursor += inevent.len;
|
||||
/* Got an event */
|
||||
Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd));
|
||||
JanetKV *event = janet_struct_begin(6);
|
||||
janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd));
|
||||
janet_struct_put(event, janet_ckeywordv("wd-path"), path);
|
||||
if (janet_checktype(name, JANET_NIL)) {
|
||||
/* We were watching a file directly, so path is the full path. Split into dirname / basename */
|
||||
JanetString spath = janet_unwrap_string(path);
|
||||
const uint8_t *cursor = spath + janet_string_length(spath);
|
||||
const uint8_t *cursor_end = cursor;
|
||||
while (cursor > spath && cursor[0] != '/') {
|
||||
cursor--;
|
||||
}
|
||||
|
||||
/* Got an event */
|
||||
Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd));
|
||||
JanetKV *event = janet_struct_begin(6);
|
||||
janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd));
|
||||
janet_struct_put(event, janet_ckeywordv("wd-path"), path);
|
||||
if (janet_checktype(name, JANET_NIL)) {
|
||||
/* We were watching a file directly, so path is the full path. Split into dirname / basename */
|
||||
JanetString spath = janet_unwrap_string(path);
|
||||
const uint8_t *cursor = spath + janet_string_length(spath);
|
||||
const uint8_t *cursor_end = cursor;
|
||||
while (cursor > spath && cursor[0] != '/') {
|
||||
cursor--;
|
||||
}
|
||||
if (cursor == spath) {
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), name);
|
||||
} else {
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath))));
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1))));
|
||||
}
|
||||
} else {
|
||||
if (cursor == spath) {
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), name);
|
||||
} else {
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath))));
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1))));
|
||||
}
|
||||
janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie));
|
||||
Janet etype = janet_ckeywordv("type");
|
||||
const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]);
|
||||
for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) {
|
||||
if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name));
|
||||
}
|
||||
Janet eventv = janet_wrap_struct(janet_struct_end(event));
|
||||
|
||||
janet_channel_give(watcher->channel, eventv);
|
||||
} else {
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), name);
|
||||
}
|
||||
janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie));
|
||||
Janet etype = janet_ckeywordv("type");
|
||||
const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]);
|
||||
for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) {
|
||||
if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name));
|
||||
}
|
||||
Janet eventv = janet_wrap_struct(janet_struct_end(event));
|
||||
|
||||
/* Read some more if possible */
|
||||
goto read_more;
|
||||
janet_channel_give(watcher->channel, eventv);
|
||||
}
|
||||
break;
|
||||
|
||||
/* Read some more if possible */
|
||||
goto read_more;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +271,8 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) {
|
||||
#define WATCHFLAG_RECURSIVE 0x100000u
|
||||
|
||||
static const JanetWatchFlagName watcher_flags_windows[] = {
|
||||
{"all",
|
||||
{
|
||||
"all",
|
||||
FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
||||
FILE_NOTIFY_CHANGE_CREATION |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
@@ -282,7 +281,8 @@ static const JanetWatchFlagName watcher_flags_windows[] = {
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE |
|
||||
FILE_NOTIFY_CHANGE_SECURITY |
|
||||
FILE_NOTIFY_CHANGE_SIZE |
|
||||
WATCHFLAG_RECURSIVE},
|
||||
WATCHFLAG_RECURSIVE
|
||||
},
|
||||
{"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES},
|
||||
{"creation", FILE_NOTIFY_CHANGE_CREATION},
|
||||
{"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME},
|
||||
@@ -302,9 +302,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) {
|
||||
}
|
||||
JanetKeyword keyw = janet_unwrap_keyword(options[i]);
|
||||
const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows,
|
||||
sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName),
|
||||
sizeof(JanetWatchFlagName),
|
||||
keyw);
|
||||
sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName),
|
||||
sizeof(JanetWatchFlagName),
|
||||
keyw);
|
||||
if (!result) {
|
||||
janet_panicf("unknown windows filewatch flag %v", options[i]);
|
||||
}
|
||||
@@ -326,7 +326,7 @@ static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uin
|
||||
#define FILE_INFO_PADDING (4096 * 4)
|
||||
|
||||
typedef struct {
|
||||
OVERLAPPED overlapped;
|
||||
JanetOverlapped overlapped;
|
||||
JanetStream *stream;
|
||||
JanetWatcher *watcher;
|
||||
JanetFiber *fiber;
|
||||
@@ -339,19 +339,19 @@ typedef struct {
|
||||
|
||||
static void read_dir_changes(OverlappedWatch *ow) {
|
||||
BOOL result = ReadDirectoryChangesW(ow->stream->handle,
|
||||
(NotifyChange *) ow->buf,
|
||||
FILE_INFO_PADDING,
|
||||
(ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE,
|
||||
ow->flags & ~WATCHFLAG_RECURSIVE,
|
||||
NULL,
|
||||
(OVERLAPPED *) ow,
|
||||
NULL);
|
||||
(NotifyChange *) ow->buf,
|
||||
FILE_INFO_PADDING,
|
||||
(ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE,
|
||||
ow->flags & ~WATCHFLAG_RECURSIVE,
|
||||
NULL,
|
||||
(OVERLAPPED *) ow,
|
||||
NULL);
|
||||
if (!result) {
|
||||
janet_panicv(janet_ev_lasterr());
|
||||
}
|
||||
}
|
||||
|
||||
static const char* watcher_actions_windows[] = {
|
||||
static const char *watcher_actions_windows[] = {
|
||||
"unknown",
|
||||
"added",
|
||||
"removed",
|
||||
@@ -382,48 +382,47 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
case JANET_ASYNC_EVENT_FAILED:
|
||||
janet_stream_close(ow->stream);
|
||||
break;
|
||||
case JANET_ASYNC_EVENT_COMPLETE:
|
||||
{
|
||||
if (!watcher->is_watching) {
|
||||
janet_stream_close(ow->stream);
|
||||
break;
|
||||
}
|
||||
|
||||
NotifyChange *fni = (NotifyChange *) ow->buf;
|
||||
|
||||
while (1) {
|
||||
/* Got an event */
|
||||
|
||||
/* Extract name */
|
||||
Janet filename;
|
||||
if (fni->FileNameLength) {
|
||||
int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL);
|
||||
janet_assert(nbytes, "bad utf8 path");
|
||||
uint8_t *into = janet_string_begin(nbytes);
|
||||
WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL);
|
||||
filename = janet_wrap_string(janet_string_end(into));
|
||||
} else {
|
||||
filename = janet_cstringv("");
|
||||
}
|
||||
|
||||
JanetKV *event = janet_struct_begin(3);
|
||||
janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action]));
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), filename);
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path));
|
||||
Janet eventv = janet_wrap_struct(janet_struct_end(event));
|
||||
|
||||
janet_channel_give(watcher->channel, eventv);
|
||||
|
||||
/* Next event */
|
||||
if (!fni->NextEntryOffset) break;
|
||||
fni = (NotifyChange *) ((char *)fni + fni->NextEntryOffset);
|
||||
}
|
||||
|
||||
/* Make another call to read directory changes */
|
||||
read_dir_changes(ow);
|
||||
janet_async_in_flight(fiber);
|
||||
case JANET_ASYNC_EVENT_COMPLETE: {
|
||||
if (!watcher->is_watching) {
|
||||
janet_stream_close(ow->stream);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
NotifyChange *fni = (NotifyChange *) ow->buf;
|
||||
|
||||
while (1) {
|
||||
/* Got an event */
|
||||
|
||||
/* Extract name */
|
||||
Janet filename;
|
||||
if (fni->FileNameLength) {
|
||||
int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL);
|
||||
janet_assert(nbytes, "bad utf8 path");
|
||||
uint8_t *into = janet_string_begin(nbytes);
|
||||
WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL);
|
||||
filename = janet_wrap_string(janet_string_end(into));
|
||||
} else {
|
||||
filename = janet_cstringv("");
|
||||
}
|
||||
|
||||
JanetKV *event = janet_struct_begin(3);
|
||||
janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action]));
|
||||
janet_struct_put(event, janet_ckeywordv("file-name"), filename);
|
||||
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path));
|
||||
Janet eventv = janet_wrap_struct(janet_struct_end(event));
|
||||
|
||||
janet_channel_give(watcher->channel, eventv);
|
||||
|
||||
/* Next event */
|
||||
if (!fni->NextEntryOffset) break;
|
||||
fni = (NotifyChange *)((char *)fni + fni->NextEntryOffset);
|
||||
}
|
||||
|
||||
/* Make another call to read directory changes */
|
||||
read_dir_changes(ow);
|
||||
janet_async_in_flight(fiber);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,12 +438,12 @@ static void start_listening_ow(OverlappedWatch *ow) {
|
||||
|
||||
static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) {
|
||||
HANDLE handle = CreateFileA(path,
|
||||
FILE_LIST_DIRECTORY | GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS,
|
||||
NULL);
|
||||
FILE_LIST_DIRECTORY | GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS,
|
||||
NULL);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
janet_panicv(janet_ev_lasterr());
|
||||
}
|
||||
@@ -457,7 +456,7 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t
|
||||
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 */
|
||||
ow->overlapped.as.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) {
|
||||
@@ -522,23 +521,23 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t
|
||||
(void) watcher;
|
||||
(void) flags;
|
||||
(void) path;
|
||||
janet_panic("nyi");
|
||||
janet_panic("filewatch not supported on this platform");
|
||||
}
|
||||
|
||||
static void janet_watcher_remove(JanetWatcher *watcher, const char *path) {
|
||||
(void) watcher;
|
||||
(void) path;
|
||||
janet_panic("nyi");
|
||||
janet_panic("filewatch not supported on this platform");
|
||||
}
|
||||
|
||||
static void janet_watcher_listen(JanetWatcher *watcher) {
|
||||
(void) watcher;
|
||||
janet_panic("nyi");
|
||||
janet_panic("filewatch not supported on this platform");
|
||||
}
|
||||
|
||||
static void janet_watcher_unlisten(JanetWatcher *watcher) {
|
||||
(void) watcher;
|
||||
janet_panic("nyi");
|
||||
janet_panic("filewatch not supported on this platform");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -574,20 +573,21 @@ static const JanetAbstractType janet_filewatch_at = {
|
||||
};
|
||||
|
||||
JANET_CORE_FN(cfun_filewatch_make,
|
||||
"(filewatch/new channel &opt default-flags)",
|
||||
"Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n"
|
||||
"When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. "
|
||||
"The contents of the channel depend on the OS, but will contain some common keys:\n\n"
|
||||
"* `:type` -- the type of the event that was raised.\n\n"
|
||||
"* `:file-name` -- the base file name of the file that triggered the event.\n\n"
|
||||
"* `:dir-name` -- the directory name of the file that triggered the event.\n\n"
|
||||
"Events also will contain keys specific to the host OS.\n\n"
|
||||
"Windows has no extra properties on events.\n\n"
|
||||
"Linux has the following extra properties on events:\n\n"
|
||||
"* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n"
|
||||
"* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n"
|
||||
"* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n"
|
||||
"") {
|
||||
"(filewatch/new channel & default-flags)",
|
||||
"Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n"
|
||||
"When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. "
|
||||
"The contents of the channel depend on the OS, but will contain some common keys:\n\n"
|
||||
"* `:type` -- the type of the event that was raised.\n\n"
|
||||
"* `:file-name` -- the base file name of the file that triggered the event.\n\n"
|
||||
"* `:dir-name` -- the directory name of the file that triggered the event.\n\n"
|
||||
"Events also will contain keys specific to the host OS.\n\n"
|
||||
"Windows has no extra properties on events.\n\n"
|
||||
"Linux has the following extra properties on events:\n\n"
|
||||
"* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n"
|
||||
"* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n"
|
||||
"* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n"
|
||||
"") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
|
||||
janet_arity(argc, 1, -1);
|
||||
JanetChannel *channel = janet_getchannel(argv, 0);
|
||||
JanetWatcher *watcher = janet_abstract(&janet_filewatch_at, sizeof(JanetWatcher));
|
||||
@@ -597,44 +597,44 @@ JANET_CORE_FN(cfun_filewatch_make,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_filewatch_add,
|
||||
"(filewatch/add watcher path &opt flags)",
|
||||
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
|
||||
"Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n"
|
||||
"* `:all` - trigger an event for all of the below triggers.\n\n"
|
||||
"* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n"
|
||||
"* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n"
|
||||
"* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n"
|
||||
"* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n"
|
||||
"* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n"
|
||||
"* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n"
|
||||
"* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n"
|
||||
"* `:recursive` - watch subdirectories recursively\n\n"
|
||||
"Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n"
|
||||
"* `:access` - IN_ACCESS\n\n"
|
||||
"* `:all` - IN_ALL_EVENTS\n\n"
|
||||
"* `:attrib` - IN_ATTRIB\n\n"
|
||||
"* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n"
|
||||
"* `:close-write` - IN_CLOSE_WRITE\n\n"
|
||||
"* `:create` - IN_CREATE\n\n"
|
||||
"* `:delete` - IN_DELETE\n\n"
|
||||
"* `:delete-self` - IN_DELETE_SELF\n\n"
|
||||
"* `:ignored` - IN_IGNORED\n\n"
|
||||
"* `:modify` - IN_MODIFY\n\n"
|
||||
"* `:move-self` - IN_MOVE_SELF\n\n"
|
||||
"* `:moved-from` - IN_MOVED_FROM\n\n"
|
||||
"* `:moved-to` - IN_MOVED_TO\n\n"
|
||||
"* `:open` - IN_OPEN\n\n"
|
||||
"* `:q-overflow` - IN_Q_OVERFLOW\n\n"
|
||||
"* `:unmount` - IN_UNMOUNT\n\n\n"
|
||||
"On Windows, events will have the following possible types:\n\n"
|
||||
"* `:unknown`\n\n"
|
||||
"* `:added`\n\n"
|
||||
"* `:removed`\n\n"
|
||||
"* `:modified`\n\n"
|
||||
"* `:renamed-old`\n\n"
|
||||
"* `:renamed-new`\n\n"
|
||||
"On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n"
|
||||
"") {
|
||||
"(filewatch/add watcher path flag & more-flags)",
|
||||
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
|
||||
"Windows/MINGW (flags correspond to `FILE_NOTIFY_CHANGE_*` flags in win32 documentation):\n\n"
|
||||
"* `:all` - trigger an event for all of the below triggers.\n\n"
|
||||
"* `:attributes` - `FILE_NOTIFY_CHANGE_ATTRIBUTES`\n\n"
|
||||
"* `:creation` - `FILE_NOTIFY_CHANGE_CREATION`\n\n"
|
||||
"* `:dir-name` - `FILE_NOTIFY_CHANGE_DIR_NAME`\n\n"
|
||||
"* `:last-access` - `FILE_NOTIFY_CHANGE_LAST_ACCESS`\n\n"
|
||||
"* `:last-write` - `FILE_NOTIFY_CHANGE_LAST_WRITE`\n\n"
|
||||
"* `:security` - `FILE_NOTIFY_CHANGE_SECURITY`\n\n"
|
||||
"* `:size` - `FILE_NOTIFY_CHANGE_SIZE`\n\n"
|
||||
"* `:recursive` - watch subdirectories recursively\n\n"
|
||||
"Linux (flags correspond to `IN_*` flags from <sys/inotify.h>):\n\n"
|
||||
"* `:access` - `IN_ACCESS`\n\n"
|
||||
"* `:all` - `IN_ALL_EVENTS`\n\n"
|
||||
"* `:attrib` - `IN_ATTRIB`\n\n"
|
||||
"* `:close-nowrite` - `IN_CLOSE_NOWRITE`\n\n"
|
||||
"* `:close-write` - `IN_CLOSE_WRITE`\n\n"
|
||||
"* `:create` - `IN_CREATE`\n\n"
|
||||
"* `:delete` - `IN_DELETE`\n\n"
|
||||
"* `:delete-self` - `IN_DELETE_SELF`\n\n"
|
||||
"* `:ignored` - `IN_IGNORED`\n\n"
|
||||
"* `:modify` - `IN_MODIFY`\n\n"
|
||||
"* `:move-self` - `IN_MOVE_SELF`\n\n"
|
||||
"* `:moved-from` - `IN_MOVED_FROM`\n\n"
|
||||
"* `:moved-to` - `IN_MOVED_TO`\n\n"
|
||||
"* `:open` - `IN_OPEN`\n\n"
|
||||
"* `:q-overflow` - `IN_Q_OVERFLOW`\n\n"
|
||||
"* `:unmount` - `IN_UNMOUNT`\n\n\n"
|
||||
"On Windows, events will have the following possible types:\n\n"
|
||||
"* `:unknown`\n\n"
|
||||
"* `:added`\n\n"
|
||||
"* `:removed`\n\n"
|
||||
"* `:modified`\n\n"
|
||||
"* `:renamed-old`\n\n"
|
||||
"* `:renamed-new`\n\n"
|
||||
"On Linux, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n"
|
||||
"") {
|
||||
janet_arity(argc, 2, -1);
|
||||
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
|
||||
const char *path = janet_getcstring(argv, 1);
|
||||
@@ -644,8 +644,8 @@ JANET_CORE_FN(cfun_filewatch_add,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_filewatch_remove,
|
||||
"(filewatch/remove watcher path)",
|
||||
"Remove a path from the watcher.") {
|
||||
"(filewatch/remove watcher path)",
|
||||
"Remove a path from the watcher.") {
|
||||
janet_fixarity(argc, 2);
|
||||
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
|
||||
const char *path = janet_getcstring(argv, 1);
|
||||
@@ -654,8 +654,8 @@ JANET_CORE_FN(cfun_filewatch_remove,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_filewatch_listen,
|
||||
"(filewatch/listen watcher)",
|
||||
"Listen for changes in the watcher.") {
|
||||
"(filewatch/listen watcher)",
|
||||
"Listen for changes in the watcher.") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
|
||||
janet_watcher_listen(watcher);
|
||||
@@ -663,8 +663,8 @@ JANET_CORE_FN(cfun_filewatch_listen,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_filewatch_unlisten,
|
||||
"(filewatch/unlisten watcher)",
|
||||
"Stop listening for changes on a given watcher.") {
|
||||
"(filewatch/unlisten watcher)",
|
||||
"Stop listening for changes on a given watcher.") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
|
||||
janet_watcher_unlisten(watcher);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -346,6 +346,9 @@ static void janet_deinit_block(JanetGCObject *mem) {
|
||||
break;
|
||||
case JANET_MEMORY_ABSTRACT: {
|
||||
JanetAbstractHead *head = (JanetAbstractHead *)mem;
|
||||
if (head->type->gcperthread) {
|
||||
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
|
||||
}
|
||||
if (head->type->gc) {
|
||||
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
|
||||
}
|
||||
@@ -497,15 +500,11 @@ void janet_sweep() {
|
||||
/* If not visited... */
|
||||
if (!janet_truthy(items[i].value)) {
|
||||
void *abst = janet_unwrap_abstract(items[i].key);
|
||||
if (0 == janet_abstract_decref(abst)) {
|
||||
/* Run finalizer */
|
||||
JanetAbstractHead *head = janet_abstract_head(abst);
|
||||
if (head->type->gc) {
|
||||
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
|
||||
}
|
||||
/* Free memory */
|
||||
janet_free(janet_abstract_head(abst));
|
||||
JanetAbstractHead *head = janet_abstract_head(abst);
|
||||
if (head->type->gcperthread) {
|
||||
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
|
||||
}
|
||||
janet_abstract_decref_maybe_free(abst);
|
||||
|
||||
/* Mark as tombstone in place */
|
||||
items[i].key = janet_wrap_nil();
|
||||
@@ -672,13 +671,11 @@ void janet_clear_memory(void) {
|
||||
for (int32_t i = 0; i < janet_vm.threaded_abstracts.capacity; i++) {
|
||||
if (janet_checktype(items[i].key, JANET_ABSTRACT)) {
|
||||
void *abst = janet_unwrap_abstract(items[i].key);
|
||||
if (0 == janet_abstract_decref(abst)) {
|
||||
JanetAbstractHead *head = janet_abstract_head(abst);
|
||||
if (head->type->gc) {
|
||||
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
|
||||
}
|
||||
janet_free(janet_abstract_head(abst));
|
||||
JanetAbstractHead *head = janet_abstract_head(abst);
|
||||
if (head->type->gcperthread) {
|
||||
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
|
||||
}
|
||||
janet_abstract_decref_maybe_free(abst);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose & contributors
|
||||
* Copyright (c) 2026 Calvin Rose & contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -191,21 +191,21 @@ Janet janet_wrap_u64(uint64_t x) {
|
||||
|
||||
JANET_CORE_FN(cfun_it_s64_new,
|
||||
"(int/s64 value)",
|
||||
"Create a boxed signed 64 bit integer from a string value.") {
|
||||
"Create a boxed signed 64 bit integer from a string value or a number.") {
|
||||
janet_fixarity(argc, 1);
|
||||
return janet_wrap_s64(janet_unwrap_s64(argv[0]));
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_it_u64_new,
|
||||
"(int/u64 value)",
|
||||
"Create a boxed unsigned 64 bit integer from a string value.") {
|
||||
"Create a boxed unsigned 64 bit integer from a string value or a number.") {
|
||||
janet_fixarity(argc, 1);
|
||||
return janet_wrap_u64(janet_unwrap_u64(argv[0]));
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_to_number,
|
||||
"(int/to-number value)",
|
||||
"Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int32.") {
|
||||
"Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int64.") {
|
||||
janet_fixarity(argc, 1);
|
||||
if (janet_type(argv[0]) == JANET_ABSTRACT) {
|
||||
void *abst = janet_unwrap_abstract(argv[0]);
|
||||
|
||||
100
src/core/io.c
100
src/core/io.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#ifndef JANET_WINDOWS
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -42,6 +43,7 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx);
|
||||
static Janet io_file_next(void *p, Janet key);
|
||||
|
||||
#ifdef JANET_WINDOWS
|
||||
#include <io.h>
|
||||
#define ftell _ftelli64
|
||||
#define fseek _fseeki64
|
||||
#endif
|
||||
@@ -108,11 +110,12 @@ static int32_t checkflags(const uint8_t *str) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
static void *makef(FILE *f, int32_t flags) {
|
||||
static void *makef(FILE *f, int32_t flags, size_t bufsize) {
|
||||
JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile));
|
||||
iof->file = f;
|
||||
iof->flags = flags;
|
||||
#ifndef JANET_WINDOWS
|
||||
iof->vbufsize = bufsize;
|
||||
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
|
||||
/* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is
|
||||
* not standard. */
|
||||
if (!(flags & JANET_FILE_NOT_CLOSEABLE))
|
||||
@@ -163,8 +166,17 @@ JANET_CORE_FN(cfun_io_fopen,
|
||||
flags = JANET_FILE_READ;
|
||||
}
|
||||
FILE *f = fopen((const char *)fname, (const char *)fmode);
|
||||
size_t bufsize = BUFSIZ;
|
||||
if (f != NULL) {
|
||||
size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
|
||||
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
|
||||
struct stat st;
|
||||
fstat(fileno(f), &st);
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
fclose(f);
|
||||
janet_panicf("cannot open directory: %s", fname);
|
||||
}
|
||||
#endif
|
||||
bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
|
||||
if (bufsize != BUFSIZ) {
|
||||
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);
|
||||
if (result) {
|
||||
@@ -172,7 +184,7 @@ JANET_CORE_FN(cfun_io_fopen,
|
||||
}
|
||||
}
|
||||
}
|
||||
return f ? janet_makefile(f, flags)
|
||||
return f ? janet_wrap_abstract(makef(f, flags, bufsize))
|
||||
: (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil())
|
||||
: janet_wrap_nil();
|
||||
}
|
||||
@@ -240,9 +252,9 @@ JANET_CORE_FN(cfun_io_fread,
|
||||
|
||||
/* Write bytes to a file */
|
||||
JANET_CORE_FN(cfun_io_fwrite,
|
||||
"(file/write f bytes)",
|
||||
"Writes to a file. 'bytes' must be string, buffer, or symbol. Returns the "
|
||||
"file.") {
|
||||
"(file/write f & bytes)",
|
||||
"Writes to a file `f`. Each value of `bytes` must be a "
|
||||
"string, buffer, symbol, or keyword. Returns the file.") {
|
||||
janet_arity(argc, 1, -1);
|
||||
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
|
||||
if (iof->flags & JANET_FILE_CLOSED)
|
||||
@@ -308,6 +320,41 @@ static int cfun_io_gc(void *p, size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Cross-platform fsync binding for Janet */
|
||||
JANET_CORE_FN(cfun_io_fsync,
|
||||
"(file/sync f)",
|
||||
"Flushes all operating system buffers to disk for file `f`. Guarantees data is physically "
|
||||
"written to disk in a platform-dependent way. Returns the file handle if successful, raises error if not.") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
|
||||
if (iof->flags & JANET_FILE_CLOSED)
|
||||
janet_panic("file is closed");
|
||||
#ifdef JANET_WINDOWS
|
||||
{
|
||||
int fd = _fileno(iof->file);
|
||||
if (fd < 0)
|
||||
janet_panic("invalid file descriptor");
|
||||
HANDLE hFile = (HANDLE)_get_osfhandle(fd);
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
janet_panic("invalid file handle");
|
||||
if (!FlushFileBuffers(hFile))
|
||||
janet_panic("could not flush file buffers");
|
||||
}
|
||||
#elif defined(_POSIX_VERSION)
|
||||
{
|
||||
int fd = fileno(iof->file);
|
||||
if (fd < 0)
|
||||
janet_panic("invalid file descriptor");
|
||||
if (fsync(fd) != 0)
|
||||
janet_panic("could not fsync file");
|
||||
}
|
||||
#else
|
||||
janet_panic("fsync not supported on this platform");
|
||||
#endif
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
|
||||
/* Close a file */
|
||||
JANET_CORE_FN(cfun_io_fclose,
|
||||
"(file/close f)",
|
||||
@@ -382,6 +429,7 @@ static JanetMethod io_file_methods[] = {
|
||||
{"seek", cfun_io_fseek},
|
||||
{"tell", cfun_io_ftell},
|
||||
{"write", cfun_io_fwrite},
|
||||
{"sync", cfun_io_fsync},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -401,12 +449,23 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) {
|
||||
JanetFile *iof = (JanetFile *)p;
|
||||
if (ctx->flags & JANET_MARSHAL_UNSAFE) {
|
||||
janet_marshal_abstract(ctx, p);
|
||||
int fno = -1;
|
||||
#ifdef JANET_WINDOWS
|
||||
janet_marshal_int(ctx, _fileno(iof->file));
|
||||
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
|
||||
fno = _fileno(iof->file);
|
||||
} else {
|
||||
fno = _dup(_fileno(iof->file));
|
||||
}
|
||||
#else
|
||||
janet_marshal_int(ctx, fileno(iof->file));
|
||||
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
|
||||
fno = fileno(iof->file);
|
||||
} else {
|
||||
fno = dup(fileno(iof->file));
|
||||
}
|
||||
#endif
|
||||
janet_marshal_int(ctx, fno);
|
||||
janet_marshal_int(ctx, iof->flags);
|
||||
janet_marshal_size(ctx, iof->vbufsize);
|
||||
} else {
|
||||
janet_panic("cannot marshal file in safe mode");
|
||||
}
|
||||
@@ -435,6 +494,11 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx) {
|
||||
} else {
|
||||
iof->flags = flags;
|
||||
}
|
||||
iof->vbufsize = janet_unmarshal_size(ctx);
|
||||
if (iof->vbufsize != BUFSIZ) {
|
||||
int result = setvbuf(iof->file, NULL, iof->vbufsize ? _IOFBF : _IONBF, iof->vbufsize);
|
||||
janet_assert(!result, "unmarshal setvbuf");
|
||||
}
|
||||
return iof;
|
||||
} else {
|
||||
janet_panic("cannot unmarshal file in safe mode");
|
||||
@@ -712,8 +776,15 @@ JANET_CORE_FN(cfun_io_eflush,
|
||||
void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
Janet x = janet_dyn(name);
|
||||
JanetType xtype = janet_type(x);
|
||||
JanetType xtype;
|
||||
Janet x;
|
||||
if (!name || name[0] == '\0') { /* Allow NULL or empty string to just use dflt_file directly */
|
||||
x = janet_wrap_nil();
|
||||
xtype = JANET_NIL;
|
||||
} else {
|
||||
x = janet_dyn(name);
|
||||
xtype = janet_type(x);
|
||||
}
|
||||
switch (xtype) {
|
||||
default:
|
||||
/* Other values simply do nothing */
|
||||
@@ -769,11 +840,11 @@ FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) {
|
||||
}
|
||||
|
||||
JanetFile *janet_makejfile(FILE *f, int32_t flags) {
|
||||
return makef(f, flags);
|
||||
return makef(f, flags, BUFSIZ);
|
||||
}
|
||||
|
||||
Janet janet_makefile(FILE *f, int32_t flags) {
|
||||
return janet_wrap_abstract(makef(f, flags));
|
||||
return janet_wrap_abstract(makef(f, flags, BUFSIZ));
|
||||
}
|
||||
|
||||
JanetAbstract janet_checkfile(Janet j) {
|
||||
@@ -811,6 +882,7 @@ void janet_lib_io(JanetTable *env) {
|
||||
JANET_CORE_REG("file/flush", cfun_io_fflush),
|
||||
JANET_CORE_REG("file/seek", cfun_io_fseek),
|
||||
JANET_CORE_REG("file/tell", cfun_io_ftell),
|
||||
JANET_CORE_REG("file/sync", cfun_io_fsync),
|
||||
JANET_REG_END
|
||||
};
|
||||
janet_core_cfuns_ext(env, NULL, io_cfuns);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -276,6 +276,8 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
|
||||
pushint(st, def->max_arity);
|
||||
pushint(st, def->constants_length);
|
||||
pushint(st, def->bytecode_length);
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS)
|
||||
pushint(st, def->named_args_count);
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_HASENVS)
|
||||
pushint(st, def->environments_length);
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS)
|
||||
@@ -914,6 +916,7 @@ static const uint8_t *unmarshal_one_def(
|
||||
def->sourcemap = NULL;
|
||||
def->symbolmap = NULL;
|
||||
def->symbolmap_length = 0;
|
||||
def->named_args_count = 0;
|
||||
janet_v_push(st->lookup_defs, def);
|
||||
|
||||
/* Set default lengths to zero */
|
||||
@@ -933,6 +936,8 @@ static const uint8_t *unmarshal_one_def(
|
||||
/* Read some lengths */
|
||||
constants_length = readnat(st, &data);
|
||||
bytecode_length = readnat(st, &data);
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS)
|
||||
def->named_args_count = readnat(st, &data);
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_HASENVS)
|
||||
environments_length = readnat(st, &data);
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS)
|
||||
@@ -1693,6 +1698,7 @@ JANET_CORE_FN(cfun_unmarshal,
|
||||
"Unmarshal a value from a buffer. An optional lookup table "
|
||||
"can be provided to allow for aliases to be resolved. Returns the value "
|
||||
"unmarshalled from the buffer.") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_UNMARSHAL);
|
||||
janet_arity(argc, 1, 2);
|
||||
JanetByteView view = janet_getbytes(argv, 0);
|
||||
JanetTable *reg = NULL;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -271,28 +271,30 @@ JANET_DEFINE_MATHOP(cosh, "Returns the hyperbolic cosine of x.")
|
||||
JANET_DEFINE_MATHOP(acosh, "Returns the hyperbolic arccosine of x.")
|
||||
JANET_DEFINE_MATHOP(sin, "Returns the sine of x.")
|
||||
JANET_DEFINE_MATHOP(sinh, "Returns the hyperbolic sine of x.")
|
||||
JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.")
|
||||
JANET_DEFINE_MATHOP(tan, "Returns the tangent of x.")
|
||||
JANET_DEFINE_MATHOP(tanh, "Returns the hyperbolic tangent of x.")
|
||||
JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.")
|
||||
JANET_DEFINE_MATHOP(exp, "Returns e to the power of x.")
|
||||
JANET_DEFINE_MATHOP(exp2, "Returns 2 to the power of x.")
|
||||
JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
|
||||
#ifndef JANET_PLAN9
|
||||
JANET_DEFINE_MATHOP(expm1, "Returns e to the power of x minus 1.")
|
||||
JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.")
|
||||
JANET_DEFINE_MATHOP(erf, "Returns the error function of x.")
|
||||
JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.")
|
||||
JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).")
|
||||
JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).")
|
||||
JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.")
|
||||
JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.")
|
||||
#endif
|
||||
JANET_DEFINE_MATHOP(log, "Returns the natural logarithm of x.")
|
||||
JANET_DEFINE_MATHOP(log10, "Returns the log base 10 of x.")
|
||||
JANET_DEFINE_MATHOP(log2, "Returns the log base 2 of x.")
|
||||
JANET_DEFINE_MATHOP(sqrt, "Returns the square root of x.")
|
||||
JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.")
|
||||
JANET_DEFINE_MATHOP(ceil, "Returns the smallest integer value number that is not less than x.")
|
||||
JANET_DEFINE_MATHOP(floor, "Returns the largest integer value number that is not greater than x.")
|
||||
JANET_DEFINE_MATHOP(trunc, "Returns the integer between x and 0 nearest to x.")
|
||||
JANET_DEFINE_MATHOP(round, "Returns the integer nearest to x.")
|
||||
JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
|
||||
JANET_DEFINE_MATHOP(erf, "Returns the error function of x.")
|
||||
JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.")
|
||||
JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).")
|
||||
JANET_DEFINE_NAMED_MATHOP("abs", fabs, "Return the absolute value of x.")
|
||||
JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).")
|
||||
|
||||
#define JANET_DEFINE_MATH2OP(name, fop, signature, doc)\
|
||||
JANET_CORE_FN(janet_##name, signature, doc) {\
|
||||
@@ -305,7 +307,9 @@ JANET_CORE_FN(janet_##name, signature, doc) {\
|
||||
JANET_DEFINE_MATH2OP(atan2, atan2, "(math/atan2 y x)", "Returns the arctangent of y/x. Works even when x is 0.")
|
||||
JANET_DEFINE_MATH2OP(pow, pow, "(math/pow a x)", "Returns a to the power of x.")
|
||||
JANET_DEFINE_MATH2OP(hypot, hypot, "(math/hypot a b)", "Returns c from the equation c^2 = a^2 + b^2.")
|
||||
#ifndef JANET_PLAN9
|
||||
JANET_DEFINE_MATH2OP(nextafter, nextafter, "(math/next x y)", "Returns the next representable floating point value after x in the direction of y.")
|
||||
#endif
|
||||
|
||||
JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") {
|
||||
janet_fixarity(argc, 1);
|
||||
@@ -386,7 +390,17 @@ void janet_lib_math(JanetTable *env) {
|
||||
JANET_CORE_REG("math/log10", janet_log10),
|
||||
JANET_CORE_REG("math/log2", janet_log2),
|
||||
JANET_CORE_REG("math/sqrt", janet_sqrt),
|
||||
#ifndef JANET_PLAN9
|
||||
JANET_CORE_REG("math/cbrt", janet_cbrt),
|
||||
JANET_CORE_REG("math/gamma", janet_tgamma),
|
||||
JANET_CORE_REG("math/log-gamma", janet_lgamma),
|
||||
JANET_CORE_REG("math/erfc", janet_erfc),
|
||||
JANET_CORE_REG("math/erf", janet_erf),
|
||||
JANET_CORE_REG("math/expm1", janet_expm1),
|
||||
JANET_CORE_REG("math/atanh", janet_atanh),
|
||||
JANET_CORE_REG("math/asinh", janet_asinh),
|
||||
JANET_CORE_REG("math/next", janet_nextafter),
|
||||
#endif
|
||||
JANET_CORE_REG("math/floor", janet_floor),
|
||||
JANET_CORE_REG("math/ceil", janet_ceil),
|
||||
JANET_CORE_REG("math/pow", janet_pow),
|
||||
@@ -394,8 +408,6 @@ void janet_lib_math(JanetTable *env) {
|
||||
JANET_CORE_REG("math/sinh", janet_sinh),
|
||||
JANET_CORE_REG("math/cosh", janet_cosh),
|
||||
JANET_CORE_REG("math/tanh", janet_tanh),
|
||||
JANET_CORE_REG("math/atanh", janet_atanh),
|
||||
JANET_CORE_REG("math/asinh", janet_asinh),
|
||||
JANET_CORE_REG("math/acosh", janet_acosh),
|
||||
JANET_CORE_REG("math/atan2", janet_atan2),
|
||||
JANET_CORE_REG("math/rng", cfun_rng_make),
|
||||
@@ -405,14 +417,8 @@ void janet_lib_math(JanetTable *env) {
|
||||
JANET_CORE_REG("math/hypot", janet_hypot),
|
||||
JANET_CORE_REG("math/exp2", janet_exp2),
|
||||
JANET_CORE_REG("math/log1p", janet_log1p),
|
||||
JANET_CORE_REG("math/gamma", janet_tgamma),
|
||||
JANET_CORE_REG("math/log-gamma", janet_lgamma),
|
||||
JANET_CORE_REG("math/erfc", janet_erfc),
|
||||
JANET_CORE_REG("math/erf", janet_erf),
|
||||
JANET_CORE_REG("math/expm1", janet_expm1),
|
||||
JANET_CORE_REG("math/trunc", janet_trunc),
|
||||
JANET_CORE_REG("math/round", janet_round),
|
||||
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),
|
||||
@@ -435,9 +441,9 @@ void janet_lib_math(JanetTable *env) {
|
||||
JANET_CORE_DEF(env, "math/int32-max", janet_wrap_number(INT32_MAX),
|
||||
"The maximum contiguous integer representable by a 32 bit signed integer");
|
||||
JANET_CORE_DEF(env, "math/int-min", janet_wrap_number(JANET_INTMIN_DOUBLE),
|
||||
"The minimum contiguous integer representable by a double (2^53)");
|
||||
"The minimum contiguous integer representable by a double (-(2^53))");
|
||||
JANET_CORE_DEF(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE),
|
||||
"The maximum contiguous integer representable by a double (-(2^53))");
|
||||
"The maximum contiguous integer representable by a double (2^53)");
|
||||
#ifdef NAN
|
||||
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN)");
|
||||
#else
|
||||
|
||||
290
src/core/net.c
290
src/core/net.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose and contributors.
|
||||
* Copyright (c) 2026 Calvin Rose and contributors.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -120,7 +120,55 @@ static void janet_net_socknoblock(JSock s) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Allow specifying IPV6 vs. IPV4 (or unix domain socket) */
|
||||
static int net_get_address_family(Janet x) {
|
||||
if (janet_checktype(x, JANET_NIL)) {
|
||||
return AF_UNSPEC;
|
||||
}
|
||||
if (janet_keyeq(x, "ipv4")) {
|
||||
return AF_INET;
|
||||
}
|
||||
if (janet_keyeq(x, "ipv6")) {
|
||||
return AF_INET6;
|
||||
}
|
||||
#ifndef JANET_WINDOWS
|
||||
if (janet_keyeq(x, "unix")) {
|
||||
return AF_UNIX;
|
||||
}
|
||||
#endif
|
||||
return AF_UNSPEC;
|
||||
}
|
||||
|
||||
/* State machine for async connect */
|
||||
#ifdef JANET_WINDOWS
|
||||
|
||||
typedef struct NetStateConnect {
|
||||
/* Only used for ConnectEx */
|
||||
JanetOverlapped overlapped;
|
||||
} NetStateConnect;
|
||||
|
||||
static LPFN_CONNECTEX lazy_get_connectex(JSock sock) {
|
||||
/* Get ConnectEx */
|
||||
if (janet_vm.connect_ex_loaded) {
|
||||
return janet_vm.connect_ex;
|
||||
}
|
||||
GUID guid = WSAID_CONNECTEX;
|
||||
LPFN_CONNECTEX connect_ex_ptr = NULL;
|
||||
DWORD byte_len = 0;
|
||||
int success = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||
(void*)&guid, sizeof(guid),
|
||||
(void*)&connect_ex_ptr, sizeof(connect_ex_ptr),
|
||||
&byte_len, NULL, NULL);
|
||||
if (success) {
|
||||
janet_vm.connect_ex = connect_ex_ptr;
|
||||
} else {
|
||||
janet_vm.connect_ex = NULL;
|
||||
}
|
||||
janet_vm.connect_ex_loaded = 1;
|
||||
return janet_vm.connect_ex;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
JanetStream *stream = fiber->ev_stream;
|
||||
@@ -140,15 +188,21 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
return;
|
||||
}
|
||||
#ifdef JANET_WINDOWS
|
||||
/* We should be using ConnectEx here */
|
||||
int res = 0;
|
||||
int size = sizeof(res);
|
||||
int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_ERROR, (char *)&res, &size);
|
||||
int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_CONNECT_TIME, (char *)&res, &size);
|
||||
if (r == NO_ERROR && res == 0xFFFFFFFF) {
|
||||
return; /* This apparently indicates we haven't yet gotten a connection */
|
||||
}
|
||||
const int no_error = NO_ERROR;
|
||||
#else
|
||||
int res = 0;
|
||||
socklen_t size = sizeof res;
|
||||
socklen_t size = sizeof(res);
|
||||
int r = getsockopt(stream->handle, SOL_SOCKET, SO_ERROR, &res, &size);
|
||||
const int no_error = 0;
|
||||
#endif
|
||||
if (r == 0) {
|
||||
if (r == no_error) {
|
||||
if (res == 0) {
|
||||
janet_schedule(fiber, janet_wrap_abstract(stream));
|
||||
} else {
|
||||
@@ -162,8 +216,8 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
janet_async_end(fiber);
|
||||
}
|
||||
|
||||
static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) {
|
||||
janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, NULL);
|
||||
static JANET_NO_RETURN void net_sched_connect(JanetStream *stream, void *state) {
|
||||
janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, state);
|
||||
}
|
||||
|
||||
/* State machine for accepting connections. */
|
||||
@@ -171,7 +225,7 @@ static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) {
|
||||
#ifdef JANET_WINDOWS
|
||||
|
||||
typedef struct {
|
||||
WSAOVERLAPPED overlapped;
|
||||
JanetOverlapped overlapped;
|
||||
JanetFunction *function;
|
||||
JanetStream *lstream;
|
||||
JanetStream *astream;
|
||||
@@ -234,7 +288,7 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) {
|
||||
JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) {
|
||||
Janet err;
|
||||
NetStateAccept *state = janet_malloc(sizeof(NetStateAccept));
|
||||
memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED));
|
||||
memset(&state->overlapped, 0, sizeof(JanetOverlapped));
|
||||
memset(&state->buf, 0, 1024);
|
||||
state->function = fun;
|
||||
state->lstream = stream;
|
||||
@@ -255,7 +309,7 @@ static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet
|
||||
JanetStream *astream = make_stream(asock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE);
|
||||
state->astream = astream;
|
||||
int socksize = sizeof(SOCKADDR_STORAGE) + 16;
|
||||
if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped)) {
|
||||
if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped.as.wsaoverlapped)) {
|
||||
int code = WSAGetLastError();
|
||||
if (code == WSA_IO_PENDING) {
|
||||
/* indicates io is happening async */
|
||||
@@ -341,7 +395,7 @@ static int janet_get_sockettype(Janet *argv, int32_t argc, int32_t n) {
|
||||
/* Needs argc >= offset + 2 */
|
||||
/* For unix paths, just rertuns a single sockaddr and sets *is_unix to 1,
|
||||
* otherwise 0. Also, ignores is_bind when is a unix socket. */
|
||||
static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix) {
|
||||
static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix, socklen_t *sizeout) {
|
||||
/* Unix socket support - not yet supported on windows. */
|
||||
#ifndef JANET_WINDOWS
|
||||
if (janet_keyeq(argv[offset], "unix")) {
|
||||
@@ -352,15 +406,14 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
|
||||
}
|
||||
saddr->sun_family = AF_UNIX;
|
||||
size_t path_size = sizeof(saddr->sun_path);
|
||||
snprintf(saddr->sun_path, path_size, "%s", path);
|
||||
*sizeout = sizeof(struct sockaddr_un);
|
||||
#ifdef JANET_LINUX
|
||||
if (path[0] == '@') {
|
||||
saddr->sun_path[0] = '\0';
|
||||
snprintf(saddr->sun_path + 1, path_size - 1, "%s", path + 1);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
snprintf(saddr->sun_path, path_size, "%s", path);
|
||||
*sizeout = offsetof(struct sockaddr_un, sun_path) + janet_string_length(path);
|
||||
}
|
||||
#endif
|
||||
*is_unix = 1;
|
||||
return (struct addrinfo *) saddr;
|
||||
}
|
||||
@@ -385,6 +438,11 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
|
||||
janet_panicf("could not get address info: %s", gai_strerror(status));
|
||||
}
|
||||
*is_unix = 0;
|
||||
#ifdef JANET_WINDOWS
|
||||
*sizeout = 0;
|
||||
#else
|
||||
*sizeout = sizeof(struct sockaddr_un);
|
||||
#endif
|
||||
return ai;
|
||||
}
|
||||
|
||||
@@ -405,12 +463,13 @@ JANET_CORE_FN(cfun_net_sockaddr,
|
||||
int socktype = janet_get_sockettype(argv, argc, 2);
|
||||
int is_unix = 0;
|
||||
int make_arr = (argc >= 3 && janet_truthy(argv[3]));
|
||||
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix);
|
||||
socklen_t addrsize = 0;
|
||||
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix, &addrsize);
|
||||
#ifndef JANET_WINDOWS
|
||||
/* no unix domain socket support on windows yet */
|
||||
if (is_unix) {
|
||||
void *abst = janet_abstract(&janet_address_type, sizeof(struct sockaddr_un));
|
||||
memcpy(abst, ai, sizeof(struct sockaddr_un));
|
||||
void *abst = janet_abstract(&janet_address_type, addrsize);
|
||||
memcpy(abst, ai, addrsize);
|
||||
Janet ret = janet_wrap_abstract(abst);
|
||||
return make_arr ? janet_wrap_array(janet_array_n(&ret, 1)) : ret;
|
||||
}
|
||||
@@ -461,7 +520,8 @@ JANET_CORE_FN(cfun_net_connect,
|
||||
}
|
||||
|
||||
/* Where we're connecting to */
|
||||
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix);
|
||||
socklen_t addrlen = 0;
|
||||
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix, &addrlen);
|
||||
|
||||
/* Check if we're binding address */
|
||||
struct addrinfo *binding = NULL;
|
||||
@@ -486,7 +546,6 @@ JANET_CORE_FN(cfun_net_connect,
|
||||
/* Create socket */
|
||||
JSock sock = JSOCKDEFAULT;
|
||||
void *addr = NULL;
|
||||
socklen_t addrlen = 0;
|
||||
#ifndef JANET_WINDOWS
|
||||
if (is_unix) {
|
||||
sock = socket(AF_UNIX, socktype | JSOCKFLAGS, 0);
|
||||
@@ -496,7 +555,6 @@ JANET_CORE_FN(cfun_net_connect,
|
||||
janet_panicf("could not create socket: %V", v);
|
||||
}
|
||||
addr = (void *) ai;
|
||||
addrlen = sizeof(struct sockaddr_un);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
@@ -543,18 +601,52 @@ JANET_CORE_FN(cfun_net_connect,
|
||||
}
|
||||
|
||||
/* Wrap socket in abstract type JanetStream */
|
||||
JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE);
|
||||
|
||||
/* Set up the socket for non-blocking IO before connecting */
|
||||
janet_net_socknoblock(sock);
|
||||
uint32_t udp_flag = 0;
|
||||
if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER;
|
||||
JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag);
|
||||
|
||||
/* Connect to socket */
|
||||
#ifdef JANET_WINDOWS
|
||||
int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
|
||||
int err = WSAGetLastError();
|
||||
freeaddrinfo(ai);
|
||||
int status = 0;
|
||||
int err = 0;
|
||||
LPFN_CONNECTEX connect_ex = NULL;
|
||||
if (socktype == SOCK_STREAM && ((connect_ex = lazy_get_connectex(sock)))) {
|
||||
/* Prefer ConnecEx as it works well with overlapped IO. */
|
||||
janet_net_socknoblock(sock);
|
||||
NetStateConnect *state = janet_malloc(sizeof(NetStateConnect));
|
||||
memset(state, 0, sizeof(NetStateConnect));
|
||||
BOOL success = connect_ex(sock, addr, addrlen, NULL, 0, NULL, &state->overlapped.as.overlapped);
|
||||
freeaddrinfo(ai);
|
||||
if (success) {
|
||||
/* Did not fail */
|
||||
} else {
|
||||
int err = WSAGetLastError();
|
||||
if (err == ERROR_IO_PENDING) {
|
||||
/* Did not actually fail yet */
|
||||
} else {
|
||||
janet_free(state);
|
||||
Janet lasterr = janet_ev_lasterr();
|
||||
janet_panicf("could not connect socket (ConnectEx): %V", lasterr);
|
||||
}
|
||||
}
|
||||
|
||||
net_sched_connect(stream, state);
|
||||
} else {
|
||||
/* Default to blocking connect if ConnectEx not available */
|
||||
status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
|
||||
err = WSAGetLastError();
|
||||
freeaddrinfo(ai);
|
||||
/* Set up the socket for non-blocking IO after connecting on windows by default */
|
||||
janet_net_socknoblock(sock);
|
||||
}
|
||||
|
||||
#else
|
||||
int status = connect(sock, addr, addrlen);
|
||||
/* Set up the socket for non-blocking IO before connecting */
|
||||
janet_net_socknoblock(sock);
|
||||
int status;
|
||||
do {
|
||||
status = connect(sock, addr, addrlen);
|
||||
} while (status == -1 && errno == EINTR);
|
||||
int err = errno;
|
||||
if (is_unix) {
|
||||
janet_free(ai);
|
||||
@@ -563,10 +655,19 @@ JANET_CORE_FN(cfun_net_connect,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (status) {
|
||||
if (status == 0) {
|
||||
/* Connect completed synchronously (common for unix domain sockets).
|
||||
* Return the stream directly without scheduling an async wait,
|
||||
* as edge-triggered kqueue may not signal EVFILT_WRITE if the socket
|
||||
* is already connected when registered. */
|
||||
return janet_wrap_abstract(stream);
|
||||
}
|
||||
|
||||
#ifdef JANET_WINDOWS
|
||||
if (status == SOCKET_ERROR) {
|
||||
if (err != WSAEWOULDBLOCK) {
|
||||
#else
|
||||
if (status == -1) {
|
||||
if (err != EINPROGRESS) {
|
||||
#endif
|
||||
JSOCKCLOSE(sock);
|
||||
@@ -575,20 +676,84 @@ JANET_CORE_FN(cfun_net_connect,
|
||||
}
|
||||
}
|
||||
|
||||
net_sched_connect(stream);
|
||||
net_sched_connect(stream, NULL);
|
||||
}
|
||||
|
||||
static const char *serverify_socket(JSock sfd) {
|
||||
JANET_CORE_FN(cfun_net_socket,
|
||||
"(net/socket &opt type address-family)",
|
||||
"Creates a new unbound socket. Type is an optional keyword, "
|
||||
"either a :stream (usually tcp), or :datagram (usually udp). The default is :stream. "
|
||||
"`address-family` should be one of :ipv4 or :ipv6.") {
|
||||
janet_arity(argc, 0, 2);
|
||||
|
||||
int socktype = janet_get_sockettype(argv, argc, 0);
|
||||
|
||||
/* Create socket */
|
||||
JSock sfd = JSOCKDEFAULT;
|
||||
struct addrinfo *ai = NULL;
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = socktype;
|
||||
#ifdef AI_NUMERICSERV
|
||||
hints.ai_flags = AI_NUMERICSERV; /* Explicitly prevent name resolution */
|
||||
#else
|
||||
hints.ai_flags = 0;
|
||||
#endif
|
||||
if (argc >= 2) {
|
||||
hints.ai_family = net_get_address_family(argv[1]);
|
||||
}
|
||||
int status = getaddrinfo(NULL, "0", &hints, &ai);
|
||||
if (status) {
|
||||
janet_panicf("could not get address info: %s", gai_strerror(status));
|
||||
}
|
||||
|
||||
struct addrinfo *rp = NULL;
|
||||
for (rp = ai; rp != NULL; rp = rp->ai_next) {
|
||||
#ifdef JANET_WINDOWS
|
||||
sfd = WSASocketW(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
|
||||
#else
|
||||
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
|
||||
#endif
|
||||
if (JSOCKVALID(sfd)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(ai);
|
||||
|
||||
if (!JSOCKVALID(sfd)) {
|
||||
Janet v = janet_ev_lasterr();
|
||||
janet_panicf("could not create socket: %V", v);
|
||||
}
|
||||
|
||||
/* Wrap socket in abstract type JanetStream */
|
||||
uint32_t udp_flag = 0;
|
||||
if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER;
|
||||
JanetStream *stream = make_stream(sfd, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag);
|
||||
|
||||
/* Set up the socket for non-blocking IO */
|
||||
janet_net_socknoblock(sfd);
|
||||
|
||||
return janet_wrap_abstract(stream);
|
||||
}
|
||||
|
||||
static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) {
|
||||
/* Set various socket options */
|
||||
int enable = 1;
|
||||
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
|
||||
return "setsockopt(SO_REUSEADDR) failed";
|
||||
if (reuse_addr) {
|
||||
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
|
||||
return "setsockopt(SO_REUSEADDR) failed";
|
||||
}
|
||||
}
|
||||
if (reuse_port) {
|
||||
#ifdef SO_REUSEPORT
|
||||
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
|
||||
return "setsockopt(SO_REUSEPORT) failed";
|
||||
}
|
||||
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
|
||||
return "setsockopt(SO_REUSEPORT) failed";
|
||||
}
|
||||
#else
|
||||
(void) reuse_port;
|
||||
#endif
|
||||
}
|
||||
janet_net_socknoblock(sfd);
|
||||
return NULL;
|
||||
}
|
||||
@@ -642,19 +807,22 @@ JANET_CORE_FN(cfun_net_shutdown,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_net_listen,
|
||||
"(net/listen host port &opt type)",
|
||||
"(net/listen host port &opt type no-reuse)",
|
||||
"Creates a server. Returns a new stream that is neither readable nor "
|
||||
"writeable. Use net/accept or net/accept-loop be to handle connections and start the server. "
|
||||
"The type parameter specifies the type of network connection, either "
|
||||
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
|
||||
":stream. The host and port arguments are the same as in net/address.") {
|
||||
":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will "
|
||||
"disable the use of `SO_REUSEADDR` and `SO_REUSEPORT` when creating a server on some operating systems.") {
|
||||
janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
|
||||
janet_arity(argc, 2, 3);
|
||||
janet_arity(argc, 2, 4);
|
||||
|
||||
/* Get host, port, and handler*/
|
||||
int socktype = janet_get_sockettype(argv, argc, 2);
|
||||
int is_unix = 0;
|
||||
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix);
|
||||
socklen_t addrlen = 0;
|
||||
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix, &addrlen);
|
||||
int reuse = !(argc >= 4 && janet_truthy(argv[3]));
|
||||
|
||||
JSock sfd = JSOCKDEFAULT;
|
||||
#ifndef JANET_WINDOWS
|
||||
@@ -664,8 +832,8 @@ JANET_CORE_FN(cfun_net_listen,
|
||||
janet_free(ai);
|
||||
janet_panicf("could not create socket: %V", janet_ev_lasterr());
|
||||
}
|
||||
const char *err = serverify_socket(sfd);
|
||||
if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) {
|
||||
const char *err = serverify_socket(sfd, reuse, 0);
|
||||
if (NULL != err || bind(sfd, (struct sockaddr *)ai, addrlen)) {
|
||||
JSOCKCLOSE(sfd);
|
||||
janet_free(ai);
|
||||
if (err) {
|
||||
@@ -687,7 +855,7 @@ JANET_CORE_FN(cfun_net_listen,
|
||||
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
|
||||
#endif
|
||||
if (!JSOCKVALID(sfd)) continue;
|
||||
const char *err = serverify_socket(sfd);
|
||||
const char *err = serverify_socket(sfd, reuse, reuse);
|
||||
if (NULL != err) {
|
||||
JSOCKCLOSE(sfd);
|
||||
continue;
|
||||
@@ -829,7 +997,7 @@ JANET_CORE_FN(cfun_stream_accept_loop,
|
||||
JANET_CORE_FN(cfun_stream_accept,
|
||||
"(net/accept stream &opt timeout)",
|
||||
"Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. "
|
||||
"Takes an optional timeout in seconds, after which will return nil. "
|
||||
"Takes an optional timeout in seconds, after which will raise an error. "
|
||||
"Returns a new duplex stream which represents a connection to the client.") {
|
||||
janet_arity(argc, 1, 2);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
@@ -844,7 +1012,7 @@ JANET_CORE_FN(cfun_stream_read,
|
||||
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
|
||||
"`n` can also be the keyword `:all` to read into the buffer until end of stream. "
|
||||
"If less than n bytes are available (and more than 0), will push those bytes and return early. "
|
||||
"Takes an optional timeout in seconds, after which will return nil. "
|
||||
"Takes an optional timeout in seconds, after which will raise an error. "
|
||||
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") {
|
||||
janet_arity(argc, 2, 4);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
@@ -864,7 +1032,7 @@ JANET_CORE_FN(cfun_stream_read,
|
||||
JANET_CORE_FN(cfun_stream_chunk,
|
||||
"(net/chunk stream nbytes &opt buf timeout)",
|
||||
"Same a net/read, but will wait for all n bytes to arrive rather than return early. "
|
||||
"Takes an optional timeout in seconds, after which will return nil.") {
|
||||
"Takes an optional timeout in seconds, after which will raise an error.") {
|
||||
janet_arity(argc, 2, 4);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
|
||||
@@ -878,7 +1046,7 @@ JANET_CORE_FN(cfun_stream_chunk,
|
||||
JANET_CORE_FN(cfun_stream_recv_from,
|
||||
"(net/recv-from stream nbytes buf &opt timeout)",
|
||||
"Receives data from a server stream and puts it into a buffer. Returns the socket-address the "
|
||||
"packet came from. Takes an optional timeout in seconds, after which will return nil.") {
|
||||
"packet came from. Takes an optional timeout in seconds, after which will raise an error.") {
|
||||
janet_arity(argc, 3, 4);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET);
|
||||
@@ -892,7 +1060,7 @@ JANET_CORE_FN(cfun_stream_recv_from,
|
||||
JANET_CORE_FN(cfun_stream_write,
|
||||
"(net/write stream data &opt timeout)",
|
||||
"Write data to a stream, suspending the current fiber until the write "
|
||||
"completes. Takes an optional timeout in seconds, after which will return nil. "
|
||||
"completes. Takes an optional timeout in seconds, after which will raise an error. "
|
||||
"Returns nil, or raises an error if the write failed.") {
|
||||
janet_arity(argc, 2, 3);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
@@ -911,7 +1079,7 @@ JANET_CORE_FN(cfun_stream_write,
|
||||
JANET_CORE_FN(cfun_stream_send_to,
|
||||
"(net/send-to stream dest data &opt timeout)",
|
||||
"Writes a datagram to a server stream. dest is a the destination address of the packet. "
|
||||
"Takes an optional timeout in seconds, after which will return nil. "
|
||||
"Takes an optional timeout in seconds, after which will raise an error. "
|
||||
"Returns stream.") {
|
||||
janet_arity(argc, 3, 4);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
@@ -962,6 +1130,8 @@ static const struct sockopt_type sockopt_type_list[] = {
|
||||
#ifndef JANET_NO_IPV6
|
||||
{ "ipv6-join-group", IPPROTO_IPV6, IPV6_JOIN_GROUP, JANET_POINTER },
|
||||
{ "ipv6-leave-group", IPPROTO_IPV6, IPV6_LEAVE_GROUP, JANET_POINTER },
|
||||
{ "ipv6-multicast-hops", IPPROTO_IPV6, IPV6_MULTICAST_HOPS, JANET_NUMBER },
|
||||
{ "ipv6-unicast-hops", IPPROTO_IPV6, IPV6_UNICAST_HOPS, JANET_NUMBER },
|
||||
#endif
|
||||
{ NULL, 0, 0, JANET_POINTER }
|
||||
};
|
||||
@@ -978,7 +1148,10 @@ JANET_CORE_FN(cfun_net_setsockopt,
|
||||
"- :ip-add-membership string\n"
|
||||
"- :ip-drop-membership string\n"
|
||||
"- :ipv6-join-group string\n"
|
||||
"- :ipv6-leave-group string\n") {
|
||||
"- :ipv6-leave-group string\n"
|
||||
"- :ipv6-multicast-hops number\n"
|
||||
"- :ipv6-unicast-hops number\n"
|
||||
) {
|
||||
janet_arity(argc, 3, 3);
|
||||
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
|
||||
janet_stream_flags(stream, JANET_STREAM_SOCKET);
|
||||
@@ -997,6 +1170,7 @@ JANET_CORE_FN(cfun_net_setsockopt,
|
||||
}
|
||||
|
||||
union {
|
||||
unsigned char v_uchar;
|
||||
int v_int;
|
||||
struct ip_mreq v_mreq;
|
||||
#ifndef JANET_NO_IPV6
|
||||
@@ -1011,8 +1185,19 @@ JANET_CORE_FN(cfun_net_setsockopt,
|
||||
val.v_int = janet_getboolean(argv, 2);
|
||||
optlen = sizeof(val.v_int);
|
||||
} else if (st->type == JANET_NUMBER) {
|
||||
#if defined(JANET_BSD) || defined(JANET_ILLUMOS)
|
||||
int v_int = janet_getinteger(argv, 2);
|
||||
if (st->optname == IP_MULTICAST_TTL) {
|
||||
val.v_uchar = v_int;
|
||||
optlen = sizeof(val.v_uchar);
|
||||
} else {
|
||||
val.v_int = v_int;
|
||||
optlen = sizeof(val.v_int);
|
||||
}
|
||||
#else
|
||||
val.v_int = janet_getinteger(argv, 2);
|
||||
optlen = sizeof(val.v_int);
|
||||
#endif
|
||||
} else if (st->optname == IP_ADD_MEMBERSHIP || st->optname == IP_DROP_MEMBERSHIP) {
|
||||
const char *addr = janet_getcstring(argv, 2);
|
||||
memset(&val.v_mreq, 0, sizeof val.v_mreq);
|
||||
@@ -1067,6 +1252,7 @@ void janet_lib_net(JanetTable *env) {
|
||||
JanetRegExt net_cfuns[] = {
|
||||
JANET_CORE_REG("net/address", cfun_net_sockaddr),
|
||||
JANET_CORE_REG("net/listen", cfun_net_listen),
|
||||
JANET_CORE_REG("net/socket", cfun_net_socket),
|
||||
JANET_CORE_REG("net/accept", cfun_stream_accept),
|
||||
JANET_CORE_REG("net/accept-loop", cfun_stream_accept_loop),
|
||||
JANET_CORE_REG("net/read", cfun_stream_read),
|
||||
@@ -1090,6 +1276,8 @@ void janet_net_init(void) {
|
||||
#ifdef JANET_WINDOWS
|
||||
WSADATA wsaData;
|
||||
janet_assert(!WSAStartup(MAKEWORD(2, 2), &wsaData), "could not start winsock");
|
||||
janet_vm.connect_ex_loaded = 0;
|
||||
janet_vm.connect_ex = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
436
src/core/os.c
436
src/core/os.c
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -105,15 +105,6 @@ static int to_hex(uint8_t c) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef int (*Consumer)(JanetParser *p, JanetParseState *state, uint8_t c);
|
||||
struct JanetParseState {
|
||||
int32_t counter;
|
||||
int32_t argn;
|
||||
int flags;
|
||||
size_t line;
|
||||
size_t column;
|
||||
Consumer consumer;
|
||||
};
|
||||
|
||||
/* Define a stack on the main parser struct */
|
||||
#define DEF_PARSER_STACK(NAME, T, STACK, STACKCOUNT, STACKCAP) \
|
||||
@@ -231,7 +222,7 @@ static void delim_error(JanetParser *parser, size_t stack_index, char c, const c
|
||||
janet_buffer_push_u8(buffer, '`');
|
||||
}
|
||||
}
|
||||
janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column);
|
||||
janet_formatb(buffer, " opened at line %d, column %d", (int32_t) s->line, (int32_t) s->column);
|
||||
}
|
||||
parser->error = (const char *) janet_string(buffer->data, buffer->count);
|
||||
parser->flag |= JANET_PARSER_GENERATED_ERROR;
|
||||
@@ -363,8 +354,7 @@ static int stringend(JanetParser *p, JanetParseState *state) {
|
||||
JanetParseState top = p->states[p->statecount - 1];
|
||||
int32_t indent_col = (int32_t) top.column - 1;
|
||||
uint8_t *r = bufstart, *end = r + buflen;
|
||||
/* Check if there are any characters before the start column -
|
||||
* if so, do not reindent. */
|
||||
/* Unless there are only spaces before EOLs, disable reindenting */
|
||||
int reindent = 1;
|
||||
while (reindent && (r < end)) {
|
||||
if (*r++ == '\n') {
|
||||
@@ -374,34 +364,36 @@ static int stringend(JanetParser *p, JanetParseState *state) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') reindent = 1;
|
||||
}
|
||||
}
|
||||
/* Now reindent if able to, otherwise just drop leading newline. */
|
||||
if (!reindent) {
|
||||
if (buflen > 0 && bufstart[0] == '\n') {
|
||||
buflen--;
|
||||
bufstart++;
|
||||
}
|
||||
} else {
|
||||
/* Now reindent if able */
|
||||
if (reindent) {
|
||||
uint8_t *w = bufstart;
|
||||
r = bufstart;
|
||||
while (r < end) {
|
||||
if (*r == '\n') {
|
||||
if (r == bufstart) {
|
||||
/* Skip leading newline */
|
||||
r++;
|
||||
} else {
|
||||
*w++ = *r++;
|
||||
}
|
||||
*w++ = *r++;
|
||||
for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++);
|
||||
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') *w++ = *r++;
|
||||
} else {
|
||||
*w++ = *r++;
|
||||
}
|
||||
}
|
||||
buflen = (int32_t)(w - bufstart);
|
||||
}
|
||||
/* Check for trailing newline character so we can remove it */
|
||||
if (buflen > 0 && bufstart[buflen - 1] == '\n') {
|
||||
/* Check for leading EOL so we can remove it */
|
||||
if (buflen > 1 && bufstart[0] == '\r' && bufstart[1] == '\n') { /* Windows EOL */
|
||||
buflen = buflen - 2;
|
||||
bufstart = bufstart + 2;
|
||||
} else if (buflen > 0 && bufstart[0] == '\n') { /* Unix EOL */
|
||||
buflen--;
|
||||
bufstart++;
|
||||
}
|
||||
/* Check for trailing EOL so we can remove it */
|
||||
if (buflen > 1 && bufstart[buflen - 2] == '\r' && bufstart[buflen - 1] == '\n') { /* Windows EOL */
|
||||
buflen = buflen - 2;
|
||||
} else if (buflen > 0 && bufstart[buflen - 1] == '\n') { /* Unix EOL */
|
||||
buflen--;
|
||||
}
|
||||
}
|
||||
|
||||
228
src/core/peg.c
228
src/core/peg.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -194,6 +194,41 @@ tail:
|
||||
return memcmp(text, rule + 2, len) ? NULL : text + len;
|
||||
}
|
||||
|
||||
case RULE_DEBUG: {
|
||||
char buffer[32] = {0};
|
||||
size_t len = (size_t)(s->outer_text_end - text);
|
||||
memcpy(buffer, text, (len > 31 ? 31 : len));
|
||||
janet_eprintf("?? at [%s] (index %d)\n", buffer, (int32_t)(text - s->text_start));
|
||||
int has_color = janet_truthy(janet_dyn("err-color"));
|
||||
/* Accumulate buffer */
|
||||
if (s->scratch->count) {
|
||||
janet_eprintf("accumulate buffer: %v\n", janet_wrap_buffer(s->scratch));
|
||||
}
|
||||
/* Normal captures */
|
||||
if (s->captures->count) {
|
||||
janet_eprintf("stack [%d]:\n", s->captures->count);
|
||||
for (int32_t i = 0; i < s->captures->count; i++) {
|
||||
if (has_color) {
|
||||
janet_eprintf(" [%d]: %M\n", i, s->captures->data[i]);
|
||||
} else {
|
||||
janet_eprintf(" [%d]: %m\n", i, s->captures->data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Tagged captures */
|
||||
if (s->tagged_captures->count) {
|
||||
janet_eprintf("tag stack [%d]:\n", s->tagged_captures->count);
|
||||
for (int32_t i = 0; i < s->tagged_captures->count; i++) {
|
||||
if (has_color) {
|
||||
janet_eprintf(" [%d] tag=%d: %M\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]);
|
||||
} else {
|
||||
janet_eprintf(" [%d] tag=%d: %m\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
case RULE_NCHAR: {
|
||||
uint32_t n = rule[1];
|
||||
return (text + n > s->text_end) ? NULL : text + n;
|
||||
@@ -342,7 +377,7 @@ tail:
|
||||
while (captured < hi) {
|
||||
CapState cs2 = cap_save(s);
|
||||
next_text = peg_rule(s, rule_a, text);
|
||||
if (!next_text || next_text == text) {
|
||||
if (!next_text || ((next_text == text) && (hi == UINT32_MAX))) {
|
||||
cap_load(s, cs2);
|
||||
break;
|
||||
}
|
||||
@@ -465,6 +500,16 @@ tail:
|
||||
return result;
|
||||
}
|
||||
|
||||
case RULE_ONLY_TAGS: {
|
||||
CapState cs = cap_save(s);
|
||||
down1(s);
|
||||
const uint8_t *result = peg_rule(s, s->bytecode + rule[1], text);
|
||||
up1(s);
|
||||
if (!result) return NULL;
|
||||
cap_load_keept(s, cs);
|
||||
return result;
|
||||
}
|
||||
|
||||
case RULE_GROUP: {
|
||||
uint32_t tag = rule[2];
|
||||
int oldmode = s->mode;
|
||||
@@ -486,6 +531,30 @@ tail:
|
||||
return result;
|
||||
}
|
||||
|
||||
case RULE_NTH: {
|
||||
uint32_t nth = rule[1];
|
||||
if (nth > INT32_MAX) nth = INT32_MAX;
|
||||
uint32_t tag = rule[3];
|
||||
int oldmode = s->mode;
|
||||
CapState cs = cap_save(s);
|
||||
s->mode = PEG_MODE_NORMAL;
|
||||
down1(s);
|
||||
const uint8_t *result = peg_rule(s, s->bytecode + rule[2], text);
|
||||
up1(s);
|
||||
s->mode = oldmode;
|
||||
if (!result) return NULL;
|
||||
int32_t num_sub_captures = s->captures->count - cs.cap;
|
||||
Janet cap;
|
||||
if (num_sub_captures > (int32_t) nth) {
|
||||
cap = s->captures->data[cs.cap + nth];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
cap_load_keept(s, cs);
|
||||
pushcap(s, cap, tag);
|
||||
return result;
|
||||
}
|
||||
|
||||
case RULE_SUB: {
|
||||
const uint8_t *text_start = text;
|
||||
const uint32_t *rule_window = s->bytecode + rule[1];
|
||||
@@ -510,45 +579,85 @@ tail:
|
||||
return window_end;
|
||||
}
|
||||
|
||||
case RULE_TIL: {
|
||||
const uint32_t *rule_terminus = s->bytecode + rule[1];
|
||||
const uint32_t *rule_subpattern = s->bytecode + rule[2];
|
||||
|
||||
const uint8_t *terminus_start = text;
|
||||
const uint8_t *terminus_end = NULL;
|
||||
down1(s);
|
||||
while (terminus_start <= s->text_end) {
|
||||
CapState cs2 = cap_save(s);
|
||||
terminus_end = peg_rule(s, rule_terminus, terminus_start);
|
||||
cap_load(s, cs2);
|
||||
if (terminus_end) {
|
||||
break;
|
||||
}
|
||||
terminus_start++;
|
||||
}
|
||||
up1(s);
|
||||
|
||||
if (!terminus_end) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const uint8_t *saved_end = s->text_end;
|
||||
s->text_end = terminus_start;
|
||||
down1(s);
|
||||
const uint8_t *matched = peg_rule(s, rule_subpattern, text);
|
||||
up1(s);
|
||||
s->text_end = saved_end;
|
||||
|
||||
if (!matched) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return terminus_end;
|
||||
}
|
||||
|
||||
case RULE_SPLIT: {
|
||||
const uint8_t *saved_end = s->text_end;
|
||||
const uint32_t *rule_separator = s->bytecode + rule[1];
|
||||
const uint32_t *rule_subpattern = s->bytecode + rule[2];
|
||||
|
||||
const uint8_t *separator_end = NULL;
|
||||
do {
|
||||
const uint8_t *text_start = text;
|
||||
const uint8_t *chunk_start = text;
|
||||
const uint8_t *chunk_end = NULL;
|
||||
|
||||
while (text <= saved_end) {
|
||||
/* Find next split (or end of text) */
|
||||
CapState cs = cap_save(s);
|
||||
down1(s);
|
||||
while (text <= s->text_end) {
|
||||
separator_end = peg_rule(s, rule_separator, text);
|
||||
while (text <= saved_end) {
|
||||
chunk_end = text;
|
||||
const uint8_t *check = peg_rule(s, rule_separator, text);
|
||||
cap_load(s, cs);
|
||||
if (separator_end) {
|
||||
if (check) {
|
||||
text = check;
|
||||
break;
|
||||
}
|
||||
text++;
|
||||
}
|
||||
up1(s);
|
||||
|
||||
if (separator_end) {
|
||||
s->text_end = text;
|
||||
text = separator_end;
|
||||
}
|
||||
|
||||
/* Match between splits */
|
||||
s->text_end = chunk_end;
|
||||
down1(s);
|
||||
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, text_start);
|
||||
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, chunk_start);
|
||||
up1(s);
|
||||
s->text_end = saved_end;
|
||||
if (!subpattern_end) return NULL; /* Don't match anything */
|
||||
|
||||
if (!subpattern_end) {
|
||||
return NULL;
|
||||
}
|
||||
} while (separator_end);
|
||||
/* Ensure forward progress */
|
||||
if (text == chunk_start) return NULL;
|
||||
chunk_start = text;
|
||||
}
|
||||
|
||||
s->text_end = saved_end;
|
||||
return s->text_end;
|
||||
}
|
||||
|
||||
case RULE_REPLACE:
|
||||
case RULE_MATCHSPLICE:
|
||||
case RULE_MATCHTIME: {
|
||||
uint32_t tag = rule[3];
|
||||
int oldmode = s->mode;
|
||||
@@ -589,8 +698,17 @@ tail:
|
||||
break;
|
||||
}
|
||||
cap_load_keept(s, cs);
|
||||
if (rule[0] == RULE_MATCHTIME && !janet_truthy(cap)) return NULL;
|
||||
pushcap(s, cap, tag);
|
||||
if (rule[0] != RULE_REPLACE && !janet_truthy(cap)) return NULL; /* matchtime or matchtime flatten */
|
||||
const Janet *elements = NULL;
|
||||
int32_t len = 0;
|
||||
if ((rule[0] == RULE_MATCHSPLICE) && janet_indexed_view(cap, &elements, &len)) {
|
||||
/* unpack and flatten capture */
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
pushcap(s, elements[i], tag);
|
||||
}
|
||||
} else {
|
||||
pushcap(s, cap, tag);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1061,6 +1179,9 @@ static void spec_thru(Builder *b, int32_t argc, const Janet *argv) {
|
||||
static void spec_drop(Builder *b, int32_t argc, const Janet *argv) {
|
||||
spec_onerule(b, argc, argv, RULE_DROP);
|
||||
}
|
||||
static void spec_only_tags(Builder *b, int32_t argc, const Janet *argv) {
|
||||
spec_onerule(b, argc, argv, RULE_ONLY_TAGS);
|
||||
}
|
||||
|
||||
/* Rule of the form [rule, tag] */
|
||||
static void spec_cap1(Builder *b, int32_t argc, const Janet *argv, uint32_t op) {
|
||||
@@ -1084,6 +1205,15 @@ static void spec_unref(Builder *b, int32_t argc, const Janet *argv) {
|
||||
spec_cap1(b, argc, argv, RULE_UNREF);
|
||||
}
|
||||
|
||||
static void spec_nth(Builder *b, int32_t argc, const Janet *argv) {
|
||||
peg_arity(b, argc, 2, 3);
|
||||
Reserve r = reserve(b, 4);
|
||||
uint32_t nth = peg_getnat(b, argv[0]);
|
||||
uint32_t rule = peg_compile1(b, argv[1]);
|
||||
uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0;
|
||||
emit_3(r, RULE_NTH, nth, rule, tag);
|
||||
}
|
||||
|
||||
static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) {
|
||||
peg_arity(b, argc, 1, 3);
|
||||
Reserve r = reserve(b, 4);
|
||||
@@ -1150,6 +1280,14 @@ static void spec_constant(Builder *b, int32_t argc, const Janet *argv) {
|
||||
emit_2(r, RULE_CONSTANT, emit_constant(b, argv[0]), tag);
|
||||
}
|
||||
|
||||
static void spec_debug(Builder *b, int32_t argc, const Janet *argv) {
|
||||
peg_arity(b, argc, 0, 0);
|
||||
Reserve r = reserve(b, 1);
|
||||
uint32_t empty = 0;
|
||||
(void) argv;
|
||||
emit_rule(r, RULE_DEBUG, 0, &empty);
|
||||
}
|
||||
|
||||
static void spec_replace(Builder *b, int32_t argc, const Janet *argv) {
|
||||
peg_arity(b, argc, 2, 3);
|
||||
Reserve r = reserve(b, 4);
|
||||
@@ -1159,7 +1297,7 @@ static void spec_replace(Builder *b, int32_t argc, const Janet *argv) {
|
||||
emit_3(r, RULE_REPLACE, subrule, constant, tag);
|
||||
}
|
||||
|
||||
static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) {
|
||||
static void spec_matchtime_impl(Builder *b, int32_t argc, const Janet *argv, uint32_t op) {
|
||||
peg_arity(b, argc, 2, 3);
|
||||
Reserve r = reserve(b, 4);
|
||||
uint32_t subrule = peg_compile1(b, argv[0]);
|
||||
@@ -1170,7 +1308,15 @@ static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) {
|
||||
}
|
||||
uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0;
|
||||
uint32_t cindex = emit_constant(b, fun);
|
||||
emit_3(r, RULE_MATCHTIME, subrule, cindex, tag);
|
||||
emit_3(r, op, subrule, cindex, tag);
|
||||
}
|
||||
|
||||
static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) {
|
||||
spec_matchtime_impl(b, argc, argv, RULE_MATCHTIME);
|
||||
}
|
||||
|
||||
static void spec_matchtime_splice(Builder *b, int32_t argc, const Janet *argv) {
|
||||
spec_matchtime_impl(b, argc, argv, RULE_MATCHSPLICE);
|
||||
}
|
||||
|
||||
static void spec_sub(Builder *b, int32_t argc, const Janet *argv) {
|
||||
@@ -1181,6 +1327,14 @@ static void spec_sub(Builder *b, int32_t argc, const Janet *argv) {
|
||||
emit_2(r, RULE_SUB, subrule1, subrule2);
|
||||
}
|
||||
|
||||
static void spec_til(Builder *b, int32_t argc, const Janet *argv) {
|
||||
peg_fixarity(b, argc, 2);
|
||||
Reserve r = reserve(b, 3);
|
||||
uint32_t subrule1 = peg_compile1(b, argv[0]);
|
||||
uint32_t subrule2 = peg_compile1(b, argv[1]);
|
||||
emit_2(r, RULE_TIL, subrule1, subrule2);
|
||||
}
|
||||
|
||||
static void spec_split(Builder *b, int32_t argc, const Janet *argv) {
|
||||
peg_fixarity(b, argc, 2);
|
||||
Reserve r = reserve(b, 3);
|
||||
@@ -1238,6 +1392,7 @@ static const SpecialPair peg_specials[] = {
|
||||
{"<-", spec_capture},
|
||||
{">", spec_look},
|
||||
{"?", spec_opt},
|
||||
{"??", spec_debug},
|
||||
{"accumulate", spec_accumulate},
|
||||
{"any", spec_any},
|
||||
{"argument", spec_argument},
|
||||
@@ -1248,9 +1403,11 @@ static const SpecialPair peg_specials[] = {
|
||||
{"between", spec_between},
|
||||
{"capture", spec_capture},
|
||||
{"choice", spec_choice},
|
||||
{"cms", spec_matchtime_splice},
|
||||
{"cmt", spec_matchtime},
|
||||
{"column", spec_column},
|
||||
{"constant", spec_constant},
|
||||
{"debug", spec_debug},
|
||||
{"drop", spec_drop},
|
||||
{"error", spec_error},
|
||||
{"group", spec_group},
|
||||
@@ -1262,7 +1419,9 @@ static const SpecialPair peg_specials[] = {
|
||||
{"line", spec_line},
|
||||
{"look", spec_look},
|
||||
{"not", spec_not},
|
||||
{"nth", spec_nth},
|
||||
{"number", spec_capture_number},
|
||||
{"only-tags", spec_only_tags},
|
||||
{"opt", spec_opt},
|
||||
{"position", spec_position},
|
||||
{"quote", spec_capture},
|
||||
@@ -1275,6 +1434,7 @@ static const SpecialPair peg_specials[] = {
|
||||
{"split", spec_split},
|
||||
{"sub", spec_sub},
|
||||
{"thru", spec_thru},
|
||||
{"til", spec_til},
|
||||
{"to", spec_to},
|
||||
{"uint", spec_uint_le},
|
||||
{"uint-be", spec_uint_be},
|
||||
@@ -1368,6 +1528,11 @@ static uint32_t peg_compile1(Builder *b, Janet peg) {
|
||||
emit_bytes(b, RULE_LITERAL, len, str);
|
||||
break;
|
||||
}
|
||||
case JANET_BUFFER: {
|
||||
const JanetBuffer *buf = janet_unwrap_buffer(peg);
|
||||
emit_bytes(b, RULE_LITERAL, buf->count, buf->data);
|
||||
break;
|
||||
}
|
||||
case JANET_TABLE: {
|
||||
/* Build grammar table */
|
||||
JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg));
|
||||
@@ -1496,7 +1661,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
|
||||
/* After here, no panics except for the bad: label. */
|
||||
|
||||
/* Keep track at each index if an instruction was
|
||||
* reference (0x01) or is in a main bytecode position
|
||||
* referenced (0x01) or is in a main bytecode position
|
||||
* (0x02). This lets us do a linear scan and not
|
||||
* need to a depth first traversal. It is stricter
|
||||
* than a dfs by not allowing certain kinds of unused
|
||||
@@ -1519,6 +1684,10 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
|
||||
case RULE_LITERAL:
|
||||
i += 2 + ((rule[1] + 3) >> 2);
|
||||
break;
|
||||
case RULE_DEBUG:
|
||||
/* [0 words] */
|
||||
i += 1;
|
||||
break;
|
||||
case RULE_NCHAR:
|
||||
case RULE_NOTNCHAR:
|
||||
case RULE_RANGE:
|
||||
@@ -1602,6 +1771,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
|
||||
break;
|
||||
case RULE_REPLACE:
|
||||
case RULE_MATCHTIME:
|
||||
case RULE_MATCHSPLICE:
|
||||
/* [rule, constant, tag] */
|
||||
if (rule[1] >= blen) goto bad;
|
||||
if (rule[2] >= clen) goto bad;
|
||||
@@ -1609,6 +1779,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
|
||||
i += 4;
|
||||
break;
|
||||
case RULE_SUB:
|
||||
case RULE_TIL:
|
||||
case RULE_SPLIT:
|
||||
/* [rule, rule] */
|
||||
if (rule[1] >= blen) goto bad;
|
||||
@@ -1619,6 +1790,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
|
||||
break;
|
||||
case RULE_ERROR:
|
||||
case RULE_DROP:
|
||||
case RULE_ONLY_TAGS:
|
||||
case RULE_NOT:
|
||||
case RULE_TO:
|
||||
case RULE_THRU:
|
||||
@@ -1632,6 +1804,12 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
|
||||
if (rule[1] > JANET_MAX_READINT_WIDTH) goto bad;
|
||||
i += 3;
|
||||
break;
|
||||
case RULE_NTH:
|
||||
/* [nth, rule, tag] */
|
||||
if (rule[2] >= blen) goto bad;
|
||||
op_flags[rule[2]] |= 0x01;
|
||||
i += 4;
|
||||
break;
|
||||
default:
|
||||
goto bad;
|
||||
}
|
||||
@@ -1725,8 +1903,8 @@ 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 supplement "
|
||||
"the grammar of the peg for otherwise undefined peg keywords.") {
|
||||
"if the same peg will be used multiple times. `(dyn :peg-grammar)` replaces "
|
||||
"`default-peg-grammar` for the grammar of the peg.") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetPeg *peg = compile_peg(argv[0]);
|
||||
return janet_wrap_abstract(peg);
|
||||
|
||||
126
src/core/pp.c
126
src/core/pp.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -487,6 +487,7 @@ static const char *janet_pretty_colors[] = {
|
||||
#define JANET_PRETTY_DICT_ONELINE 4
|
||||
#define JANET_PRETTY_IND_ONELINE 10
|
||||
#define JANET_PRETTY_DICT_LIMIT 30
|
||||
#define JANET_PRETTY_DICT_KEYSORT_LIMIT 2000
|
||||
#define JANET_PRETTY_ARRAY_LIMIT 160
|
||||
|
||||
/* Helper for pretty printing */
|
||||
@@ -625,55 +626,78 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
|
||||
if (S->depth == 0) {
|
||||
janet_buffer_push_cstring(S->buffer, "...");
|
||||
} else {
|
||||
int32_t i = 0, len = 0, cap = 0;
|
||||
int32_t len = 0, cap = 0;
|
||||
const JanetKV *kvs = NULL;
|
||||
janet_dictionary_view(x, &kvs, &len, &cap);
|
||||
if (!istable && !(S->flags & JANET_PRETTY_ONELINE) && len >= JANET_PRETTY_DICT_ONELINE)
|
||||
janet_buffer_push_u8(S->buffer, ' ');
|
||||
if (is_dict_value && len >= JANET_PRETTY_DICT_ONELINE) print_newline(S, 0);
|
||||
int32_t ks_start = S->keysort_start;
|
||||
|
||||
/* Ensure buffer is large enough to sort keys. */
|
||||
int truncated = 0;
|
||||
int64_t mincap = (int64_t) len + (int64_t) ks_start;
|
||||
if (mincap > INT32_MAX) {
|
||||
truncated = 1;
|
||||
len = 0;
|
||||
mincap = ks_start;
|
||||
}
|
||||
|
||||
if (S->keysort_capacity < mincap) {
|
||||
if (mincap >= INT32_MAX / 2) {
|
||||
S->keysort_capacity = INT32_MAX;
|
||||
} else {
|
||||
S->keysort_capacity = (int32_t)(mincap * 2);
|
||||
/* Shortcut for huge dictionaries, don't bother sorting keys */
|
||||
if (len > JANET_PRETTY_DICT_KEYSORT_LIMIT) {
|
||||
if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) {
|
||||
len = JANET_PRETTY_DICT_LIMIT;
|
||||
truncated = 1;
|
||||
}
|
||||
S->keysort_buffer = janet_srealloc(S->keysort_buffer, sizeof(int32_t) * S->keysort_capacity);
|
||||
if (NULL == S->keysort_buffer) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
int32_t j = 0;
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
while (janet_checktype(kvs[j].key, JANET_NIL)) j++;
|
||||
if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE);
|
||||
janet_pretty_one(S, kvs[j].key, 0);
|
||||
janet_buffer_push_u8(S->buffer, ' ');
|
||||
janet_pretty_one(S, kvs[j].value, 1);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
if (truncated) {
|
||||
print_newline(S, 0);
|
||||
janet_buffer_push_cstring(S->buffer, "...");
|
||||
}
|
||||
} else {
|
||||
/* Sorted keys dictionaries */
|
||||
|
||||
janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start);
|
||||
S->keysort_start += len;
|
||||
if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) {
|
||||
len = JANET_PRETTY_DICT_LIMIT;
|
||||
truncated = 1;
|
||||
}
|
||||
/* Ensure buffer is large enough to sort keys. */
|
||||
int64_t mincap = (int64_t) len + (int64_t) ks_start;
|
||||
if (mincap > INT32_MAX) {
|
||||
truncated = 1;
|
||||
len = 0;
|
||||
mincap = ks_start;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE);
|
||||
int32_t j = S->keysort_buffer[i + ks_start];
|
||||
janet_pretty_one(S, kvs[j].key, 0);
|
||||
janet_buffer_push_u8(S->buffer, ' ');
|
||||
janet_pretty_one(S, kvs[j].value, 1);
|
||||
}
|
||||
if (S->keysort_capacity < mincap) {
|
||||
if (mincap >= INT32_MAX / 2) {
|
||||
S->keysort_capacity = INT32_MAX;
|
||||
} else {
|
||||
S->keysort_capacity = (int32_t)(mincap * 2);
|
||||
}
|
||||
S->keysort_buffer = janet_srealloc(S->keysort_buffer, sizeof(int32_t) * S->keysort_capacity);
|
||||
if (NULL == S->keysort_buffer) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
print_newline(S, 0);
|
||||
janet_buffer_push_cstring(S->buffer, "...");
|
||||
}
|
||||
janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start);
|
||||
S->keysort_start += len;
|
||||
if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) {
|
||||
len = JANET_PRETTY_DICT_LIMIT;
|
||||
truncated = 1;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE);
|
||||
int32_t j = S->keysort_buffer[i + ks_start];
|
||||
janet_pretty_one(S, kvs[j].key, 0);
|
||||
janet_buffer_push_u8(S->buffer, ' ');
|
||||
janet_pretty_one(S, kvs[j].value, 1);
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
print_newline(S, 0);
|
||||
janet_buffer_push_cstring(S->buffer, "...");
|
||||
}
|
||||
|
||||
}
|
||||
S->keysort_start = ks_start;
|
||||
}
|
||||
S->indent -= 2;
|
||||
@@ -897,7 +921,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) {
|
||||
case 's':
|
||||
case 'S': {
|
||||
const char *str = va_arg(args, const char *);
|
||||
int32_t len = c[-1] == 's'
|
||||
int32_t len = (c[-1] == 's')
|
||||
? (int32_t) strlen(str)
|
||||
: janet_string_length((JanetString) str);
|
||||
if (form[2] == '\0')
|
||||
@@ -1023,10 +1047,20 @@ void janet_buffer_format(
|
||||
char form[MAX_FORMAT], item[MAX_ITEM];
|
||||
char width[3], precision[3];
|
||||
int nb = 0; /* number of bytes in added item */
|
||||
if (++arg >= argc)
|
||||
janet_panic("not enough values for format");
|
||||
#ifdef JANET_PLAN9
|
||||
if (*strfrmt == 'r') {
|
||||
rerrstr(item, MAX_ITEM);
|
||||
nb = strlen(item);
|
||||
} else
|
||||
#endif
|
||||
if (++arg >= argc)
|
||||
janet_panic("not enough values for format");
|
||||
strfrmt = scanformat(strfrmt, form, width, precision);
|
||||
switch (*strfrmt++) {
|
||||
#ifdef JANET_PLAN9
|
||||
case 'r':
|
||||
break;
|
||||
#endif
|
||||
case 'c': {
|
||||
nb = snprintf(item, MAX_ITEM, form, (int)
|
||||
janet_getinteger(argv, arg));
|
||||
@@ -1060,19 +1094,11 @@ void janet_buffer_format(
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
JanetByteView bytes = janet_getbytes(argv, arg);
|
||||
const uint8_t *s = bytes.bytes;
|
||||
int32_t l = bytes.len;
|
||||
const char *s = janet_getcbytes(argv, arg);
|
||||
if (form[2] == '\0')
|
||||
janet_buffer_push_bytes(b, s, l);
|
||||
janet_buffer_push_cstring(b, s);
|
||||
else {
|
||||
if (l != (int32_t) strlen((const char *) s))
|
||||
janet_panic("string contains zeros");
|
||||
if (!strchr(form, '.') && l >= 100) {
|
||||
janet_panic("no precision and string is too long to be formatted");
|
||||
} else {
|
||||
nb = snprintf(item, MAX_ITEM, form, s);
|
||||
}
|
||||
nb = snprintf(item, MAX_ITEM, form, s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -26,9 +26,10 @@
|
||||
#include "state.h"
|
||||
#endif
|
||||
|
||||
/* Run a string */
|
||||
/* Run a string of code. The return value is a set of error flags, JANET_DO_ERROR_RUNTIME, JANET_DO_ERROR_COMPILE, and JANET_DOR_ERROR_PARSE if
|
||||
* any errors were encountered in those phases. More information is printed to stderr. */
|
||||
int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) {
|
||||
JanetParser parser;
|
||||
JanetParser *parser;
|
||||
int errflags = 0, done = 0;
|
||||
int32_t index = 0;
|
||||
Janet ret = janet_wrap_nil();
|
||||
@@ -37,14 +38,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
|
||||
|
||||
if (where) janet_gcroot(janet_wrap_string(where));
|
||||
if (NULL == sourcePath) sourcePath = "<unknown>";
|
||||
janet_parser_init(&parser);
|
||||
parser = janet_abstract(&janet_parser_type, sizeof(JanetParser));
|
||||
janet_parser_init(parser);
|
||||
janet_gcroot(janet_wrap_abstract(parser));
|
||||
|
||||
/* While we haven't seen an error */
|
||||
while (!done) {
|
||||
|
||||
/* Evaluate parsed values */
|
||||
while (janet_parser_has_more(&parser)) {
|
||||
Janet form = janet_parser_produce(&parser);
|
||||
while (janet_parser_has_more(parser)) {
|
||||
Janet form = janet_parser_produce(parser);
|
||||
JanetCompileResult cres = janet_compile(form, env, where);
|
||||
if (cres.status == JANET_COMPILE_OK) {
|
||||
JanetFunction *f = janet_thunk(cres.funcdef);
|
||||
@@ -53,27 +56,30 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
|
||||
JanetSignal status = janet_continue(fiber, janet_wrap_nil(), &ret);
|
||||
if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) {
|
||||
janet_stacktrace_ext(fiber, ret, "");
|
||||
errflags |= 0x01;
|
||||
errflags |= JANET_DO_ERROR_RUNTIME;
|
||||
done = 1;
|
||||
}
|
||||
} else {
|
||||
ret = janet_wrap_string(cres.error);
|
||||
int32_t line = (int32_t) parser.line;
|
||||
int32_t col = (int32_t) parser.column;
|
||||
int32_t line = (int32_t) parser->line;
|
||||
int32_t col = (int32_t) parser->column;
|
||||
if ((cres.error_mapping.line > 0) &&
|
||||
(cres.error_mapping.column > 0)) {
|
||||
line = cres.error_mapping.line;
|
||||
col = cres.error_mapping.column;
|
||||
}
|
||||
JanetString ctx = janet_formatc("%s:%d:%d: compile error",
|
||||
sourcePath, line, col);
|
||||
JanetString errstr = janet_formatc("%s: %s",
|
||||
(const char *)ctx,
|
||||
(const char *)cres.error);
|
||||
ret = janet_wrap_string(errstr);
|
||||
if (cres.macrofiber) {
|
||||
janet_eprintf("%s:%d:%d: compile error", sourcePath,
|
||||
line, col);
|
||||
janet_eprintf("%s", (const char *)ctx);
|
||||
janet_stacktrace_ext(cres.macrofiber, ret, "");
|
||||
} else {
|
||||
janet_eprintf("%s:%d:%d: compile error: %s\n", sourcePath,
|
||||
line, col, (const char *)cres.error);
|
||||
janet_eprintf("%s\n", (const char *)errstr);
|
||||
}
|
||||
errflags |= 0x02;
|
||||
errflags |= JANET_DO_ERROR_COMPILE;
|
||||
done = 1;
|
||||
}
|
||||
}
|
||||
@@ -81,26 +87,28 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
|
||||
if (done) break;
|
||||
|
||||
/* Dispatch based on parse state */
|
||||
switch (janet_parser_status(&parser)) {
|
||||
switch (janet_parser_status(parser)) {
|
||||
case JANET_PARSE_DEAD:
|
||||
done = 1;
|
||||
break;
|
||||
case JANET_PARSE_ERROR: {
|
||||
const char *e = janet_parser_error(&parser);
|
||||
errflags |= 0x04;
|
||||
ret = janet_cstringv(e);
|
||||
int32_t line = (int32_t) parser.line;
|
||||
int32_t col = (int32_t) parser.column;
|
||||
janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e);
|
||||
errflags |= JANET_DO_ERROR_PARSE;
|
||||
int32_t line = (int32_t) parser->line;
|
||||
int32_t col = (int32_t) parser->column;
|
||||
JanetString errstr = janet_formatc("%s:%d:%d: parse error: %s",
|
||||
sourcePath, line, col,
|
||||
janet_parser_error(parser));
|
||||
ret = janet_wrap_string(errstr);
|
||||
janet_eprintf("%s\n", (const char *)errstr);
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
case JANET_PARSE_ROOT:
|
||||
case JANET_PARSE_PENDING:
|
||||
if (index >= len) {
|
||||
janet_parser_eof(&parser);
|
||||
janet_parser_eof(parser);
|
||||
} else {
|
||||
janet_parser_consume(&parser, bytes[index++]);
|
||||
janet_parser_consume(parser, bytes[index++]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -108,7 +116,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
|
||||
}
|
||||
|
||||
/* Clean up and return errors */
|
||||
janet_parser_deinit(&parser);
|
||||
janet_gcunroot(janet_wrap_abstract(parser));
|
||||
if (where) janet_gcunroot(janet_wrap_string(where));
|
||||
#ifdef JANET_EV
|
||||
/* Enter the event loop if we are not already in it */
|
||||
@@ -119,7 +127,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
|
||||
janet_loop();
|
||||
if (fiber) {
|
||||
janet_gcunroot(janet_wrap_fiber(fiber));
|
||||
ret = fiber->last_value;
|
||||
if (!errflags)
|
||||
ret = fiber->last_value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -307,14 +307,14 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv)
|
||||
/* Add attributes to a global def or var table */
|
||||
static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) {
|
||||
int32_t i;
|
||||
JanetTable *tab = janet_table(2);
|
||||
const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL
|
||||
? ((const char *)janet_unwrap_symbol(argv[0]))
|
||||
: "<multiple bindings>";
|
||||
if (argn < 2) {
|
||||
janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind));
|
||||
return NULL;
|
||||
}
|
||||
JanetTable *tab = janet_table(2);
|
||||
const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL
|
||||
? ((const char *)janet_unwrap_symbol(argv[0]))
|
||||
: "<multiple bindings>";
|
||||
for (i = 1; i < argn - 1; i++) {
|
||||
Janet attr = argv[i];
|
||||
switch (janet_type(attr)) {
|
||||
@@ -362,6 +362,23 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt
|
||||
janet_indexed_view(lhs, &view_lhs.items, &view_lhs.len);
|
||||
janet_indexed_view(rhs, &view_rhs.items, &view_rhs.len);
|
||||
int found_amp = 0;
|
||||
int found_splice = 0;
|
||||
/* Check for (def [x y z] [(splice [1 2 3]) 4 5 6]), bail out of optimization */
|
||||
for (int32_t i = 0; i < view_rhs.len; i++) {
|
||||
if (!janet_checktype(view_rhs.items[i], JANET_TUPLE)) {
|
||||
continue;
|
||||
}
|
||||
JanetTuple tup = janet_unwrap_tuple(view_rhs.items[i]);
|
||||
if (janet_tuple_length(tup) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (janet_symeq(tup[0], "splice")) {
|
||||
found_splice = 1;
|
||||
/* Good error will be generated later. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Check for (def [x & more] [1 2 3]), bail out of optimization */
|
||||
for (int32_t i = 0; i < view_lhs.len; i++) {
|
||||
if (janet_symeq(view_lhs.items[i], "&")) {
|
||||
found_amp = 1;
|
||||
@@ -369,7 +386,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_amp) {
|
||||
if (!found_amp && !found_splice) {
|
||||
for (int32_t i = 0; i < view_lhs.len; i++) {
|
||||
Janet sub_rhs = view_rhs.len <= i ? janet_wrap_nil() : view_rhs.items[i];
|
||||
into = dohead_destructure(c, into, subopts, view_lhs.items[i], sub_rhs);
|
||||
@@ -387,7 +404,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt
|
||||
}
|
||||
|
||||
/* Def or var a symbol in a local scope */
|
||||
static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret) {
|
||||
static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret, int no_unused) {
|
||||
int isUnnamedRegister = !(ret.flags & JANET_SLOT_NAMED) &&
|
||||
ret.index > 0 &&
|
||||
ret.envindex >= 0;
|
||||
@@ -408,7 +425,11 @@ static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, Janet
|
||||
ret = localslot;
|
||||
}
|
||||
ret.flags |= flags;
|
||||
janetc_nameslot(c, head, ret);
|
||||
if ((c->scope->flags & JANET_SCOPE_TOP) || no_unused) {
|
||||
janetc_nameslot_no_unused(c, head, ret);
|
||||
} else {
|
||||
janetc_nameslot(c, head, ret);
|
||||
}
|
||||
return !isUnnamedRegister;
|
||||
}
|
||||
|
||||
@@ -422,8 +443,7 @@ static int varleaf(
|
||||
JanetSlot refslot;
|
||||
JanetTable *entry = janet_table_clone(reftab);
|
||||
|
||||
Janet redef_kw = janet_ckeywordv("redef");
|
||||
int is_redef = janet_truthy(janet_table_get(c->env, redef_kw));
|
||||
int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef"));
|
||||
|
||||
JanetArray *ref;
|
||||
JanetBinding old_binding;
|
||||
@@ -443,7 +463,17 @@ static int varleaf(
|
||||
janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0);
|
||||
return 1;
|
||||
} else {
|
||||
return namelocal(c, sym, JANET_SLOT_MUTABLE, s);
|
||||
int no_unused = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "unused"));
|
||||
return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused);
|
||||
}
|
||||
}
|
||||
|
||||
static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) {
|
||||
if (!(c->scope->flags & JANET_SCOPE_TOP) && attr_table && attr_table->count) {
|
||||
/* A macro is a normal lint, other metadata is a strict lint */
|
||||
if (janet_truthy(janet_table_get_keyword(attr_table, "macro"))) {
|
||||
janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,6 +483,7 @@ static JanetSlot janetc_var(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
if (c->result.status == JANET_COMPILE_ERROR) {
|
||||
return janetc_cslot(janet_wrap_nil());
|
||||
}
|
||||
check_metadata_lint(c, attr_table);
|
||||
SlotHeadPair *into = NULL;
|
||||
into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]);
|
||||
if (c->result.status == JANET_COMPILE_ERROR) {
|
||||
@@ -479,9 +510,8 @@ static int defleaf(
|
||||
janet_table_put(entry, janet_ckeywordv("source-map"),
|
||||
janet_wrap_tuple(janetc_make_sourcemap(c)));
|
||||
|
||||
Janet redef_kw = janet_ckeywordv("redef");
|
||||
int is_redef = janet_truthy(janet_table_get(c->env, redef_kw));
|
||||
if (is_redef) janet_table_put(entry, redef_kw, janet_wrap_true());
|
||||
int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef"));
|
||||
if (is_redef) janet_table_put(entry, janet_ckeywordv("redef"), janet_wrap_true());
|
||||
|
||||
if (is_redef) {
|
||||
JanetBinding binding = janet_resolve_ext(c->env, sym);
|
||||
@@ -504,7 +534,8 @@ static int defleaf(
|
||||
/* Add env entry to env */
|
||||
janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry));
|
||||
}
|
||||
return namelocal(c, sym, 0, s);
|
||||
int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused"));
|
||||
return namelocal(c, sym, 0, s, no_unused);
|
||||
}
|
||||
|
||||
static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
@@ -513,6 +544,7 @@ static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
if (c->result.status == JANET_COMPILE_ERROR) {
|
||||
return janetc_cslot(janet_wrap_nil());
|
||||
}
|
||||
check_metadata_lint(c, attr_table);
|
||||
opts.flags &= ~JANET_FOPTS_HINT;
|
||||
SlotHeadPair *into = NULL;
|
||||
into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]);
|
||||
@@ -652,8 +684,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
|
||||
/* Write jumps - only add jump lengths if jump actually emitted */
|
||||
labeld = janet_v_count(c->buffer);
|
||||
c->buffer[labeljr] |= (labelr - labeljr) << 16;
|
||||
if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8;
|
||||
if (labeljr < labeld) {
|
||||
c->buffer[labeljr] |= (labelr - labeljr) << 16;
|
||||
if (!tail && labeljd < labeld) c->buffer[labeljd] |= (labeld - labeljd) << 8;
|
||||
}
|
||||
|
||||
if (tail) target.flags |= JANET_SLOT_RETURNED;
|
||||
return target;
|
||||
@@ -875,7 +909,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv)
|
||||
janetc_regalloc_freetemp(&c->scope->ra, tempself, JANETC_REGTEMP_0);
|
||||
/* Compile function */
|
||||
JanetFuncDef *def = janetc_pop_funcdef(c);
|
||||
def->name = janet_cstring("_while");
|
||||
def->name = janet_cstring("while");
|
||||
janet_def_addflags(def);
|
||||
int32_t defindex = janetc_addfuncdef(c, def);
|
||||
/* And then load the closure and call it. */
|
||||
@@ -1042,6 +1076,14 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Compile named arguments */
|
||||
if (namedargs) {
|
||||
Janet param = janet_wrap_table(named_table);
|
||||
destructure(c, param, named_slot, defleaf, NULL);
|
||||
janetc_freeslot(c, named_slot);
|
||||
janet_v_free(named_params);
|
||||
}
|
||||
|
||||
/* Compile destructed params */
|
||||
int32_t j = 0;
|
||||
for (i = 0; i < paramcount; i++) {
|
||||
@@ -1055,14 +1097,6 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
}
|
||||
janet_v_free(destructed_params);
|
||||
|
||||
/* Compile named arguments */
|
||||
if (namedargs) {
|
||||
Janet param = janet_wrap_table(named_table);
|
||||
destructure(c, param, named_slot, defleaf, NULL);
|
||||
janetc_freeslot(c, named_slot);
|
||||
janet_v_free(named_params);
|
||||
}
|
||||
|
||||
max_arity = (vararg || allow_extra) ? INT32_MAX : arity;
|
||||
if (!seenopt) min_arity = arity;
|
||||
|
||||
@@ -1084,7 +1118,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
JanetSlot slot = janetc_farslot(c);
|
||||
slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION;
|
||||
janetc_emit_s(c, JOP_LOAD_SELF, slot, 1);
|
||||
janetc_nameslot(c, sym, slot);
|
||||
janetc_nameslot_no_unused(c, sym, slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,8 +1139,12 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
def->arity = arity;
|
||||
def->min_arity = min_arity;
|
||||
def->max_arity = max_arity;
|
||||
if (named_table != NULL) {
|
||||
def->named_args_count = named_table->count;
|
||||
}
|
||||
if (vararg) def->flags |= JANET_FUNCDEF_FLAG_VARARG;
|
||||
if (structarg) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG;
|
||||
if (namedargs) def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
|
||||
|
||||
if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */
|
||||
janet_def_addflags(def);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -23,11 +23,16 @@
|
||||
#ifndef JANET_STATE_H_defined
|
||||
#define JANET_STATE_H_defined
|
||||
|
||||
#ifndef JANET_AMALG
|
||||
#include "features.h"
|
||||
#include <janet.h>
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#ifdef JANET_EV
|
||||
#ifndef JANET_WINDOWS
|
||||
#ifdef JANET_WINDOWS
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
#endif
|
||||
@@ -53,13 +58,22 @@ typedef struct {
|
||||
void *data;
|
||||
} JanetQueue;
|
||||
|
||||
#ifdef JANET_EV
|
||||
typedef struct {
|
||||
JanetTimestamp when;
|
||||
JanetFiber *fiber;
|
||||
JanetFiber *curr_fiber;
|
||||
uint32_t sched_id;
|
||||
int is_error;
|
||||
int has_worker;
|
||||
#ifdef JANET_WINDOWS
|
||||
HANDLE worker;
|
||||
HANDLE worker_event;
|
||||
#else
|
||||
pthread_t worker;
|
||||
#endif
|
||||
} JanetTimeout;
|
||||
#endif
|
||||
|
||||
/* Registry table for C functions - contains metadata that can
|
||||
* be looked up by cfunction pointer. All strings here are pointing to
|
||||
@@ -100,6 +114,7 @@ struct JanetVM {
|
||||
* return point for panics. */
|
||||
jmp_buf *signal_buf;
|
||||
Janet *return_reg;
|
||||
int coerce_error;
|
||||
|
||||
/* The global registry for c functions. Used to store meta-data
|
||||
* along with otherwise bare c function pointers. */
|
||||
@@ -167,6 +182,8 @@ struct JanetVM {
|
||||
JanetTable signal_handlers;
|
||||
#ifdef JANET_WINDOWS
|
||||
void **iocp;
|
||||
void *connect_ex; /* MSWsock extension if available */
|
||||
int connect_ex_loaded;
|
||||
#elif defined(JANET_EV_EPOLL)
|
||||
pthread_attr_t new_thread_attr;
|
||||
JanetHandle selfpipe[2];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -71,10 +71,10 @@ int janet_string_compare(const uint8_t *lhs, const uint8_t *rhs) {
|
||||
int janet_string_equalconst(const uint8_t *lhs, const uint8_t *rhs, int32_t rlen, int32_t rhash) {
|
||||
int32_t lhash = janet_string_hash(lhs);
|
||||
int32_t llen = janet_string_length(lhs);
|
||||
if (lhs == rhs)
|
||||
return 1;
|
||||
if (lhash != rhash || llen != rlen)
|
||||
return 0;
|
||||
if (lhs == rhs)
|
||||
return 1;
|
||||
return !memcmp(lhs, rhs, rlen);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -301,6 +301,7 @@ int janet_scan_number_base(
|
||||
if (base == 0) {
|
||||
base = 10;
|
||||
}
|
||||
int exp_base = base;
|
||||
|
||||
/* Skip leading zeros */
|
||||
while (str < end && (*str == '0' || *str == '.')) {
|
||||
@@ -322,6 +323,12 @@ int janet_scan_number_base(
|
||||
} else if (*str == '&') {
|
||||
foundexp = 1;
|
||||
break;
|
||||
} else if (base == 16 && (*str == 'P' || *str == 'p')) { /* IEEE hex float */
|
||||
foundexp = 1;
|
||||
exp_base = 10;
|
||||
base = 2;
|
||||
ex *= 4; /* We need to correct the current exponent after we change the base */
|
||||
break;
|
||||
} else if (base == 10 && (*str == 'E' || *str == 'e')) {
|
||||
foundexp = 1;
|
||||
break;
|
||||
@@ -360,9 +367,9 @@ int janet_scan_number_base(
|
||||
}
|
||||
while (str < end) {
|
||||
int digit = digit_lookup[*str & 0x7F];
|
||||
if (*str > 127 || digit >= base) goto error;
|
||||
if (*str > 127 || digit >= exp_base) goto error;
|
||||
if (ee < (INT32_MAX / 40)) {
|
||||
ee = base * ee + digit;
|
||||
ee = exp_base * ee + digit;
|
||||
}
|
||||
str++;
|
||||
seenadigit = 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -294,6 +294,16 @@ JANET_CORE_FN(cfun_struct_to_table,
|
||||
return janet_wrap_table(tab);
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_struct_rawget,
|
||||
"(struct/rawget st key)",
|
||||
"Gets a value from a struct `st` without looking at the prototype struct. "
|
||||
"If `st` does not contain the key directly, the function will return "
|
||||
"nil without checking the prototype. Returns the value in the struct.") {
|
||||
janet_fixarity(argc, 2);
|
||||
JanetStruct st = janet_getstruct(argv, 0);
|
||||
return janet_struct_rawget(st, argv[1]);
|
||||
}
|
||||
|
||||
/* Load the struct module */
|
||||
void janet_lib_struct(JanetTable *env) {
|
||||
JanetRegExt struct_cfuns[] = {
|
||||
@@ -301,6 +311,7 @@ void janet_lib_struct(JanetTable *env) {
|
||||
JANET_CORE_REG("struct/getproto", cfun_struct_getproto),
|
||||
JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten),
|
||||
JANET_CORE_REG("struct/to-table", cfun_struct_to_table),
|
||||
JANET_CORE_REG("struct/rawget", cfun_struct_rawget),
|
||||
JANET_REG_END
|
||||
};
|
||||
janet_core_cfuns_ext(env, NULL, struct_cfuns);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -155,6 +155,17 @@ Janet janet_table_get(JanetTable *t, Janet key) {
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
|
||||
/* Used internally for compiler stuff */
|
||||
Janet janet_table_get_keyword(JanetTable *t, const char *keyword) {
|
||||
int32_t keyword_len = (int32_t) strlen(keyword);
|
||||
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
|
||||
JanetKV *bucket = (JanetKV *) janet_dict_find_keyword(t->data, t->capacity, (const uint8_t *) keyword, keyword_len);
|
||||
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
|
||||
return bucket->value;
|
||||
}
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
|
||||
/* Get a value out of the table, and record which prototype it was from. */
|
||||
Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which) {
|
||||
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
|
||||
@@ -372,12 +383,14 @@ JANET_CORE_FN(cfun_table_setproto,
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_table_tostruct,
|
||||
"(table/to-struct tab)",
|
||||
"Convert a table to a struct. Returns a new struct. This function "
|
||||
"does not take into account prototype tables.") {
|
||||
janet_fixarity(argc, 1);
|
||||
"(table/to-struct tab &opt proto)",
|
||||
"Convert a table to a struct. Returns a new struct.") {
|
||||
janet_arity(argc, 1, 2);
|
||||
JanetTable *t = janet_gettable(argv, 0);
|
||||
return janet_wrap_struct(janet_table_to_struct(t));
|
||||
JanetStruct proto = janet_optstruct(argv, argc, 1, NULL);
|
||||
JanetStruct st = janet_table_to_struct(t);
|
||||
janet_struct_proto(st) = proto;
|
||||
return janet_wrap_struct(st);
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_table_rawget,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
|
||||
110
src/core/util.c
110
src/core/util.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -79,6 +79,7 @@ const char *const janet_type_names[16] = {
|
||||
"pointer"
|
||||
};
|
||||
|
||||
/* Docstring for signal lists these */
|
||||
const char *const janet_signal_names[14] = {
|
||||
"ok",
|
||||
"error",
|
||||
@@ -96,6 +97,7 @@ const char *const janet_signal_names[14] = {
|
||||
"await"
|
||||
};
|
||||
|
||||
/* Docstring for fiber/status lists these */
|
||||
const char *const janet_status_names[16] = {
|
||||
"dead",
|
||||
"error",
|
||||
@@ -115,14 +117,20 @@ const char *const janet_status_names[16] = {
|
||||
"alive"
|
||||
};
|
||||
|
||||
uint32_t janet_hash_mix(uint32_t input, uint32_t more) {
|
||||
uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2));
|
||||
return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2));
|
||||
}
|
||||
|
||||
#ifndef JANET_PRF
|
||||
|
||||
int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
|
||||
if (NULL == str) return 5381;
|
||||
if (NULL == str || len == 0) return 5381;
|
||||
const uint8_t *end = str + len;
|
||||
uint32_t hash = 5381;
|
||||
while (str < end)
|
||||
hash = (hash << 5) + hash + *str++;
|
||||
hash = janet_hash_mix(hash, (uint32_t) len);
|
||||
return (int32_t) hash;
|
||||
}
|
||||
|
||||
@@ -238,11 +246,6 @@ int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
|
||||
|
||||
#endif
|
||||
|
||||
uint32_t janet_hash_mix(uint32_t input, uint32_t more) {
|
||||
uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2));
|
||||
return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2));
|
||||
}
|
||||
|
||||
/* Computes hash of an array of values */
|
||||
int32_t janet_array_calchash(const Janet *array, int32_t len) {
|
||||
const Janet *end = array + len;
|
||||
@@ -265,7 +268,7 @@ int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len) {
|
||||
return (int32_t) hash;
|
||||
}
|
||||
|
||||
/* Calculate next power of 2. May overflow. If n is 0,
|
||||
/* Calculate next power of 2. May overflow. If n < 0,
|
||||
* will return 0. */
|
||||
int32_t janet_tablen(int32_t n) {
|
||||
if (n < 0) return 0;
|
||||
@@ -318,6 +321,54 @@ const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) {
|
||||
return first_bucket;
|
||||
}
|
||||
|
||||
/* Helper to find a keyword, symbol, or string in a Janet struct or table without allocating
|
||||
* memory or needing to find interned symbols */
|
||||
const JanetKV *janet_dict_find_keyword(
|
||||
const JanetKV *buckets, int32_t cap,
|
||||
const uint8_t *cstr, int32_t cstr_len) {
|
||||
int32_t hash = janet_string_calchash(cstr, cstr_len);
|
||||
int32_t index = janet_maphash(cap, hash);
|
||||
int32_t i;
|
||||
const JanetKV *first_bucket = NULL;
|
||||
/* Higher half */
|
||||
for (i = index; i < cap; i++) {
|
||||
const JanetKV *kv = buckets + i;
|
||||
if (janet_checktype(kv->key, JANET_NIL)) {
|
||||
if (janet_checktype(kv->value, JANET_NIL)) {
|
||||
return kv;
|
||||
} else if (NULL == first_bucket) {
|
||||
first_bucket = kv;
|
||||
}
|
||||
} else if (janet_checktype(kv->key, JANET_KEYWORD)) {
|
||||
/* Works for symbol and keyword, too */
|
||||
JanetString str = janet_unwrap_string(kv->key);
|
||||
int32_t len = janet_string_length(str);
|
||||
if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) {
|
||||
return buckets + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Lower half */
|
||||
for (i = 0; i < index; i++) {
|
||||
const JanetKV *kv = buckets + i;
|
||||
if (janet_checktype(kv->key, JANET_NIL)) {
|
||||
if (janet_checktype(kv->value, JANET_NIL)) {
|
||||
return kv;
|
||||
} else if (NULL == first_bucket) {
|
||||
first_bucket = kv;
|
||||
}
|
||||
} else if (janet_checktype(kv->key, JANET_KEYWORD)) {
|
||||
/* Works for symbol and keyword, too */
|
||||
JanetString str = janet_unwrap_string(kv->key);
|
||||
int32_t len = janet_string_length(str);
|
||||
if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) {
|
||||
return buckets + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return first_bucket;
|
||||
}
|
||||
|
||||
/* Get a value from a janet struct or table. */
|
||||
Janet janet_dictionary_get(const JanetKV *data, int32_t cap, Janet key) {
|
||||
const JanetKV *kv = janet_dict_find(data, cap, key);
|
||||
@@ -625,8 +676,11 @@ JanetBinding janet_binding_from_entry(Janet entry) {
|
||||
return binding;
|
||||
entry_table = janet_unwrap_table(entry);
|
||||
|
||||
/* deprecation check */
|
||||
Janet deprecate = janet_table_get(entry_table, janet_ckeywordv("deprecated"));
|
||||
Janet deprecate = janet_table_get_keyword(entry_table, "deprecated");
|
||||
int macro = janet_truthy(janet_table_get_keyword(entry_table, "macro"));
|
||||
Janet value = janet_table_get_keyword(entry_table, "value");
|
||||
Janet ref = janet_table_get_keyword(entry_table, "ref");
|
||||
|
||||
if (janet_checktype(deprecate, JANET_KEYWORD)) {
|
||||
JanetKeyword depkw = janet_unwrap_keyword(deprecate);
|
||||
if (!janet_cstrcmp(depkw, "relaxed")) {
|
||||
@@ -640,11 +694,8 @@ JanetBinding janet_binding_from_entry(Janet entry) {
|
||||
binding.deprecation = JANET_BINDING_DEP_NORMAL;
|
||||
}
|
||||
|
||||
int macro = janet_truthy(janet_table_get(entry_table, janet_ckeywordv("macro")));
|
||||
Janet value = janet_table_get(entry_table, janet_ckeywordv("value"));
|
||||
Janet ref = janet_table_get(entry_table, janet_ckeywordv("ref"));
|
||||
int ref_is_valid = janet_checktype(ref, JANET_ARRAY);
|
||||
int redef = ref_is_valid && janet_truthy(janet_table_get(entry_table, janet_ckeywordv("redef")));
|
||||
int redef = ref_is_valid && janet_truthy(janet_table_get_keyword(entry_table, "redef"));
|
||||
|
||||
if (macro) {
|
||||
binding.value = redef ? ref : value;
|
||||
@@ -845,11 +896,15 @@ int janet_checksize(Janet x) {
|
||||
return 0;
|
||||
double dval = janet_unwrap_number(x);
|
||||
if (dval != (double)((size_t) dval)) return 0;
|
||||
#ifdef JANET_PLAN9
|
||||
return dval <= SIZE_MAX;
|
||||
#else
|
||||
if (SIZE_MAX > JANET_INTMAX_INT64) {
|
||||
return dval <= JANET_INTMAX_INT64;
|
||||
} else {
|
||||
return dval <= SIZE_MAX;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
JanetTable *janet_get_core_table(const char *name) {
|
||||
@@ -928,27 +983,24 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) {
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach.h>
|
||||
int janet_gettime(struct timespec *spec, enum JanetTimeSource source) {
|
||||
if (source == JANET_TIME_REALTIME) {
|
||||
if (source == JANET_TIME_CPUTIME) {
|
||||
clock_t tmp = clock();
|
||||
spec->tv_sec = tmp / CLOCKS_PER_SEC;
|
||||
spec->tv_nsec = ((tmp - (spec->tv_sec * CLOCKS_PER_SEC)) * 1000000000) / CLOCKS_PER_SEC;
|
||||
} else {
|
||||
clock_serv_t cclock;
|
||||
mach_timespec_t mts;
|
||||
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
|
||||
clock_id_t cid = CALENDAR_CLOCK;
|
||||
if (source == JANET_TIME_REALTIME) {
|
||||
cid = CALENDAR_CLOCK;
|
||||
} else if (source == JANET_TIME_MONOTONIC) {
|
||||
cid = SYSTEM_CLOCK;
|
||||
}
|
||||
host_get_clock_service(mach_host_self(), cid, &cclock);
|
||||
clock_get_time(cclock, &mts);
|
||||
mach_port_deallocate(mach_task_self(), cclock);
|
||||
spec->tv_sec = mts.tv_sec;
|
||||
spec->tv_nsec = mts.tv_nsec;
|
||||
} else if (source == JANET_TIME_MONOTONIC) {
|
||||
clock_serv_t cclock;
|
||||
int nsecs;
|
||||
mach_msg_type_number_t count;
|
||||
host_get_clock_service(mach_host_self(), clock, &cclock);
|
||||
clock_get_attributes(cclock, CLOCK_GET_TIME_RES, (clock_attr_t)&nsecs, &count);
|
||||
mach_port_deallocate(mach_task_self(), cclock);
|
||||
clock_getres(CLOCK_MONOTONIC, spec);
|
||||
}
|
||||
if (source == JANET_TIME_CPUTIME) {
|
||||
clock_t tmp = clock();
|
||||
spec->tv_sec = tmp;
|
||||
spec->tv_nsec = (tmp - spec->tv_sec) * 1.0e9;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -66,42 +66,72 @@
|
||||
|
||||
/* Utils */
|
||||
uint32_t janet_hash_mix(uint32_t input, uint32_t more);
|
||||
|
||||
#define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1))
|
||||
|
||||
int janet_valid_utf8(const uint8_t *str, int32_t len);
|
||||
|
||||
int janet_is_symbol_char(uint8_t c);
|
||||
|
||||
extern const char janet_base64[65];
|
||||
|
||||
int32_t janet_array_calchash(const Janet *array, int32_t len);
|
||||
|
||||
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len);
|
||||
|
||||
int32_t janet_string_calchash(const uint8_t *str, int32_t len);
|
||||
|
||||
int32_t janet_tablen(int32_t n);
|
||||
|
||||
void safe_memcpy(void *dest, const void *src, size_t len);
|
||||
|
||||
void janet_buffer_push_types(JanetBuffer *buffer, int types);
|
||||
|
||||
const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key);
|
||||
|
||||
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,
|
||||
size_t itemsize,
|
||||
const uint8_t *key);
|
||||
|
||||
void janet_buffer_format(
|
||||
JanetBuffer *b,
|
||||
const char *strfrmt,
|
||||
int32_t argstart,
|
||||
int32_t argc,
|
||||
Janet *argv);
|
||||
|
||||
Janet janet_next_impl(Janet ds, Janet key, int is_interpreter);
|
||||
|
||||
JanetBinding janet_binding_from_entry(Janet entry);
|
||||
|
||||
JanetByteView janet_text_substitution(
|
||||
Janet *subst,
|
||||
const uint8_t *bytes,
|
||||
uint32_t len,
|
||||
JanetArray *extra_args);
|
||||
|
||||
const JanetKV *janet_dict_find_keyword(
|
||||
const JanetKV *buckets,
|
||||
int32_t cap,
|
||||
const uint8_t *cstr,
|
||||
int32_t cstr_len);
|
||||
|
||||
Janet janet_table_get_keyword(JanetTable *t, const char *keyword);
|
||||
|
||||
/* Registry functions */
|
||||
void janet_registry_put(
|
||||
JanetCFunction key,
|
||||
@@ -167,7 +197,26 @@ typedef void *Clib;
|
||||
#endif
|
||||
char *get_processed_name(const char *name);
|
||||
|
||||
#ifdef JANET_PLAN9
|
||||
#define RETRY_EINTR(RC, CALL) (RC) = CALL;
|
||||
#else
|
||||
#define RETRY_EINTR(RC, CALL) do { (RC) = CALL; } while((RC) < 0 && errno == EINTR)
|
||||
#endif
|
||||
|
||||
#ifdef JANET_EV
|
||||
#ifdef JANET_WINDOWS
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
typedef struct {
|
||||
union {
|
||||
OVERLAPPED overlapped;
|
||||
WSAOVERLAPPED wsaoverlapped;
|
||||
} as;
|
||||
uint32_t bytes_transferred;
|
||||
} JanetOverlapped;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Initialize builtin libraries */
|
||||
void janet_lib_io(JanetTable *env);
|
||||
@@ -205,9 +254,9 @@ int janet_make_pipe(JanetHandle handles[2], int mode);
|
||||
#ifdef JANET_FILEWATCH
|
||||
void janet_lib_filewatch(JanetTable *env);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef JANET_FFI
|
||||
void janet_lib_ffi(JanetTable *env);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -322,7 +322,8 @@ int32_t janet_hash(Janet x) {
|
||||
break;
|
||||
case JANET_TUPLE:
|
||||
hash = janet_tuple_hash(janet_unwrap_tuple(x));
|
||||
hash += (janet_tuple_flag(janet_unwrap_tuple(x)) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : 0;
|
||||
uint32_t inc = (janet_tuple_flag(janet_unwrap_tuple(x)) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : 0;
|
||||
hash = (int32_t)((uint32_t)hash + inc); /* avoid overflow undefined behavior */
|
||||
break;
|
||||
case JANET_STRUCT:
|
||||
hash = janet_struct_hash(janet_unwrap_struct(x));
|
||||
@@ -334,10 +335,9 @@ int32_t janet_hash(Janet x) {
|
||||
} as;
|
||||
as.d = janet_unwrap_number(x);
|
||||
as.d += 0.0; /* normalize negative 0 */
|
||||
uint32_t lo = (uint32_t)(as.u & 0xFFFFFFFF);
|
||||
as.u = murmur64(as.u);
|
||||
uint32_t hi = (uint32_t)(as.u >> 32);
|
||||
uint32_t hilo = (hi ^ lo) * 2654435769u;
|
||||
hash = (int32_t)((hilo << 16) | (hilo >> 16));
|
||||
hash = (int32_t)hi;
|
||||
break;
|
||||
}
|
||||
case JANET_ABSTRACT: {
|
||||
@@ -495,7 +495,7 @@ Janet janet_in(Janet ds, Janet key) {
|
||||
if (!(type->get)(janet_unwrap_abstract(ds), key, &value))
|
||||
janet_panicf("key %v not found in %v ", key, ds);
|
||||
} else {
|
||||
janet_panicf("no getter for %v ", ds);
|
||||
janet_panicf("no getter for %v", ds);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -622,7 +622,7 @@ Janet janet_getindex(Janet ds, int32_t index) {
|
||||
if (!(type->get)(janet_unwrap_abstract(ds), janet_wrap_integer(index), &value))
|
||||
value = janet_wrap_nil();
|
||||
} else {
|
||||
janet_panicf("no getter for %v ", ds);
|
||||
janet_panicf("no getter for %v", ds);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -724,6 +724,9 @@ void janet_putindex(Janet ds, int32_t index, Janet value) {
|
||||
JanetArray *array = janet_unwrap_array(ds);
|
||||
if (index >= array->count) {
|
||||
janet_array_ensure(array, index + 1, 2);
|
||||
for (int32_t i = array->count; i < index + 1; i++) {
|
||||
array->data[i] = janet_wrap_nil();
|
||||
}
|
||||
array->count = index + 1;
|
||||
}
|
||||
array->data[index] = value;
|
||||
@@ -735,6 +738,7 @@ void janet_putindex(Janet ds, int32_t index, Janet value) {
|
||||
janet_panicf("can only put integers in buffers, got %v", value);
|
||||
if (index >= buffer->count) {
|
||||
janet_buffer_ensure(buffer, index + 1, 2);
|
||||
memset(buffer->data + buffer->count, 0, index + 1 - buffer->count);
|
||||
buffer->count = index + 1;
|
||||
}
|
||||
buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF);
|
||||
@@ -767,7 +771,11 @@ void janet_put(Janet ds, Janet key, Janet value) {
|
||||
JanetArray *array = janet_unwrap_array(ds);
|
||||
int32_t index = getter_checkint(type, key, INT32_MAX - 1);
|
||||
if (index >= array->count) {
|
||||
janet_array_setcount(array, index + 1);
|
||||
janet_array_ensure(array, index + 1, 2);
|
||||
for (int32_t i = array->count; i < index + 1; i++) {
|
||||
array->data[i] = janet_wrap_nil();
|
||||
}
|
||||
array->count = index + 1;
|
||||
}
|
||||
array->data[index] = value;
|
||||
break;
|
||||
@@ -778,7 +786,9 @@ void janet_put(Janet ds, Janet key, Janet value) {
|
||||
if (!janet_checkint(value))
|
||||
janet_panicf("can only put integers in buffers, got %v", value);
|
||||
if (index >= buffer->count) {
|
||||
janet_buffer_setcount(buffer, index + 1);
|
||||
janet_buffer_ensure(buffer, index + 1, 2);
|
||||
memset(buffer->data + buffer->count, 0, index + 1 - buffer->count);
|
||||
buffer->count = index + 1;
|
||||
}
|
||||
buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF);
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
|
||||
116
src/core/vm.c
116
src/core/vm.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -115,7 +115,7 @@
|
||||
#define vm_maybe_auto_suspend(COND)
|
||||
#else
|
||||
#define vm_maybe_auto_suspend(COND) do { \
|
||||
if ((COND) && janet_vm.auto_suspend) { \
|
||||
if ((COND) && janet_atomic_load_relaxed(&janet_vm.auto_suspend)) { \
|
||||
fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \
|
||||
vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \
|
||||
} \
|
||||
@@ -129,7 +129,9 @@
|
||||
if (!janet_checktype(op1, JANET_NUMBER)) {\
|
||||
vm_commit();\
|
||||
Janet _argv[2] = { op1, janet_wrap_number(CS) };\
|
||||
stack[A] = janet_mcall(#op, 2, _argv);\
|
||||
Janet a = janet_mcall(#op, 2, _argv);\
|
||||
stack = fiber->data + fiber->frame;\
|
||||
stack[A] = a;\
|
||||
vm_checkgc_pcnext();\
|
||||
} else {\
|
||||
double x1 = janet_unwrap_number(op1);\
|
||||
@@ -143,7 +145,9 @@
|
||||
if (!janet_checktype(op1, JANET_NUMBER)) {\
|
||||
vm_commit();\
|
||||
Janet _argv[2] = { op1, janet_wrap_number(CS) };\
|
||||
stack[A] = janet_mcall(#op, 2, _argv);\
|
||||
Janet a = janet_mcall(#op, 2, _argv);\
|
||||
stack = fiber->data + fiber->frame;\
|
||||
stack[A] = a;\
|
||||
vm_checkgc_pcnext();\
|
||||
} else {\
|
||||
double y1 = janet_unwrap_number(op1);\
|
||||
@@ -166,7 +170,9 @@
|
||||
vm_pcnext();\
|
||||
} else {\
|
||||
vm_commit();\
|
||||
stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\
|
||||
Janet a = janet_binop_call(#op, "r" #op, op1, op2);\
|
||||
stack = fiber->data + fiber->frame;\
|
||||
stack[A] = a;\
|
||||
vm_checkgc_pcnext();\
|
||||
}\
|
||||
}
|
||||
@@ -186,7 +192,9 @@
|
||||
vm_pcnext();\
|
||||
} else {\
|
||||
vm_commit();\
|
||||
stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\
|
||||
Janet a = janet_binop_call(#op, "r" #op, op1, op2);\
|
||||
stack = fiber->data + fiber->frame;\
|
||||
stack[A] = a;\
|
||||
vm_checkgc_pcnext();\
|
||||
}\
|
||||
}
|
||||
@@ -203,7 +211,9 @@
|
||||
vm_pcnext();\
|
||||
} else {\
|
||||
vm_commit();\
|
||||
stack[A] = janet_wrap_boolean(janet_compare(op1, op2) op 0);\
|
||||
Janet a = janet_wrap_boolean(janet_compare(op1, op2) op 0);\
|
||||
stack = fiber->data + fiber->frame;\
|
||||
stack[A] = a;\
|
||||
vm_checkgc_pcnext();\
|
||||
}\
|
||||
}
|
||||
@@ -217,7 +227,9 @@
|
||||
vm_pcnext();\
|
||||
} else {\
|
||||
vm_commit();\
|
||||
stack[A] = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\
|
||||
Janet a = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\
|
||||
stack = fiber->data + fiber->frame;\
|
||||
stack[A] = a;\
|
||||
vm_checkgc_pcnext();\
|
||||
}\
|
||||
}
|
||||
@@ -710,7 +722,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_pcnext();
|
||||
} else {
|
||||
vm_commit();
|
||||
stack[A] = janet_binop_call("div", "rdiv", op1, op2);
|
||||
Janet a = janet_binop_call("div", "rdiv", op1, op2);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
vm_checkgc_pcnext();
|
||||
}
|
||||
}
|
||||
@@ -730,7 +744,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_pcnext();
|
||||
} else {
|
||||
vm_commit();
|
||||
stack[A] = janet_binop_call("mod", "rmod", op1, op2);
|
||||
Janet a = janet_binop_call("mod", "rmod", op1, op2);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
vm_checkgc_pcnext();
|
||||
}
|
||||
}
|
||||
@@ -745,7 +761,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_pcnext();
|
||||
} else {
|
||||
vm_commit();
|
||||
stack[A] = janet_binop_call("%", "r%", op1, op2);
|
||||
Janet a = janet_binop_call("%", "r%", op1, op2);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
vm_checkgc_pcnext();
|
||||
}
|
||||
}
|
||||
@@ -766,7 +784,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_pcnext();
|
||||
} else {
|
||||
vm_commit();
|
||||
stack[A] = janet_unary_call("~", op);
|
||||
Janet a = janet_unary_call("~", op);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
vm_checkgc_pcnext();
|
||||
}
|
||||
}
|
||||
@@ -798,14 +818,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_JUMP)
|
||||
pc += DS;
|
||||
vm_maybe_auto_suspend(DS <= 0);
|
||||
pc += DS;
|
||||
vm_next();
|
||||
|
||||
VM_OP(JOP_JUMP_IF)
|
||||
if (janet_truthy(stack[A])) {
|
||||
pc += ES;
|
||||
vm_maybe_auto_suspend(ES <= 0);
|
||||
pc += ES;
|
||||
} else {
|
||||
pc++;
|
||||
}
|
||||
@@ -815,15 +835,15 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
if (janet_truthy(stack[A])) {
|
||||
pc++;
|
||||
} else {
|
||||
pc += ES;
|
||||
vm_maybe_auto_suspend(ES <= 0);
|
||||
pc += ES;
|
||||
}
|
||||
vm_next();
|
||||
|
||||
VM_OP(JOP_JUMP_IF_NIL)
|
||||
if (janet_checktype(stack[A], JANET_NIL)) {
|
||||
pc += ES;
|
||||
vm_maybe_auto_suspend(ES <= 0);
|
||||
pc += ES;
|
||||
} else {
|
||||
pc++;
|
||||
}
|
||||
@@ -833,8 +853,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
if (janet_checktype(stack[A], JANET_NIL)) {
|
||||
pc++;
|
||||
} else {
|
||||
pc += ES;
|
||||
vm_maybe_auto_suspend(ES <= 0);
|
||||
pc += ES;
|
||||
}
|
||||
vm_next();
|
||||
|
||||
@@ -872,8 +892,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
stack[A] = janet_wrap_boolean(!janet_checktype(stack[B], JANET_NUMBER) || (janet_unwrap_number(stack[B]) != (double) CS));
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_COMPARE)
|
||||
stack[A] = janet_wrap_integer(janet_compare(stack[B], stack[C]));
|
||||
VM_OP(JOP_COMPARE) {
|
||||
Janet a = janet_wrap_integer(janet_compare(stack[B], stack[C]));
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
}
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_NEXT)
|
||||
@@ -1104,11 +1127,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
}
|
||||
fiber->child = child;
|
||||
JanetSignal sig = janet_continue_no_check(child, stack[C], &retreg);
|
||||
stack = fiber->data + fiber->frame;
|
||||
if (sig != JANET_SIGNAL_OK && !(child->flags & (1 << sig))) {
|
||||
vm_return(sig, retreg);
|
||||
}
|
||||
fiber->child = NULL;
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = retreg;
|
||||
vm_checkgc_pcnext();
|
||||
}
|
||||
@@ -1157,6 +1180,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_commit();
|
||||
fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL;
|
||||
janet_put(stack[A], stack[B], stack[C]);
|
||||
stack = fiber->data + fiber->frame;
|
||||
fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL;
|
||||
vm_checkgc_pcnext();
|
||||
|
||||
@@ -1164,27 +1188,44 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
|
||||
vm_commit();
|
||||
fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL;
|
||||
janet_putindex(stack[A], C, stack[B]);
|
||||
stack = fiber->data + fiber->frame;
|
||||
fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL;
|
||||
vm_checkgc_pcnext();
|
||||
|
||||
VM_OP(JOP_IN)
|
||||
vm_commit();
|
||||
stack[A] = janet_in(stack[B], stack[C]);
|
||||
{
|
||||
Janet a = janet_in(stack[B], stack[C]);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
}
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_GET)
|
||||
vm_commit();
|
||||
stack[A] = janet_get(stack[B], stack[C]);
|
||||
{
|
||||
Janet a = janet_get(stack[B], stack[C]);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
}
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_GET_INDEX)
|
||||
vm_commit();
|
||||
stack[A] = janet_getindex(stack[B], C);
|
||||
{
|
||||
Janet a = janet_getindex(stack[B], C);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
}
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_LENGTH)
|
||||
vm_commit();
|
||||
stack[A] = janet_lengthv(stack[E]);
|
||||
{
|
||||
Janet a = janet_lengthv(stack[E]);
|
||||
stack = fiber->data + fiber->frame;
|
||||
stack[A] = a;
|
||||
}
|
||||
vm_pcnext();
|
||||
|
||||
VM_OP(JOP_MAKE_ARRAY) {
|
||||
@@ -1373,7 +1414,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
|
||||
|
||||
/* Run vm */
|
||||
janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP;
|
||||
int old_coerce_error = janet_vm.coerce_error;
|
||||
janet_vm.coerce_error = 1;
|
||||
JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil());
|
||||
janet_vm.coerce_error = old_coerce_error;
|
||||
|
||||
/* Teardown */
|
||||
janet_vm.stackn = oldn;
|
||||
@@ -1384,6 +1428,15 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
|
||||
}
|
||||
|
||||
if (signal != JANET_SIGNAL_OK) {
|
||||
/* Should match logic in janet_signalv */
|
||||
#ifdef JANET_EV
|
||||
if (janet_vm.root_fiber != NULL && signal == JANET_SIGNAL_EVENT) {
|
||||
janet_vm.root_fiber->sched_id++;
|
||||
}
|
||||
#endif
|
||||
if (signal != JANET_SIGNAL_ERROR) {
|
||||
*janet_vm.return_reg = janet_wrap_string(janet_formatc("%v coerced from %s to error", *janet_vm.return_reg, janet_signal_names[signal]));
|
||||
}
|
||||
janet_panicv(*janet_vm.return_reg);
|
||||
}
|
||||
|
||||
@@ -1430,8 +1483,10 @@ void janet_try_init(JanetTryState *state) {
|
||||
state->vm_fiber = janet_vm.fiber;
|
||||
state->vm_jmp_buf = janet_vm.signal_buf;
|
||||
state->vm_return_reg = janet_vm.return_reg;
|
||||
state->coerce_error = janet_vm.coerce_error;
|
||||
janet_vm.return_reg = &(state->payload);
|
||||
janet_vm.signal_buf = &(state->buf);
|
||||
janet_vm.coerce_error = 0;
|
||||
}
|
||||
|
||||
void janet_restore(JanetTryState *state) {
|
||||
@@ -1440,6 +1495,7 @@ void janet_restore(JanetTryState *state) {
|
||||
janet_vm.fiber = state->vm_fiber;
|
||||
janet_vm.signal_buf = state->vm_jmp_buf;
|
||||
janet_vm.return_reg = state->vm_return_reg;
|
||||
janet_vm.coerce_error = state->coerce_error;
|
||||
}
|
||||
|
||||
static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) {
|
||||
@@ -1503,6 +1559,15 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
|
||||
}
|
||||
}
|
||||
|
||||
/* If this is a nested continue (root_fiber already set), root the fiber
|
||||
* so it survives GC. janet_collect only marks root_fiber, so without
|
||||
* this a nested fiber (e.g., from janet_pcall in a C function) would be
|
||||
* invisible to GC and could be collected while actively running. */
|
||||
int fiber_rooted = (janet_vm.root_fiber != NULL);
|
||||
if (fiber_rooted) {
|
||||
janet_gcroot(janet_wrap_fiber(fiber));
|
||||
}
|
||||
|
||||
/* Save global state */
|
||||
JanetTryState tstate;
|
||||
JanetSignal sig = janet_try(&tstate);
|
||||
@@ -1518,6 +1583,9 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
|
||||
if (janet_vm.root_fiber == fiber) janet_vm.root_fiber = NULL;
|
||||
janet_fiber_set_status(fiber, sig);
|
||||
janet_restore(&tstate);
|
||||
if (fiber_rooted) {
|
||||
janet_gcunroot(janet_wrap_fiber(fiber));
|
||||
}
|
||||
fiber->last_value = tstate.payload;
|
||||
*out = tstate.payload;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
@@ -67,11 +67,21 @@ extern "C" {
|
||||
#define JANET_LINUX 1
|
||||
#endif
|
||||
|
||||
/* Check for Android */
|
||||
#ifdef __ANDROID__
|
||||
#define JANET_ANDROID 1
|
||||
#endif
|
||||
|
||||
/* Check for Cygwin */
|
||||
#if defined(__CYGWIN__)
|
||||
#define JANET_CYGWIN 1
|
||||
#endif
|
||||
|
||||
/* Check for Illumos */
|
||||
#if defined(__illumos__)
|
||||
#define JANET_ILLUMOS 1
|
||||
#endif
|
||||
|
||||
/* Check Unix */
|
||||
#if defined(_AIX) \
|
||||
|| defined(__APPLE__) /* Darwin */ \
|
||||
@@ -111,6 +121,7 @@ extern "C" {
|
||||
|| (defined(__sparc__) && defined(__arch64__) || defined (__sparcv9)) /* BE */ \
|
||||
|| defined(__s390x__) /* S390 64-bit (BE) */ \
|
||||
|| (defined(__ppc64__) || defined(__PPC64__)) \
|
||||
|| defined(PLAN9_arm64) || defined(PLAN9_amd64) \
|
||||
|| defined(__aarch64__) /* ARM 64-bit */ \
|
||||
|| (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ \
|
||||
|| defined(__loongarch64) /* LoongArch64 64-bit */
|
||||
@@ -137,6 +148,7 @@ extern "C" {
|
||||
|| defined(__s390x__) /* S390 64-bit */ \
|
||||
|| defined(__s390__) /* S390 32-bit */ \
|
||||
|| defined(__ARMEB__) /* ARM big endian */ \
|
||||
|| defined(__AARCH64EB__) /* ARM64 big endian */ \
|
||||
|| ((defined(__CC_ARM) || defined(__ARMCC__)) /* ARM RealView compiler */ \
|
||||
&& defined(__BIG_ENDIAN))
|
||||
#define JANET_BIG_ENDIAN 1
|
||||
@@ -157,7 +169,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* Check sun */
|
||||
#ifdef __sun
|
||||
#if defined(__sun) && !defined(JANET_ILLUMOS)
|
||||
#define JANET_NO_UTC_MKTIME
|
||||
#endif
|
||||
|
||||
@@ -165,14 +177,12 @@ extern "C" {
|
||||
/* Also enable the thread library only if not single-threaded */
|
||||
#ifdef JANET_SINGLE_THREADED
|
||||
#define JANET_THREAD_LOCAL
|
||||
#undef JANET_THREADS
|
||||
#elif defined(__GNUC__)
|
||||
#elif !(defined(JANET_THREAD_LOCAL)) && defined(__GNUC__)
|
||||
#define JANET_THREAD_LOCAL __thread
|
||||
#elif defined(_MSC_BUILD)
|
||||
#elif !(defined(JANET_THREAD_LOCAL)) && defined(_MSC_BUILD)
|
||||
#define JANET_THREAD_LOCAL __declspec(thread)
|
||||
#else
|
||||
#elif !(defined(JANET_THREAD_LOCAL))
|
||||
#define JANET_THREAD_LOCAL
|
||||
#undef JANET_THREADS
|
||||
#endif
|
||||
|
||||
/* Enable or disable dynamic module loading. Enabled by default. */
|
||||
@@ -591,6 +601,7 @@ typedef void *JanetAbstract;
|
||||
#define JANET_STREAM_WRITABLE 0x400
|
||||
#define JANET_STREAM_ACCEPTABLE 0x800
|
||||
#define JANET_STREAM_UDPSERVER 0x1000
|
||||
#define JANET_STREAM_NOT_CLOSEABLE 0x2000
|
||||
#define JANET_STREAM_TOCLOSE 0x10000
|
||||
|
||||
typedef enum {
|
||||
@@ -657,12 +668,15 @@ JANET_API void janet_stream_level_triggered(JanetStream *stream);
|
||||
* signals. Define them here */
|
||||
#ifdef JANET_WINDOWS
|
||||
typedef long JanetAtomicInt;
|
||||
#elif defined(JANET_PLAN9)
|
||||
typedef long JanetAtomicInt;
|
||||
#else
|
||||
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);
|
||||
JANET_API JanetAtomicInt janet_atomic_load_relaxed(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
|
||||
@@ -1060,6 +1074,7 @@ struct JanetAbstractHead {
|
||||
#define JANET_FUNCDEF_FLAG_HASSOURCEMAP 0x800000
|
||||
#define JANET_FUNCDEF_FLAG_STRUCTARG 0x1000000
|
||||
#define JANET_FUNCDEF_FLAG_HASCLOBITSET 0x2000000
|
||||
#define JANET_FUNCDEF_FLAG_NAMEDARGS 0x4000000
|
||||
#define JANET_FUNCDEF_FLAG_TAG 0xFFFF
|
||||
|
||||
/* Source mapping structure for a bytecode instruction */
|
||||
@@ -1101,6 +1116,7 @@ struct JanetFuncDef {
|
||||
int32_t environments_length;
|
||||
int32_t defs_length;
|
||||
int32_t symbolmap_length;
|
||||
int32_t named_args_count;
|
||||
};
|
||||
|
||||
/* A function environment */
|
||||
@@ -1124,9 +1140,21 @@ struct JanetFunction {
|
||||
JanetFuncEnv *envs[];
|
||||
};
|
||||
|
||||
/* Use to read Janet data structures into memory from source code */
|
||||
typedef struct JanetParseState JanetParseState;
|
||||
typedef struct JanetParser JanetParser;
|
||||
|
||||
typedef int (*Consumer)(JanetParser *p, JanetParseState *state, uint8_t c);
|
||||
|
||||
struct JanetParseState {
|
||||
int32_t counter;
|
||||
int32_t argn;
|
||||
int flags;
|
||||
size_t line;
|
||||
size_t column;
|
||||
Consumer consumer;
|
||||
};
|
||||
|
||||
enum JanetParserStatus {
|
||||
JANET_PARSE_ROOT,
|
||||
JANET_PARSE_ERROR,
|
||||
@@ -1162,7 +1190,10 @@ typedef struct {
|
||||
const JanetAbstractType *at;
|
||||
} JanetMarshalContext;
|
||||
|
||||
/* Defines an abstract type */
|
||||
/* Defines an abstract type. Use a const pointer to one of these structures
|
||||
* when creating abstract types. The memory for this pointer should not be free
|
||||
* until after janet_deinit is called. Usually, this means declaring JanetAbstractType's
|
||||
* as const data at file scope, and creating instances with janet_abstract(&MyType, sizeof(MyTypeStruct)); */
|
||||
struct JanetAbstractType {
|
||||
const char *name;
|
||||
int (*gc)(void *data, size_t len);
|
||||
@@ -1178,6 +1209,7 @@ struct JanetAbstractType {
|
||||
Janet(*call)(void *p, int32_t argc, Janet *argv);
|
||||
size_t (*length)(void *p, size_t len);
|
||||
JanetByteView(*bytes)(void *p, size_t len);
|
||||
int (*gcperthread)(void *data, size_t len);
|
||||
};
|
||||
|
||||
/* Some macros to let us add extra types to JanetAbstract types without
|
||||
@@ -1197,7 +1229,8 @@ struct JanetAbstractType {
|
||||
#define JANET_ATEND_NEXT NULL,JANET_ATEND_CALL
|
||||
#define JANET_ATEND_CALL NULL,JANET_ATEND_LENGTH
|
||||
#define JANET_ATEND_LENGTH NULL,JANET_ATEND_BYTES
|
||||
#define JANET_ATEND_BYTES
|
||||
#define JANET_ATEND_BYTES NULL,JANET_ATEND_GCPERTHREAD
|
||||
#define JANET_ATEND_GCPERTHREAD
|
||||
|
||||
struct JanetReg {
|
||||
const char *name;
|
||||
@@ -1248,6 +1281,7 @@ typedef struct JanetFile JanetFile;
|
||||
struct JanetFile {
|
||||
FILE *file;
|
||||
int32_t flags;
|
||||
size_t vbufsize;
|
||||
};
|
||||
|
||||
/* For janet_try and janet_restore */
|
||||
@@ -1261,6 +1295,7 @@ typedef struct {
|
||||
/* new state */
|
||||
jmp_buf buf;
|
||||
Janet payload;
|
||||
int coerce_error;
|
||||
} JanetTryState;
|
||||
|
||||
/***** END SECTION TYPES *****/
|
||||
@@ -1411,6 +1446,7 @@ JANET_API void janet_loop(void);
|
||||
* } else {
|
||||
* janet_schedule(interrupted_fiber, janet_wrap_nil());
|
||||
* }
|
||||
* janet_interpreter_interrupt_handled(NULL);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
@@ -1442,6 +1478,7 @@ JANET_NO_RETURN JANET_API void janet_sleep_await(double sec);
|
||||
/* For use inside listeners - adds a timeout to the current fiber, such that
|
||||
* it will be resumed after sec seconds if no other event schedules the current fiber. */
|
||||
JANET_API void janet_addtimeout(double sec);
|
||||
JANET_API void janet_addtimeout_nil(double sec);
|
||||
JANET_API void janet_ev_inc_refcount(void);
|
||||
JANET_API void janet_ev_dec_refcount(void);
|
||||
|
||||
@@ -1449,18 +1486,27 @@ JANET_API void janet_ev_dec_refcount(void);
|
||||
JANET_API void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size);
|
||||
JANET_API void *janet_abstract_end_threaded(void *x);
|
||||
JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size);
|
||||
|
||||
/* Allow reference counting on threaded abstract types. This is useful when external code , either
|
||||
* in the current OS thread or in a different OS thread, takes a pointer to this abstract type. The programmer
|
||||
* should tncrement the reference count when taking the pointer, and then decrement and possibly cleanup and free
|
||||
* if the reference count is 0. */
|
||||
JANET_API int32_t janet_abstract_incref(void *abst);
|
||||
JANET_API int32_t janet_abstract_decref(void *abst);
|
||||
|
||||
/* If this returns 0, *abst will be deinitialized and freed. Useful shorthand if there is no other cleanup for
|
||||
* this abstract type before calling `janet_free` on it's backing memory. */
|
||||
JANET_API int32_t janet_abstract_decref_maybe_free(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 JanetChannel *janet_channel_make(uint32_t limit);
|
||||
JANET_API JanetChannel *janet_channel_make_threaded(uint32_t limit);
|
||||
JANET_API JanetChannel *janet_getchannel(const Janet *argv, int32_t n);
|
||||
JANET_API 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 */
|
||||
/* Expose some OS sync primitives - mutexes and reader-writer locks */
|
||||
JANET_API size_t janet_os_mutex_size(void);
|
||||
JANET_API size_t janet_os_rwlock_size(void);
|
||||
JANET_API void janet_os_mutex_init(JanetOSMutex *mutex);
|
||||
@@ -1485,6 +1531,9 @@ JANET_API Janet janet_ev_lasterr(void);
|
||||
* We could just use a pointer but this prevents malloc/free in the common case
|
||||
* of only a handful of arguments. */
|
||||
typedef struct {
|
||||
#ifdef JANET_WINDOWS
|
||||
char padding[48]; /* On windows, used for OVERLAPPED storage */
|
||||
#endif
|
||||
int tag;
|
||||
int argi;
|
||||
void *argp;
|
||||
@@ -1528,7 +1577,8 @@ JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGeneric
|
||||
/* Callback used by janet_ev_threaded_await */
|
||||
JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value);
|
||||
|
||||
/* Read async from a stream */
|
||||
/* Read async from a stream. These function yield to the event-loop with janet_await(), and so do not return.
|
||||
* When the fiber is resumed, the fiber will simply continue to the next Janet abstract machine instruction. */
|
||||
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
|
||||
@@ -1537,7 +1587,8 @@ JANET_NO_RETURN JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuff
|
||||
JANET_NO_RETURN JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags);
|
||||
#endif
|
||||
|
||||
/* Write async to a stream */
|
||||
/* Write async to a stream. These function yield to the event-loop with janet_await(), and so do not return.
|
||||
* When the fiber is resumed, the fiber will simply continue to the next Janet abstract machine instruction. */
|
||||
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
|
||||
@@ -1549,17 +1600,63 @@ JANET_NO_RETURN JANET_API void janet_ev_sendto_string(JanetStream *stream, Janet
|
||||
|
||||
#endif
|
||||
|
||||
/* Parsing */
|
||||
/* Parsing.
|
||||
*
|
||||
* E.g.
|
||||
*
|
||||
* JanetParser parser;
|
||||
* janet_parser_init(&parser);
|
||||
* for (int i = 0; i < source_code_length + 1; i++) {
|
||||
* if (i >= source_code_length) {
|
||||
* janet_parser_eof(&parser);
|
||||
* } else {
|
||||
* janet_parser_consume(&parser, source_code[i]);
|
||||
* }
|
||||
* while (janet_parser_has_more(&parser)) {
|
||||
* Janet x = janet_parser_produce(&parser);
|
||||
* janet_printf("got value: %v\n", x);
|
||||
* }
|
||||
* switch (janet_parser_status(&parser)) {
|
||||
* case JANET_PARSE_PENDING: break;
|
||||
* case JANET_PARSE_ERROR: janet_eprintf("error: %s\n", janet_parser_error(&parser)); break;
|
||||
* case JANET_PARSE_ROOT: break;
|
||||
* case JANET_PARSE_DEAD: break;
|
||||
* }
|
||||
* }
|
||||
* janet_parser_deinit(&parser);
|
||||
*
|
||||
* */
|
||||
extern JANET_API const JanetAbstractType janet_parser_type;
|
||||
|
||||
/* Construct/destruct a parser. Parsers can be allocated on the stack or the heap. */
|
||||
JANET_API void janet_parser_init(JanetParser *parser);
|
||||
JANET_API void janet_parser_deinit(JanetParser *parser);
|
||||
|
||||
/* Feed bytes into the parser. Check the parser state after every byte to handle errors. */
|
||||
JANET_API void janet_parser_consume(JanetParser *parser, uint8_t c);
|
||||
|
||||
/* Check the current status of the parser */
|
||||
JANET_API enum JanetParserStatus janet_parser_status(JanetParser *parser);
|
||||
|
||||
/* Produce a value from the parser. Call this when janet_parser_has_more(&parser) is non-zero. */
|
||||
JANET_API Janet janet_parser_produce(JanetParser *parser);
|
||||
|
||||
/* Produce a value from the parser, wrapped in a tuple. The tuple is used to carry the source mapping information of the
|
||||
* top level form, such as a line number or symbol. */
|
||||
JANET_API Janet janet_parser_produce_wrapped(JanetParser *parser);
|
||||
|
||||
/* When there is an error while parsing (janet_parser_status(&parser) == JANET_PARSE_ERROR), get a nice error string.
|
||||
* Calling this will also flush the parser. */
|
||||
JANET_API const char *janet_parser_error(JanetParser *parser);
|
||||
|
||||
/* If there is a parsing error, flush the parser to set the state back to empty.
|
||||
* This allows for better error recover and less confusing error messages on bad syntax deep inside nested data structures. */
|
||||
JANET_API void janet_parser_flush(JanetParser *parser);
|
||||
|
||||
/* Indicate that there is no more source code */
|
||||
JANET_API void janet_parser_eof(JanetParser *parser);
|
||||
|
||||
/* If non-zero, the parser has values ready to be produced. */
|
||||
JANET_API int janet_parser_has_more(JanetParser *parser);
|
||||
|
||||
/* Assembly */
|
||||
@@ -1603,7 +1700,13 @@ JANET_API JanetCompileResult janet_compile_lint(
|
||||
JANET_API JanetTable *janet_core_env(JanetTable *replacements);
|
||||
JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements);
|
||||
|
||||
/* Execute strings */
|
||||
/* Execute strings.
|
||||
*
|
||||
* These functions wrap parsing, compilation, and evalutation into convenient functions.
|
||||
* */
|
||||
#define JANET_DO_ERROR_RUNTIME 0x01
|
||||
#define JANET_DO_ERROR_COMPILE 0x02
|
||||
#define JANET_DO_ERROR_PARSE 0x04
|
||||
JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out);
|
||||
JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out);
|
||||
|
||||
@@ -1794,21 +1897,41 @@ JANET_API JanetTable *janet_env_lookup(JanetTable *env);
|
||||
JANET_API void janet_env_lookup_into(JanetTable *renv, JanetTable *env, const char *prefix, int recurse);
|
||||
|
||||
/* GC */
|
||||
JANET_API void janet_mark(Janet x);
|
||||
JANET_API void janet_sweep(void);
|
||||
|
||||
/* The main interface to garbage collection. Call this to do a full mark and sweep cleanup. */
|
||||
JANET_API void janet_collect(void);
|
||||
JANET_API void janet_clear_memory(void);
|
||||
|
||||
/* Add "roots" to the garbage collector to prevent the runtime from freeing objects.
|
||||
* This is only needed if code outside of Janet keeps references to Janet values */
|
||||
JANET_API void janet_gcroot(Janet root);
|
||||
JANET_API int janet_gcunroot(Janet root);
|
||||
JANET_API int janet_gcunrootall(Janet root);
|
||||
|
||||
/* Allow disabling garbage collection temporarily or for certain sections of code.
|
||||
* this is a very cheap operation. */
|
||||
JANET_API int janet_gclock(void);
|
||||
JANET_API void janet_gcunlock(int handle);
|
||||
|
||||
/* The mark and sweep components of the mark and sweep collector. Prefer using janet_collect directly. */
|
||||
JANET_API void janet_mark(Janet x);
|
||||
JANET_API void janet_sweep(void);
|
||||
|
||||
/* Clear all gced memory and call all destructors. Used as part of the standard cleanup routune, most programmers will not need this. */
|
||||
JANET_API void janet_clear_memory(void);
|
||||
|
||||
/* Remove all GC roots. Used as part of the standard cleanup routine, most programmers will not need this. */
|
||||
JANET_API int janet_gcunrootall(Janet root);
|
||||
|
||||
/* Hint to the collector that memory of size s was just allocated to help it better understand when to free memory. */
|
||||
JANET_API void janet_gcpressure(size_t s);
|
||||
|
||||
/* Functions */
|
||||
JANET_API JanetFuncDef *janet_funcdef_alloc(void);
|
||||
JANET_API JanetFunction *janet_thunk(JanetFuncDef *def);
|
||||
|
||||
/* Get a function that when called with no args, will return x. */
|
||||
JANET_API JanetFunction *janet_thunk_delay(Janet x);
|
||||
|
||||
/* Do some simple verfification on constructed bytecode to disallow any trivial incorrect bytecode. */
|
||||
JANET_API int janet_verify(JanetFuncDef *def);
|
||||
|
||||
/* Pretty printing */
|
||||
@@ -1857,7 +1980,7 @@ JANET_API void janet_vm_free(JanetVM *vm);
|
||||
JANET_API void janet_vm_save(JanetVM *into);
|
||||
JANET_API void janet_vm_load(JanetVM *from);
|
||||
JANET_API void janet_interpreter_interrupt(JanetVM *vm);
|
||||
JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm);
|
||||
JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); /* Call this after running interrupt handler */
|
||||
JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out);
|
||||
JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig);
|
||||
JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f);
|
||||
@@ -1882,9 +2005,14 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr
|
||||
#define JANET_SANDBOX_FFI_USE 2048
|
||||
#define JANET_SANDBOX_FFI_JIT 4096
|
||||
#define JANET_SANDBOX_SIGNAL 8192
|
||||
#define JANET_SANDBOX_CHROOT 16384
|
||||
#define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT)
|
||||
#define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP)
|
||||
#define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN)
|
||||
#define JANET_SANDBOX_COMPILE 32768
|
||||
#define JANET_SANDBOX_ASM 65536
|
||||
#define JANET_SANDBOX_THREADS 131072
|
||||
#define JANET_SANDBOX_UNMARSHAL 262144
|
||||
#define JANET_SANDBOX_ALL (UINT32_MAX)
|
||||
JANET_API void janet_sandbox(uint32_t flags);
|
||||
JANET_API void janet_sandbox_assert(uint32_t forbidden_flags);
|
||||
@@ -1929,7 +2057,14 @@ JANET_API JanetBinding janet_resolve_ext(JanetTable *env, JanetSymbol sym);
|
||||
/* Get values from the core environment. */
|
||||
JANET_API Janet janet_resolve_core(const char *name);
|
||||
|
||||
/* New C API */
|
||||
/* New C API
|
||||
*
|
||||
* The "New" C API is intended to make constructing good documentation and source maps
|
||||
* much more straightforward. This not only ensures doc strings for functions in native
|
||||
* modules, it also add source code mapping for C functions so that programmers can see which
|
||||
* file and line a native function that calls janet_panic came from.
|
||||
*
|
||||
* */
|
||||
|
||||
/* Shorthand for janet C function declarations */
|
||||
#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv)
|
||||
@@ -2084,6 +2219,8 @@ JANET_API int32_t janet_optinteger(const Janet *argv, int32_t argc, int32_t n, i
|
||||
JANET_API int64_t janet_optinteger64(const Janet *argv, int32_t argc, int32_t n, int64_t dflt);
|
||||
JANET_API size_t janet_optsize(const Janet *argv, int32_t argc, int32_t n, size_t dflt);
|
||||
JANET_API JanetAbstract janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetAbstractType *at, JanetAbstract dflt);
|
||||
JANET_API uint32_t janet_optuinteger(const Janet *argv, int32_t argc, int32_t n, uint32_t dflt);
|
||||
JANET_API uint64_t janet_optuinteger64(const Janet *argv, int32_t argc, int32_t n, uint64_t dflt);
|
||||
|
||||
/* Mutable optional types specify a size default, and construct a new value if none is provided */
|
||||
JANET_API JanetBuffer *janet_optbuffer(const Janet *argv, int32_t argc, int32_t n, int32_t dflt_len);
|
||||
@@ -2180,8 +2317,13 @@ typedef enum {
|
||||
RULE_UNREF, /* [rule, tag] */
|
||||
RULE_CAPTURE_NUM, /* [rule, tag] */
|
||||
RULE_SUB, /* [rule, rule] */
|
||||
RULE_SPLIT /* [rule, rule] */
|
||||
} JanetPegOpcod;
|
||||
RULE_TIL, /* [rule, rule] */
|
||||
RULE_SPLIT, /* [rule, rule] */
|
||||
RULE_NTH, /* [nth, rule, tag] */
|
||||
RULE_ONLY_TAGS, /* [rule] */
|
||||
RULE_MATCHSPLICE, /* [rule, constant, tag] */
|
||||
RULE_DEBUG, /* [] */
|
||||
} JanetPegOpcode;
|
||||
|
||||
typedef struct {
|
||||
uint32_t *bytecode;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
@@ -79,9 +79,11 @@ static void simpleline(JanetBuffer *buffer) {
|
||||
int c;
|
||||
for (;;) {
|
||||
c = fgetc(in);
|
||||
#ifndef JANET_PLAN9
|
||||
if (c < 0 && !feof(in) && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (feof(in) || c < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -110,6 +112,8 @@ static JANET_THREAD_LOCAL int gbl_historyi = 0;
|
||||
static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_match_count = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_lines_below = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_history_loaded = 0;
|
||||
static JANET_THREAD_LOCAL char *gbl_history_file = NULL;
|
||||
#endif
|
||||
|
||||
/* Fallback */
|
||||
@@ -307,7 +311,9 @@ static int curpos(void) {
|
||||
char buf[32];
|
||||
int cols, rows;
|
||||
unsigned int i = 0;
|
||||
#ifndef JANET_PLAN9
|
||||
if (write_console("\x1b[6n", 4) != 4) return -1;
|
||||
#endif
|
||||
while (i < sizeof(buf) - 1) {
|
||||
if (read_console(buf + i, 1) != 1) break;
|
||||
if (buf[i] == 'R') break;
|
||||
@@ -379,10 +385,12 @@ static void refresh(void) {
|
||||
janet_buffer_push_cstring(&b, gbl_prompt);
|
||||
janet_buffer_push_bytes(&b, (uint8_t *) _buf, _len);
|
||||
/* Erase to right */
|
||||
janet_buffer_push_cstring(&b, "\x1b[0K");
|
||||
janet_buffer_push_cstring(&b, "\x1b[0K\r");
|
||||
/* Move cursor to original position. */
|
||||
snprintf(seq, 64, "\r\x1b[%dC", (int)(_pos + gbl_plen));
|
||||
janet_buffer_push_cstring(&b, seq);
|
||||
if (_pos + gbl_plen) {
|
||||
snprintf(seq, 64, "\x1b[%dC", (int)(_pos + gbl_plen));
|
||||
janet_buffer_push_cstring(&b, seq);
|
||||
}
|
||||
if (write_console((char *) b.data, b.count) == -1) {
|
||||
exit(1);
|
||||
}
|
||||
@@ -424,6 +432,63 @@ static int insert(char c, int draw) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calc_history_file(void) {
|
||||
char *hist = getenv("JANET_HISTFILE");
|
||||
if (hist != NULL) {
|
||||
gbl_history_file = sdup(hist);
|
||||
} else {
|
||||
gbl_history_file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void loadhistory(void) {
|
||||
if (gbl_history_loaded) return;
|
||||
calc_history_file();
|
||||
gbl_history_loaded = 1;
|
||||
if (NULL == gbl_history_file) return;
|
||||
FILE *history_file = fopen(gbl_history_file, "rb");
|
||||
if (NULL == history_file) return;
|
||||
JanetParser p;
|
||||
janet_parser_init(&p);
|
||||
int c = 0;
|
||||
while ((c = fgetc(history_file))) {
|
||||
if (c == EOF) {
|
||||
janet_parser_eof(&p);
|
||||
} else {
|
||||
janet_parser_consume(&p, c);
|
||||
}
|
||||
|
||||
while (janet_parser_has_more(&p) && gbl_history_count < JANET_HISTORY_MAX) {
|
||||
if (janet_parser_status(&p) == JANET_PARSE_ERROR) {
|
||||
janet_eprintf("bad history file: %s\n", janet_parser_error(&p));
|
||||
goto parsing_done;
|
||||
}
|
||||
Janet x = janet_parser_produce(&p);
|
||||
const char *cstr = (const char *) janet_to_string(x);
|
||||
if (cstr[0]) { /* Drop empty strings */
|
||||
gbl_history[gbl_history_count++] = sdup(cstr);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == EOF) break;
|
||||
}
|
||||
parsing_done:
|
||||
janet_parser_deinit(&p);
|
||||
gbl_historyi = 0;
|
||||
fclose(history_file);
|
||||
}
|
||||
|
||||
static void savehistory(void) {
|
||||
if (gbl_history_count < 1 || (gbl_history_file == NULL)) return;
|
||||
FILE *history_file = fopen(gbl_history_file, "wb");
|
||||
for (int i = 0; i < gbl_history_count; i++) {
|
||||
if (gbl_history[i][0]) { /* Drop empty strings */
|
||||
janet_dynprintf(NULL, history_file, "%j\n", janet_cstringv(gbl_history[i]));
|
||||
}
|
||||
}
|
||||
fclose(history_file);
|
||||
}
|
||||
|
||||
static void historymove(int delta) {
|
||||
if (gbl_history_count > 1) {
|
||||
janet_free(gbl_history[gbl_historyi]);
|
||||
@@ -890,6 +955,7 @@ static int line() {
|
||||
case 3: /* ctrl-c */
|
||||
clearlines();
|
||||
norawmode();
|
||||
savehistory();
|
||||
#ifdef _WIN32
|
||||
ExitProcess(1);
|
||||
#else
|
||||
@@ -1083,17 +1149,21 @@ void janet_line_init() {
|
||||
}
|
||||
|
||||
void janet_line_deinit() {
|
||||
int i;
|
||||
norawmode();
|
||||
for (i = 0; i < gbl_history_count; i++)
|
||||
for (int i = 0; i < gbl_history_count; i++)
|
||||
janet_free(gbl_history[i]);
|
||||
gbl_historyi = 0;
|
||||
if (gbl_history_file) {
|
||||
janet_free(gbl_history_file);
|
||||
gbl_history_file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void janet_line_get(const char *p, JanetBuffer *buffer) {
|
||||
gbl_prompt = p;
|
||||
buffer->count = 0;
|
||||
gbl_historyi = 0;
|
||||
loadhistory();
|
||||
if (check_simpleline(buffer)) return;
|
||||
FILE *out = janet_dynfile("err", stderr);
|
||||
if (line()) {
|
||||
@@ -1129,6 +1199,10 @@ int main(int argc, char **argv) {
|
||||
JanetArray *args;
|
||||
JanetTable *env;
|
||||
|
||||
#ifdef JANET_PLAN9
|
||||
setfcr(0);
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
setup_console_output();
|
||||
#endif
|
||||
@@ -1184,6 +1258,10 @@ int main(int argc, char **argv) {
|
||||
status = janet_loop_fiber(fiber);
|
||||
|
||||
/* Deinitialize vm */
|
||||
|
||||
#if !defined(JANET_SIMPLE_GETLINE)
|
||||
savehistory();
|
||||
#endif
|
||||
janet_deinit();
|
||||
janet_line_deinit();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Calvin Rose
|
||||
* Copyright (c) 2026 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
|
||||
|
||||
143
test/c/test-gc-pcall.c
Normal file
143
test/c/test-gc-pcall.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Test that GC does not collect fibers during janet_pcall.
|
||||
*
|
||||
* Bug: janet_collect() marks janet_vm.root_fiber but not janet_vm.fiber.
|
||||
* When janet_pcall is called from a C function, the inner fiber becomes
|
||||
* janet_vm.fiber while root_fiber still points to the outer fiber. If GC
|
||||
* triggers inside the inner fiber's execution, the inner fiber is not in
|
||||
* any GC root set and can be collected — including its stack memory —
|
||||
* while it is actively running.
|
||||
*
|
||||
* Two tests:
|
||||
* 1. Single nesting: F1 -> C func -> janet_pcall -> F2
|
||||
* F2 is not marked (it's janet_vm.fiber but not root_fiber)
|
||||
* 2. Deep nesting: F1 -> C func -> janet_pcall -> F2 -> C func -> janet_pcall -> F3
|
||||
* F2 is not marked (saved only in a C stack local tstate.vm_fiber)
|
||||
*
|
||||
* Build (after building janet):
|
||||
* cc -o build/test-gc-pcall test/test-gc-pcall.c \
|
||||
* -Isrc/include -Isrc/conf build/libjanet.a -lm -lpthread -ldl
|
||||
*
|
||||
* Run:
|
||||
* ./build/test-gc-pcall
|
||||
*/
|
||||
|
||||
#include "janet.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/* C function that calls a Janet callback via janet_pcall. */
|
||||
static Janet cfun_call_via_pcall(int32_t argc, Janet *argv) {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetFunction *fn = janet_getfunction(argv, 0);
|
||||
|
||||
Janet result;
|
||||
JanetFiber *fiber = NULL;
|
||||
JanetSignal sig = janet_pcall(fn, 0, NULL, &result, &fiber);
|
||||
|
||||
if (sig != JANET_SIGNAL_OK) {
|
||||
janet_panicv(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int run_test(JanetTable *env, const char *name, const char *source) {
|
||||
printf(" %s... ", name);
|
||||
fflush(stdout);
|
||||
Janet result;
|
||||
int status = janet_dostring(env, source, name, &result);
|
||||
if (status != 0) {
|
||||
printf("FAIL (crashed or errored)\n");
|
||||
return 1;
|
||||
}
|
||||
printf("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Test 1: Single nesting.
|
||||
* F1 -> cfun_call_via_pcall -> janet_pcall -> F2
|
||||
* F2 is janet_vm.fiber but not root_fiber, so GC can collect it.
|
||||
*
|
||||
* All allocations are done in Janet code so GC checks trigger in the
|
||||
* VM loop (janet_gcalloc does NOT call janet_collect — only the VM's
|
||||
* vm_checkgc_next does). */
|
||||
static const char test_single[] =
|
||||
"(gcsetinterval 1024)\n"
|
||||
"(def cb\n"
|
||||
" (do\n"
|
||||
" (def captured @{:key \"value\" :nested @[1 2 3 4 5]})\n"
|
||||
" (fn []\n"
|
||||
" (var result nil)\n"
|
||||
" (for i 0 500\n"
|
||||
" (def t @{:i i :s (string \"iter-\" i) :arr @[i (+ i 1) (+ i 2)]})\n"
|
||||
" (set result (get captured :key)))\n"
|
||||
" result)))\n"
|
||||
"(for round 0 200\n"
|
||||
" (def result (call-via-pcall cb))\n"
|
||||
" (assert (= result \"value\")\n"
|
||||
" (string \"round \" round \": expected 'value', got \" (describe result))))\n";
|
||||
|
||||
/* Test 2: Deep nesting.
|
||||
* F1 -> cfun_call_via_pcall -> janet_pcall -> F2 -> cfun_call_via_pcall -> janet_pcall -> F3
|
||||
* F2 is saved only in C stack local tstate.vm_fiber, invisible to GC.
|
||||
* F2's stack data can be freed if F2 is collected during F3's execution.
|
||||
*
|
||||
* The inner callback allocates in Janet code (not C) to ensure the
|
||||
* VM loop triggers GC checks during F3's execution. */
|
||||
static const char test_deep[] =
|
||||
"(gcsetinterval 1024)\n"
|
||||
"(def inner-cb\n"
|
||||
" (do\n"
|
||||
" (def captured @{:key \"deep\" :nested @[10 20 30]})\n"
|
||||
" (fn []\n"
|
||||
" (var result nil)\n"
|
||||
" (for i 0 500\n"
|
||||
" (def t @{:i i :s (string \"iter-\" i) :arr @[i (+ i 1) (+ i 2)]})\n"
|
||||
" (set result (get captured :key)))\n"
|
||||
" result)))\n"
|
||||
"\n"
|
||||
"(def outer-cb\n"
|
||||
" (do\n"
|
||||
" (def state @{:count 0 :data @[\"a\" \"b\" \"c\" \"d\" \"e\"]})\n"
|
||||
" (fn []\n"
|
||||
" # This runs on F2. Calling call-via-pcall here creates F3.\n"
|
||||
" # F2 becomes unreachable: it's not root_fiber (that's F1)\n"
|
||||
" # and it's no longer janet_vm.fiber (that's now F3).\n"
|
||||
" (def inner-result (call-via-pcall inner-cb))\n"
|
||||
" # If F2 was collected during F3's execution, accessing\n"
|
||||
" # state here reads freed memory.\n"
|
||||
" (put state :count (+ (state :count) 1))\n"
|
||||
" (string inner-result \"-\" (state :count)))))\n"
|
||||
"\n"
|
||||
"(for round 0 200\n"
|
||||
" (def result (call-via-pcall outer-cb))\n"
|
||||
" (def expected (string \"deep-\" (+ round 1)))\n"
|
||||
" (assert (= result expected)\n"
|
||||
" (string \"round \" round \": expected '\" expected \"', got '\" (describe result) \"'\")))\n";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
int failures = 0;
|
||||
|
||||
janet_init();
|
||||
|
||||
JanetTable *env = janet_core_env(NULL);
|
||||
|
||||
janet_def(env, "call-via-pcall",
|
||||
janet_wrap_cfunction(cfun_call_via_pcall),
|
||||
"Call a function via janet_pcall from C.");
|
||||
|
||||
printf("Testing janet_pcall GC safety:\n");
|
||||
failures += run_test(env, "single-nesting", test_single);
|
||||
failures += run_test(env, "deep-nesting", test_deep);
|
||||
|
||||
janet_deinit();
|
||||
|
||||
if (failures > 0) {
|
||||
printf("\n%d test(s) FAILED\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("\nAll tests passed.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
# Helper code for running tests
|
||||
|
||||
# Turn on strict linting by default in test suite.
|
||||
(put root-env *lint-warn* :strict)
|
||||
|
||||
(var num-tests-passed 0)
|
||||
(var num-tests-run 0)
|
||||
(var suite-name 0)
|
||||
@@ -7,7 +10,7 @@
|
||||
(var skip-count 0)
|
||||
(var skip-n 0)
|
||||
|
||||
(def is-verbose (os/getenv "VERBOSE"))
|
||||
(var is-verbose (os/getenv "VERBOSE"))
|
||||
|
||||
(defn- assert-no-tail
|
||||
"Override's the default assert with some nice error handling."
|
||||
@@ -19,7 +22,6 @@
|
||||
(break x))
|
||||
(default e "assert error")
|
||||
(when x (++ num-tests-passed))
|
||||
(def str (string e))
|
||||
(def stack (debug/stack (fiber/current)))
|
||||
(def frame (last stack))
|
||||
(def line-info (string/format "%s:%d"
|
||||
@@ -39,7 +41,7 @@
|
||||
(defmacro assert
|
||||
[x &opt e]
|
||||
(def xx (gensym))
|
||||
(default e ~',x)
|
||||
(default e (string/format "%j" x))
|
||||
~(do
|
||||
(def ,xx ,x)
|
||||
(,assert-no-tail ,xx ,e)
|
||||
@@ -50,6 +52,11 @@
|
||||
(def errsym (keyword (gensym)))
|
||||
~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg))
|
||||
|
||||
(defmacro assert-error-value
|
||||
[msg errval & forms]
|
||||
(def e (gensym))
|
||||
~(assert (= ,errval (try (do ,;forms) ([,e] ,e))) ,msg))
|
||||
|
||||
(defn check-compile-error
|
||||
[form]
|
||||
(def result (compile form))
|
||||
@@ -60,8 +67,8 @@
|
||||
(def e (gensym))
|
||||
(def f (gensym))
|
||||
(if is-verbose
|
||||
~(try (do ,;forms (,assert true ,msg)) ([,e ,f] (,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m ")))
|
||||
~(try (do ,;forms (,assert true ,msg)) ([_] (,assert false ,msg)))))
|
||||
~(try (do ,;forms (as-macro ,assert true ,msg)) ([,e ,f] (as-macro ,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m ")))
|
||||
~(try (do ,;forms (as-macro ,assert true ,msg)) ([_] (as-macro ,assert false ,msg)))))
|
||||
|
||||
(defn start-suite [&opt x]
|
||||
(default x (dyn :current-file))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 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
|
||||
@@ -86,5 +86,10 @@
|
||||
(assert-error "array/join error 4" (array/join @[] "abc123"))
|
||||
(assert-error "array/join error 5" (array/join @[] "abc123"))
|
||||
|
||||
# Regression 1714
|
||||
(repeat 10
|
||||
(assert (deep= (put @[] 100 10) (put (seq [_ :range [0 101]] nil) 100 10)) "regression 1714")
|
||||
(assert (deep= (put @[] 200 10) (put (seq [_ :range [0 201]] nil) 200 10)) "regression 1714"))
|
||||
|
||||
(end-suite)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 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
|
||||
@@ -21,6 +21,8 @@
|
||||
(import ./helper :prefix "" :exit true)
|
||||
(start-suite)
|
||||
|
||||
(setdyn *lint-warn* :none)
|
||||
|
||||
# Assembly test
|
||||
# Fibonacci sequence, implemented with naive recursion.
|
||||
# a679f60
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 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
|
||||
@@ -21,6 +21,8 @@
|
||||
(import ./helper :prefix "" :exit true)
|
||||
(start-suite)
|
||||
|
||||
(setdyn *lint-warn* :none)
|
||||
|
||||
# Let
|
||||
# 807f981
|
||||
(assert (= (let [a 1 b 2] (+ a b)) 3) "simple let")
|
||||
@@ -865,6 +867,13 @@
|
||||
(assert (deep= ~(,import* "a" :as "b" :fresh maybe)
|
||||
(macex '(import a :as b :fresh maybe))) "import macro 2")
|
||||
|
||||
# 2af3f21d
|
||||
(assert-error "import macro 2" (macex '(import a :fresh)))
|
||||
(assert-error "import macro 3" (macex '(import a :as b :fresh)))
|
||||
(assert-error "import macro 4" (macex '(import b "notakeyword" value)))
|
||||
(assert (deep= ~(,import* "a" :fresh nil)
|
||||
(macex '(import a :fresh nil))) "import macro 5")
|
||||
|
||||
# #477 walk preserving bracket type
|
||||
# 0a1d902f4
|
||||
(assert (= :brackets (tuple/type (postwalk identity '[])))
|
||||
@@ -896,11 +905,18 @@
|
||||
(struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2"))
|
||||
(table/setproto table-to-freeze @{:a @[1 2 3]})
|
||||
|
||||
(assert (deep= {:a [1 2 3] :b [1 2 3 4] :c 22 :d "test" :e "test2"}
|
||||
(freeze table-to-freeze)))
|
||||
(assert (deep= struct-to-thaw (freeze table-to-freeze)))
|
||||
(assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze)))
|
||||
(assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw)))
|
||||
|
||||
# Check that freezing mutable keys is deterministic
|
||||
# for issue #1535
|
||||
(def hashes @{})
|
||||
(repeat 200
|
||||
(def x (freeze {@"" 1 @"" 2 @"" 3 @"" 4 @"" 5}))
|
||||
(put hashes (hash x) true))
|
||||
(assert (= 1 (length hashes)) "freeze mutable keys is deterministic")
|
||||
|
||||
# Make sure Carriage Returns don't end up in doc strings
|
||||
# e528b86
|
||||
(assert (not (string/find "\r"
|
||||
@@ -986,4 +1002,34 @@
|
||||
(assert (deep= (get (dyn 'a) :source-form) source))
|
||||
(setdyn *debug* nil)
|
||||
|
||||
# issue #1516
|
||||
(assert-error "assertf 1 argument" (macex '(assertf true)))
|
||||
(assert (assertf true "fun message") "assertf 2 arguments")
|
||||
(assert (assertf true "%s message" "mystery") "assertf 3 arguments")
|
||||
(assert (assertf (not nil) "%s message" "ordinary") "assertf not nil")
|
||||
(assert-error "assertf error 2" (assertf false "fun message"))
|
||||
(assert-error "assertf error 3" (assertf false "%s message" "mystery"))
|
||||
(assert-error "assertf error 4" (assertf nil "%s %s" "alice" "bob"))
|
||||
|
||||
# issue #1535
|
||||
(loop [i :range [1 1000]]
|
||||
(assert (deep-not= @{:key1 "value1" @"key" "value2"}
|
||||
@{:key1 "value1" @"key" "value2"}) "deep= mutable keys"))
|
||||
(assert (deep-not= {"abc" 123} {@"abc" 123}) "deep= mutable keys vs immutable key")
|
||||
(assert (deep-not= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys")
|
||||
(assert (deep-not= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2")
|
||||
(assert (deep-not= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3")
|
||||
(assert (deep-not= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4")
|
||||
(assert (deep-not= @{:key1 "value1" @"key2" @"value2"}
|
||||
@{:key1 "value1" @"key2" "value2"}) "deep= mutable keys")
|
||||
(assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"}
|
||||
@{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys")
|
||||
|
||||
# different try overloads
|
||||
(assert (= (try (error :error) ([] :caught)) :caught))
|
||||
(assert (= (try (error :error) ([e] e)) :error))
|
||||
(assert (= (try (error :error) ([e fib] [e (fiber? fib)])) [:error true]))
|
||||
# regression test for #1659
|
||||
(assert (= (try (error :error) ([_ _] :caught)) :caught))
|
||||
|
||||
(end-suite)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2024 Calvin Rose
|
||||
# Copyright (c) 2026 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
|
||||
@@ -179,5 +179,10 @@
|
||||
(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"))
|
||||
|
||||
# Regression 1714
|
||||
(repeat 10
|
||||
(assert (deep= (put @"" 100 10) (put (buffer (string/repeat "\0" 101)) 100 10)) "regression 1714")
|
||||
(assert (deep= (put @"" 200 10) (put (buffer (string/repeat "\0" 201)) 200 10)) "regression 1714"))
|
||||
|
||||
(end-suite)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2024 Calvin Rose
|
||||
# Copyright (c) 2026 Calvin Rose
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
@@ -117,6 +117,20 @@
|
||||
(assert (= 0 (length (bundle/list))) "bundles are listed correctly 7")
|
||||
(assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8")
|
||||
|
||||
# Try installing a bundle that is missing bundle script
|
||||
(assert-error-value "bundle missing bundle script"
|
||||
"bundle must contain bundle.janet or bundle/init.janet"
|
||||
(bundle/install "./examples/sample-bad-bundle1"))
|
||||
(assert (= 0 (length (bundle/list))) "check failure 0")
|
||||
(assert (= 0 (length (bundle/topolist))) "check failure 1")
|
||||
|
||||
# Try installing a bundle that fails check
|
||||
(assert-error-value "bundle check hook fails"
|
||||
"Check failed!"
|
||||
(bundle/install "./examples/sample-bad-bundle2" :check true))
|
||||
(assert (= 0 (length (bundle/list))) "check failure 0")
|
||||
(assert (= 0 (length (bundle/topolist))) "check failure 1")
|
||||
|
||||
(rmrf syspath)
|
||||
|
||||
(end-suite)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 Calvin Rose
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 Calvin Rose
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 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
|
||||
@@ -30,7 +30,7 @@
|
||||
(defn myfun [x]
|
||||
(var a 10)
|
||||
(set a (do
|
||||
(def y x)
|
||||
(def _y x)
|
||||
(if x 8 9))))
|
||||
|
||||
(assert (= (myfun true) 8) "check do form regression")
|
||||
@@ -46,8 +46,10 @@
|
||||
# Edge case should cause old compilers to fail due to
|
||||
# if statement optimization
|
||||
# 17283241
|
||||
(setdyn *lint-warn* :relaxed)
|
||||
(var var-a 1)
|
||||
(var var-b (if false 2 (string "hello")))
|
||||
(setdyn *lint-warn* nil)
|
||||
|
||||
(assert (= var-b "hello") "regression 1")
|
||||
|
||||
@@ -73,5 +75,80 @@
|
||||
(foo 0)
|
||||
10)
|
||||
|
||||
# Issue #1699 - fuzz case with bad def
|
||||
(def result
|
||||
(compile '(defn sum3
|
||||
"Solve the 3SUM problem in O(n^2) time."
|
||||
[s]
|
||||
(def)tab @{})))
|
||||
(assert (get result :error) "bad sum3 fuzz issue valgrind")
|
||||
|
||||
# Issue #1700
|
||||
(def result
|
||||
(compile
|
||||
'(defn fuzz-case-1
|
||||
[start end &]
|
||||
(if end
|
||||
(if e start (lazy-range (+ 1 start) end)))
|
||||
1)))
|
||||
(assert (get result :error) "fuzz case issue #1700")
|
||||
|
||||
# Issue #1702 - fuzz case with upvalues
|
||||
(def result
|
||||
(compile
|
||||
'(each item [1 2 3]
|
||||
# Generate a lot of upvalues (more than 224)
|
||||
(def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"")
|
||||
(with-dyns [:out out-buf] 1))))
|
||||
(assert result "bad upvalues fuzz case")
|
||||
|
||||
# Named argument linting
|
||||
# Enhancement for #1654
|
||||
|
||||
(defn fnamed [&named x y z] [x y z])
|
||||
(defn fkeys [&keys ks] ks)
|
||||
(defn fnamed2 [_a _b _c &named x y z] [x y z])
|
||||
(defn fkeys2 [_a _b _c &keys ks] ks)
|
||||
(defn fnamed3 [{:x x} &named a b c] [x a b c])
|
||||
(defn fnamed4 [_y &opt _z &named a b c] [a b c])
|
||||
(defn fnamed5 [&opt _z &named a b c] [a b c])
|
||||
(defn g [x &opt y &named z] [x y z])
|
||||
|
||||
(defn check-good-compile
|
||||
[code msg]
|
||||
(def lints @[])
|
||||
(def result (compile code (curenv) "suite-compile.janet" lints))
|
||||
(assert (and (function? result) (empty? lints)) msg))
|
||||
|
||||
(defn check-lint-compile
|
||||
[code msg]
|
||||
(def lints @[])
|
||||
(def result (compile code (curenv) "suite-compile.janet" lints))
|
||||
(assert (and (function? result) (next lints)) msg))
|
||||
|
||||
(check-good-compile '(fnamed) "named no args")
|
||||
(check-good-compile '(fnamed :x 1 :y 2 :z 3) "named full args")
|
||||
(check-lint-compile '(fnamed :x) "named odd args")
|
||||
(check-lint-compile '(fnamed :w 0) "named wrong key args")
|
||||
(check-good-compile '(fkeys :a 1) "keys even args")
|
||||
(check-lint-compile '(fkeys :a 1 :b) "keys odd args")
|
||||
(check-good-compile '(fnamed2 nil nil nil) "named 2 no args")
|
||||
(check-good-compile '(fnamed2 nil nil nil :x 1 :y 2 :z 3) "named 2 full args")
|
||||
(check-lint-compile '(fnamed2 nil nil nil :x) "named 2 odd args")
|
||||
(check-lint-compile '(fnamed2 nil nil nil :w 0) "named 2 wrong key args")
|
||||
(check-good-compile '(fkeys2 nil nil nil :a 1) "keys 2 even args")
|
||||
(check-lint-compile '(fkeys2 nil nil nil :a 1 :b) "keys 2 odd args")
|
||||
(check-good-compile '(fnamed3 {:x 1} :a 1 :b 2 :c 3) "named 3 good")
|
||||
(check-lint-compile '(fnamed3 {:x 1} :a 1 :b 2 :d 3) "named 3 lint")
|
||||
(check-good-compile '(fnamed4 10 20 :a 1 :b 2 :c 3) "named 4 good")
|
||||
(check-lint-compile '(fnamed4 10 20 :a 1 :b 2 :d 3) "named 4 lint")
|
||||
(check-good-compile '(fnamed5 10 :a 1 :b 2 :c 3) "named 5 good")
|
||||
(check-lint-compile '(fnamed5 10 :a 1 :b 2 :d 3) "named 5 lint")
|
||||
(check-good-compile '(g 1) "g good 1")
|
||||
(check-good-compile '(g 1 2) "g good 2")
|
||||
(check-good-compile '(g 1 2 :z 10) "g good 3")
|
||||
(check-lint-compile '(g 1 2 :z) "g lint 1")
|
||||
(check-lint-compile '(g 1 2 :z 4 5) "g lint 2")
|
||||
|
||||
(end-suite)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2023 Calvin Rose
|
||||
# Copyright (c) 2026 Calvin Rose
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
@@ -174,6 +174,7 @@
|
||||
(assert (deep= (range 0 17 4) @[0 4 8 12 16]) "(range 0 17 4)")
|
||||
(assert (deep= (range 16 0 -4) @[16 12 8 4]) "(range 16 0 -4)")
|
||||
(assert (deep= (range 17 0 -4) @[17 13 9 5 1]) "(range 17 0 -4)")
|
||||
(assert-error "large range" (range 0xFFFFFFFFFF))
|
||||
|
||||
(assert (= (length (range 10)) 10) "(range 10)")
|
||||
(assert (= (length (range -10)) 0) "(range -10)")
|
||||
@@ -184,5 +185,26 @@
|
||||
(assert-no-error "iterate over coro 2" (keys (generate [x :range [0 10]] x)))
|
||||
(assert-no-error "iterate over coro 3" (pairs (generate [x :range [0 10]] x)))
|
||||
|
||||
(end-suite)
|
||||
# thaw
|
||||
(def ds1 [1 2 3 {:a 2} {:b 3} 4 5 6])
|
||||
(def ds2 [1 2 3 {:a 2 {:c :d} {:e :f}} {:b 3} 4 5 6])
|
||||
(assert (deep= (thaw ds1) (thaw-keep-keys ds1)) "thaw vs. thaw-keep-keys 1")
|
||||
(assert (deep-not= (thaw ds2) (thaw-keep-keys ds2)) "thaw vs. thaw-keep-keys 2")
|
||||
|
||||
# match
|
||||
(setdyn *lint-warn* :none)
|
||||
(assert (= :yes (match [1 2 3] [x y z w] :no1 [x y $] :no2 [x y z] :yes)) "match dollar suffix 1")
|
||||
(assert (= :yes (match [1 2 3] [x y z w] :no1 [x y z $] :yes [x y z] :no2)) "match dollar suffix 2")
|
||||
(setdyn *lint-warn* nil)
|
||||
|
||||
# Issue #1687
|
||||
(assert-no-error "def destructure splice works 1" (do (def [a] [;[1]]) a))
|
||||
(assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n))
|
||||
(assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a))
|
||||
|
||||
# Issue #1709
|
||||
(assert (= (macex1 '|(set (my-table [2 1]) 'foo))
|
||||
'(fn :short-fn [] (set (my-table [2 1]) (quote foo))))
|
||||
"Macro expand inside set preserves tuple type correctly")
|
||||
|
||||
(end-suite)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user