1
0
mirror of https://github.com/janet-lang/janet synced 2025-11-08 19:43:41 +00:00

Compare commits

..

135 Commits

Author SHA1 Message Date
Calvin Rose
a62d1c35d4 Add some systems that support addchdir. 2025-04-06 16:55:22 -05:00
Calvin Rose
9a737d1fc4 Add ifdefs for various platforms to check for the support of addchdir.
This will need to be expanded but should support most modern systems.
2025-04-06 16:48:32 -05:00
Calvin Rose
03672cc82b Merge branch 'master' into posix-spawn-chdir 2025-04-06 14:58:04 -05:00
Calvin Rose
73af64e6ff Fix signature on windows. 2025-04-06 14:49:54 -05:00
Calvin Rose
2262759751 Add :cd argument to os/execute and os/spawn. 2025-04-06 14:46:23 -05:00
Calvin Rose
332f123abe Replace if not errorlevel 0 with if errorlevel 1
The former is just bad syntax.
2025-04-05 20:59:45 -05:00
Calvin Rose
38e841fc5c Get rid of test error. 2025-04-05 20:52:47 -05:00
Calvin Rose
e8187fdee5 Fix windows build 2025-04-05 20:26:57 -05:00
Calvin Rose
2fedb67cb3 formatting 2025-04-05 17:32:07 -05:00
Calvin Rose
bdab93c999 Add {:err :out} option to os/spawn.
This special case in the os/spawn interface allows easily
redirecting both stderr and stdout to the same pipe.
2025-04-05 17:30:46 -05:00
Calvin Rose
a9ff8b388f Add os/getpid 2025-04-05 16:20:17 -05:00
Calvin Rose
b12dfd784e Merge pull request #1578 from sogaiu/tweak-some-docstrings
Tweak doc for mapcat, count, keep, all, some
2025-04-04 21:45:09 -05:00
sogaiu
e2cc8f2965 Tweak doc for mapcat, count, keep, all, some 2025-04-05 10:42:04 +09:00
Calvin Rose
17524d2ed3 Merge pull request #1577 from sogaiu/cleanup-unix-domain-socket
Remove unix domain socket after test
2025-04-04 07:11:42 -05:00
sogaiu
d2ee4aa074 Remove unix domain socket after test 2025-04-04 20:22:10 +09:00
Calvin Rose
363e32d455 Adopt docstring from #1574 2025-04-03 20:52:40 -05:00
Calvin Rose
31920e574d Add explicit Alpine Linux directions.
Statically linking Janet is quite useful, especially for users who want
to use jpm and janet-pm to build standalone binaries.
2025-04-01 19:47:03 -05:00
Calvin Rose
cf714ed591 Notify user when script is installed during a bundle installation.
Lets user know what to add to PATH.
2025-04-01 18:52:23 -05:00
Calvin Rose
b458404b41 Update ffi/write to append to a buffer by default. 2025-03-28 10:00:40 -05:00
Calvin Rose
707463a645 Merge pull request #1575 from pyrmont/feature.ev-interrupt2
Expand scope of code that works with `ev/deadline` again
2025-03-26 09:42:51 -05:00
Michael Camilleri
eac37ab869 Avoid leaking memory used for JanetThreadedTimeout struct 2025-03-25 22:02:53 +09:00
Michael Camilleri
a24e5b1eaa Prevent deadline timeout being less than zero 2025-03-25 21:41:59 +09:00
Michael Camilleri
09ac85b1b9 Move janet_timeout_* function definitions before first use 2025-03-25 21:15:28 +09:00
Michael Camilleri
87c1eab7d4 Define JanetTimeout only if event loop enabled 2025-03-25 21:14:21 +09:00
Michael Camilleri
5a29a28c11 Include <windows.h> in state.h 2025-03-25 20:34:45 +09:00
Michael Camilleri
2ed186664f Support using a background thread to monitor deadlines 2025-03-25 20:27:44 +09:00
Calvin Rose
73334f3485 Prepare for 1.38.0 release. 2025-03-18 21:14:45 -05:00
Calvin Rose
a5b8da8d67 Merge pull request #1572 from tsujamin/janet-ffi-define-fix
Remove janet_lib_ffi() dependency on JANET_EV
2025-03-18 19:12:44 -07:00
Benjamin Roberts
e8cccfced5 Remove janet_lib_ffi() dependency on JANET_EV 2025-03-18 10:54:18 -04:00
Calvin Rose
88984f7ffb Add bundle/replace
Slight generalization of bundle/reinstall to allow inplace upgrades
of a bundle without reinstalling all dependent bundles as well.
2025-03-17 17:58:40 -05:00
Calvin Rose
182170b3be Make installed manifests nicer to work with. 2025-03-16 20:39:16 -05:00
Calvin Rose
f92412841b Merge pull request #1569 from sogaiu/vm-bug-1566 2025-03-12 21:13:49 -07:00
sogaiu
18c00e89da Fix vm bugs from #1566 2025-03-13 11:43:43 +09:00
Calvin Rose
7c38a55a9a Merge pull request #1568 from rwtolbert/msys2_mingw_build_fix
Update Makefile and meson to build with msys2 gcc and clang
2025-03-10 17:56:00 -07:00
Bob Tolbert
a15916ec9c Update Makefile and meson to build with msys2 gcc and clang 2025-03-10 10:13:36 -05:00
Calvin Rose
3583d4c92f Fix defdyn in flychecking. 2025-03-08 20:03:02 -06:00
Calvin Rose
a456c67a7b Merge pull request #1564 from pyrmont/bugfix.qemu-segfault
Use Alpine Linux as base image for s390x testing
2025-03-05 05:52:20 -08:00
Michael Camilleri
6e226e4073 Use sh rather than bash on Alpine Linux 2025-03-04 15:19:44 +09:00
Michael Camilleri
d23e6614b1 Use Alpine Linux as the base image rather than Ubuntu 2025-03-04 15:17:07 +09:00
Michael Camilleri
ab6afa72fd Use simpler base image 2025-03-04 15:01:25 +09:00
Calvin Rose
9538b8a77c Merge pull request #1562 from pyrmont/bugfix.subprocess-cleanup
Improve subprocess-related docstrings and functions
2025-03-03 18:47:59 -08:00
Calvin Rose
36d3804dd2 Merge pull request #1563 from pyrmont/bugfix.copyright-dates
Add script to update copyright dates
2025-03-03 18:47:38 -08:00
Michael Camilleri
a34b8ea68f Update older dates to 2025 2025-03-04 11:32:15 +09:00
Michael Camilleri
55c10f98bb Check more file extensions 2025-03-04 11:31:56 +09:00
Michael Camilleri
4b5a2a14c0 Update year in copyright disclaimer to 2025 2025-03-04 11:24:09 +09:00
Michael Camilleri
665705d06d Add Janet script to update copyright disclaimer 2025-03-04 11:23:26 +09:00
Michael Camilleri
cc8cd4bace Use uraimo GitHub action for s390x testing 2025-03-04 09:14:05 +09:00
Michael Camilleri
8f8b6ed001 Remove note on cancellation and tidy up wording 2025-03-03 23:28:37 +09:00
Calvin Rose
aa9efee868 Expose bundle operations in the CLI.
Bundle installation is a fairly natural command on the command line.
2025-03-03 08:14:09 -06:00
Calvin Rose
e0a0e2ed42 Prevent multi-scheduling with os/proc-wait. (address #1562) 2025-03-02 10:44:15 -06:00
Michael Camilleri
39f5c539d7 Clarify wording of docstrings further 2025-03-02 13:25:14 +09:00
Michael Camilleri
1b6437a4f8 Remove unnecessary returns 2025-03-02 08:30:51 +09:00
Michael Camilleri
c62c1a58f0 Reword documentation relating to subprocess creation 2025-03-02 08:29:32 +09:00
Calvin Rose
3441bcbd69 Add more cases for checking coerce error.
Channel operations inside a janet_call could wreak more
havoc later, when scheduled fibers are shooting off when they shouldn't
be. The easiest way to prevent this is simply check that we are not
inside janet_call before doing channel operations. We also get nicer
error messages this way.
2025-02-16 10:40:38 -06:00
Calvin Rose
2e6001316a Work on bug #1556 2025-02-16 09:09:33 -06:00
Calvin Rose
53bcc15207 Merge pull request #1557 from sogaiu/update-changelog-2025-02
Update CHANGELOG.md
2025-02-09 23:22:45 -08:00
sogaiu
dd609bb1bb Update CHANGELOG.md 2025-02-08 14:29:22 +09:00
Calvin Rose
5c56c7fa91 Formatting in peg.c 2025-02-07 21:05:28 -06:00
Calvin Rose
9a892363a3 Address issue #1558
Don't attempt SO_REUSEPORT on unix domain sockets since it doesn't make
sense and may cause a failure to bind without extra effort by the
programmer.
2025-02-07 21:05:07 -06:00
Calvin Rose
5f550ea5d4 Merge pull request #1555 from sogaiu/tweak-apply-short-fn-docstrings
Tweak apply and short-fn docstrings
2025-02-06 16:44:45 -08:00
sogaiu
1b6cc023a5 Tweak apply and short-fn docstrings 2025-02-06 22:35:59 +09:00
Calvin Rose
410f8d69bc Merge pull request #1528 from ianthehenry/til-peg-special
add (til) PEG special
2025-02-03 19:07:08 -08:00
Calvin Rose
6da44bdb6a Get rid of early termination rule in all finite loops. 2025-02-03 07:36:00 -06:00
Calvin Rose
d30fd27575 Address #1554 - capture first 0-width match in repeating rules and
optionals.

This fixes some undesirable interplay between lookaheads and looping
(repeating) rules where lookaheads caused the loop to immediately
terminate _and_ discard any captures. This change adds an exception if
there is a 0-width match at the first iteration of the loop, in which
case captures will be kept, although the loop will still be terminated
(any further iteration of the loop would possibly cause an infinite
 loop).

This allows optionals to work as expected when combined with lookahead.
There is also a question of whether or not optional should be
implemented as separate rule rather than as `(between 0 1 subrule)`.
2025-02-02 20:34:36 -06:00
Calvin Rose
1b278fc657 Update reduce2 documentation to fix issue #1545 2025-02-01 17:31:55 -06:00
Calvin Rose
eecffe01a5 Add some more test cases for hex floats. 2025-02-01 08:09:57 -06:00
Calvin Rose
f63a33884f Add hexfloats - Address #1550 2025-01-30 07:36:18 -06:00
Calvin Rose
fa75a395cb Add support for buffer peg literals - address #1549 2025-01-26 09:48:48 -06:00
Calvin Rose
1f34ec9902 Merge pull request #1547 from pyrmont/bugfix.codeql-upgrade
Update CodeQL actions to latest version
2025-01-25 09:24:40 -08:00
Michael Camilleri
f75c08a78e Add 'tools: linked' to CodeQL workflow 2025-01-24 04:00:52 +09:00
Michael Camilleri
5e93f0e34b Trigger workflow to run again 2025-01-24 03:02:15 +09:00
Calvin Rose
49f151e265 Allocate parser with GC.
This fixes janet_dobytes, which manually allocated a parser on the
stack, and then possibly ran garbage collection before it was
deallocated.
2025-01-22 09:21:56 -06:00
Michael Camilleri
2b73a15ad8 Update CodeQL actions to latest version 2025-01-22 23:47:44 +09:00
Calvin Rose
06d581dde3 Fix #1546 - large ranges.
Raise an error for very large ranges instead of internal assert.
2025-01-20 09:02:22 -06:00
Calvin Rose
2b49903c82 Merge pull request #1544 from cideM/docstring-fixes
Fix docstrings for int/u64, int/s64 and int/to-number
2025-01-14 06:30:08 -08:00
Florian Beeres
a17ae977a5 docstring int/u64 int/s64: supports number as well
Update the docstrings of the u64 and s64 functions to indicate that they
work on numbers as well strings. Previously the docstring only mentioned
string support.
2025-01-11 22:51:21 +01:00
Florian Beeres
8a6b44cb4e docstring int/to-number: supports int64, not int32
Update the cfun_to_number docstring to indicate that it handles values
up to JANET_INTMAX_INT64 (75845c0283),
rather than up to int32, what the current docstring says.
2025-01-11 22:46:44 +01:00
Calvin Rose
60d9f97750 Address issue #1539 - infinite loop in peg split
Other looping rules ensure forward progress by terminating if an
iteration is at the same location as the previous iteration. Do the same
for split.
2025-01-01 11:28:10 -06:00
Calvin Rose
f252933f62 Merge pull request #1541 from rwtolbert/feature/win_arm64_dist
Brief: Add Arm64 .msi support on Windows
2025-01-01 09:19:57 -08:00
Calvin Rose
6dbd7b476c Merge pull request #1538 from sogaiu/tweak-exit-value-doc
Tweak *exit-value* doc - address #1537
2024-12-31 10:28:03 -08:00
Bob Tolbert
a47eb847fb Brief: Add Arm64 .msi support on Windows
Summary: Small update to add Windows on Arm64 support.

Also requires the latest version 3.14 of the WiX toolset.
2024-12-31 11:47:27 -06:00
Calvin Rose
ba5990ef21 Add switch to turn off "reuse" behavior for server sockets. 2024-12-31 09:05:54 -06:00
sogaiu
753911fe2d Tweak *exit-value* doc - address #1537 2024-12-27 16:02:45 +09:00
Calvin Rose
746ced5501 Revert behavior of deep= on mutable keys.
Mutable keys are a minefield for comparisons, as resolving
equality require re-implementing a lot of the internal structures, as
well as dealing with multiple mutable keys that are in the same
equivalency class by deep=.

Simplifying the implementation to not resole mutable keys is much
simpler, faster, and has the benefit that deep= and deep-not= do not
need to allocate.
2024-12-21 09:03:01 -06:00
Calvin Rose
1b49934e4f Allow table/to-struct to take a prototype.
Use this prototype struct in freeze.
2024-12-19 19:41:19 -06:00
Calvin Rose
682f0f584f freeze with mutable keys should be determinsic help address #1535 2024-12-19 19:31:01 -06:00
Calvin Rose
611b2a6c3a Add more test cases for #1535 2024-12-19 18:37:51 -06:00
Calvin Rose
8043caf581 Update CHANGELOG. 2024-12-19 18:31:05 -06:00
Calvin Rose
b2d2690eb9 Merge pull request #1534 from pyrmont/bugfix.windows-longstrings
Support dedenting long-strings with Windows EOLs
2024-12-19 16:24:21 -08:00
Calvin Rose
7f745a34c3 Allow for mutable keys correctly in deep= 2024-12-19 18:20:05 -06:00
Calvin Rose
b16cf17246 Merge pull request #1533 from pyrmont/feature.file-socket
Add `ev/to-file` for synchronous resource operations
2024-12-17 20:50:47 -08:00
Michael Camilleri
67e8518ba6 Support dedenting longstrings with Windows EOLs 2024-12-17 05:14:59 +09:00
Michael Camilleri
e94e8dc484 Remove special casing for MinGW 2024-12-16 08:12:14 +09:00
Michael Camilleri
1a24d4fc86 Raise error if using ev/to-file on MinGW 2024-12-15 21:00:52 +09:00
Michael Camilleri
6ee05785d1 Disable buffering for files created with ev/to-file 2024-12-15 20:37:58 +09:00
Michael Camilleri
268ff666d2 Move <fcntl.h> header to general imports 2024-12-15 20:02:41 +09:00
Michael Camilleri
91bb34c3bf Add missing <io.h> header for Windows 2024-12-15 19:17:48 +09:00
Michael Camilleri
17d5fb3210 Fix ev/to-file on Windows 2024-12-15 18:56:35 +09:00
Michael Camilleri
687b987f7e Add ev/to-file for synchronous resource operations 2024-12-15 17:38:01 +09:00
Calvin Rose
4daecc9a41 Prevent await inside janet_call - address #1531
This was partially implemented before, but not in the case where the
await or other signal itself was created by a C function. We have to
separate code paths for generating signals - one via normal returns in
janet_vm_continue, and the other via longjump. This adds handling for
the longjump case, as well as improved messaging.
2024-12-14 10:34:36 -06:00
Calvin Rose
a85eacadda Merge pull request #1532 from strangepete/fstat-directory-test
file/open: check if directory
2024-12-14 06:00:41 -08:00
peteee
ed63987fd1 use fileno()
Should use fileno() instead of direct
2024-12-14 07:17:28 -05:00
peteee
ff173047f4 file/open: check if directory
Adds fstat() directory test after fopen(), which can return non-NULL when passed a directory name on Linux
2024-12-13 00:20:44 -05:00
Calvin Rose
83e8aab289 Prepare for 1.37.1 release and fix CI. 2024-12-05 20:18:16 -06:00
Calvin Rose
85cb35e68f Prepare for 1.37.0 release. 2024-12-05 17:51:06 -06:00
Ian Henry
952906279c add (til) PEG special
(til sep subpattern) is a specialized (sub) that behaves like
(sub (to sep) subpattern), but advances over the input like (thru sep).
2024-12-04 21:17:10 -08:00
Calvin Rose
5b79b48ae0 Address #1524 - fix meson cross compilation linking.
In the cross compilation case, we need to resolve our
dependencies on libc twice, once for the build machine and once for the
target machine. This includes pthreads, -libc, and android-spawn.
2024-12-03 21:05:37 -06:00
Calvin Rose
7c44127bcb Merge pull request #1526 from sogaiu/master
Additional tweak to address #1523
2024-12-02 05:46:00 -08:00
sogaiu
9338312103 Additional tweak to address #1523 2024-12-02 11:21:56 +09:00
Calvin Rose
a0eeb630e7 Correct documentation for issue #1523
net/* API documentation was not consistent with the implementation. The
`ev/*` module documentation was, however. On timeout, all networking
function calls raise an error and do not return nil. That was the old
behavior.
2024-12-01 09:04:03 -06:00
Calvin Rose
6535d72bd4 Merge pull request #1522 from sogaiu/remove-pstatus
Remove unused var pstatus
2024-11-25 06:15:43 -08:00
sogaiu
a7d424bc81 Remove unused var pstatus 2024-11-25 12:39:53 +09:00
Calvin Rose
2bceba4a7a Assertf with no arguments does not make sense. 2024-11-24 19:14:18 -06:00
Calvin Rose
e3159bb0f5 Update CHANGELOG.md 2024-11-23 10:29:03 -06:00
Calvin Rose
5d1bd8a932 Add an extra has mix round to string hashes.
This should improve hashing quality of strings.
2024-11-17 11:31:12 -06:00
Calvin Rose
bafa6bfff0 Merge pull request #1519 from ianthehenry/fix-string-equal-with-byteview
fix janet_string_equalconst
2024-11-17 07:33:47 -08:00
Ian Henry
e2eb7ab4b2 fix janet_string_equalconst
Check string length before pointer equality, so that a string is not considered
equal to a prefix slice of itself.
2024-11-16 21:20:26 -08:00
Calvin Rose
9f4497a5ae Merge pull request #1518 from pyrmont/bugfix.s390x-workflow
Update Docker command to use `--platform` flag
2024-11-11 12:24:20 -08:00
Michael Camilleri
70de8bf092 Update Docker command to use --platform flag 2024-11-12 04:02:54 +09:00
Calvin Rose
e52575e23a Merge pull request #1517 from sogaiu/add-assertf
Add assertf and use in boot.janet. Address #1516
2024-10-31 07:27:05 -07:00
sogaiu
10994cbc6a Add some tests for assertf 2024-10-30 23:41:31 +09:00
sogaiu
abad9d7db9 Add assertf and use in boot.janet. Address #1516 2024-10-30 17:43:00 +09:00
Calvin Rose
5e443cd29d Merge pull request #1514 from ArtSin/fix-formatb-int32_t-arg
Cast arguments to `int32_t` before passing to `janet_formatb` with `%d` format specifier
2024-10-25 05:36:08 -07:00
Calvin Rose
7bf3a9d24c Merge pull request #1515 from sogaiu/tweak-install-info-in-readme
Clarify installation info a bit
2024-10-25 05:34:53 -07:00
sogaiu
d80a7094ae Clarify installation info a bit 2024-10-25 20:04:56 +09:00
ArtSin
ad77bc391c Cast arguments to int32_t before passing to janet_formatb with %d format specifier
`s->line` and `s->column` in `delim_error` are `size_t`, which is typically 64-bit, but `va_arg` in `janet_formatbv` reads `int32_t` for `%d`.
2024-10-20 12:03:40 +04:00
Calvin Rose
2b84fb14b4 Fix Issue #1512 2024-10-18 18:17:06 -05:00
Calvin Rose
07155ce657 Don't error on empty struct. 2024-10-18 17:53:21 -05:00
Calvin Rose
046d28662d Merge pull request #1513 from sogaiu/add-nth-and-only-tags-to-changelog
Mention nth and only-tags in changelog
2024-10-18 05:45:36 -07:00
sogaiu
84dd3db620 Mention nth and only-tags in changelog 2024-10-16 14:16:05 +09:00
Calvin Rose
282f2671ea Formatting. 2024-10-11 20:10:46 -05:00
Calvin Rose
3fc2be3e6e Use _Exit since it is standard in c99 2024-10-11 20:10:04 -05:00
Calvin Rose
d10c1fe759 Use msvc compiler intrinsics for atomics.
This will let us use GCC atomics on mingw.
2024-10-11 20:03:06 -05:00
Calvin Rose
d18472b07d More CI testing - add meson min build for windows. 2024-10-10 20:42:12 -05:00
Calvin Rose
43a68dcd2a Include windows.h for atomics always in capi.c 2024-10-10 20:32:28 -05:00
105 changed files with 1630 additions and 730 deletions

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -14,11 +14,11 @@ jobs:
matrix:
os: [ ubuntu-latest, macos-latest, macos-13 ]
steps:
- name: Checkout
- name: Checkout the repository
uses: actions/checkout@master
- name: Build
- name: Compile the project
run: make clean && make
- name: Test
- name: Test the project
run: make test
test-windows:
@@ -32,24 +32,51 @@ jobs:
uses: actions/checkout@master
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Build
- name: Build the project
shell: cmd
run: build_win
- name: Test
- name: Test the project
shell: cmd
run: build_win test
- name: Test installer build
shell: cmd
run: build_win dist
test-windows-min:
name: Build and test on Windows Minimal build
strategy:
matrix:
os: [ windows-2019 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install Python Dependencies
run: pip install meson ninja
- name: Build
shell: cmd
run: |
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
cd build_meson_min
ninja
test-mingw:
name: Build on Windows with Mingw
runs-on: windows-2019
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
strategy:
matrix:
msystem: [ UCRT64, MINGW64, MINGW32, CLANG64 ]
msystem: [ UCRT64, CLANG64 ]
steps:
- name: Checkout
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup Mingw
uses: msys2/setup-msys2@v2
@@ -63,15 +90,15 @@ jobs:
- name: Build
shell: cmd
run: make -j4 CC=gcc
- name: Test
- name: Test
shell: cmd
run: make test -j4 CC=gcc
run: make -j4 CC=gcc test
test-mingw-linux:
name: Build and test with Mingw on Linux + Wine
runs-on: ubuntu-latest
steps:
- name: Checkout
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup Mingw and wine
run: |
@@ -79,24 +106,24 @@ jobs:
sudo apt-get update
sudo apt-get install libstdc++6:i386 libgcc-s1:i386
sudo apt-get install gcc-mingw-w64-x86-64-win32 wine wine32 wine64
- name: Build
- name: Compile the project
run: make clean && make CC=x86_64-w64-mingw32-gcc LD=x86_64-w64-mingw32-gcc UNAME=MINGW RUN=wine
- name: Test
- name: Test the project
run: make test UNAME=MINGW RUN=wine VERBOSE=1
test-arm-linux:
name: Build and test ARM32 cross compilation
runs-on: ubuntu-latest
steps:
- name: Checkout
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup qemu and cross compiler
run: |
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabi qemu-user
- name: Build
run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc
- name: Test
- name: Compile the project
run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc
- name: Test the project
run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" SUBRUN="qemu-arm -L /usr/arm-linux-gnueabi/" test VERBOSE=1
test-s390x-linux:
@@ -105,7 +132,7 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Do Qemu build and test
run: |
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --rm -v .:/janet s390x/ubuntu bash -c "apt-get -y update && apt-get -y install git build-essential && cd /janet && make -j3 && make test"
- name: Enable qemu
run: docker run --privileged --rm tonistiigi/binfmt --install s390x
- name: Build and run on emulated architecture
run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test"

View File

@@ -1,11 +1,39 @@
# Changelog
All notable changes to this project will be documented in this file.
## ??? - Unreleased
## Unreleased - ???
- 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.
## 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.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2023 Calvin Rose and contributors
Copyright (c) 2025 Calvin Rose and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -57,6 +57,7 @@ LDFLAGS?=-rdynamic
LIBJANET_LDFLAGS?=$(LD_FLAGS)
RUN:=$(RUN)
COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC
BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 $(COMMON_CFLAGS) -g
BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS)
@@ -94,12 +95,18 @@ endif
endif
# Mingw
MINGW_COMPILER=
ifeq ($(findstring MINGW,$(UNAME)), MINGW)
MINGW_COMPILER=gcc
CLIBS:=-lws2_32 -lpsapi -lwsock32
LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB)
LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB)
JANET_TARGET:=$(JANET_TARGET).exe
JANET_BOOT:=$(JANET_BOOT).exe
COMPILER_VERSION:=$(shell $(CC) --version)
ifeq ($(findstring clang,$(COMPILER_VERSION)), clang)
MINGW_COMPILER=clang
endif
endif
@@ -206,9 +213,14 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile
########################
ifeq ($(UNAME), Darwin)
SONAME=libjanet.1.37.dylib
SONAME=libjanet.1.38.dylib
else
SONAME=libjanet.so.1.37
SONAME=libjanet.so.1.38
endif
ifeq ($(MINGW_COMPILER), clang)
SONAME=
SONAME_SETTER=
endif
build/c/shell.c: src/mainclient/shell.c

View File

@@ -165,6 +165,21 @@ make install-jpm-git
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.
```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
```
### 32-bit Haiku
32-bit Haiku build instructions are the same as the UNIX-like build instructions,
@@ -207,7 +222,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.
@@ -250,8 +265,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

View File

@@ -41,34 +41,34 @@ 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
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 +91,7 @@ exit /b 0
@rem Clean build artifacts
:CLEAN
del *.exe *.lib *.exp
del *.exe *.lib *.exp *.msi *.wixpdb
rd /s /q build
if exist dist (
rd /s /q dist
@@ -102,7 +102,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 +138,18 @@ if defined APPVEYOR_REPO_TAG_NAME (
set RELEASE_VERSION=%JANET_VERSION%
)
if defined CI (
set WIXBIN="c:\Program Files (x86)\WiX Toolset v3.11\bin\"
set WIXBIN="%WIX%bin\"
echo WIXBIN = %WIXBIN%
) else (
set WIXBIN=
)
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %BUILDARCH% -out build\
set WIXARCH=%BUILDARCH%
if "%WIXARCH%"=="aarch64" (
set WIXARCH=arm64
)
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %WIXARCH% -out build\
%WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi
exit /b 0

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose and contributors
# Copyright (c) 2025 Calvin Rose and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -20,16 +20,34 @@
project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.37.0')
version : '1.38.0')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet')
# Link math library on all systems
# Compilers
cc = meson.get_compiler('c')
native_cc = meson.get_compiler('c', native : true)
# Native deps
native_m_dep = native_cc.find_library('m', required : false)
native_dl_dep = native_cc.find_library('dl', required : false)
native_android_spawn_dep = native_cc.find_library('android-spawn', required : false)
native_thread_dep = dependency('threads', native : true)
# Deps
m_dep = cc.find_library('m', required : false)
dl_dep = cc.find_library('dl', required : false)
# for MINGW/MSYS2
native_ws2_dep = native_cc.find_library('ws2_32', required: false)
native_psapi_dep = native_cc.find_library('psapi', required: false)
native_wsock_dep = native_cc.find_library('wsock32', required: false)
ws2_dep = cc.find_library('ws2_32', required: false)
psapi_dep = cc.find_library('psapi', required: false)
wsock_dep = cc.find_library('wsock32', required: false)
android_spawn_dep = cc.find_library('android-spawn', required : false)
thread_dep = dependency('threads')
@@ -164,11 +182,18 @@ mainclient_src = [
'src/mainclient/shell.c'
]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep, ws2_dep, psapi_dep, wsock_dep]
janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep, native_ws2_dep, native_psapi_dep, native_wsock_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
janet_native_dependencies += native_thread_dep
endif
# Build boot binary
janet_boot = executable('janet-boot', core_src, boot_src,
include_directories : incdir,
c_args : '-DJANET_BOOTSTRAP',
dependencies : [m_dep, dl_dep, thread_dep, android_spawn_dep],
dependencies : janet_native_dependencies,
native : true)
# Build janet.c
@@ -181,11 +206,6 @@ janetc = custom_target('janetc',
'JANET_PATH', janet_path
])
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
endif
# Allow building with no shared library
if cc.has_argument('-fvisibility=hidden')
lib_cflags = ['-fvisibility=hidden']
@@ -231,7 +251,7 @@ if meson.is_cross_build()
endif
janet_nativeclient = executable('janet-native', janetc, mainclient_src,
include_directories : incdir,
dependencies : janet_dependencies,
dependencies : janet_native_dependencies,
c_args : extra_native_cflags,
native : true)
else

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
# The core janet library
# Copyright 2024 © Calvin Rose
# Copyright 2025 © Calvin Rose
###
###
@@ -204,6 +204,16 @@
[fmt & args]
(error (string/format fmt ;args)))
(defmacro assertf
"Convenience macro that combines `assert` and `string/format`."
[x fmt & args]
(def v (gensym))
~(do
(def ,v ,x)
(if ,v
,v
(,errorf ,fmt ,;args))))
(defmacro default
``Define a default value for an optional argument.
Expands to `(def sym (if (= nil sym) val sym))`.``
@@ -986,7 +996,7 @@
(defn reduce2
``The 2-argument version of `reduce` that does not take an initialization value.
Instead, the first element of the array is used for initialization.``
Instead, the first element of the array is used for initialization. If `ind` is empty, will evaluate to nil.``
[f ind]
(var k (next ind))
(if (= nil k) (break nil))
@@ -1074,16 +1084,28 @@
(map-aggregator ,maptype ,res (,f x ;call-buffer)))))))
(defn map
`Map a function over every value in a data structure and
return an array of the results.`
```
Map a function `f` over every value in a data structure `ind`
and return an array of results, but only if no `inds` are
provided. Multiple data structures can be handled if each
`inds` is a data structure and `f` is a function of arity
one more than the number of `inds`. The resulting array has
a length that is the shortest of `ind` and each of `inds`.
```
[f ind & inds]
(def res @[])
(map-template :map res f ind inds)
res)
(defn mapcat
``Map a function over every element in an array or tuple and
use `array/concat` to concatenate the results.``
```
Map a function `f` over every value in a data structure `ind`
and use `array/concat` to concatenate the results, but only if
no `inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `f` is a function of
arity one more than the number of `inds`. The resulting array
has a length that is the shortest of `ind` and each of `inds`.
```
[f ind & inds]
(def res @[])
(map-template :mapcat res f ind inds)
@@ -1100,18 +1122,30 @@
res)
(defn count
``Count the number of items in `ind` for which `(pred item)`
is true.``
```
Count the number of values in a data structure `ind` for which
applying `pred` yields a truthy value, but only if no `inds` are
provided. Multiple data structures can be handled if each `inds`
is a data structure and `pred` is a function of arity one more
than the number of `inds`. Note that `pred` is only applied to
values at indeces up to the largest index of the shortest of
`ind` and each of `inds`.
```
[pred ind & inds]
(var res 0)
(map-template :count res pred ind inds)
res)
(defn keep
``Given a predicate `pred`, return a new array containing the truthy results
of applying `pred` to each element in the indexed collection `ind`. This is
different from `filter` which returns an array of the original elements where
the predicate is truthy.``
```
Given a predicate `pred`, return a new array containing the
truthy results of applying `pred` to each value in the data
structure `ind`, but only if no `inds` are provided. Multiple
data structures can be handled if each `inds` is a data
structure and `pred` is a function of arity one more than the
number of `inds`. The resulting array has a length that is the
shortest of `ind` and each of `inds`.
```
[pred ind & inds]
(def res @[])
(map-template :keep res pred ind inds)
@@ -1301,7 +1335,7 @@
(defdyn *redef* "When set, allow dynamically rebinding top level defs. Will slow generated code and is intended to be used for development.")
(defdyn *debug* "Enables a built in debugger on errors and other useful features for debugging in a repl.")
(defdyn *exit* "When set, will cause the current context to complete. Can be set to exit from repl (or file), for example.")
(defdyn *exit-value* "Set the return value from `run-context` upon an exit. By default, `run-context` will return nil.")
(defdyn *exit-value* "Set the return value from `run-context` upon an exit.")
(defdyn *task-id* "When spawning a thread or fiber, the task-id can be assigned for concurrency control.")
(defdyn *current-file*
@@ -2193,72 +2227,62 @@
ret)
(defn all
``Returns true if `(pred item)` is truthy for every item in `ind`.
Otherwise, returns the first falsey result encountered.
Returns true if `ind` is empty.``
```
Returns true if applying `pred` to every value in a data
structure `ind` results in only truthy values, but only if no
`inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `pred` is a function
of arity one more than the number of `inds`. Returns the first
falsey result encountered. Note that `pred` is only called as
many times as the length of the shortest of `ind` and each of
`inds`. If `ind` or any of `inds` are empty, returns true.
```
[pred ind & inds]
(var res true)
(map-template :all res pred ind inds)
res)
(defn some
``Returns nil if `(pred item)` is false or nil for every item in `ind`.
Otherwise, returns the first truthy result encountered.``
```
Returns nil if applying `pred` to every value in a data
structure `ind` results in only falsey values, but only if no
`inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `pred` is a function
of arity one more than the number of `inds`. Returns the first
truthy result encountered. Note that `pred` is only called as
many times as the length of the shortest of `ind` and each of
`inds`. If `ind` or any of `inds` are empty, returns nil.
```
[pred ind & inds]
(var res nil)
(map-template :some res pred ind inds)
res)
(defn deep-not=
``Like `not=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `not=`.``
[x y]
(def tx (type x))
(or
(not= tx (type y))
(case tx
:tuple (or (not= (length x) (length y))
(do
(var ret false)
(forv i 0 (length x)
(def xx (in x i))
(def yy (in y i))
(if (deep-not= xx yy)
(break (set ret true))))
ret))
:array (or (not= (length x) (length y))
(do
(var ret false)
(forv i 0 (length x)
(def xx (in x i))
(def yy (in y i))
(if (deep-not= xx yy)
(break (set ret true))))
ret))
:struct (deep-not= (kvs x) (kvs y))
:table (deep-not= (table/to-struct x) (table/to-struct y))
:buffer (not= (string x) (string y))
(not= x y))))
(defn deep=
``Like `=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `=`.``
[x y]
(not (deep-not= x y)))
(defn freeze
`Freeze an object (make it immutable) and do a deep copy, making
child values also immutable. Closures, fibers, and abstract types
will not be recursively frozen, but all other types will.`
[x]
(case (type x)
:array (tuple/slice (map freeze x))
:tuple (tuple/slice (map freeze x))
:table (if-let [p (table/getproto x)]
(freeze (merge (table/clone p) x))
(struct ;(map freeze (kvs x))))
:struct (struct ;(map freeze (kvs x)))
:buffer (string x)
(def tx (type x))
(cond
(or (= tx :array) (= tx :tuple))
(tuple/slice (map freeze x))
(or (= tx :table) (= tx :struct))
(let [temp-tab @{}]
# Handle multiple unique keys that freeze. Result should
# be independent of iteration order.
(eachp [k v] x
(def kk (freeze k))
(def vv (freeze v))
(def old (get temp-tab kk))
(def new (if (= nil old) vv (max vv old)))
(put temp-tab kk new))
(table/to-struct temp-tab (freeze (getproto x))))
(= tx :buffer)
(string x)
x))
(defn thaw
@@ -2274,6 +2298,41 @@
:string (buffer ds)
ds))
(defn deep-not=
``Like `not=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `not=`.``
[x y]
(def tx (type x))
(or
(not= tx (type y))
(cond
(or (= tx :tuple) (= tx :array))
(or (not= (length x) (length y))
(do
(var ret false)
(forv i 0 (length x)
(def xx (in x i))
(def yy (in y i))
(if (deep-not= xx yy)
(break (set ret true))))
ret))
(or (= tx :struct) (= tx :table))
(or (not= (length x) (length y))
(do
(def rawget (if (= tx :struct) struct/rawget table/rawget))
(var ret false)
(eachp [k v] x
(if (deep-not= (rawget y k) v) (break (set ret true))))
ret))
(= tx :buffer) (not= 0 (- (length x) (length y)) (memcmp x y))
(not= x y))))
(defn deep=
``Like `=`, but mutable types (arrays, tables, buffers) are considered
equal if they have identical structure. Much slower than `=`.``
[x y]
(not (deep-not= x y)))
(defn macex
``Expand macros completely.
`on-binding` is an optional callback for whenever a normal symbolic binding
@@ -2325,17 +2384,11 @@
(defmacro short-fn
```
Shorthand for `fn`. Arguments are given as `$n`, where `n` is the 0-indexed
argument of the function. `$` is also an alias for the first (index 0) argument.
The `$&` symbol will make the anonymous function variadic if it appears in the
body of the function, and can be combined with positional arguments.
Example usage:
(short-fn (+ $ $)) # A function that doubles its arguments.
(short-fn (string $0 $1)) # accepting multiple args.
|(+ $ $) # use pipe reader macro for terse function literals.
|(+ $&) # variadic functions
Shorthand for `fn`. Arguments are given as `$n`, where `n` is the
0-indexed argument of the function. `$` is also an alias for the
first (index 0) argument. The `$&` symbol will make the anonymous
function variadic if it appears in the body of the function, and
can be combined with positional arguments.
```
[arg &opt name]
(var max-param-seen -1)
@@ -2655,7 +2708,6 @@
(do
(var pindex 0)
(var pstatus nil)
(def len (length buf))
(when (= len 0)
(:eof p)
@@ -2845,8 +2897,8 @@
(when (and (string? pattern) (string/has-prefix? ":sys:/" pattern))
(set last-index index)
(array/push copies [(string/replace ":sys:" path pattern) ;(drop 1 entry)])))
(array/insert mp (+ 1 last-index) ;copies)
mp)
(array/insert mp (+ 1 last-index) ;copies)
mp)
(module/add-paths ":native:" :native)
(module/add-paths "/init.janet" :source)
@@ -3864,8 +3916,8 @@
(compwhen (dyn 'net/listen)
(defn net/server
"Start a server asynchronously with `net/listen` and `net/accept-loop`. Returns the new server stream."
[host port &opt handler type]
(def s (net/listen host port type))
[host port &opt handler type no-reuse]
(def s (net/listen host port type no-reuse))
(if handler
(ev/go (fn [] (net/accept-loop s handler))))
s))
@@ -3934,7 +3986,7 @@
(defn make-sig []
(ffi/signature :default real-ret-type ;computed-type-args))
(defn make-ptr []
(assert (ffi/lookup (if lazy (llib) lib) raw-symbol) (string "failed to find ffi symbol " raw-symbol)))
(assertf (ffi/lookup (if lazy (llib) lib) raw-symbol) "failed to find ffi symbol %v" raw-symbol))
(if lazy
~(defn ,alias ,;meta [,;formal-args]
(,ffi/call (,(delay (make-ptr))) (,(delay (make-sig))) ,;formal-args))
@@ -3971,7 +4023,7 @@
(def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true
'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- is-safe-def
'defglobal is-safe-def 'varglobal is-safe-def})
'defglobal is-safe-def 'varglobal is-safe-def 'defdyn true})
(def- importers {'import true 'import* true 'dofile true 'require true})
(defn- use-2 [evaluator args]
@@ -4087,7 +4139,7 @@
(when (empty? b) (buffer/trim b) (os/chmod to perm) (break))
(file/write fto b)
(buffer/clear b)))
(errorf "destination file %s cannot be opened for writing" to))
(errorf "destination file %s cannot be opened for writing" to))
(errorf "source file %s cannot be opened for reading" from)))
(defn- copyrf
@@ -4105,13 +4157,17 @@
[manifest]
(def bn (get manifest :name))
(def manifest-name (get-manifest-filename bn))
(spit manifest-name (string/format "%j\n" manifest)))
(def b @"")
(buffer/format b "%j" manifest) # make sure it is valid jdn
(buffer/clear b)
(buffer/format b "%.99m\n" manifest)
(spit manifest-name b))
(defn bundle/manifest
"Get the manifest for a give installed bundle"
[bundle-name]
(def name (get-manifest-filename bundle-name))
(assert (fexists name) (string "no bundle " bundle-name " found"))
(assertf (fexists name) "no bundle %v found" bundle-name)
(parse (slurp name)))
(defn- get-bundle-module
@@ -4124,7 +4180,7 @@
(os/cd workdir)
([_] (print "cannot enter source directory " workdir " for bundle " bundle-name)))
(defer (os/cd dir)
(def new-env (make-env (curenv)))
(def new-env (make-env))
(put new-env *module-cache* @{})
(put new-env *module-loading* @{})
(put new-env *module-make-env* (fn make-bundle-env [&] (make-env new-env)))
@@ -4139,7 +4195,6 @@
[module bundle-name hook & args]
(def hookf (module/value module (symbol hook)))
(unless hookf (break))
(def manifest (bundle/manifest bundle-name))
(def dir (os/cwd))
(os/cd (get module :workdir "."))
(defer (os/cd dir)
@@ -4254,14 +4309,12 @@
(def missing (seq [d :in deps :when (not (bundle/installed? d))] (string d)))
(when (next missing) (errorf "missing dependencies %s" (string/join missing ", "))))
(def bundle-name (get config :name default-bundle-name))
(assert bundle-name (errorf "unable to infer bundle name for %v, use :name argument" path))
(assert (not (string/check-set "\\/" bundle-name))
(string "bundle name "
bundle-name
" cannot contain path separators"))
(assertf bundle-name "unable to infer bundle name for %v, use :name argument" path)
(assertf (not (string/check-set "\\/" bundle-name))
"bundle name %v cannot contain path separators" bundle-name)
(assert (next bundle-name) "cannot use empty bundle-name")
(assert (not (fexists (get-manifest-filename bundle-name)))
"bundle is already installed")
(assertf (not (fexists (get-manifest-filename bundle-name)))
"bundle %v is already installed" bundle-name)
# Setup installed paths
(prime-bundle-paths)
(os/mkdir (bundle-dir bundle-name))
@@ -4300,6 +4353,9 @@
(when check
(do-hook module bundle-name :check man)))
(print "installed " bundle-name)
(when (get man :has-bin-script)
(def binpath (string (dyn *syspath*) s "bin"))
(eprintf "executable scripts have been installed to %s" binpath))
bundle-name)
(defn- bundle/pack
@@ -4310,7 +4366,7 @@
(var i 0)
(def man (bundle/manifest bundle-name))
(def files (get man :files @[]))
(assert (os/mkdir dest-dir) (string "could not create directory " dest-dir " (or it already exists)"))
(assertf (os/mkdir dest-dir) "could not create directory %v (or it already exists)" dest-dir)
(def s (sep))
(os/mkdir (string dest-dir s "bundle"))
(def install-hook (string dest-dir s "bundle" s "init.janet"))
@@ -4334,14 +4390,15 @@
(spit install-hook b))
dest-dir)
(defn bundle/reinstall
"Reinstall an existing bundle from the local source code."
[bundle-name &keys new-config]
(defn bundle/replace
"Reinstall an existing bundle from a new directory. Similar to bundle/reinstall,
but installs the replacement bundle from any directory. This is necesarry to replace a package without
breaking any dependencies."
[bundle-name path &keys new-config]
(def manifest (bundle/manifest bundle-name))
(def path (get manifest :local-source))
(def config (get manifest :config @{}))
(def s (sep))
(assert (= :directory (os/stat path :mode)) "local source not available")
(assertf (= :directory (os/stat path :mode)) "local source %v not available" path)
(def backup-dir (string (dyn *syspath*) s bundle-name ".backup"))
(rmrf backup-dir)
(def backup-bundle-source (bundle/pack bundle-name backup-dir true))
@@ -4354,6 +4411,14 @@
(rmrf backup-bundle-source)
bundle-name)
(defn bundle/reinstall
"Reinstall an existing bundle from the local source code."
[bundle-name &keys new-config]
(def manifest (bundle/manifest bundle-name))
(def path (get manifest :local-source))
(bundle/replace bundle-name path ;(kvs new-config))
bundle-name)
(defn bundle/add-directory
"Add a directory during the install process relative to `(dyn *syspath*)`"
[manifest dest &opt chmod-mode]
@@ -4401,7 +4466,7 @@
(defn bundle/add
"Add files and directories during a bundle install relative to `(dyn *syspath*)`.
Added paths will be recorded in the bundle manifest such that they are properly tracked
Added files and directories will be recorded in the bundle manifest such that they are properly tracked
and removed during an upgrade or uninstall."
[manifest src &opt dest chmod-mode]
(default dest src)
@@ -4420,10 +4485,12 @@
`Shorthand for adding scripts during an install. Scripts will be installed to
(string (dyn *syspath*) "/bin") by default and will be set to be executable.`
[manifest src &opt dest chmod-mode]
(default dest (last (string/split "/" src)))
(def s (sep))
(default dest (last (string/split s src)))
(default chmod-mode 8r755)
(os/mkdir (string (dyn *syspath*) (sep) "bin"))
(bundle/add-file manifest src (string "bin" (sep) dest) chmod-mode))
(os/mkdir (string (dyn *syspath*) s "bin"))
(put manifest :has-bin-script true)
(bundle/add-file manifest src (string "bin" s dest) chmod-mode))
(defn bundle/update-all
"Reinstall all bundles"
@@ -4486,6 +4553,12 @@
"-nocolor" "n"
"-color" "N"
"-library" "l"
"-install" "b"
"-reinstall" "B"
"-uninstall" "u"
"-update-all" "U"
"-list" "L"
"-prune" "P"
"-lint-warn" "w"
"-lint-error" "x"})
@@ -4496,7 +4569,7 @@
(setdyn *args* args)
(var should-repl false)
(var should-repl nil)
(var no-file true)
(var quiet false)
(var raw-stdin false)
@@ -4551,6 +4624,12 @@
--library (-l) lib : Use a module before processing more arguments
--lint-warn (-w) level : Set the lint warning level - default is "normal"
--lint-error (-x) level : Set the lint error level - default is "none"
--install (-b) dirpath : Install a bundle from a directory
--reinstall (-B) name : Reinstall a bundle by bundle name
--uninstall (-u) name : Uninstall a bundle by bundle name
--update-all (-U) : Reinstall all installed bundles
--prune (-P) : Uninstalled all bundles that are orphaned
--list (-L) : List all installed bundles
-- : Stop handling options
```)
(os/exit 0)
@@ -4595,6 +4674,30 @@
((thunk) ;subargs)
(error (get thunk :error)))
math/inf)
"b"
(compif (dyn 'bundle/install)
(fn [i &] (bundle/install (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2)
(fn [i &] (eprint "--install not supported with reduced os") 2))
"B"
(compif (dyn 'bundle/reinstall)
(fn [i &] (bundle/reinstall (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2)
(fn [i &] (eprint "--reinstall not supported with reduced os") 2))
"u"
(compif (dyn 'bundle/uninstall)
(fn [i &] (bundle/uninstall (in args (+ i 1))) (set no-file false) (if (= nil should-repl) (set should-repl false)) 2)
(fn [i &] (eprint "--uninstall not supported with reduced os") 2))
"P"
(compif (dyn 'bundle/prune)
(fn [i &] (bundle/prune) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1)
(fn [i &] (eprint "--prune not supported with reduced os") 1))
"U"
(compif (dyn 'bundle/update-all)
(fn [i &] (bundle/update-all) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1)
(fn [i &] (eprint "--update-all not supported with reduced os") 1))
"L"
(compif (dyn 'bundle/list)
(fn [i &] (each l (bundle/list) (print l)) (set no-file false) (if (= nil should-repl) (set should-repl false)) 1)
(fn [i &] (eprint "--list not supported with reduced os") 1))
"d" (fn [&] (set debug-flag true) 1)
"w" (fn [i &] (set warn-level (get-lint-level i)) 2)
"x" (fn [i &] (set error-level (get-lint-level i)) 2)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -4,10 +4,10 @@
#define JANETCONF_H
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 37
#define JANET_VERSION_MINOR 38
#define JANET_VERSION_PATCH 0
#define JANET_VERSION_EXTRA "-dev"
#define JANET_VERSION "1.37.0-dev"
#define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.38.0"
/* #define JANET_BUILD "local" */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -34,7 +34,6 @@
#endif
#endif
/* Even if single threaded, include this for atomics! */
#ifdef JANET_WINDOWS
#include <windows.h>
#endif
@@ -63,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;
@@ -549,8 +560,8 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA
/* Atomic refcounts */
JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
#ifdef JANET_WINDOWS
return InterlockedIncrement(x);
#ifdef _MSC_VER
return _InterlockedIncrement(x);
#elif defined(JANET_USE_STDATOMIC)
return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1;
#else
@@ -559,8 +570,8 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
}
JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
#ifdef JANET_WINDOWS
return InterlockedDecrement(x);
#ifdef _MSC_VER
return _InterlockedDecrement(x);
#elif defined(JANET_USE_STDATOMIC)
return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1;
#else
@@ -569,8 +580,8 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
}
JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
#ifdef JANET_WINDOWS
return InterlockedOr(x, 0);
#ifdef _MSC_VER
return _InterlockedOr(x, 0);
#elif defined(JANET_USE_STDATOMIC)
return atomic_load_explicit(x, memory_order_acquire);
#else

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -449,8 +449,9 @@ JANET_CORE_FN(janet_core_range,
}
count = (count > 0) ? count : 0;
int32_t int_count;
janet_assert(count >= 0, "bad range code");
if (count > (double) INT32_MAX) {
int_count = INT32_MAX;
janet_panicf("range is too large, %f elements", count);
} else {
int_count = (int32_t) ceil(count);
}
@@ -1001,12 +1002,11 @@ static void make_apply(JanetTable *env) {
janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG,
"apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm),
JDOC("(apply f & args)\n\n"
"Applies a function to a variable number of arguments. Each element in args "
"is used as an argument to f, except the last element in args, which is expected to "
"be an array-like. Each element in this last argument is then also pushed as an argument to "
"f. For example:\n\n"
"\t(apply + 1000 (range 10))\n\n"
"sums the first 10 integers and 1000."));
"Applies a function f to a variable number of arguments. Each "
"element in args is used as an argument to f, except the last "
"element in args, which is expected to be an array or a tuple. "
"Each element in this last argument is then also pushed as an "
"argument to f."));
}
static const uint32_t error_asm[] = {
@@ -1159,82 +1159,82 @@ JanetTable *janet_core_env(JanetTable *replacements) {
janet_quick_asm(env, JANET_FUN_CMP,
"cmp", 2, 2, 2, 2, cmp_asm, sizeof(cmp_asm),
JDOC("(cmp x y)\n\n"
"Returns -1 if x is strictly less than y, 1 if y is strictly greater "
"than x, and 0 otherwise. To return 0, x and y must be the exact same type."));
"Returns -1 if x is strictly less than y, 1 if y is strictly greater "
"than x, and 0 otherwise. To return 0, x and y must be the exact same type."));
janet_quick_asm(env, JANET_FUN_NEXT,
"next", 2, 1, 2, 2, next_asm, sizeof(next_asm),
JDOC("(next ds &opt key)\n\n"
"Gets the next key in a data structure. Can be used to iterate through "
"the keys of a data structure in an unspecified order. Keys are guaranteed "
"to be seen only once per iteration if the data structure is not mutated "
"during iteration. If key is nil, next returns the first key. If next "
"returns nil, there are no more keys to iterate through."));
"Gets the next key in a data structure. Can be used to iterate through "
"the keys of a data structure in an unspecified order. Keys are guaranteed "
"to be seen only once per iteration if the data structure is not mutated "
"during iteration. If key is nil, next returns the first key. If next "
"returns nil, there are no more keys to iterate through."));
janet_quick_asm(env, JANET_FUN_PROP,
"propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm),
JDOC("(propagate x fiber)\n\n"
"Propagate a signal from a fiber to the current fiber and "
"set the last value of the current fiber to `x`. The signal "
"value is then available as the status of the current fiber. "
"The resulting stack trace from the current fiber will include "
"frames from fiber. If fiber is in a state that can be resumed, "
"resuming the current fiber will first resume `fiber`. "
"This function can be used to re-raise an error without losing "
"the original stack trace."));
"Propagate a signal from a fiber to the current fiber and "
"set the last value of the current fiber to `x`. The signal "
"value is then available as the status of the current fiber. "
"The resulting stack trace from the current fiber will include "
"frames from fiber. If fiber is in a state that can be resumed, "
"resuming the current fiber will first resume `fiber`. "
"This function can be used to re-raise an error without losing "
"the original stack trace."));
janet_quick_asm(env, JANET_FUN_DEBUG,
"debug", 1, 0, 1, 1, debug_asm, sizeof(debug_asm),
JDOC("(debug &opt x)\n\n"
"Throws a debug signal that can be caught by a parent fiber and used to inspect "
"the running state of the current fiber. Returns the value passed in by resume."));
"Throws a debug signal that can be caught by a parent fiber and used to inspect "
"the running state of the current fiber. Returns the value passed in by resume."));
janet_quick_asm(env, JANET_FUN_ERROR,
"error", 1, 1, 1, 1, error_asm, sizeof(error_asm),
JDOC("(error e)\n\n"
"Throws an error e that can be caught and handled by a parent fiber."));
"Throws an error e that can be caught and handled by a parent fiber."));
janet_quick_asm(env, JANET_FUN_YIELD,
"yield", 1, 0, 1, 2, yield_asm, sizeof(yield_asm),
JDOC("(yield &opt x)\n\n"
"Yield a value to a parent fiber. When a fiber yields, its execution is paused until "
"another thread resumes it. The fiber will then resume, and the last yield call will "
"return the value that was passed to resume."));
"Yield a value to a parent fiber. When a fiber yields, its execution is paused until "
"another thread resumes it. The fiber will then resume, and the last yield call will "
"return the value that was passed to resume."));
janet_quick_asm(env, JANET_FUN_CANCEL,
"cancel", 2, 2, 2, 2, cancel_asm, sizeof(cancel_asm),
JDOC("(cancel fiber err)\n\n"
"Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. "
"Returns the same result as resume."));
"Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. "
"Returns the same result as resume."));
janet_quick_asm(env, JANET_FUN_RESUME,
"resume", 2, 1, 2, 2, resume_asm, sizeof(resume_asm),
JDOC("(resume fiber &opt x)\n\n"
"Resume a new or suspended fiber and optionally pass in a value to the fiber that "
"will be returned to the last yield in the case of a pending fiber, or the argument to "
"the dispatch function in the case of a new fiber. Returns either the return result of "
"the fiber's dispatch function, or the value from the next yield call in fiber."));
"Resume a new or suspended fiber and optionally pass in a value to the fiber that "
"will be returned to the last yield in the case of a pending fiber, or the argument to "
"the dispatch function in the case of a new fiber. Returns either the return result of "
"the fiber's dispatch function, or the value from the next yield call in fiber."));
janet_quick_asm(env, JANET_FUN_IN,
"in", 3, 2, 3, 4, in_asm, sizeof(in_asm),
JDOC("(in ds key &opt dflt)\n\n"
"Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, "
"strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, "
"and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can "
"take any value as a key except nil and will return nil or dflt if not found."));
"Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, "
"strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, "
"and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can "
"take any value as a key except nil and will return nil or dflt if not found."));
janet_quick_asm(env, JANET_FUN_GET,
"get", 3, 2, 3, 4, get_asm, sizeof(in_asm),
JDOC("(get ds key &opt dflt)\n\n"
"Get the value mapped to key in data structure ds, and return dflt or nil if not found. "
"Similar to in, but will not throw an error if the key is invalid for the data structure "
"unless the data structure is an abstract type. In that case, the abstract type getter may throw "
"an error."));
"Get the value mapped to key in data structure ds, and return dflt or nil if not found. "
"Similar to in, but will not throw an error if the key is invalid for the data structure "
"unless the data structure is an abstract type. In that case, the abstract type getter may throw "
"an error."));
janet_quick_asm(env, JANET_FUN_PUT,
"put", 3, 3, 3, 3, put_asm, sizeof(put_asm),
JDOC("(put ds key value)\n\n"
"Associate a key with a value in any mutable associative data structure. Indexed data structures "
"(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds "
"value is provided. In an array, extra space will be filled with nils, and in a buffer, extra "
"space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype "
"will hide the association defined by the prototype, but will not mutate the prototype table. Putting "
"a value nil into a table will remove the key from the table. Returns the data structure ds."));
"Associate a key with a value in any mutable associative data structure. Indexed data structures "
"(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds "
"value is provided. In an array, extra space will be filled with nils, and in a buffer, extra "
"space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype "
"will hide the association defined by the prototype, but will not mutate the prototype table. Putting "
"a value nil into a table will remove the key from the table. Returns the data structure ds."));
janet_quick_asm(env, JANET_FUN_LENGTH,
"length", 1, 1, 1, 1, length_asm, sizeof(length_asm),
JDOC("(length ds)\n\n"
"Returns the length or count of a data structure in constant time as an integer. For "
"structs and tables, returns the number of key-value pairs in the data structure."));
"Returns the length or count of a data structure in constant time as an integer. For "
"structs and tables, returns the number of key-value pairs in the data structure."));
janet_quick_asm(env, JANET_FUN_BNOT,
"bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm),
JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x."));
@@ -1243,74 +1243,74 @@ JanetTable *janet_core_env(JanetTable *replacements) {
/* Variadic ops */
templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD,
JDOC("(+ & xs)\n\n"
"Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0."));
"Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0."));
templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT,
JDOC("(- & xs)\n\n"
"Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the "
"negative value of that element. Otherwise, returns the first element in xs minus the sum of "
"the rest of the elements."));
"Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the "
"negative value of that element. Otherwise, returns the first element in xs minus the sum of "
"the rest of the elements."));
templatize_varop(env, JANET_FUN_MULTIPLY, "*", 1, 1, JOP_MULTIPLY,
JDOC("(* & xs)\n\n"
"Returns the product of all elements in xs. If xs is empty, returns 1."));
"Returns the product of all elements in xs. If xs is empty, returns 1."));
templatize_varop(env, JANET_FUN_DIVIDE, "/", 1, 1, JOP_DIVIDE,
JDOC("(/ & xs)\n\n"
"Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns "
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
"values."));
"Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns "
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
"values."));
templatize_varop(env, JANET_FUN_DIVIDE_FLOOR, "div", 1, 1, JOP_DIVIDE_FLOOR,
JDOC("(div & xs)\n\n"
"Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns "
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
"values."));
"Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns "
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
"values."));
templatize_varop(env, JANET_FUN_MODULO, "mod", 0, 1, JOP_MODULO,
JDOC("(mod & xs)\n\n"
"Returns the result of applying the modulo operator on the first value of xs with each remaining value. "
"`(mod x 0)` is defined to be `x`."));
"Returns the result of applying the modulo operator on the first value of xs with each remaining value. "
"`(mod x 0)` is defined to be `x`."));
templatize_varop(env, JANET_FUN_REMAINDER, "%", 0, 1, JOP_REMAINDER,
JDOC("(% & xs)\n\n"
"Returns the remainder of dividing the first value of xs by each remaining value."));
"Returns the remainder of dividing the first value of xs by each remaining value."));
templatize_varop(env, JANET_FUN_BAND, "band", -1, -1, JOP_BAND,
JDOC("(band & xs)\n\n"
"Returns the bit-wise and of all values in xs. Each x in xs must be an integer."));
"Returns the bit-wise and of all values in xs. Each x in xs must be an integer."));
templatize_varop(env, JANET_FUN_BOR, "bor", 0, 0, JOP_BOR,
JDOC("(bor & xs)\n\n"
"Returns the bit-wise or of all values in xs. Each x in xs must be an integer."));
"Returns the bit-wise or of all values in xs. Each x in xs must be an integer."));
templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR,
JDOC("(bxor & xs)\n\n"
"Returns the bit-wise xor of all values in xs. Each in xs must be an integer."));
"Returns the bit-wise xor of all values in xs. Each in xs must be an integer."));
templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT,
JDOC("(blshift x & shifts)\n\n"
"Returns the value of x bit shifted left by the sum of all values in shifts. x "
"and each element in shift must be an integer."));
"Returns the value of x bit shifted left by the sum of all values in shifts. x "
"and each element in shift must be an integer."));
templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT,
JDOC("(brshift x & shifts)\n\n"
"Returns the value of x bit shifted right by the sum of all values in shifts. x "
"and each element in shift must be an integer."));
"Returns the value of x bit shifted right by the sum of all values in shifts. x "
"and each element in shift must be an integer."));
templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED,
JDOC("(brushift x & shifts)\n\n"
"Returns the value of x bit shifted right by the sum of all values in shifts. x "
"and each element in shift must be an integer. The sign of x is not preserved, so "
"for positive shifts the return value will always be positive."));
"Returns the value of x bit shifted right by the sum of all values in shifts. x "
"and each element in shift must be an integer. The sign of x is not preserved, so "
"for positive shifts the return value will always be positive."));
/* Variadic comparators */
templatize_comparator(env, JANET_FUN_GT, ">", 0, JOP_GREATER_THAN,
JDOC("(> & xs)\n\n"
"Check if xs is in descending order. Returns a boolean."));
"Check if xs is in descending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_LT, "<", 0, JOP_LESS_THAN,
JDOC("(< & xs)\n\n"
"Check if xs is in ascending order. Returns a boolean."));
"Check if xs is in ascending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_GTE, ">=", 0, JOP_GREATER_THAN_EQUAL,
JDOC("(>= & xs)\n\n"
"Check if xs is in non-ascending order. Returns a boolean."));
"Check if xs is in non-ascending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_LTE, "<=", 0, JOP_LESS_THAN_EQUAL,
JDOC("(<= & xs)\n\n"
"Check if xs is in non-descending order. Returns a boolean."));
"Check if xs is in non-descending order. Returns a boolean."));
templatize_comparator(env, JANET_FUN_EQ, "=", 0, JOP_EQUALS,
JDOC("(= & xs)\n\n"
"Check if all values in xs are equal. Returns a boolean."));
"Check if all values in xs are equal. Returns a boolean."));
templatize_comparator(env, JANET_FUN_NEQ, "not=", 1, JOP_EQUALS,
JDOC("(not= & xs)\n\n"
"Check if any values in xs are not equal. Returns a boolean."));
"Check if any values in xs are not equal. Returns a boolean."));
/* Platform detection */
janet_def(env, "janet/version", janet_cstringv(JANET_VERSION),
@@ -1319,7 +1319,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
JDOC("The build identifier of the running janet program."));
janet_def(env, "janet/config-bits", janet_wrap_integer(JANET_CURRENT_CONFIG_BITS),
JDOC("The flag set of config options from janetconf.h which is used to check "
"if native modules are compatible with the host program."));
"if native modules are compatible with the host program."));
/* Allow references to the environment */
janet_def(env, "root-env", janet_wrap_table(env),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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,13 @@ typedef struct {
JanetHandle write_pipe;
} JanetEVThreadInit;
/* Structure used to initialize threads that run timeouts */
typedef struct {
double sec;
JanetVM *vm;
JanetFiber *fiber;
} JanetThreadedTimeout;
#define JANET_MAX_Q_CAPACITY 0x7FFFFFF
static void janet_q_init(JanetQueue *q) {
@@ -622,9 +630,67 @@ 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);
}
#ifdef JANET_WINDOWS
static VOID CALLBACK janet_timeout_stop(ULONG_PTR ptr) {
UNREFERENCED_PARAMETER(ptr);
ExitThread(0);
}
#endif
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);
SleepEx((DWORD)(tto.sec * 1000), TRUE);
if (janet_fiber_can_resume(tto.fiber)) {
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) {
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);
if (janet_fiber_can_resume(tto.fiber)) {
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);
}
@@ -1024,6 +1090,9 @@ JANET_CORE_FN(cfun_channel_push,
"Returns the channel if the write succeeded, nil otherwise.") {
janet_fixarity(argc, 2);
JanetChannel *channel = janet_getchannel(argv, 0);
if (janet_vm.coerce_error) {
janet_panic("cannot give to channel inside janet_call");
}
if (janet_channel_push(channel, argv[1], 0)) {
janet_await();
}
@@ -1036,6 +1105,9 @@ JANET_CORE_FN(cfun_channel_pop,
janet_fixarity(argc, 1);
JanetChannel *channel = janet_getchannel(argv, 0);
Janet item;
if (janet_vm.coerce_error) {
janet_panic("cannot take from channel inside janet_call");
}
if (janet_channel_pop(channel, &item, 0)) {
janet_schedule(janet_vm.root_fiber, item);
}
@@ -1072,6 +1144,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) {
@@ -1408,6 +1484,17 @@ JanetFiber *janet_loop1(void) {
while ((has_timeout = peek_timeout(&to))) {
if (to.curr_fiber != NULL) {
if (!janet_fiber_can_resume(to.curr_fiber)) {
if (to.has_worker) {
#ifdef JANET_WINDOWS
QueueUserAPC(janet_timeout_stop, to.worker, 0);
WaitForSingleObject(to.worker, INFINITE);
CloseHandle(to.worker);
#else
pthread_cancel(to.worker);
void *res;
pthread_join(to.worker, &res);
#endif
}
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
pop_timeout(0);
continue;
@@ -1776,6 +1863,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);
}
@@ -3064,26 +3167,53 @@ 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) {
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 worker = CreateThread(NULL, 0, janet_timeout_body, tto, 0, 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;
} else {
to.has_worker = 0;
}
add_timeout(to);
return janet_wrap_fiber(tocancel);
}
@@ -3263,6 +3393,64 @@ JANET_CORE_FN(janet_cfun_rwlock_write_release,
return argv[0];
}
static JanetFile *get_file_for_stream(JanetStream *stream) {
int32_t flags = 0;
char fmt[4] = {0};
int index = 0;
if (stream->flags & JANET_STREAM_READABLE) {
flags |= JANET_FILE_READ;
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
fmt[index++] = 'r';
}
if (stream->flags & JANET_STREAM_WRITABLE) {
flags |= JANET_FILE_WRITE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
int currindex = index;
fmt[index++] = (currindex == 0) ? 'w' : '+';
}
if (index == 0) return NULL;
/* duplicate handle when converting stream to file */
#ifdef JANET_WINDOWS
int htype = 0;
if (fmt[0] == 'r' && fmt[1] == '+') {
htype = _O_RDWR;
} else if (fmt[0] == 'r') {
htype = _O_RDONLY;
} else if (fmt[0] == 'w') {
htype = _O_WRONLY;
}
int fd = _open_osfhandle((intptr_t) stream->handle, htype);
if (fd < 0) return NULL;
int fd_dup = _dup(fd);
if (fd_dup < 0) return NULL;
FILE *f = _fdopen(fd_dup, fmt);
if (NULL == f) {
_close(fd_dup);
return NULL;
}
#else
int fd_dup = dup(stream->handle);
if (fd_dup < 0) return NULL;
FILE *f = fdopen(fd_dup, fmt);
if (NULL == f) {
close(fd_dup);
return NULL;
}
#endif
return janet_makejfile(f, flags);
}
JANET_CORE_FN(janet_cfun_to_file,
"(ev/to-file)",
"Create core/file copy of the stream. This value can be used "
"when blocking IO behavior is needed.") {
janet_fixarity(argc, 1);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
JanetFile *iof = get_file_for_stream(stream);
if (iof == NULL) janet_panic("cannot make file from stream");
return janet_wrap_abstract(iof);
}
JANET_CORE_FN(janet_cfun_ev_all_tasks,
"(ev/all-tasks)",
"Get an array of all active fibers that are being used by the scheduler.") {
@@ -3307,6 +3495,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
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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;
}
@@ -1684,7 +1686,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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -47,7 +47,7 @@ typedef struct {
#ifndef JANET_WINDOWS
JanetStream *stream;
#endif
JanetTable* watch_descriptors;
JanetTable *watch_descriptors;
JanetChannel *channel;
uint32_t default_flags;
int is_watching;
@@ -85,9 +85,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) {
}
JanetKeyword keyw = janet_unwrap_keyword(options[i]);
const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_linux,
sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName),
sizeof(JanetWatchFlagName),
keyw);
sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName),
sizeof(JanetWatchFlagName),
keyw);
if (!result) {
janet_panicf("unknown inotify flag %v", options[i]);
}
@@ -154,99 +154,97 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber);
break;
case JANET_ASYNC_EVENT_ERR:
{
janet_schedule(fiber, janet_wrap_nil());
case JANET_ASYNC_EVENT_ERR: {
janet_schedule(fiber, janet_wrap_nil());
janet_async_end(fiber);
break;
}
read_more:
case JANET_ASYNC_EVENT_HUP:
case JANET_ASYNC_EVENT_INIT:
case JANET_ASYNC_EVENT_READ: {
Janet name = janet_wrap_nil();
/* Assumption - read will never return partial events *
* From documentation:
*
* The behavior when the buffer given to read(2) is too small to
* return information about the next event depends on the kernel
* version: before Linux 2.6.21, read(2) returns 0; since Linux
* 2.6.21, read(2) fails with the error EINVAL. Specifying a buffer
* of size
*
* sizeof(struct inotify_event) + NAME_MAX + 1
*
* will be sufficient to read at least one event. */
ssize_t nread;
do {
nread = read(stream->handle, buf, sizeof(buf));
} while (nread == -1 && errno == EINTR);
/* Check for errors - special case errors that can just be waited on to fix */
if (nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
janet_cancel(fiber, janet_ev_lasterr());
fiber->ev_state = NULL;
janet_async_end(fiber);
break;
}
read_more:
case JANET_ASYNC_EVENT_HUP:
case JANET_ASYNC_EVENT_INIT:
case JANET_ASYNC_EVENT_READ:
{
Janet name = janet_wrap_nil();
if (nread < (ssize_t) sizeof(struct inotify_event)) break;
/* Assumption - read will never return partial events *
* From documentation:
*
* The behavior when the buffer given to read(2) is too small to
* return information about the next event depends on the kernel
* version: before Linux 2.6.21, read(2) returns 0; since Linux
* 2.6.21, read(2) fails with the error EINVAL. Specifying a buffer
* of size
*
* sizeof(struct inotify_event) + NAME_MAX + 1
*
* will be sufficient to read at least one event. */
ssize_t nread;
do {
nread = read(stream->handle, buf, sizeof(buf));
} while (nread == -1 && errno == EINTR);
/* Check for errors - special case errors that can just be waited on to fix */
if (nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
janet_cancel(fiber, janet_ev_lasterr());
fiber->ev_state = NULL;
janet_async_end(fiber);
break;
/* Iterate through all events read from the buffer */
char *cursor = buf;
while (cursor < buf + nread) {
struct inotify_event inevent;
memcpy(&inevent, cursor, sizeof(inevent));
cursor += sizeof(inevent);
/* Read path of inevent */
if (inevent.len) {
name = janet_cstringv(cursor);
cursor += inevent.len;
}
if (nread < (ssize_t) sizeof(struct inotify_event)) break;
/* Iterate through all events read from the buffer */
char *cursor = buf;
while (cursor < buf + nread) {
struct inotify_event inevent;
memcpy(&inevent, cursor, sizeof(inevent));
cursor += sizeof(inevent);
/* Read path of inevent */
if (inevent.len) {
name = janet_cstringv(cursor);
cursor += inevent.len;
/* Got an event */
Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd));
JanetKV *event = janet_struct_begin(6);
janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd));
janet_struct_put(event, janet_ckeywordv("wd-path"), path);
if (janet_checktype(name, JANET_NIL)) {
/* We were watching a file directly, so path is the full path. Split into dirname / basename */
JanetString spath = janet_unwrap_string(path);
const uint8_t *cursor = spath + janet_string_length(spath);
const uint8_t *cursor_end = cursor;
while (cursor > spath && cursor[0] != '/') {
cursor--;
}
/* Got an event */
Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd));
JanetKV *event = janet_struct_begin(6);
janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd));
janet_struct_put(event, janet_ckeywordv("wd-path"), path);
if (janet_checktype(name, JANET_NIL)) {
/* We were watching a file directly, so path is the full path. Split into dirname / basename */
JanetString spath = janet_unwrap_string(path);
const uint8_t *cursor = spath + janet_string_length(spath);
const uint8_t *cursor_end = cursor;
while (cursor > spath && cursor[0] != '/') {
cursor--;
}
if (cursor == spath) {
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
janet_struct_put(event, janet_ckeywordv("file-name"), name);
} else {
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath))));
janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1))));
}
} else {
if (cursor == spath) {
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
janet_struct_put(event, janet_ckeywordv("file-name"), name);
} else {
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath))));
janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1))));
}
janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie));
Janet etype = janet_ckeywordv("type");
const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]);
for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) {
if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name));
}
Janet eventv = janet_wrap_struct(janet_struct_end(event));
janet_channel_give(watcher->channel, eventv);
} else {
janet_struct_put(event, janet_ckeywordv("dir-name"), path);
janet_struct_put(event, janet_ckeywordv("file-name"), name);
}
janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie));
Janet etype = janet_ckeywordv("type");
const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]);
for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) {
if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name));
}
Janet eventv = janet_wrap_struct(janet_struct_end(event));
/* Read some more if possible */
goto read_more;
janet_channel_give(watcher->channel, eventv);
}
break;
/* Read some more if possible */
goto read_more;
}
break;
}
}
@@ -273,7 +271,8 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) {
#define WATCHFLAG_RECURSIVE 0x100000u
static const JanetWatchFlagName watcher_flags_windows[] = {
{"all",
{
"all",
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_DIR_NAME |
@@ -282,7 +281,8 @@ static const JanetWatchFlagName watcher_flags_windows[] = {
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_SECURITY |
FILE_NOTIFY_CHANGE_SIZE |
WATCHFLAG_RECURSIVE},
WATCHFLAG_RECURSIVE
},
{"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES},
{"creation", FILE_NOTIFY_CHANGE_CREATION},
{"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME},
@@ -302,9 +302,9 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) {
}
JanetKeyword keyw = janet_unwrap_keyword(options[i]);
const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows,
sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName),
sizeof(JanetWatchFlagName),
keyw);
sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName),
sizeof(JanetWatchFlagName),
keyw);
if (!result) {
janet_panicf("unknown windows filewatch flag %v", options[i]);
}
@@ -339,19 +339,19 @@ typedef struct {
static void read_dir_changes(OverlappedWatch *ow) {
BOOL result = ReadDirectoryChangesW(ow->stream->handle,
(NotifyChange *) ow->buf,
FILE_INFO_PADDING,
(ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE,
ow->flags & ~WATCHFLAG_RECURSIVE,
NULL,
(OVERLAPPED *) ow,
NULL);
(NotifyChange *) ow->buf,
FILE_INFO_PADDING,
(ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE,
ow->flags & ~WATCHFLAG_RECURSIVE,
NULL,
(OVERLAPPED *) ow,
NULL);
if (!result) {
janet_panicv(janet_ev_lasterr());
}
}
static const char* watcher_actions_windows[] = {
static const char *watcher_actions_windows[] = {
"unknown",
"added",
"removed",
@@ -382,48 +382,47 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
case JANET_ASYNC_EVENT_FAILED:
janet_stream_close(ow->stream);
break;
case JANET_ASYNC_EVENT_COMPLETE:
{
if (!watcher->is_watching) {
janet_stream_close(ow->stream);
break;
}
NotifyChange *fni = (NotifyChange *) ow->buf;
while (1) {
/* Got an event */
/* Extract name */
Janet filename;
if (fni->FileNameLength) {
int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL);
janet_assert(nbytes, "bad utf8 path");
uint8_t *into = janet_string_begin(nbytes);
WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL);
filename = janet_wrap_string(janet_string_end(into));
} else {
filename = janet_cstringv("");
}
JanetKV *event = janet_struct_begin(3);
janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action]));
janet_struct_put(event, janet_ckeywordv("file-name"), filename);
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path));
Janet eventv = janet_wrap_struct(janet_struct_end(event));
janet_channel_give(watcher->channel, eventv);
/* Next event */
if (!fni->NextEntryOffset) break;
fni = (NotifyChange *) ((char *)fni + fni->NextEntryOffset);
}
/* Make another call to read directory changes */
read_dir_changes(ow);
janet_async_in_flight(fiber);
case JANET_ASYNC_EVENT_COMPLETE: {
if (!watcher->is_watching) {
janet_stream_close(ow->stream);
break;
}
break;
NotifyChange *fni = (NotifyChange *) ow->buf;
while (1) {
/* Got an event */
/* Extract name */
Janet filename;
if (fni->FileNameLength) {
int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL);
janet_assert(nbytes, "bad utf8 path");
uint8_t *into = janet_string_begin(nbytes);
WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL);
filename = janet_wrap_string(janet_string_end(into));
} else {
filename = janet_cstringv("");
}
JanetKV *event = janet_struct_begin(3);
janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action]));
janet_struct_put(event, janet_ckeywordv("file-name"), filename);
janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path));
Janet eventv = janet_wrap_struct(janet_struct_end(event));
janet_channel_give(watcher->channel, eventv);
/* Next event */
if (!fni->NextEntryOffset) break;
fni = (NotifyChange *)((char *)fni + fni->NextEntryOffset);
}
/* Make another call to read directory changes */
read_dir_changes(ow);
janet_async_in_flight(fiber);
}
break;
}
}
@@ -439,12 +438,12 @@ static void start_listening_ow(OverlappedWatch *ow) {
static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) {
HANDLE handle = CreateFileA(path,
FILE_LIST_DIRECTORY | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
FILE_LIST_DIRECTORY | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
janet_panicv(janet_ev_lasterr());
}
@@ -574,20 +573,20 @@ static const JanetAbstractType janet_filewatch_at = {
};
JANET_CORE_FN(cfun_filewatch_make,
"(filewatch/new channel &opt default-flags)",
"Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n"
"When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. "
"The contents of the channel depend on the OS, but will contain some common keys:\n\n"
"* `:type` -- the type of the event that was raised.\n\n"
"* `:file-name` -- the base file name of the file that triggered the event.\n\n"
"* `:dir-name` -- the directory name of the file that triggered the event.\n\n"
"Events also will contain keys specific to the host OS.\n\n"
"Windows has no extra properties on events.\n\n"
"Linux has the following extra properties on events:\n\n"
"* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n"
"* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n"
"* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n"
"") {
"(filewatch/new channel &opt default-flags)",
"Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n"
"When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. "
"The contents of the channel depend on the OS, but will contain some common keys:\n\n"
"* `:type` -- the type of the event that was raised.\n\n"
"* `:file-name` -- the base file name of the file that triggered the event.\n\n"
"* `:dir-name` -- the directory name of the file that triggered the event.\n\n"
"Events also will contain keys specific to the host OS.\n\n"
"Windows has no extra properties on events.\n\n"
"Linux has the following extra properties on events:\n\n"
"* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n"
"* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n"
"* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n"
"") {
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
janet_arity(argc, 1, -1);
JanetChannel *channel = janet_getchannel(argv, 0);
@@ -598,44 +597,44 @@ JANET_CORE_FN(cfun_filewatch_make,
}
JANET_CORE_FN(cfun_filewatch_add,
"(filewatch/add watcher path &opt flags)",
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
"Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n"
"* `:all` - trigger an event for all of the below triggers.\n\n"
"* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n"
"* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n"
"* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n"
"* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n"
"* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n"
"* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n"
"* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n"
"* `:recursive` - watch subdirectories recursively\n\n"
"Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n"
"* `:access` - IN_ACCESS\n\n"
"* `:all` - IN_ALL_EVENTS\n\n"
"* `:attrib` - IN_ATTRIB\n\n"
"* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n"
"* `:close-write` - IN_CLOSE_WRITE\n\n"
"* `:create` - IN_CREATE\n\n"
"* `:delete` - IN_DELETE\n\n"
"* `:delete-self` - IN_DELETE_SELF\n\n"
"* `:ignored` - IN_IGNORED\n\n"
"* `:modify` - IN_MODIFY\n\n"
"* `:move-self` - IN_MOVE_SELF\n\n"
"* `:moved-from` - IN_MOVED_FROM\n\n"
"* `:moved-to` - IN_MOVED_TO\n\n"
"* `:open` - IN_OPEN\n\n"
"* `:q-overflow` - IN_Q_OVERFLOW\n\n"
"* `:unmount` - IN_UNMOUNT\n\n\n"
"On Windows, events will have the following possible types:\n\n"
"* `:unknown`\n\n"
"* `:added`\n\n"
"* `:removed`\n\n"
"* `:modified`\n\n"
"* `:renamed-old`\n\n"
"* `:renamed-new`\n\n"
"On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n"
"") {
"(filewatch/add watcher path &opt flags)",
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
"Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n"
"* `:all` - trigger an event for all of the below triggers.\n\n"
"* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n"
"* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n"
"* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n"
"* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n"
"* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n"
"* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n"
"* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n"
"* `:recursive` - watch subdirectories recursively\n\n"
"Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n"
"* `:access` - IN_ACCESS\n\n"
"* `:all` - IN_ALL_EVENTS\n\n"
"* `:attrib` - IN_ATTRIB\n\n"
"* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n"
"* `:close-write` - IN_CLOSE_WRITE\n\n"
"* `:create` - IN_CREATE\n\n"
"* `:delete` - IN_DELETE\n\n"
"* `:delete-self` - IN_DELETE_SELF\n\n"
"* `:ignored` - IN_IGNORED\n\n"
"* `:modify` - IN_MODIFY\n\n"
"* `:move-self` - IN_MOVE_SELF\n\n"
"* `:moved-from` - IN_MOVED_FROM\n\n"
"* `:moved-to` - IN_MOVED_TO\n\n"
"* `:open` - IN_OPEN\n\n"
"* `:q-overflow` - IN_Q_OVERFLOW\n\n"
"* `:unmount` - IN_UNMOUNT\n\n\n"
"On Windows, events will have the following possible types:\n\n"
"* `:unknown`\n\n"
"* `:added`\n\n"
"* `:removed`\n\n"
"* `:modified`\n\n"
"* `:renamed-old`\n\n"
"* `:renamed-new`\n\n"
"On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n"
"") {
janet_arity(argc, 2, -1);
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);
const char *path = janet_getcstring(argv, 1);
@@ -645,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);
@@ -655,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);
@@ -664,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);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose & contributors
* Copyright (c) 2025 Calvin Rose & contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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]);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -31,6 +31,7 @@
#ifndef JANET_WINDOWS
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
@@ -164,6 +165,14 @@ JANET_CORE_FN(cfun_io_fopen,
}
FILE *f = fopen((const char *)fname, (const char *)fmode);
if (f != NULL) {
#ifndef JANET_WINDOWS
struct stat st;
fstat(fileno(f), &st);
if (S_ISDIR(st.st_mode)) {
fclose(f);
janet_panicf("cannot open directory: %s", fname);
}
#endif
size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
if (bufsize != BUFSIZ) {
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose and contributors.
* Copyright (c) 2025 Calvin Rose and contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -554,7 +554,10 @@ JANET_CORE_FN(cfun_net_connect,
int err = WSAGetLastError();
freeaddrinfo(ai);
#else
int status = connect(sock, addr, addrlen);
int status;
do {
status = connect(sock, addr, addrlen);
} while (status == -1 && errno == EINTR);
int err = errno;
if (is_unix) {
janet_free(ai);
@@ -578,17 +581,23 @@ JANET_CORE_FN(cfun_net_connect,
net_sched_connect(stream);
}
static const char *serverify_socket(JSock sfd) {
static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) {
/* Set various socket options */
int enable = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEADDR) failed";
if (reuse_addr) {
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEADDR) failed";
}
}
if (reuse_port) {
#ifdef SO_REUSEPORT
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEPORT) failed";
}
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEPORT) failed";
}
#else
(void) reuse_port;
#endif
}
janet_net_socknoblock(sfd);
return NULL;
}
@@ -642,19 +651,21 @@ JANET_CORE_FN(cfun_net_shutdown,
}
JANET_CORE_FN(cfun_net_listen,
"(net/listen host port &opt type)",
"(net/listen host port &opt type no-reuse)",
"Creates a server. Returns a new stream that is neither readable nor "
"writeable. Use net/accept or net/accept-loop be to handle connections and start the server. "
"The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
":stream. The host and port arguments are the same as in net/address.") {
":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will "
"disable the use of SO_REUSEADDR and SO_REUSEPORT when creating a server on some operating systems.") {
janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
janet_arity(argc, 2, 3);
janet_arity(argc, 2, 4);
/* Get host, port, and handler*/
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix);
int reuse = !(argc >= 4 && janet_truthy(argv[3]));
JSock sfd = JSOCKDEFAULT;
#ifndef JANET_WINDOWS
@@ -664,7 +675,7 @@ JANET_CORE_FN(cfun_net_listen,
janet_free(ai);
janet_panicf("could not create socket: %V", janet_ev_lasterr());
}
const char *err = serverify_socket(sfd);
const char *err = serverify_socket(sfd, reuse, 0);
if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) {
JSOCKCLOSE(sfd);
janet_free(ai);
@@ -687,7 +698,7 @@ JANET_CORE_FN(cfun_net_listen,
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
#endif
if (!JSOCKVALID(sfd)) continue;
const char *err = serverify_socket(sfd);
const char *err = serverify_socket(sfd, reuse, reuse);
if (NULL != err) {
JSOCKCLOSE(sfd);
continue;
@@ -829,7 +840,7 @@ JANET_CORE_FN(cfun_stream_accept_loop,
JANET_CORE_FN(cfun_stream_accept,
"(net/accept stream &opt timeout)",
"Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. "
"Takes an optional timeout in seconds, after which will return nil. "
"Takes an optional timeout in seconds, after which will raise an error. "
"Returns a new duplex stream which represents a connection to the client.") {
janet_arity(argc, 1, 2);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
@@ -844,7 +855,7 @@ JANET_CORE_FN(cfun_stream_read,
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
"`n` can also be the keyword `:all` to read into the buffer until end of stream. "
"If less than n bytes are available (and more than 0), will push those bytes and return early. "
"Takes an optional timeout in seconds, after which will return nil. "
"Takes an optional timeout in seconds, after which will raise an error. "
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
@@ -864,7 +875,7 @@ JANET_CORE_FN(cfun_stream_read,
JANET_CORE_FN(cfun_stream_chunk,
"(net/chunk stream nbytes &opt buf timeout)",
"Same a net/read, but will wait for all n bytes to arrive rather than return early. "
"Takes an optional timeout in seconds, after which will return nil.") {
"Takes an optional timeout in seconds, after which will raise an error.") {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
@@ -878,7 +889,7 @@ JANET_CORE_FN(cfun_stream_chunk,
JANET_CORE_FN(cfun_stream_recv_from,
"(net/recv-from stream nbytes buf &opt timeout)",
"Receives data from a server stream and puts it into a buffer. Returns the socket-address the "
"packet came from. Takes an optional timeout in seconds, after which will return nil.") {
"packet came from. Takes an optional timeout in seconds, after which will raise an error.") {
janet_arity(argc, 3, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET);
@@ -892,7 +903,7 @@ JANET_CORE_FN(cfun_stream_recv_from,
JANET_CORE_FN(cfun_stream_write,
"(net/write stream data &opt timeout)",
"Write data to a stream, suspending the current fiber until the write "
"completes. Takes an optional timeout in seconds, after which will return nil. "
"completes. Takes an optional timeout in seconds, after which will raise an error. "
"Returns nil, or raises an error if the write failed.") {
janet_arity(argc, 2, 3);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
@@ -911,7 +922,7 @@ JANET_CORE_FN(cfun_stream_write,
JANET_CORE_FN(cfun_stream_send_to,
"(net/send-to stream dest data &opt timeout)",
"Writes a datagram to a server stream. dest is a the destination address of the packet. "
"Takes an optional timeout in seconds, after which will return nil. "
"Takes an optional timeout in seconds, after which will raise an error. "
"Returns stream.") {
janet_arity(argc, 3, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose and contributors.
* Copyright (c) 2025 Calvin Rose and contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,9 +27,10 @@
#include "gc.h"
#endif
#include <stdlib.h>
#ifndef JANET_REDUCED_OS
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
@@ -54,6 +55,7 @@
#include <sys/utime.h>
#include <io.h>
#include <process.h>
#define JANET_SPAWN_CHDIR
#else
#include <spawn.h>
#include <utime.h>
@@ -72,6 +74,20 @@ extern char **environ;
#endif
#endif
/* Detect availability of posix_spawn_file_actions_addchdir_np. Since
* this doesn't seem to follow any standard, just a common extension, we
* must enumerate supported systems for availability. Define JANET_SPAWN_NO_CHDIR
* to disable this. */
#ifndef JANET_SPAWN_NO_CHDIR
#ifdef __GLIBC__
#define JANET_SPAWN_CHDIR
#elif defined(JANET_APPLE) /* Some older versions may not work here. */
#define JANET_SPAWN_CHDIR
#elif defined(__FreeBSD__) /* Not all BSDs work, for example openBSD doesn't seem to support this */
#define JANET_SPAWN_CHDIR
#endif
#endif
/* Not POSIX, but all Unixes but Solaris have this function. */
#if defined(JANET_POSIX) && !defined(__sun)
time_t timegm(struct tm *tm);
@@ -251,7 +267,7 @@ JANET_CORE_FN(os_exit,
}
janet_deinit();
if (argc >= 2 && janet_truthy(argv[1])) {
_exit(status);
_Exit(status);
} else {
exit(status);
}
@@ -540,11 +556,12 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) {
proc->flags &= ~JANET_PROC_WAITING;
janet_gcunroot(janet_wrap_abstract(proc));
janet_gcunroot(janet_wrap_fiber(args.fiber));
if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) {
JanetString s = janet_formatc("command failed with non-zero exit code %d", status);
janet_cancel(args.fiber, janet_wrap_string(s));
} else {
if (janet_fiber_can_resume(args.fiber)) {
uint32_t sched_id = (uint32_t) args.argi;
if (janet_fiber_can_resume(args.fiber) && args.fiber->sched_id == sched_id) {
if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) {
JanetString s = janet_formatc("command failed with non-zero exit code %d", status);
janet_cancel(args.fiber, janet_wrap_string(s));
} else {
janet_schedule(args.fiber, janet_wrap_integer(status));
}
}
@@ -602,6 +619,7 @@ os_proc_wait_impl(JanetProc *proc) {
memset(&targs, 0, sizeof(targs));
targs.argp = proc;
targs.fiber = janet_root_fiber();
targs.argi = (uint32_t) targs.fiber->sched_id;
janet_gcroot(janet_wrap_abstract(proc));
janet_gcroot(janet_wrap_fiber(targs.fiber));
janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb);
@@ -628,16 +646,15 @@ os_proc_wait_impl(JanetProc *proc) {
JANET_CORE_FN(os_proc_wait,
"(os/proc-wait proc)",
"Suspend the current fiber until the subprocess completes. Returns the subprocess return code. "
"os/proc-wait cannot be called twice on the same process. If `ev/with-deadline` cancels `os/proc-wait` "
"with an error or os/proc-wait is cancelled with any error caused by anything else, os/proc-wait still "
"finishes in the background. Only after os/proc-wait finishes, a process is cleaned up by the operating "
"system. Thus, a process becomes a zombie process if os/proc-wait is not called.") {
"Suspend the current fiber until the subprocess `proc` completes. Once `proc` "
"completes, return the exit code of `proc`. If called more than once on the same "
"core/process value, will raise an error. When creating subprocesses using "
"`os/spawn`, this function should be called on the returned value to avoid zombie "
"processes.") {
janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV
os_proc_wait_impl(proc);
return janet_wrap_nil();
#else
return os_proc_wait_impl(proc);
#endif
@@ -742,12 +759,13 @@ static int get_signal_kw(const Janet *argv, int32_t n) {
JANET_CORE_FN(os_proc_kill,
"(os/proc-kill proc &opt wait signal)",
"Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process "
"handle on windows. If os/proc-wait already finished for proc, os/proc-kill raises an error. After "
"sending signal to proc, if `wait` is truthy, will wait for the process to finish and return the exit "
"code by calling os/proc-wait. Otherwise, returns `proc`. If signal is specified, send it instead. "
"Signal keywords are named after their C counterparts but in lowercase with the leading `SIG` stripped. "
"Signals are ignored on windows.") {
"Kill the subprocess `proc` by sending SIGKILL to it on POSIX systems, or by closing "
"the process handle on Windows. If `proc` has already completed, raise an error. If "
"`wait` is truthy, will wait for `proc` to complete and return the exit code (this "
"will raise an error if `proc` is being waited for). Otherwise, return `proc`. If "
"`signal` is provided, send it instead of SIGKILL. Signal keywords are named after "
"their C counterparts but in lowercase with the leading SIG stripped. `signal` is "
"ignored on Windows.") {
janet_arity(argc, 1, 3);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
if (proc->flags & JANET_PROC_WAITED) {
@@ -775,7 +793,6 @@ JANET_CORE_FN(os_proc_kill,
if (argc > 1 && janet_truthy(argv[1])) {
#ifdef JANET_EV
os_proc_wait_impl(proc);
return janet_wrap_nil();
#else
return os_proc_wait_impl(proc);
#endif
@@ -786,9 +803,9 @@ JANET_CORE_FN(os_proc_kill,
JANET_CORE_FN(os_proc_close,
"(os/proc-close proc)",
"Close pipes created by `os/spawn` if they have not been closed. Then, if os/proc-wait was not already "
"called on proc, os/proc-wait is called on it, and it returns the exit code returned by os/proc-wait. "
"Otherwise, returns nil.") {
"Close pipes created for subprocess `proc` by `os/spawn` if they have not been "
"closed. Then, if `proc` is not being waited for, wait. If this function waits, when "
"`proc` completes, return the exit code of `proc`. Otherwise, return nil.") {
janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV
@@ -806,12 +823,24 @@ JANET_CORE_FN(os_proc_close,
}
#ifdef JANET_EV
os_proc_wait_impl(proc);
return janet_wrap_nil();
#else
return os_proc_wait_impl(proc);
#endif
}
JANET_CORE_FN(os_proc_getpid,
"(os/getpid)",
"Get the process ID of the current process.") {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_fixarity(argc, 0);
(void) argv;
#ifdef JANET_WINDOWS
return janet_wrap_number((double) _getpid());
#else
return janet_wrap_number((double) getpid());
#endif
}
static void swap_handles(JanetHandle *handles) {
JanetHandle temp = handles[0];
handles[0] = handles[1];
@@ -1136,6 +1165,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL;
JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
int stderr_is_stdout = 0;
int pipe_errflag = 0; /* Track errors setting up pipes */
int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0;
@@ -1160,11 +1190,28 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) {
new_err = make_pipes(&pipe_err, 0, &pipe_errflag);
pipe_owner_flags |= JANET_PROC_OWNS_STDERR;
} else if (is_spawn && janet_keyeq(maybe_stderr, "out")) {
stderr_is_stdout = 1;
} else if (!janet_checktype(maybe_stderr, JANET_NIL)) {
new_err = janet_getjstream(&maybe_stderr, 0, &orig_err);
}
}
/* Optional working directory. Available for both os/execute and os/spawn. */
const char *chdir_path = NULL;
if (argc > 2) {
JanetDictView tab = janet_getdictionary(argv, 2);
Janet workdir = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("cd"));
if (janet_checktype(workdir, JANET_STRING)) {
chdir_path = (const char *) janet_unwrap_string(workdir);
#ifndef JANET_SPAWN_CHDIR
janet_panicf(":cd argument not supported on this system - %s", chdir_path);
#endif
} else if (!janet_checktype(workdir, JANET_NIL)) {
janet_panicf("expected string for :cd argumnet, got %v", workdir);
}
}
/* Clean up if any of the pipes have any issues */
if (pipe_errflag) {
if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in);
@@ -1179,6 +1226,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo;
LPCSTR lpCurrentDirectory = NULL;
memset(&saAttr, 0, sizeof(saAttr));
memset(&processInfo, 0, sizeof(processInfo));
memset(&startupInfo, 0, sizeof(startupInfo));
@@ -1195,6 +1243,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
}
const char *path = (const char *) janet_unwrap_string(exargs.items[0]);
if (chdir_path != NULL) {
lpCurrentDirectory = chdir_path;
}
/* Do IO redirection */
if (pipe_in != JANET_HANDLE_NONE) {
@@ -1217,6 +1269,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
startupInfo.hStdError = pipe_err;
} else if (new_err != NULL) {
startupInfo.hStdError = new_err;
} else if (stderr_is_stdout) {
startupInfo.hStdError = startupInfo.hStdOutput;
} else {
startupInfo.hStdError = (HANDLE) _get_osfhandle(2);
}
@@ -1229,7 +1283,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
TRUE, /* handle inheritance */
0, /* flags */
use_environ ? NULL : envp, /* pass in environment */
NULL, /* use parents starting directory */
lpCurrentDirectory,
&startupInfo,
&processInfo)) {
cp_failed = 1;
@@ -1267,9 +1321,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* exec mode */
if (mode == JANET_EXECUTE_EXEC) {
#ifdef JANET_WINDOWS
janet_panic("not supported on windows");
#else
int status;
if (!use_environ) {
environ = envp;
@@ -1282,7 +1333,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
}
} while (status == -1 && errno == EINTR);
janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT));
#endif
}
/* Use posix_spawn to spawn new process */
@@ -1290,6 +1340,15 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* Posix spawn setup */
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
#ifdef JANET_SPAWN_CHDIR
if (chdir_path != NULL) {
#ifdef JANET_SPAWN_CHDIR_NO_NP
posix_spawn_file_actions_addchdir(&actions, chdir_path);
#else
posix_spawn_file_actions_addchdir_np(&actions, chdir_path);
#endif
}
#endif
if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
posix_spawn_file_actions_addclose(&actions, pipe_in);
@@ -1312,6 +1371,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_err != JANET_HANDLE_NONE && new_err != 2) {
posix_spawn_file_actions_adddup2(&actions, new_err, 2);
posix_spawn_file_actions_addclose(&actions, new_err);
} else if (stderr_is_stdout) {
posix_spawn_file_actions_adddup2(&actions, 1, 2);
}
pid_t pid;
@@ -1383,45 +1444,57 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
JANET_CORE_FN(os_execute,
"(os/execute args &opt flags env)",
"Execute a program on the system and pass it string arguments. `flags` "
"is a keyword that modifies how the program will execute.\n"
"* :e - enables passing an environment to the program. Without :e, the "
"Execute a program on the system and return the exit code. `args` is an array/tuple "
"of strings. The first string is the name of the program and the remainder are "
"arguments passed to the program. `flags` is a keyword made from the following "
"characters that modifies how the program executes:\n"
"* :e - enables passing an environment to the program. Without 'e', the "
"current environment is inherited.\n"
"* :p - allows searching the current PATH for the binary to execute. "
"Without this flag, binaries must use absolute paths.\n"
"* :x - raise error if exit code is non-zero.\n"
"* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n"
"`env` is a table or struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. "
":in, :out, and :err should be core/file values or core/stream values. core/file values and core/stream "
"values passed to :in, :out, and :err should be closed manually because os/execute doesn't close them. "
"Returns the exit code of the program.") {
"* :p - allows searching the current PATH for the program to execute. "
"Without this flag, the first element of `args` must be an absolute path.\n"
"* :x - raises error if exit code is non-zero.\n"
"* :d - prevents the garbage collector terminating the program (if still running) "
"and calling the equivalent of `os/proc-wait` (allows zombie processes).\n"
"`env` is a table/struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the "
"subprocess. :in, :out, and :err should be core/file or core/stream values. "
"If core/stream values are used, the caller is responsible for ensuring pipes do not "
"cause the program to block and deadlock.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_EXECUTE);
}
JANET_CORE_FN(os_spawn,
"(os/spawn args &opt flags env)",
"Execute a program on the system and return a handle to the process. Otherwise, takes the "
"same arguments as `os/execute`. Does not wait for the process. For each of the :in, :out, and :err keys "
"of the `env` argument, one can also pass in the keyword `:pipe` to get streams for standard IO of the "
"subprocess that can be read from and written to. The returned value `proc` has the fields :in, :out, "
":err, and the additional field :pid on unix-like platforms. `(os/proc-wait proc)` must be called to "
"rejoin the subprocess. After `(os/proc-wait proc)` finishes, proc gains a new field, :return-code. "
"If :x flag is passed to os/spawn, non-zero exit code will cause os/proc-wait to raise an error. "
"If pipe streams created with :pipe keyword are not closed in time, janet can run out of file "
"descriptors. They can be closed individually, or `os/proc-close` can close all pipe streams on proc. "
"If pipe streams aren't read before `os/proc-wait` finishes, then pipe buffers become full, and the "
"process cannot finish because the process cannot print more on pipe buffers which are already full. "
"If the process cannot finish, os/proc-wait cannot finish, either.") {
"Execute a program on the system and return a core/process value representing the "
"spawned subprocess. Takes the same arguments as `os/execute` but does not wait for "
"the subprocess to complete. Unlike `os/execute`, the value `:pipe` can be used for "
":in, :out and :err keys in `env`. If used, the returned core/process will have a "
"writable stream in the :in field and readable streams in the :out and :err fields. "
"On non-Windows systems, the subprocess PID will be in the :pid field. The caller is "
"responsible for waiting on the process (e.g. by calling `os/proc-wait` on the "
"returned core/process value) to avoid creating zombie process. After the subprocess "
"completes, the exit value is in the :return-code field. If `flags` includes 'x', a "
"non-zero exit code will cause a waiting fiber to raise an error. The use of "
"`:pipe` may fail if there are too many active file descriptors. The caller is "
"responsible for closing pipes created by `:pipe` (either individually or using "
"`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring "
"pipes do not cause the program to block and deadlock. As a special case, the stream passed to `:err` "
"can be the keyword `:out` to redirect stderr to stdout in the subprocess.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN);
}
JANET_CORE_FN(os_posix_exec,
"(os/posix-exec args &opt flags env)",
"Use the execvpe or execve system calls to replace the current process with an interface similar to os/execute. "
"However, instead of creating a subprocess, the current process is replaced. Is not supported on windows, and "
"However, instead of creating a subprocess, the current process is replaced. Is not supported on Windows, and "
"does not allow redirection of stdio.") {
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("not supported on Windows");
#else
return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC);
#endif
}
JANET_CORE_FN(os_posix_fork,
@@ -1432,7 +1505,7 @@ JANET_CORE_FN(os_posix_fork,
janet_fixarity(argc, 0);
(void) argv;
#ifdef JANET_WINDOWS
janet_panic("not supported");
janet_panic("not supported on Windows");
#else
pid_t result;
do {
@@ -1879,7 +1952,6 @@ JANET_CORE_FN(os_mktime,
/* utc time */
#ifdef JANET_NO_UTC_MKTIME
janet_panic("os/mktime UTC not supported on this platform");
return janet_wrap_nil();
#else
t = timegm(&t_info);
#endif
@@ -1946,8 +2018,7 @@ JANET_CORE_FN(os_link,
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("os/link not supported on Windows");
return janet_wrap_nil();
janet_panic("not supported on Windows");
#else
const char *oldpath = janet_getcstring(argv, 0);
const char *newpath = janet_getcstring(argv, 1);
@@ -1965,8 +2036,7 @@ JANET_CORE_FN(os_symlink,
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("os/symlink not supported on Windows");
return janet_wrap_nil();
janet_panic("not supported on Windows");
#else
const char *oldpath = janet_getcstring(argv, 0);
const char *newpath = janet_getcstring(argv, 1);
@@ -2068,8 +2138,7 @@ JANET_CORE_FN(os_readlink,
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("os/readlink not supported on Windows");
return janet_wrap_nil();
janet_panic("not supported on Windows");
#else
static char buffer[PATH_MAX];
const char *path = janet_getcstring(argv, 0);
@@ -2325,7 +2394,6 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
return sg->fn(&st);
}
janet_panicf("unexpected keyword %v", janet_wrap_keyword(key));
return janet_wrap_nil();
}
}
@@ -2794,6 +2862,7 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/proc-wait", os_proc_wait),
JANET_CORE_REG("os/proc-kill", os_proc_kill),
JANET_CORE_REG("os/proc-close", os_proc_close),
JANET_CORE_REG("os/getpid", os_proc_getpid),
#endif
/* high resolution timers */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -231,7 +231,7 @@ static void delim_error(JanetParser *parser, size_t stack_index, char c, const c
janet_buffer_push_u8(buffer, '`');
}
}
janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column);
janet_formatb(buffer, " opened at line %d, column %d", (int32_t) s->line, (int32_t) s->column);
}
parser->error = (const char *) janet_string(buffer->data, buffer->count);
parser->flag |= JANET_PARSER_GENERATED_ERROR;
@@ -363,8 +363,7 @@ static int stringend(JanetParser *p, JanetParseState *state) {
JanetParseState top = p->states[p->statecount - 1];
int32_t indent_col = (int32_t) top.column - 1;
uint8_t *r = bufstart, *end = r + buflen;
/* Check if there are any characters before the start column -
* if so, do not reindent. */
/* Unless there are only spaces before EOLs, disable reindenting */
int reindent = 1;
while (reindent && (r < end)) {
if (*r++ == '\n') {
@@ -374,34 +373,36 @@ static int stringend(JanetParser *p, JanetParseState *state) {
break;
}
}
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') reindent = 1;
}
}
/* Now reindent if able to, otherwise just drop leading newline. */
if (!reindent) {
if (buflen > 0 && bufstart[0] == '\n') {
buflen--;
bufstart++;
}
} else {
/* Now reindent if able */
if (reindent) {
uint8_t *w = bufstart;
r = bufstart;
while (r < end) {
if (*r == '\n') {
if (r == bufstart) {
/* Skip leading newline */
r++;
} else {
*w++ = *r++;
}
*w++ = *r++;
for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++);
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') *w++ = *r++;
} else {
*w++ = *r++;
}
}
buflen = (int32_t)(w - bufstart);
}
/* Check for trailing newline character so we can remove it */
if (buflen > 0 && bufstart[buflen - 1] == '\n') {
/* Check for leading EOL so we can remove it */
if (buflen > 1 && bufstart[0] == '\r' && bufstart[1] == '\n') { /* Windows EOL */
buflen = buflen - 2;
bufstart = bufstart + 2;
} else if (buflen > 0 && bufstart[0] == '\n') { /* Unix EOL */
buflen--;
bufstart++;
}
/* Check for trailing EOL so we can remove it */
if (buflen > 1 && bufstart[buflen - 2] == '\r' && bufstart[buflen - 1] == '\n') { /* Windows EOL */
buflen = buflen - 2;
} else if (buflen > 0 && bufstart[buflen - 1] == '\n') { /* Unix EOL */
buflen--;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -342,7 +342,7 @@ tail:
while (captured < hi) {
CapState cs2 = cap_save(s);
next_text = peg_rule(s, rule_a, text);
if (!next_text || next_text == text) {
if (!next_text || ((next_text == text) && (hi == UINT32_MAX))) {
cap_load(s, cs2);
break;
}
@@ -544,41 +544,80 @@ tail:
return window_end;
}
case RULE_TIL: {
const uint32_t *rule_terminus = s->bytecode + rule[1];
const uint32_t *rule_subpattern = s->bytecode + rule[2];
const uint8_t *terminus_start = text;
const uint8_t *terminus_end = NULL;
down1(s);
while (terminus_start <= s->text_end) {
CapState cs2 = cap_save(s);
terminus_end = peg_rule(s, rule_terminus, terminus_start);
cap_load(s, cs2);
if (terminus_end) {
break;
}
terminus_start++;
}
up1(s);
if (!terminus_end) {
return NULL;
}
const uint8_t *saved_end = s->text_end;
s->text_end = terminus_start;
down1(s);
const uint8_t *matched = peg_rule(s, rule_subpattern, text);
up1(s);
s->text_end = saved_end;
if (!matched) {
return NULL;
}
return terminus_end;
}
case RULE_SPLIT: {
const uint8_t *saved_end = s->text_end;
const uint32_t *rule_separator = s->bytecode + rule[1];
const uint32_t *rule_subpattern = s->bytecode + rule[2];
const uint8_t *separator_end = NULL;
do {
const uint8_t *text_start = text;
const uint8_t *chunk_start = text;
const uint8_t *chunk_end = NULL;
while (text <= saved_end) {
/* Find next split (or end of text) */
CapState cs = cap_save(s);
down1(s);
while (text <= s->text_end) {
separator_end = peg_rule(s, rule_separator, text);
while (text <= saved_end) {
chunk_end = text;
const uint8_t *check = peg_rule(s, rule_separator, text);
cap_load(s, cs);
if (separator_end) {
if (check) {
text = check;
break;
}
text++;
}
up1(s);
if (separator_end) {
s->text_end = text;
text = separator_end;
}
/* Match between splits */
s->text_end = chunk_end;
down1(s);
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, text_start);
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, chunk_start);
up1(s);
s->text_end = saved_end;
if (!subpattern_end) return NULL; /* Don't match anything */
if (!subpattern_end) {
return NULL;
}
} while (separator_end);
/* Ensure forward progress */
if (text == chunk_start) return NULL;
chunk_start = text;
}
s->text_end = saved_end;
return s->text_end;
}
@@ -1227,6 +1266,14 @@ static void spec_sub(Builder *b, int32_t argc, const Janet *argv) {
emit_2(r, RULE_SUB, subrule1, subrule2);
}
static void spec_til(Builder *b, int32_t argc, const Janet *argv) {
peg_fixarity(b, argc, 2);
Reserve r = reserve(b, 3);
uint32_t subrule1 = peg_compile1(b, argv[0]);
uint32_t subrule2 = peg_compile1(b, argv[1]);
emit_2(r, RULE_TIL, subrule1, subrule2);
}
static void spec_split(Builder *b, int32_t argc, const Janet *argv) {
peg_fixarity(b, argc, 2);
Reserve r = reserve(b, 3);
@@ -1323,6 +1370,7 @@ static const SpecialPair peg_specials[] = {
{"split", spec_split},
{"sub", spec_sub},
{"thru", spec_thru},
{"til", spec_til},
{"to", spec_to},
{"uint", spec_uint_le},
{"uint-be", spec_uint_be},
@@ -1416,6 +1464,11 @@ static uint32_t peg_compile1(Builder *b, Janet peg) {
emit_bytes(b, RULE_LITERAL, len, str);
break;
}
case JANET_BUFFER: {
const JanetBuffer *buf = janet_unwrap_buffer(peg);
emit_bytes(b, RULE_LITERAL, buf->count, buf->data);
break;
}
case JANET_TABLE: {
/* Build grammar table */
JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg));
@@ -1657,6 +1710,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
i += 4;
break;
case RULE_SUB:
case RULE_TIL:
case RULE_SPLIT:
/* [rule, rule] */
if (rule[1] >= blen) goto bad;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -28,7 +28,7 @@
/* Run a string */
int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) {
JanetParser parser;
JanetParser *parser;
int errflags = 0, done = 0;
int32_t index = 0;
Janet ret = janet_wrap_nil();
@@ -37,14 +37,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
if (where) janet_gcroot(janet_wrap_string(where));
if (NULL == sourcePath) sourcePath = "<unknown>";
janet_parser_init(&parser);
parser = janet_abstract(&janet_parser_type, sizeof(JanetParser));
janet_parser_init(parser);
janet_gcroot(janet_wrap_abstract(parser));
/* While we haven't seen an error */
while (!done) {
/* Evaluate parsed values */
while (janet_parser_has_more(&parser)) {
Janet form = janet_parser_produce(&parser);
while (janet_parser_has_more(parser)) {
Janet form = janet_parser_produce(parser);
JanetCompileResult cres = janet_compile(form, env, where);
if (cres.status == JANET_COMPILE_OK) {
JanetFunction *f = janet_thunk(cres.funcdef);
@@ -58,8 +60,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
}
} else {
ret = janet_wrap_string(cres.error);
int32_t line = (int32_t) parser.line;
int32_t col = (int32_t) parser.column;
int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser->column;
if ((cres.error_mapping.line > 0) &&
(cres.error_mapping.column > 0)) {
line = cres.error_mapping.line;
@@ -81,16 +83,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
if (done) break;
/* Dispatch based on parse state */
switch (janet_parser_status(&parser)) {
switch (janet_parser_status(parser)) {
case JANET_PARSE_DEAD:
done = 1;
break;
case JANET_PARSE_ERROR: {
const char *e = janet_parser_error(&parser);
const char *e = janet_parser_error(parser);
errflags |= 0x04;
ret = janet_cstringv(e);
int32_t line = (int32_t) parser.line;
int32_t col = (int32_t) parser.column;
int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser->column;
janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e);
done = 1;
break;
@@ -98,9 +100,9 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
case JANET_PARSE_ROOT:
case JANET_PARSE_PENDING:
if (index >= len) {
janet_parser_eof(&parser);
janet_parser_eof(parser);
} else {
janet_parser_consume(&parser, bytes[index++]);
janet_parser_consume(parser, bytes[index++]);
}
break;
}
@@ -108,7 +110,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
}
/* Clean up and return errors */
janet_parser_deinit(&parser);
janet_gcunroot(janet_wrap_abstract(parser));
if (where) janet_gcunroot(janet_wrap_string(where));
#ifdef JANET_EV
/* Enter the event loop if we are not already in it */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,7 +27,9 @@
#include <stdint.h>
#ifdef JANET_EV
#ifndef JANET_WINDOWS
#ifdef JANET_WINDOWS
#include <windows.h>
#else
#include <pthread.h>
#endif
#endif
@@ -53,13 +55,21 @@ 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;
#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 +110,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. */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -372,12 +372,14 @@ JANET_CORE_FN(cfun_table_setproto,
}
JANET_CORE_FN(cfun_table_tostruct,
"(table/to-struct tab)",
"Convert a table to a struct. Returns a new struct. This function "
"does not take into account prototype tables.") {
janet_fixarity(argc, 1);
"(table/to-struct tab &opt proto)",
"Convert a table to a struct. Returns a new struct.") {
janet_arity(argc, 1, 2);
JanetTable *t = janet_gettable(argv, 0);
return janet_wrap_struct(janet_table_to_struct(t));
JanetStruct proto = janet_optstruct(argv, argc, 1, NULL);
JanetStruct st = janet_table_to_struct(t);
janet_struct_proto(st) = proto;
return janet_wrap_struct(st);
}
JANET_CORE_FN(cfun_table_rawget,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -117,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;
}
@@ -240,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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -205,9 +205,9 @@ int janet_make_pipe(JanetHandle handles[2], int mode);
#ifdef JANET_FILEWATCH
void janet_lib_filewatch(JanetTable *env);
#endif
#endif
#ifdef JANET_FFI
void janet_lib_ffi(JanetTable *env);
#endif
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -798,14 +798,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
VM_OP(JOP_JUMP)
pc += DS;
vm_maybe_auto_suspend(DS <= 0);
pc += DS;
vm_next();
VM_OP(JOP_JUMP_IF)
if (janet_truthy(stack[A])) {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} else {
pc++;
}
@@ -815,15 +815,15 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (janet_truthy(stack[A])) {
pc++;
} else {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
}
vm_next();
VM_OP(JOP_JUMP_IF_NIL)
if (janet_checktype(stack[A], JANET_NIL)) {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} else {
pc++;
}
@@ -833,8 +833,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (janet_checktype(stack[A], JANET_NIL)) {
pc++;
} else {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
}
vm_next();
@@ -1373,7 +1373,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
/* Run vm */
janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP;
int old_coerce_error = janet_vm.coerce_error;
janet_vm.coerce_error = 1;
JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil());
janet_vm.coerce_error = old_coerce_error;
/* Teardown */
janet_vm.stackn = oldn;
@@ -1384,6 +1387,15 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
}
if (signal != JANET_SIGNAL_OK) {
/* Should match logic in janet_signalv */
#ifdef JANET_EV
if (janet_vm.root_fiber != NULL && signal == JANET_SIGNAL_EVENT) {
janet_vm.root_fiber->sched_id++;
}
#endif
if (signal != JANET_SIGNAL_ERROR) {
*janet_vm.return_reg = janet_wrap_string(janet_formatc("%v coerced from %s to error", *janet_vm.return_reg, janet_signal_names[signal]));
}
janet_panicv(*janet_vm.return_reg);
}
@@ -1430,8 +1442,10 @@ void janet_try_init(JanetTryState *state) {
state->vm_fiber = janet_vm.fiber;
state->vm_jmp_buf = janet_vm.signal_buf;
state->vm_return_reg = janet_vm.return_reg;
state->coerce_error = janet_vm.coerce_error;
janet_vm.return_reg = &(state->payload);
janet_vm.signal_buf = &(state->buf);
janet_vm.coerce_error = 0;
}
void janet_restore(JanetTryState *state) {
@@ -1440,6 +1454,7 @@ void janet_restore(JanetTryState *state) {
janet_vm.fiber = state->vm_fiber;
janet_vm.signal_buf = state->vm_jmp_buf;
janet_vm.return_reg = state->vm_return_reg;
janet_vm.coerce_error = state->coerce_error;
}
static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -1261,6 +1261,7 @@ typedef struct {
/* new state */
jmp_buf buf;
Janet payload;
int coerce_error;
} JanetTryState;
/***** END SECTION TYPES *****/
@@ -1442,6 +1443,7 @@ JANET_NO_RETURN JANET_API void janet_sleep_await(double sec);
/* For use inside listeners - adds a timeout to the current fiber, such that
* it will be resumed after sec seconds if no other event schedules the current fiber. */
JANET_API void janet_addtimeout(double sec);
JANET_API void janet_addtimeout_nil(double sec);
JANET_API void janet_ev_inc_refcount(void);
JANET_API void janet_ev_dec_refcount(void);
@@ -2180,6 +2182,7 @@ typedef enum {
RULE_UNREF, /* [rule, tag] */
RULE_CAPTURE_NUM, /* [rule, tag] */
RULE_SUB, /* [rule, rule] */
RULE_TIL, /* [rule, rule] */
RULE_SPLIT, /* [rule, rule] */
RULE_NTH, /* [nth, rule, tag] */
RULE_ONLY_TAGS, /* [rule] */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2025 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -39,7 +39,7 @@
(defmacro assert
[x &opt e]
(def xx (gensym))
(default e ~',x)
(default e (string/format "%j" x))
~(do
(def ,xx ,x)
(,assert-no-tail ,xx ,e)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -896,11 +896,18 @@
(struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2"))
(table/setproto table-to-freeze @{:a @[1 2 3]})
(assert (deep= {:a [1 2 3] :b [1 2 3 4] :c 22 :d "test" :e "test2"}
(freeze table-to-freeze)))
(assert (deep= struct-to-thaw (freeze table-to-freeze)))
(assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze)))
(assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw)))
# Check that freezing mutable keys is deterministic
# for issue #1535
(def hashes @{})
(repeat 200
(def x (freeze {@"" 1 @"" 2 @"" 3 @"" 4 @"" 5}))
(put hashes (hash x) true))
(assert (= 1 (length hashes)) "freeze mutable keys is deterministic")
# Make sure Carriage Returns don't end up in doc strings
# e528b86
(assert (not (string/find "\r"
@@ -986,4 +993,27 @@
(assert (deep= (get (dyn 'a) :source-form) source))
(setdyn *debug* nil)
# issue #1516
(assert-error "assertf 1 argument" (macex '(assertf true)))
(assert (assertf true "fun message") "assertf 2 arguments")
(assert (assertf true "%s message" "mystery") "assertf 3 arguments")
(assert (assertf (not nil) "%s message" "ordinary") "assertf not nil")
(assert-error "assertf error 2" (assertf false "fun message"))
(assert-error "assertf error 3" (assertf false "%s message" "mystery"))
(assert-error "assertf error 4" (assertf nil "%s %s" "alice" "bob"))
# issue #1535
(loop [i :range [1 1000]]
(assert (deep-not= @{:key1 "value1" @"key" "value2"}
@{:key1 "value1" @"key" "value2"}) "deep= mutable keys"))
(assert (deep-not= {"abc" 123} {@"abc" 123}) "deep= mutable keys vs immutable key")
(assert (deep-not= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys")
(assert (deep-not= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2")
(assert (deep-not= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3")
(assert (deep-not= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4")
(assert (deep-not= @{:key1 "value1" @"key2" @"value2"}
@{:key1 "value1" @"key2" "value2"}) "deep= mutable keys")
(assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"}
@{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys")
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -174,6 +174,7 @@
(assert (deep= (range 0 17 4) @[0 4 8 12 16]) "(range 0 17 4)")
(assert (deep= (range 16 0 -4) @[16 12 8 4]) "(range 16 0 -4)")
(assert (deep= (range 17 0 -4) @[17 13 9 5 1]) "(range 17 0 -4)")
(assert-error "large range" (range 0xFFFFFFFFFF))
(assert (= (length (range 10)) 10) "(range 10)")
(assert (= (length (range -10)) 0) "(range -10)")

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors
# Copyright (c) 2025 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -199,7 +199,7 @@
(assert s "made server 1")
(defn test-echo [msg]
(with [conn (net/connect test-host test-port)]
(with [conn (assert (net/connect test-host test-port))]
(net/write conn msg)
(def res (net/read conn 1024))
(assert (= (string res) msg) (string "echo " msg))))
@@ -213,6 +213,7 @@
# Test on both server and client
# 504411e
(var iterations 0)
(defn names-handler
[stream]
(defer (:close stream)
@@ -220,21 +221,26 @@
(ev/read stream 1)
(def [host port] (net/localname stream))
(assert (= host test-host) "localname host server")
(assert (= port (scan-number test-port)) "localname port server")))
(assert (= port (scan-number test-port)) "localname port server")
(++ iterations)
(ev/write stream " ")))
# Test localname and peername
# 077bf5eba
(repeat 10
(with [s (net/server test-host test-port names-handler)]
(repeat 10
(with [conn (net/connect test-host test-port)]
(with [conn (assert (net/connect test-host test-port))]
(def [host port] (net/peername conn))
(assert (= host test-host) "peername host client ")
(assert (= port (scan-number test-port)) "peername port client")
# let server close
(ev/write conn " "))))
(++ iterations)
(ev/write conn " ")
(ev/read conn 1))))
(gccollect))
(assert (= iterations 200) "localname and peername not enough checks")
# Create pipe
# 12f09ad2d
(var pipe-counter 0)
@@ -410,6 +416,10 @@
(ev/call handler connection)
(break))))
# Make sure we can't bind again with no-reuse
(assert-error "no-reuse"
(net/listen test-host test-port :stream true))
# Read from socket
(defn expect-read
@@ -418,11 +428,11 @@
(assert (= result text) (string/format "expected %v, got %v" text result)))
# Now do our telnet chat
(def bob (net/connect test-host test-port))
(def bob (assert (net/connect test-host test-port :stream)))
(expect-read bob "Whats your name?\n")
(net/write bob "bob")
(expect-read bob "Welcome bob\n")
(def alice (net/connect test-host test-port))
(def alice (assert (net/connect test-host test-port)))
(expect-read alice "Whats your name?\n")
(net/write alice "alice")
(expect-read alice "Welcome alice\n")
@@ -436,7 +446,7 @@
(expect-read bob "[alice]:hi\n")
# Ted joins the chat server
(def ted (net/connect test-host test-port))
(def ted (assert (net/connect test-host test-port)))
(expect-read ted "Whats your name?\n")
(net/write ted "ted")
(expect-read ted "Welcome ted\n")
@@ -465,4 +475,96 @@
# Close chat server
(:close chat-server)
# Issue #1531
(defn sleep-print [x] (ev/sleep 0) (print x))
(protect (with-dyns [*out* sleep-print] (prin :foo)))
(defn level-trigger-handling [conn &] (:close conn))
(def s (assert (net/server test-host test-port level-trigger-handling)))
(def c (assert (net/connect test-host test-port)))
(:close s)
# Issue #1531 no. 2
(def c (ev/chan 0))
(ev/spawn (while (def x (ev/take c))))
(defn print-to-chan [x] (ev/give c x))
(assert-error "coerce await inside janet_call to error"
(with-dyns [*out* print-to-chan]
(pp :foo)))
(ev/chan-close c)
# soreuseport on unix domain sockets
(compwhen (or (= :macos (os/which)) (= :linux (os/which)))
(assert-no-error "unix-domain socket reuseaddr"
(let [uds-path "./unix-domain-socket"]
(defer (os/rm uds-path)
(let [s (net/listen :unix uds-path :stream)]
(:close s))))))
# net/accept-loop level triggering
(gccollect)
(def maxconn 50)
(var connect-count 0)
(defn level-trigger-handling
[conn &]
(with [conn conn]
(ev/write conn (ev/read conn 4096))
(++ connect-count)))
(def s (assert (net/server test-host test-port level-trigger-handling)))
(def cons @[])
(repeat maxconn (array/push cons (assert (net/connect test-host test-port))))
(assert (= maxconn (length cons)))
(defn do-connect [i]
(with [c (get cons i)]
(ev/write c "abc123")
(ev/read c 4096)))
(for i 0 maxconn (ev/spawn (do-connect i)))
(ev/sleep 0.1)
(assert (= maxconn connect-count))
(:close s)
# Cancel os/proc-wait with ev/deadline
(let [p (os/spawn [;run janet "-e" "(os/sleep 4)"] :p)]
(var terminated-normally false)
(assert-error "deadline expired"
(ev/with-deadline 0.01
(os/proc-wait p)
(print "uhoh")
(set terminated-normally true)))
(assert (not terminated-normally) "early termination failure")
# Without this kill, janet will wait the full 4 seconds for the subprocess to complete before exiting.
(assert-no-error "kill proc after wait failed" (os/proc-kill p)))
# Cancel os/proc-wait with ev/deadline 2
(let [p (os/spawn [;run janet "-e" "(os/sleep 0.1)"] :p)]
(var terminated-normally false)
(assert-error "deadline expired"
(ev/with-deadline 0.05
(os/proc-wait p)
(print "uhoh")
(set terminated-normally true)))
(assert (not terminated-normally) "early termination failure 2")
(ev/sleep 0.15)
(assert (not terminated-normally) "early termination failure 3"))
(let [f (coro (forever :foo))]
(ev/deadline 0.01 nil f true)
(assert-error "deadline expired" (resume f)))
# Use :err :stdout
(def- subproc-code '(do (eprint "hi") (eflush) (print "there") (flush)))
(defn ev/slurp
[f &opt buf]
(default buf @"")
(if (ev/read f 0x10000 buf)
(ev/slurp f buf)
buf))
(def p (os/spawn [;run janet "-e" (string/format "%j" subproc-code)] :px {:out :pipe :err :out}))
(def [exit-code data]
(ev/gather
(os/proc-wait p)
(ev/slurp (p :out))))
(def data (string/replace-all "\r" "" data))
(assert (zero? exit-code) "subprocess ran")
(assert (= data "hi\nthere\n") "output is correct")
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors
# Copyright (c) 2025 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -52,5 +52,14 @@
(assert (= 26 (ffi/size [:char :pack :int @[:char 21]]))
"array struct size"))
(end-suite)
(compwhen has-ffi
(assert-error "bad struct issue #1512" (ffi/struct :void)))
(compwhen has-ffi
(def buf @"")
(ffi/write :u8 10 buf)
(assert (= 1 (length buf)))
(ffi/write :u8 10 buf)
(assert (= 2 (length buf))))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose & contributors
# Copyright (c) 2025 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors
# Copyright (c) 2025 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors
# Copyright (c) 2025 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -207,7 +207,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (= 2 (length tclone)) "table/weak-values marsh 2")
(gccollect)
(assert (= 1 (length t)) "table/weak-value marsh 3")
(assert (deep= t tclone) "table/weak-values marsh 4")
(assert (deep= (freeze t) (freeze tclone)) "table/weak-values marsh 4")
# tables with prototypes
(def t (table/weak-values 1))
@@ -219,7 +219,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (= 2 (length tclone)) "marsh weak tables with prototypes 2")
(gccollect)
(assert (= 1 (length t)) "marsh weak tables with prototypes 3")
(assert (deep= t tclone) "marsh weak tables with prototypes 4")
(assert (deep= (freeze t) (freeze tclone)) "marsh weak tables with prototypes 4")
(assert (deep= (getproto t) (getproto tclone)) "marsh weak tables with prototypes 5")
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -57,6 +57,8 @@
(for i (+ index 1) (+ index indent 1)
(case (get text i)
nil (break)
(chr "\r") (if-not (= (chr "\n") (get text (inc i)))
(set rewrite false))
(chr "\n") (break)
(chr " ") nil
(set rewrite false))))
@@ -64,12 +66,17 @@
# Only re-indent if no dedented characters.
(def str
(if rewrite
(peg/replace-all ~(* "\n" (between 0 ,indent " ")) "\n" text)
(peg/replace-all ~(* '(* (? "\r") "\n") (between 0 ,indent " "))
(fn [mtch eol] eol) text)
text))
(def first-nl (= (chr "\n") (first str)))
(def last-nl (= (chr "\n") (last str)))
(string/slice str (if first-nl 1 0) (if last-nl -2)))
(def first-eol (cond
(string/has-prefix? "\r\n" str) :crlf
(string/has-prefix? "\n" str) :lf))
(def last-eol (cond
(string/has-suffix? "\r\n" str) :crlf
(string/has-suffix? "\n" str) :lf))
(string/slice str (case first-eol :crlf 2 :lf 1 0) (case last-eol :crlf -3 :lf -2)))
(defn reindent-reference
"Same as reindent but use parser functionality. Useful for
@@ -89,8 +96,10 @@
(let [a (reindent text indent)
b (reindent-reference text indent)]
(assert (= a b)
(string "indent " indent-counter " (indent=" indent ")"))))
(string/format "reindent: %q, parse: %q (indent-test #%d with indent of %d)" a b indent-counter indent)
)))
# Unix EOLs
(check-indent "" 0)
(check-indent "\n" 0)
(check-indent "\n" 1)
@@ -106,6 +115,17 @@
(check-indent "\n Hello, world!\n " 4)
(check-indent "\n Hello, world!\n dedented text\n " 4)
(check-indent "\n Hello, world!\n indented text\n " 4)
# Windows EOLs
(check-indent "\r\n" 0)
(check-indent "\r\n" 1)
(check-indent "\r\n\r\n" 0)
(check-indent "\r\n\r\n" 1)
(check-indent "\r\nHello, world!" 0)
(check-indent "\r\nHello, world!" 1)
(check-indent "\r\n Hello, world!\r\n " 4)
(check-indent "\r\n Hello, world!\r\n " 4)
(check-indent "\r\n Hello, world!\r\n dedented text\r\n " 4)
(check-indent "\r\n Hello, world!\r\n indented text\r\n " 4)
# Symbols with @ character
# d68eae9
@@ -188,5 +208,14 @@
(parser/consume p `")`)
(assert (= (parser/produce p) ["hello"]))
# Hex floats
(assert (= math/pi +0x1.921fb54442d18p+0001))
(assert (= math/int-max +0x1.ffff_ffff_ffff_ffp+0052))
(assert (= math/int-min -0x1.ffff_ffff_ffff_ffp+0052))
(assert (= 1 0x1P0))
(assert (= 2 0x1P1))
(assert (= -2 -0x1p1))
(assert (= -0.5 -0x1p-1))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -713,6 +713,41 @@
"abcdef"
@[])
(test "til: basic matching"
~(til "d" "abc")
"abcdef"
@[])
(test "til: second pattern can't see past the first occurrence of first pattern"
~(til "d" (* "abc" -1))
"abcdef"
@[])
(test "til: fails if first pattern fails"
~(til "x" "abc")
"abcdef"
nil)
(test "til: fails if second pattern fails"
~(til "abc" "x")
"abcdef"
nil)
(test "til: discards captures from initial pattern"
~(til '"d" '"abc")
"abcdef"
@["abc"])
(test "til: positions inside second match are still relative to the entire input"
~(* "one\ntw" (til 0 (* ($) (line) (column))))
"one\ntwo\nthree\n"
@[6 2 3])
(test "til: advances to the end of the first pattern's first occurrence"
~(* (til "d" "ab") "e")
"abcdef"
@[])
(test "split: basic functionality"
~(split "," '1)
"a,b,c"
@@ -772,5 +807,33 @@
"5:apple6:banana6:cherry"
@["apple" "banana" "cherry"])
# Issue #1539 - make sure split with "" doesn't infinite loop/oom
(test "issue 1539"
~(split "" (capture (to -1)))
"hello there friends"
nil)
(test "issue 1539 pt. 2"
~(split "," (capture 0))
"abc123,,,,"
@["" "" "" "" ""])
# Issue #1549 - allow buffers as peg literals
(test "issue 1549"
''@"abc123"
"abc123"
@["abc123"])
# Issue 1554 - 0-width match termination behavior
(test "issue 1554 case 1" '(any (> '1)) "abc" @[])
(test "issue 1554 case 2" '(any (? (> '1))) "abc" @[])
(test "issue 1554 case 3" '(any (> (? '1))) "abc" @[])
(test "issue 1554 case 4" '(* "a" (> '1)) "abc" @["b"])
(test "issue 1554 case 5" '(* "a" (? (> '1))) "abc" @["b"])
(test "issue 1554 case 6" '(* "a" (> (? '1))) "abc" @["b"])
(test "issue 1554 case 7" '(between 0 2 (> '1)) "abc" @["a" "a"])
(test "issue 1554 case 8" '(between 2 3 (? (> '1))) "abc" @["a" "a" "a"])
(test "issue 1554 case 9" '(between 0 0 (> (? '1))) "abc" @[])
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose & contributors
# Copyright (c) 2025 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2025 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

Some files were not shown because too many files have changed in this diff Show More