1
0
mirror of https://github.com/janet-lang/janet synced 2025-11-22 02:04:49 +00:00

Compare commits

...

135 Commits

Author SHA1 Message Date
Calvin Rose
7e0c692d4e Remove test-install target from sr.ht 2025-11-15 08:39:47 -06:00
Calvin Rose
732fe0ad03 Try new openbsd. 2025-11-15 08:33:41 -06:00
Calvin Rose
0c8622c803 posix_chroot warning 2025-11-15 08:15:26 -06:00
Calvin Rose
94f2494f8d MSVC warning 2025-11-15 08:14:28 -06:00
Calvin Rose
0f9ecc2da5 Prepare for 1.40.0 release 2025-11-15 07:31:23 -06:00
Calvin Rose
83f5da3b8f Merge pull request #1662 from McSinyx/dobytes-err-ctx
Include context in dobytes' error string
2025-11-09 09:20:23 -06:00
Calvin Rose
9b9f2a1713 Merge pull request #1660 from pyrmont/bugfix.confirm-bundle-files
Clarify error message for missing bundle script during bundle installation
2025-11-09 09:06:06 -06:00
Calvin Rose
8df4d47ede Merge pull request #1663 from McSinyx/dobytes-err-keep
Keep dobytes' error string
2025-11-09 08:40:07 -06:00
Nguyễn Gia Phong
1c372fbf32 Keep dobytes' error string
The reason for failure would be more useful
than the most recently evaluated value.
2025-11-05 17:00:04 +09:00
Nguyễn Gia Phong
8ace580498 Include context in dobytes' error string 2025-11-05 16:53:19 +09:00
Michael Camilleri
8241d9cbb4 Revert support for bundles without an info file 2025-10-27 09:32:38 +09:00
Michael Camilleri
6bd02bb5b6 Confirm necessary files during bundle installation 2025-10-26 06:53:27 +09:00
Calvin Rose
2a3308005d Merge pull request #1659 from ianthehenry/try-macro-hygiene
fix (try) macro hygiene
2025-10-25 12:15:12 -05:00
Ian Henry
0c34033b72 add some tests for the (try) macro 2025-10-24 21:56:07 -07:00
Ian Henry
f1ec0cc48b fix try macro hygiene
Allow re-using the same symbol for the fiber and error. This allows you to
write code like (try (print "hi") ([_ _] (print "oh no"))), fixing a regression
introduced in #1605.
2025-10-24 21:33:15 -07:00
Calvin Rose
98265f0637 Merge pull request #1658 from sogaiu/tweak-has-bin-script 2025-10-17 15:44:50 -05:00
sogaiu
1018cb9cca Support :has-exe as well
This is a companion change to spork's issue

As implemented, :has-been-script didn't quite
mean what was intended.  :has-exe should
be an improvement.

To prevent breakage, both :has-bin-script and
:has-exe should be supported for a while in
both spork and janet.  Eventually
:has-bin-script can be retired.
2025-10-17 22:36:45 +09:00
Calvin Rose
2204209133 Merge pull request #1656 from jpaquim/build-system-consistency
Minor adjustments to Makefile
2025-10-15 07:51:12 -05:00
jpaquim
95abff31d9 Fix file mention in CONTRIBUTING.md 2025-10-11 17:48:23 +01:00
jpaquim
a776466423 Add $(RUN) for valgrind/callgrind, add dedicated callgrind-test target 2025-10-11 17:45:44 +01:00
jpaquim
511c1f4b0a Refactor with image-only variable 2025-10-11 16:32:24 +01:00
jpaquim
c29195596e Adapt generated file comments to build type 2025-10-11 16:29:14 +01:00
jpaquim
56027227fb Add missing phony targets and documentation for install-{jpm,spork}-git 2025-10-11 16:11:05 +01:00
jpaquim
c057e14b20 Fix dependencies and variable names in Makefile 2025-10-11 16:02:02 +01:00
Calvin Rose
db7f741dad Extend environment variable behavior to NO_COLOR and JANET_PROFILE
Env vars set to empty strings should behave the same as unset variables.
2025-10-04 13:12:29 -05:00
Calvin Rose
c901edbfb9 Interpret an empty JANET_PATH as unset
This is a common idiom with environment variables, where
a variable set to the empty string should behave the same as
an unset variable.
2025-10-04 10:43:55 -05:00
Calvin Rose
8fd1672963 Merge pull request #1652 from sogaiu/fix-bundle-install-breakage 2025-09-29 09:53:47 -05:00
sogaiu
9b99fc44b9 Revert some bundle/install changes 2025-09-29 23:45:53 +09:00
Michael Camilleri
f393531335 Add bundle/add-manpage 2025-09-28 19:27:53 -05:00
Michael Camilleri
6b8e5249ca Change :dependencies hook to :postdeps 2025-09-28 19:27:53 -05:00
Michael Camilleri
6a96b615f0 Tidy up bundle/ docstrings 2025-09-28 19:27:53 -05:00
Calvin Rose
8ec465d308 Merge pull request #1651 from neuschaefer/a64be
Recognise aarch64_be as big-endian
2025-09-27 14:29:47 -05:00
J. Neuschäfer
07bfd34c2f Recognise aarch64_be as big-endian 2025-09-26 02:07:09 +00:00
Calvin Rose
5f7878c00f Merge pull request #1649 from jsks/kvs-docstring 2025-09-23 06:42:51 -05:00
Joshua Krusell
aaf8ac2217 Tweak kvs docstring 2025-09-23 11:28:06 +02:00
Calvin Rose
73b1cf547e Update CHANGELOG 2025-09-21 10:26:49 -05:00
Calvin Rose
ed2ae562c6 Merge pull request #1647 from sogaiu/tweak-flycheck-related-docstrings 2025-09-21 00:53:51 -05:00
sogaiu
dd59d84b51 Tweak some flycheck-related docstrings 2025-09-21 14:09:35 +09:00
Calvin Rose
06873fbf0b Update CHANGELOG.md 2025-09-20 14:30:10 -05:00
Calvin Rose
1ff26d702a Refactor flycheck to allow customization. Address #1638
Bindings can define their own flycheckers in a simple fashion.
2025-09-20 10:32:16 -05:00
Calvin Rose
4da568254a Manually declare chroot on macos. 2025-09-19 21:17:02 -05:00
Calvin Rose
357f1f94ca Add os/posix-chroot
Gated in a similar manner to os/posix-fork.
2025-09-19 16:32:45 -05:00
Calvin Rose
015e49c806 Merge pull request #1645 from pyrmont/bugfix.avoid-apc-use
Use `SetEvent` rather than `QueueUserAPC` on Windows
2025-09-19 16:31:13 -05:00
Michael Camilleri
6b06ab5f9c Remove unused function on Windows 2025-09-17 15:51:53 +09:00
Michael Camilleri
fe6c6e15a6 Add workaround to timer resolution issue on Windows 2025-09-17 15:48:37 +09:00
Michael Camilleri
b4eb52ca45 Start worker thread in suspended state on Windows 2025-09-16 11:57:40 +09:00
Michael Camilleri
aca5428846 Use SetEvent rather than QueueUserAPC on Windows 2025-09-16 10:24:24 +09:00
Calvin Rose
3dab9737e2 Fix #1643, but add features.h 2025-09-15 15:33:18 -05:00
Calvin Rose
e601e8faab Merge pull request #1640 from sogaiu/add-some-windows-commentary
Improve windows-related docs and notes
2025-09-15 15:29:38 -05:00
sogaiu
07cf63622f Improve windows-related docs and notes 2025-09-11 14:14:07 +09:00
Calvin Rose
8e7b1e9ce0 Don't try for backwards compat with compiled modules - Address #1639
This guarantee is difficult to maintain and shouldn't be needed.
2025-09-06 10:35:10 -05:00
Calvin Rose
355c514f0e Minor version bump for linking. 2025-09-02 07:08:51 -05:00
Calvin Rose
976329abc1 Update CHANGELOG.md 2025-09-01 15:59:39 -05:00
Calvin Rose
ab3e843433 Add test case for string/format %s of buffer 2025-09-01 14:24:51 -05:00
Calvin Rose
148e108864 Remove strnlen and correctly address #1600 2025-09-01 14:04:30 -05:00
Calvin Rose
c90c737345 Revert reordering 2. 2025-09-01 13:46:09 -05:00
Calvin Rose
13b9976382 Revert reordering of janet_deinit 2025-09-01 13:44:55 -05:00
Calvin Rose
095a81286a Add per-thread finalizer calls in missing places. 2025-09-01 12:38:11 -05:00
Calvin Rose
82416e4e4e Address #1629 - janet_deinit called before threaded channel message sent
to thread.

If we take a reference to another thread inside channel code, make sure
that we increase the refcount to avoid a use after free.
2025-09-01 12:30:29 -05:00
Calvin Rose
ae51434a05 Fix #1604 - Add JANET_DO_ERROR_* defines for failure flags from janet_dobytes. 2025-09-01 09:43:27 -05:00
Calvin Rose
bb6ac423a7 Merge pull request #1637 from jsks/spelling-fixes
Small spelling fixes
2025-08-31 09:32:02 -05:00
Joshua Krusell
c5ba3c0513 Small spelling fixes 2025-08-31 12:14:51 +02:00
Calvin Rose
e9c6678614 Update janet for 1.39.1 2025-08-30 08:11:18 -05:00
Calvin Rose
800457c1bf Update meson.build version. 2025-08-30 08:09:43 -05:00
Calvin Rose
2a85781616 Merge pull request #1632 from jsks/jsks-channel
Export channel utilities
2025-08-30 08:08:55 -05:00
Calvin Rose
7c15e7f7dc Merge pull request #1633 from aeiouaeiouaeiouaeiouaeiouaeiou/janet-legacy-macos1
os.c: use JANET_SPAWN_NO_CHDIR macros for macOS <10.15
2025-08-30 08:08:43 -05:00
aeiouaeiouaeiouaeiouaeiouaeiou
896c28b0c8 os.c: use JANET_SPAWN_NO_CHDIR macros for macOS <10.15
Signed-off-by: aeiouaeiouaeiouaeiouaeiouaeiou <aeioudev@outlook.com>
2025-08-29 13:43:28 +03:00
Joshua Krusell
e7bb0dd58e Export channel utilities 2025-08-29 12:19:53 +02:00
Calvin Rose
4e02f27eb9 Prepare for 1.39.0 release 2025-08-24 17:09:39 -05:00
Calvin Rose
fd234461d7 Merge pull request #1628 from snltd/illumos-support
illumos support
2025-08-24 16:18:09 -05:00
Calvin Rose
eabb215391 Use janet_gettime instead of clock_gettime in ev.c
We made the wrapper, let's use it. Also switch poll implementation to a
monotonic clock instead of realtime to be more inline with epoll and
kqueue.
2025-08-21 19:10:08 -05:00
snltd
deede6bae0 illumos support 2025-08-18 18:39:11 +00:00
Calvin Rose
697fdcff6d Merge pull request #1627 from nlordell/fix/system-monotonic-clock
Read System Clock for Monotonic Time
2025-08-18 07:53:26 -05:00
Nicholas Rodrigues Lordello
ad8a5cb6c7 Read System Clock for Monotonic Time
This PR changes the `janet_gettime` implementation for OSX <10.12 to
read the system clock for `(os/clock :monotonic)`. As far as I was able
to find online this is _a_ monotonic clock, although it produces
different values from `clock_gettime(CLOCK_MONOTONIC, ...)` on the same
system. I can speculate that this is related to `SYSTEM_CLOCK` monotonic
time being implemented with `mach_absolute_time` which is documented to
_not advance during sleep_, and I suspect that
`clock_gettime(CLOCK_MONOTONIC, ...)` does.

**Resources**:
- `clock_get_time` implementation for the `SYSTEM_CLOCK`:
  <e3723e1f17/osfmk/kern/clock_oldops.c (L284-L296)>
  <2ff845c2e0/osfmk/arm/rtclock.c (L248-L260)>
- `mach_absolute_time` and `mach_continuous_time` definitions:
  <e3723e1f17/osfmk/mach/mach_time.h (L55-L68)>
- Stack overflow post for implementing `clock_gettime` on OS X before 10.12:
  <https://stackoverflow.com/questions/11680461/monotonic-clock-on-osx>
2025-08-18 14:41:09 +02:00
Calvin Rose
99abada2c2 Merge pull request #1626 from nlordell/fix/clocks-per-sec
Use `CLOCKS_PER_SEC` instead of `CLOCKS_PER_SECOND`
2025-08-18 07:35:20 -05:00
Nicholas Rodrigues Lordello
0624936711 Use CLOCKS_PER_SEC
The POSIX standard defines that `clock(3)` returns a `clock_t` as a
number of clock ticks in `CLOCKS_PER_SEC` and not `CLOCKS_PER_SECOND`,
[source](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html).
2025-08-18 09:15:30 +02:00
Calvin Rose
f764788b36 Merge pull request #1624 from nlordell/fix/libjanet-ldflags
Fix `LDFLAGS` Usage in Makefile
2025-08-17 20:58:40 -05:00
Calvin Rose
4701bc6543 Add test patch for #1625 2025-08-17 20:55:52 -05:00
Nicholas Rodrigues Lordello
156fb0c999 Fix LDFLAGS Usage in Makefile
This PR fixes what appears to be a typo `LDFLAGS` written with an
additional `_` in the Makefile for setting the default linker flags for
`libjanet`.
2025-08-18 01:01:38 +02:00
Calvin Rose
bf34340737 Merge pull request #1623 from tttuuu888/work-issue-1622
Add `net/socket` for unbound socket creation for #1622
2025-08-16 06:48:43 -05:00
Seungki Kim
20535e8626 Add net/socket for unbound socket creation #1622 2025-08-14 00:40:22 +09:00
Calvin Rose
1ead670e33 Merge pull request #1621 from tttuuu888/work-issue-1620
Fix: Correctly flag UDP streams in net/connect #1620
2025-08-12 16:04:52 -05:00
Seungki Kim
3ad86108f2 Fix: Correctly flag UDP streams in net/connect #1620 2025-08-11 20:56:04 +09:00
Calvin Rose
0aee7765cf Windows quirk fix 2025-08-02 20:04:55 -05:00
Calvin Rose
4894a4673a Fix abstract unix sockets for issue #1618 - address #1618 2025-08-02 19:00:47 -05:00
Calvin Rose
f00d3199c3 Fix #1609 Remove sigaction if JANET_NO_EV defined. 2025-08-02 18:19:39 -05:00
Calvin Rose
e34a8545e6 Merge pull request #1615 from ifreund/net-server-datagram
net/server: improve error for truthy handler and type :datagram
2025-08-02 18:06:24 -05:00
Isaac Freund
f974c0667b net/server: improve error for truthy handler and type :datagram
Since it is invalid to call accept on a datagram socket, net/server
always errors if handler is truthy and type is :datagram.

Add an assert to give a better error message in this case and clarify
the documentation.

References: https://github.com/janet-lang/janet/issues/1614
2025-07-18 09:57:17 +02:00
Calvin Rose
ddc122958b Merge pull request #1607 from sarna/master
Clarify :fresh usage in import
2025-07-13 18:35:56 -05:00
Calvin Rose
2e363bf29c Remove extra call to filewatcher - address #1608 2025-07-13 18:34:12 -05:00
Calvin Rose
312f9faae8 Address #1609 - compile with JANET_NO_PROCESSES 2025-07-13 18:24:23 -05:00
sarna
8c9cd63cb1 Add tests for import arg validation 2025-07-13 16:32:50 +02:00
sarna
2af3f21d69 Validate optional args to import 2025-07-13 16:31:12 +02:00
sarna
c4e3fa03fa Clarify :fresh usage in import 2025-07-12 14:38:22 +02:00
Calvin Rose
91b7bcad3d Merge pull request #1606 from pyrmont/bugfix.bundle-install
Support complex dependency coordinates in `bundle/install`
2025-07-06 10:32:54 -05:00
Michael Camilleri
8d2a9c1148 Allow :dependencies value in info.jdn to contain dictionaries for complex dependency coordinates 2025-07-06 05:45:41 +09:00
Michael Camilleri
f1d47bd05a Use :dependencies argument in bundle/install for dependency checking 2025-07-02 23:38:36 +09:00
Calvin Rose
58b1491592 Merge pull request #1605 from iacore/patch-2
Patch try to accept (try body ([] catch-body))
2025-06-29 10:01:32 -05:00
Calvin Rose
21a6ed3bd3 Revert order change from f4ecb5a
janet_interpreter_interrupt should always be called before janet_interpreter_interrupt_handled, and the original code ensured that.
2025-06-26 19:20:07 -05:00
iacore
e815c91e85 Patch try to accept (try body ([] catch-body)) 2025-06-24 06:18:34 +00:00
Calvin Rose
d96e584869 Remove windows-2019 from github CI 2025-06-19 17:29:48 -05:00
Calvin Rose
f4ecb5a90f Reorder post event / interrupt sequence in deadline.
The interrupt message should come _after_ the post event is made.
2025-06-18 22:13:14 -05:00
Calvin Rose
f181948aa9 Merge pull request #1601 from edsrzf/limit-buffer-read
Use strnlen when checking for null byte
2025-06-14 12:25:18 -05:00
Evan Shaw
bbe6b90331 Use strnlen when checking for null byte 2025-06-14 22:29:30 +12:00
Calvin Rose
27f01e2664 Merge pull request #1597 from sogaiu/remove-more-underline-bits
Remove some underline bits from doc-format
2025-05-21 21:39:16 -05:00
sogaiu
877967966a Remove some underline bits from doc-format 2025-05-21 18:38:09 +09:00
Calvin Rose
56c5a0ca09 Address #1591 - remove _ behavior of docstring format 2025-05-20 19:17:32 -05:00
Calvin Rose
f3ad13c2d4 Always cancel thread on windows. 2025-05-18 14:02:32 -05:00
Calvin Rose
8ac4eec370 Change ifdef structure. 2025-05-18 13:20:19 -05:00
Calvin Rose
92e91259c3 Don't call pthread cancel on normal exits.
Calling pthread_cancel on threads that can exit normally is not needed.
Instead, we immediately call pthread_join if a thread can exit normally.
2025-05-18 09:52:11 -05:00
Calvin Rose
e355cb07e0 Reorder declarations. 2025-05-18 09:27:01 -05:00
Calvin Rose
5bbfcdacd5 Work on #1596 - No detached threads, make sure to call pthread_join
Call pthread_join on all worker threads for timeouts. Previously, we
were leaking some threads, as well as creating a timeout and leaving
has_worker unset on certain timeouts.
2025-05-18 08:36:53 -05:00
Calvin Rose
790a4f2636 Make tests pass with clang sanitizers.
Fix some issue with clang sanitizers, name -fsanitize=thread and
-fsanitize=undefined. The threading issue arose from the implementation
of ev/deadlock when allowing for interpreter intrerrupts, as this is
implemented by racing a timeout thread with a worker thread.

The undefined behavior issue arose in some very old code in corelib.c that will
actually work as expected for most compilers, but was both undefined and
unecessary as we have a correct implemenation in util.c.
2025-05-17 21:28:41 -05:00
Calvin Rose
84bb84b0b7 OpenBSD 7.7 -> 7.6 rollback 2025-05-16 18:58:07 -05:00
Calvin Rose
29f2b5c345 Update openbsd package for srht 2025-05-16 18:57:29 -05:00
Calvin Rose
4643c8fa35 Squashed commit of the following:
commit c5b3da1ffe
Author: Calvin Rose <calsrose@gmail.com>
Date:   Fri May 16 18:35:33 2025 -0500

    Inter
2025-05-16 18:49:45 -05:00
Calvin Rose
a8e2c8e5b8 Address #1596 - Use atomic intrinsics to check allow_interrupt flag.
Use a relaxed memory order if possible to mitigate performance issues as
much as possible. relaxed memory order should be sufficient.
2025-05-16 18:26:43 -05:00
Calvin Rose
3d3e880f52 Allow configuration of JANET_THREAD_LOCAL. Address #1595
This is to better allow configuration on various, unknown compilers.
Previously, we hardcoded how thread local storage was specified for a
few different compilers, but we were not following and C standard. In
C11, there is a standardized storage specifier _Thread_local for this
storage class, however this is now deprecated in various C++ compilers
for a new keyword, confusingly. Janet also does not claim to require the
C11 standard, so for maximum flexibilty, the storage specifier must be
specified at configure time.
2025-05-11 08:37:15 -05:00
Calvin Rose
bef8ba5e06 Merge pull request #1594 from sogaiu/tweak-docstrings
Tweak docstrings for better handling
2025-05-08 17:00:32 -05:00
Calvin Rose
523639362c Merge pull request #1593 from agent-kilo/thread-message-size-check
Try to fix cfun registry size check for cross-thread messages
2025-05-08 17:00:22 -05:00
sogaiu
4b6d5e5671 Tweak docstrings for better handling 2025-05-02 18:54:18 +09:00
Agent Kilo
a695454dae Try to fix cfun registry size check for cross-thread messages 2025-04-28 17:00:23 +08:00
Calvin Rose
f2eaa5dee8 Remove ev/to-stream.
The function has more problems than initially expected, both on Posix
systems and on Windows. Given all the caveats, it is probably best not
to include. Any function that can obtain files can use os/open instead.
The standard FILE objects also will not work anyway, and different
operating systems have different work arounds.
2025-04-18 18:20:27 -07:00
Calvin Rose
b27c830d90 Use win32 DuplicatHandle instead of _dup
There are constraints when using the posix API on win32
that aren't present with normal win32.
2025-04-11 21:36:08 -05:00
Calvin Rose
92a852f2df Add ev/to-stream.
This function is symmetrical to ev/to-file and can convert a blocking
file to a stream (with caveats).
2025-04-11 21:10:49 -05:00
Calvin Rose
647e218bed make format 2025-04-09 20:39:23 -05:00
Calvin Rose
5ebe945ffd Merge pull request #1582 from pyrmont/bugfix.termux-pthread_cancel
Fix absence of pthread_cancel in Android
2025-04-09 20:16:57 -05:00
Michael Camilleri
6254fffad0 Update changelog to include change to ev/deadline 2025-04-09 10:40:19 +09:00
Michael Camilleri
5705b2f6c7 Fix absence of pthread_cancel in Android 2025-04-09 10:37:46 +09:00
Calvin Rose
90a33bc88a Add some systems that support addchdir. 2025-04-07 21:12:28 -05:00
Calvin Rose
1ba077c87d 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-07 21:12:28 -05:00
Calvin Rose
34629ae314 Fix signature on windows. 2025-04-07 21:12:28 -05:00
Calvin Rose
3edc4f35b2 Add :cd argument to os/execute and os/spawn. 2025-04-07 21:12:28 -05:00
Calvin Rose
06d01c099f Merge pull request #1581 from sogaiu/fix-mapcat-and-keep-docstrings
Fix mapcat and keep docstrings
2025-04-07 21:11:14 -05:00
sogaiu
d493eaf485 Fix mapcat and keep docstrings 2025-04-06 20:48:52 +09:00
38 changed files with 860 additions and 338 deletions

View File

@@ -1,4 +1,4 @@
image: openbsd/7.4 image: openbsd/7.7
sources: sources:
- https://git.sr.ht/~bakpakin/janet - https://git.sr.ht/~bakpakin/janet
packages: packages:
@@ -10,7 +10,6 @@ tasks:
gmake gmake
gmake test gmake test
doas gmake install doas gmake install
gmake test-install
doas gmake uninstall doas gmake uninstall
- meson_min: | - meson_min: |
cd janet cd janet

View File

@@ -25,7 +25,7 @@ jobs:
name: Build and test on Windows name: Build and test on Windows
strategy: strategy:
matrix: matrix:
os: [ windows-latest, windows-2019 ] os: [ windows-latest, windows-2022 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
@@ -46,7 +46,7 @@ jobs:
name: Build and test on Windows Minimal build name: Build and test on Windows Minimal build
strategy: strategy:
matrix: matrix:
os: [ windows-2019 ] os: [ windows-2022 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository

View File

@@ -1,10 +1,28 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## Unreleased - ??? ## 1.40.0 - 2025-11-15
- Add `os/posix-chroot`
- Fix `ev/deadline` with interrupt race condition bug on Windows.
- Improve `flycheck` by allowing functions and macros to define their own flycheck behavior via the metadata `:flycheck`.
- Add `*flychecking*` dynamic binding to check if inside flycheck evalutation
- Add `gcperthread` callback for abstract types. This lets threaded abstracts have a finalizer that is called per thread, as well as a global finalizer.
- Add `JANET_DO_ERROR_*` flags to describe the return value of `janet_dobytes` and `janet_dostring`.
## 1.39.1 - 2025-08-30
- Add support for chdir in os/spawn on older macOS versions
- Expose channels properly in C API
## 1.39.0 - 2025-08-24
- Various bug fixes
- Add `net/socket`
- Add support for illumos OS
- Raise helpful errors for incorrect arguments to `import`.
- Allow configuring `JANET_THREAD_LOCAL` during builds to allow multi-threading on unknown compilers.
- Make `ffi/write` append to a buffer instead of insert at 0 by default. - Make `ffi/write` append to a buffer instead of insert at 0 by default.
- Add `os/getpid` to get the current process id. - Add `os/getpid` to get the current process id.
- Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes. - Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes.
Add `interrupt?` argument to `ev/deadline` to use VM interruptions.
## 1.38.0 - 2025-03-18 ## 1.38.0 - 2025-03-18
- Add `bundle/replace` - Add `bundle/replace`

View File

@@ -61,7 +61,7 @@ ensure a consistent code style for C.
## Janet style ## Janet style
All janet code in the project should be formatted similar to the code in core.janet. All janet code in the project should be formatted similar to the code in src/boot/boot.janet.
The auto formatting from janet.vim will work well. The auto formatting from janet.vim will work well.
## Typo Fixing and One-Line changes ## Typo Fixing and One-Line changes

View File

@@ -47,6 +47,7 @@ SPORK_TAG?=master
HAS_SHARED?=1 HAS_SHARED?=1
DEBUGGER=gdb DEBUGGER=gdb
SONAME_SETTER=-Wl,-soname, SONAME_SETTER=-Wl,-soname,
STRIPFLAGS=-x -S
# For cross compilation # For cross compilation
HOSTCC?=$(CC) HOSTCC?=$(CC)
@@ -54,7 +55,7 @@ HOSTAR?=$(AR)
# Symbols are (optionally) removed later, keep -g as default! # Symbols are (optionally) removed later, keep -g as default!
CFLAGS?=-O2 -g CFLAGS?=-O2 -g
LDFLAGS?=-rdynamic LDFLAGS?=-rdynamic
LIBJANET_LDFLAGS?=$(LD_FLAGS) LIBJANET_LDFLAGS?=$(LDFLAGS)
RUN:=$(RUN) RUN:=$(RUN)
@@ -80,6 +81,12 @@ ifeq ($(UNAME), Darwin)
LDCONFIG:=true LDCONFIG:=true
else ifeq ($(UNAME), Linux) else ifeq ($(UNAME), Linux)
CLIBS:=$(CLIBS) -lrt -ldl CLIBS:=$(CLIBS) -lrt -ldl
else ifeq ($(UNAME), SunOS)
BUILD_CFLAGS+=-D__EXTENSIONS__ -DJANET_NO_NANBOX
BOOT_CFLAGS+=-D__EXTENSIONS__ -DJANET_NO_NANBOX
CLIBS:=-lsocket -lm
STRIPFLAGS=-x
LDCONFIG:=false
endif endif
# For other unix likes, add flags here! # For other unix likes, add flags here!
@@ -213,9 +220,9 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile
######################## ########################
ifeq ($(UNAME), Darwin) ifeq ($(UNAME), Darwin)
SONAME=libjanet.1.38.dylib SONAME=libjanet.1.40.dylib
else else
SONAME=libjanet.so.1.38 SONAME=libjanet.so.1.40
endif endif
ifeq ($(MINGW_COMPILER), clang) ifeq ($(MINGW_COMPILER), clang)
@@ -254,6 +261,7 @@ $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS)
# Testing assumes HOSTCC=CC # Testing assumes HOSTCC=CC
TEST_SCRIPTS=$(wildcard test/suite*.janet) TEST_SCRIPTS=$(wildcard test/suite*.janet)
EXAMPLE_SCRIPTS=$(wildcard examples/*.janet)
repl: $(JANET_TARGET) repl: $(JANET_TARGET)
$(RUN) ./$(JANET_TARGET) $(RUN) ./$(JANET_TARGET)
@@ -261,21 +269,26 @@ repl: $(JANET_TARGET)
debug: $(JANET_TARGET) debug: $(JANET_TARGET)
$(DEBUGGER) ./$(JANET_TARGET) $(DEBUGGER) ./$(JANET_TARGET)
VALGRIND_COMMAND=valgrind --leak-check=full --quiet VALGRIND_COMMAND=$(RUN) valgrind --leak-check=full --quiet
CALLGRIND_COMMAND=$(RUN) valgrind --tool=callgrind
valgrind: $(JANET_TARGET) valgrind: $(JANET_TARGET)
$(VALGRIND_COMMAND) ./$(JANET_TARGET) $(VALGRIND_COMMAND) ./$(JANET_TARGET)
test: $(JANET_TARGET) $(TEST_PROGRAMS) test: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done
valtest: $(JANET_TARGET) $(TEST_PROGRAMS) valtest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done for f in examples/*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done
callgrind: $(JANET_TARGET) callgrind: $(JANET_TARGET)
for f in test/suite*.janet; do valgrind --tool=callgrind ./$(JANET_TARGET) "$$f" || exit; done $(CALLGRIND_COMMAND) ./$(JANET_TARGET)
calltest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
for f in test/suite*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done
######################## ########################
##### Distribution ##### ##### Distribution #####
@@ -289,7 +302,7 @@ build/janet-%.tar.gz: $(JANET_TARGET) \
README.md build/c/janet.c build/c/shell.c README.md build/c/janet.c build/c/shell.c
mkdir -p build/$(JANET_DIST_DIR)/bin mkdir -p build/$(JANET_DIST_DIR)/bin
cp $(JANET_TARGET) build/$(JANET_DIST_DIR)/bin/ cp $(JANET_TARGET) build/$(JANET_DIST_DIR)/bin/
strip -x -S 'build/$(JANET_DIST_DIR)/bin/janet' strip $(STRIPFLAGS) 'build/$(JANET_DIST_DIR)/bin/janet'
mkdir -p build/$(JANET_DIST_DIR)/include mkdir -p build/$(JANET_DIST_DIR)/include
cp build/janet.h build/$(JANET_DIST_DIR)/include/ cp build/janet.h build/$(JANET_DIST_DIR)/include/
mkdir -p build/$(JANET_DIST_DIR)/lib/ mkdir -p build/$(JANET_DIST_DIR)/lib/
@@ -336,7 +349,7 @@ build/janet.pc: $(JANET_TARGET)
install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h
mkdir -p '$(DESTDIR)$(BINDIR)' mkdir -p '$(DESTDIR)$(BINDIR)'
cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet' cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet'
strip -x -S '$(DESTDIR)$(BINDIR)/janet' strip $(STRIPFLAGS) '$(DESTDIR)$(BINDIR)/janet'
mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet' mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet'
cp -r build/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet' cp -r build/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet'
ln -sf ./janet/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet.h' ln -sf ./janet/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet.h'
@@ -406,9 +419,6 @@ clean:
-rm -rf build vgcore.* callgrind.* -rm -rf build vgcore.* callgrind.*
-rm -rf test/install/build test/install/modpath -rm -rf test/install/build test/install/modpath
test-install:
echo "JPM has been removed from default install."
help: help:
@echo @echo
@echo 'Janet: A Dynamic Language & Bytecode VM' @echo 'Janet: A Dynamic Language & Bytecode VM'
@@ -420,7 +430,8 @@ help:
@echo ' make test Test a built Janet' @echo ' make test Test a built Janet'
@echo ' make valgrind Assess Janet with Valgrind' @echo ' make valgrind Assess Janet with Valgrind'
@echo ' make callgrind Assess Janet with Valgrind, using Callgrind' @echo ' make callgrind Assess Janet with Valgrind, using Callgrind'
@echo ' make valtest Run the test suite with Valgrind to check for memory leaks' @echo ' make valtest Run the test suite and examples with Valgrind to check for memory leaks'
@echo ' make calltest Run the test suite and examples with Callgrind'
@echo ' make dist Create a distribution tarball' @echo ' make dist Create a distribution tarball'
@echo ' make docs Generate documentation' @echo ' make docs Generate documentation'
@echo ' make debug Run janet with GDB or LLDB' @echo ' make debug Run janet with GDB or LLDB'
@@ -430,6 +441,9 @@ help:
@echo " make format Format Janet's own source files" @echo " make format Format Janet's own source files"
@echo ' make grammar Generate a TextMate language grammar' @echo ' make grammar Generate a TextMate language grammar'
@echo @echo
@echo ' make install-jpm-git Install jpm into the current filesystem'
@echo ' make install-spork-git Install spork into the current filesystem'
@echo
.PHONY: clean install repl debug valgrind test \ .PHONY: clean install install-jpm-git install-spork-git repl debug valgrind test \
valtest dist uninstall docs grammar format help compile-commands valtest callgrind callgrind-test dist uninstall docs grammar format help compile-commands

View File

@@ -213,6 +213,10 @@ gmake install-jpm-git
NetBSD build instructions are the same as the FreeBSD build instructions. NetBSD build instructions are the same as the FreeBSD build instructions.
Alternatively, install the package directly with `pkgin install janet`. Alternatively, install the package directly with `pkgin install janet`.
### illumos
Building on illumos is exactly the same as building on FreeBSD.
### Windows ### Windows
1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15#) or [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15#). 1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15#) or [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15#).

View File

@@ -49,6 +49,7 @@ for %%f in (src\boot\*.c) do (
) )
%JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj %JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj
@if errorlevel 1 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
@rem note that there is no default sysroot being baked in
build\janet_boot . > build\c\janet.c build\janet_boot . > build\c\janet.c
@if errorlevel 1 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL

View File

@@ -0,0 +1,6 @@
# Linux only - uses abstract unix domain sockets
(ev/spawn (net/server :unix "@abc123" (fn [conn] (print (:read conn 1024)) (:close conn))))
(ev/sleep 1)
(def s (net/connect :unix "@abc123" :stream))
(:write s "hello")
(:close s)

View File

@@ -0,0 +1,3 @@
@{
:name "sample-bad-bundle1"
}

View File

@@ -0,0 +1,3 @@
@{
:name "sample-bad-bundle2"
}

View File

@@ -1,4 +1,4 @@
@{ @{
:name "sample-dep1" :name "sample-dep1"
:dependencies ["sample-dep2"] :dependencies [{:name "sample-dep2"}]
} }

View File

@@ -214,7 +214,7 @@ Don't execute a script, only compile it to check for errors. Useful for linting
.BR \-m\ syspath .BR \-m\ syspath
Set the dynamic binding :syspath to the string syspath so that Janet will load system modules Set the dynamic binding :syspath to the string syspath so that Janet will load system modules
from a directory different than the default. The default is set when Janet is built, and defaults to from a directory different than the default. The default is set when Janet is built, and defaults to
/usr/local/lib/janet on Linux/Posix, and C:/Janet/Library on Windows. This option supersedes JANET_PATH. /usr/local/lib/janet on Linux/Posix. On Windows, there is no default value. This option supersedes JANET_PATH.
.TP .TP
.BR \-c\ source\ output .BR \-c\ source\ output
@@ -255,8 +255,7 @@ and then arguments to the script.
.RS .RS
The location to look for Janet libraries. This is the only environment variable Janet needs to The location to look for Janet libraries. This is the only environment variable Janet needs to
find native and source code modules. If no JANET_PATH is set, Janet will look in find native and source code modules. If no JANET_PATH is set, Janet will look in
the default location set at compile time. This should be a list of as well as a colon the default location set at compile time. This should be a colon-separated list of directory names on Linux/Posix, and a semicolon-separated list on Windows. Note that a typical setup (i.e. not NixOS / Guix) will only use a single directory.
separate list of such directories.
.RE .RE
.B JANET_PROFILE .B JANET_PROFILE

View File

@@ -20,7 +20,7 @@
project('janet', 'c', project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.38.0') version : '1.40.0')
# Global settings # Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
@@ -105,6 +105,9 @@ endif
if get_option('arch_name') != '' if get_option('arch_name') != ''
conf.set('JANET_ARCH_NAME', get_option('arch_name')) conf.set('JANET_ARCH_NAME', get_option('arch_name'))
endif endif
if get_option('thread_local_prefix') != ''
conf.set('JANET_THREAD_LOCAL', get_option('thread_local_prefix'))
endif
jconf = configure_file(output : 'janetconf.h', jconf = configure_file(output : 'janetconf.h',
configuration : conf) configuration : conf)
@@ -278,6 +281,7 @@ test_files = [
'test/suite-corelib.janet', 'test/suite-corelib.janet',
'test/suite-debug.janet', 'test/suite-debug.janet',
'test/suite-ev.janet', 'test/suite-ev.janet',
'test/suite-ev2.janet',
'test/suite-ffi.janet', 'test/suite-ffi.janet',
'test/suite-filewatch.janet', 'test/suite-filewatch.janet',
'test/suite-inttypes.janet', 'test/suite-inttypes.janet',

View File

@@ -30,6 +30,7 @@ option('max_macro_expand', type : 'integer', min : 1, max : 8000, value : 200)
option('stack_max', type : 'integer', min : 8096, max : 0x7fffffff, value : 0x7fffffff) option('stack_max', type : 'integer', min : 8096, max : 0x7fffffff, value : 0x7fffffff)
option('arch_name', type : 'string', value: '') option('arch_name', type : 'string', value: '')
option('thread_local_prefix', type : 'string', value: '')
option('os_name', type : 'string', value: '') option('os_name', type : 'string', value: '')
option('shared', type : 'boolean', value: true) option('shared', type : 'boolean', value: true)
option('cryptorand', type : 'boolean', value: true) option('cryptorand', type : 'boolean', value: true)

View File

@@ -7,7 +7,7 @@
### ###
### ###
(def defn :macro (def defn :macro :flycheck
``` ```
(defn name & more) (defn name & more)
@@ -43,7 +43,7 @@
# Build return value # Build return value
~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start))))) ~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start)))))
(defn defmacro :macro (defn defmacro :macro :flycheck
"Define a macro." "Define a macro."
[name & more] [name & more]
(setdyn name @{}) # override old macro definitions in the case of a recursive macro (setdyn name @{}) # override old macro definitions in the case of a recursive macro
@@ -57,12 +57,12 @@
[f & args] [f & args]
(f ;args)) (f ;args))
(defmacro defmacro- (defmacro defmacro- :flycheck
"Define a private macro that will not be exported." "Define a private macro that will not be exported."
[name & more] [name & more]
(apply defn name :macro :private more)) (apply defn name :macro :private more))
(defmacro defn- (defmacro defn- :flycheck
"Define a private function that will not be exported." "Define a private function that will not be exported."
[name & more] [name & more]
(apply defn name :private more)) (apply defn name :private more))
@@ -144,7 +144,7 @@
(defmacro /= "Shorthand for (set x (/ x n))." [x & ns] ~(set ,x (,/ ,x ,;ns))) (defmacro /= "Shorthand for (set x (/ x n))." [x & ns] ~(set ,x (,/ ,x ,;ns)))
(defmacro %= "Shorthand for (set x (% x n))." [x & ns] ~(set ,x (,% ,x ,;ns))) (defmacro %= "Shorthand for (set x (% x n))." [x & ns] ~(set ,x (,% ,x ,;ns)))
(defmacro assert (defmacro assert :flycheck # should top level assert flycheck?
"Throw an error if x is not truthy. Will not evaluate `err` if x is truthy." "Throw an error if x is not truthy. Will not evaluate `err` if x is truthy."
[x &opt err] [x &opt err]
(def v (gensym)) (def v (gensym))
@@ -154,11 +154,11 @@
,v ,v
(,error ,(if err err (string/format "assert failure in %j" x)))))) (,error ,(if err err (string/format "assert failure in %j" x))))))
(defmacro defdyn (defmacro defdyn :flycheck
``Define an alias for a keyword that is used as a dynamic binding. The ``Define an alias for a keyword that is used as a dynamic binding. The
alias is a normal, lexically scoped binding that can be used instead of alias is a normal, lexically scoped binding that can be used instead of
a keyword to prevent typos. `defdyn` does not set dynamic bindings or otherwise a keyword to prevent typos. `defdyn` does not set dynamic bindings or otherwise
replace `dyn` and `setdyn`. The alias _must_ start and end with the `*` character, usually replace `dyn` and `setdyn`. The alias *must* start and end with the `*` character, usually
called "earmuffs".`` called "earmuffs".``
[alias & more] [alias & more]
(assert (symbol? alias) "alias must be a symbol") (assert (symbol? alias) "alias must be a symbol")
@@ -171,6 +171,9 @@
(defdyn *macro-form* (defdyn *macro-form*
"Inside a macro, is bound to the source form that invoked the macro") "Inside a macro, is bound to the source form that invoked the macro")
(defdyn *flychecking*
"Check if the current form is being evaluated inside `flycheck`. Will be `true` while flychecking.")
(defdyn *lint-error* (defdyn *lint-error*
"The current lint error level. The error level is the lint level at which compilation will exit with an error and not continue.") "The current lint error level. The error level is the lint level at which compilation will exit with an error and not continue.")
@@ -290,22 +293,6 @@
(array/concat accum body) (array/concat accum body)
(tuple/slice accum 0)) (tuple/slice accum 0))
(defmacro try
``Try something and catch errors. `body` is any expression,
and `catch` should be a form, the first element of which is a tuple. This tuple
should contain a binding for errors and an optional binding for
the fiber wrapping the body. Returns the result of `body` if no error,
or the result of `catch` if an error.``
[body catch]
(let [[[err fib]] catch
f (gensym)
r (gensym)]
~(let [,f (,fiber/new (fn :try [] ,body) :ie)
,r (,resume ,f)]
(if (,= (,fiber/status ,f) :error)
(do (def ,err ,r) ,(if fib ~(def ,fib ,f)) ,;(tuple/slice catch 1))
,r))))
(defmacro protect (defmacro protect
`Evaluate expressions, while capturing any errors. Evaluates to a tuple `Evaluate expressions, while capturing any errors. Evaluates to a tuple
of two elements. The first element is true if successful, false if an of two elements. The first element is true if successful, false if an
@@ -352,6 +339,26 @@
(tuple 'if $fi $fi ret)))))) (tuple 'if $fi $fi ret))))))
ret) ret)
(defmacro try
``Try something and catch errors. `body` is any expression,
and `catch` should be a form, the first element of which is a tuple. This tuple
should contain a binding for errors and an optional binding for
the fiber wrapping the body. Returns the result of `body` if no error,
or the result of `catch` if an error.``
[body catch]
(assert (and (not (empty? catch)) (indexed? (catch 0))) "the first element of `catch` must be a tuple or array")
(let [[err fib] (catch 0)
r (gensym)
f (gensym)]
~(let [,f (,fiber/new (fn :try [] ,body) :ie)
,r (,resume ,f)]
(if (,= (,fiber/status ,f) :error)
(do
,(if err ~(def ,err ,r))
,(if fib ~(def ,fib ,f))
,;(tuple/slice catch 1))
,r))))
(defmacro with-syms (defmacro with-syms
"Evaluates `body` with each symbol in `syms` bound to a generated, unique symbol." "Evaluates `body` with each symbol in `syms` bound to a generated, unique symbol."
[syms & body] [syms & body]
@@ -1103,8 +1110,9 @@
and use `array/concat` to concatenate the results, but only if and use `array/concat` to concatenate the results, but only if
no `inds` are provided. Multiple data structures can be handled no `inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `f` is a function of if each `inds` is a data structure and `f` is a function of
arity one more than the number of `inds`. The resulting array arity one more than the number of `inds`. Note that `f` is only
has a length that is the shortest of `ind` and each of `inds`. applied to values at indeces up to the largest index of the
shortest of `ind` and each of `inds`.
``` ```
[f ind & inds] [f ind & inds]
(def res @[]) (def res @[])
@@ -1143,8 +1151,8 @@
structure `ind`, but only if no `inds` are provided. Multiple structure `ind`, but only if no `inds` are provided. Multiple
data structures can be handled if each `inds` is a data data structures can be handled if each `inds` is a data
structure and `pred` is a function of arity one more than the structure and `pred` is a function of arity one more than the
number of `inds`. The resulting array has a length that is the number of `inds`. The resulting array has a length that is no
shortest of `ind` and each of `inds`. longer than the shortest of `ind` and each of `inds`.
``` ```
[pred ind & inds] [pred ind & inds]
(def res @[]) (def res @[])
@@ -1798,8 +1806,8 @@
(flatten-into @[] xs)) (flatten-into @[] xs))
(defn kvs (defn kvs
``Takes a table or struct and returns and array of key value pairs ``Takes a table or struct and returns a new array of key value pairs
like `@[k v k v ...]`. Returns a new array.`` like `@[k v k v ...]`.``
[dict] [dict]
(def ret @[]) (def ret @[])
(loop [k :keys dict] (array/push ret k (in dict k))) (loop [k :keys dict] (array/push ret k (in dict k)))
@@ -1932,7 +1940,7 @@
that will match any value without creating a binding. that will match any value without creating a binding.
While a symbol pattern will ordinarily match any value, the pattern `(@ <sym>)`, While a symbol pattern will ordinarily match any value, the pattern `(@ <sym>)`,
where <sym> is any symbol, will attempt to match `x` against a value where `<sym>` is any symbol, will attempt to match `x` against a value
already bound to `<sym>`, rather than matching and rebinding it. already bound to `<sym>`, rather than matching and rebinding it.
Any other value pattern will only match if it is equal to `x`. Any other value pattern will only match if it is equal to `x`.
@@ -2352,7 +2360,7 @@
(set macexvar macex) (set macexvar macex)
(defmacro varfn (defmacro varfn :flycheck
``Create a function that can be rebound. `varfn` has the same signature ``Create a function that can be rebound. `varfn` has the same signature
as `defn`, but defines functions in the environment as vars. If a var `name` as `defn`, but defines functions in the environment as vars. If a var `name`
already exists in the environment, it is rebound to the new function. Returns already exists in the environment, it is rebound to the new function. Returns
@@ -2573,7 +2581,7 @@
* `:env` -- the environment to compile against - default is the current env * `:env` -- the environment to compile against - default is the current env
* `:source` -- source path for better errors (use keywords for non-paths) - default * `:source` -- source path for better errors (use keywords for non-paths) - default
is :<anonymous> is `:<anonymous>`
* `:on-compile-error` -- callback when compilation fails - default is bad-compile * `:on-compile-error` -- callback when compilation fails - default is bad-compile
@@ -3179,12 +3187,17 @@
use the name of the module as a prefix. One can also use "`:export true`" use the name of the module as a prefix. One can also use "`:export true`"
to re-export the imported symbols. If "`:exit true`" is given as an argument, to re-export the imported symbols. If "`:exit true`" is given as an argument,
any errors encountered at the top level in the module will cause `(os/exit 1)` any errors encountered at the top level in the module will cause `(os/exit 1)`
to be called. Dynamic bindings will NOT be imported. Use :fresh to bypass the to be called. Dynamic bindings will NOT be imported. Use :fresh with a truthy
module cache. Use `:only [foo bar baz]` to only import select bindings into the value to bypass the module cache. Use `:only [foo bar baz]` to only import
current environment.`` select bindings into the current environment.``
[path & args] [path & args]
(assertf (even? (length args)) "args should have even length: %n" args)
(def ps (partition 2 args)) (def ps (partition 2 args))
(def argm (mapcat (fn [[k v]] [k (case k :as (string v) :only ~(quote ,v) v)]) ps)) (def argm
(mapcat (fn [[k v]]
(assertf (keyword? k) "expected keyword, got %s: %n" (type k) k)
[k (case k :as (string v) :only ~(quote ,v) v)])
ps))
(tuple import* (string path) ;argm)) (tuple import* (string path) ;argm))
(defmacro use (defmacro use
@@ -3247,12 +3260,10 @@
# Terminal codes for emission/tokenization # Terminal codes for emission/tokenization
(def delimiters (def delimiters
(if has-color (if has-color
{:underline ["\e[4m" "\e[24m"] {:code ["\e[97m" "\e[39m"]
:code ["\e[97m" "\e[39m"]
:italics ["\e[4m" "\e[24m"] :italics ["\e[4m" "\e[24m"]
:bold ["\e[1m" "\e[22m"]} :bold ["\e[1m" "\e[22m"]}
{:underline ["_" "_"] {:code ["`" "`"]
:code ["`" "`"]
:italics ["*" "*"] :italics ["*" "*"]
:bold ["**" "**"]})) :bold ["**" "**"]}))
(def modes @{}) (def modes @{})
@@ -3383,7 +3394,6 @@
(= b (chr `\`)) (do (= b (chr `\`)) (do
(++ token-length) (++ token-length)
(buffer/push token (get line (++ i)))) (buffer/push token (get line (++ i))))
(= b (chr "_")) (delim :underline)
(= b (chr "*")) (= b (chr "*"))
(if (= (chr "*") (get line (+ i 1))) (if (= (chr "*") (get line (+ i 1)))
(do (++ i) (do (++ i)
@@ -3915,8 +3925,14 @@
(compwhen (dyn 'net/listen) (compwhen (dyn 'net/listen)
(defn net/server (defn net/server
"Start a server asynchronously with `net/listen` and `net/accept-loop`. Returns the new server stream." ``
Starts a server with `net/listen`. Runs `net/accept-loop` asynchronously if
`handler` is set and `type` is `:stream` (the default). It is invalid to set
`handler` if `type` is `:datagram`. Returns the new server stream.
``
[host port &opt handler type no-reuse] [host port &opt handler type no-reuse]
(assert (not (and (= type :datagram) handler))
"handler not supported for :datagram servers")
(def s (net/listen host port type no-reuse)) (def s (net/listen host port type no-reuse))
(if handler (if handler
(ev/go (fn [] (net/accept-loop s handler)))) (ev/go (fn [] (net/accept-loop s handler))))
@@ -3935,7 +3951,7 @@
[& forms] [& forms]
(def state (gensym)) (def state (gensym))
(def loaded (gensym)) (def loaded (gensym))
~((fn [] ~((fn :delay []
(var ,state nil) (var ,state nil)
(var ,loaded nil) (var ,loaded nil)
(fn [] (fn []
@@ -3967,7 +3983,7 @@
:lazy lazy :lazy lazy
:map-symbols map-symbols})) :map-symbols map-symbols}))
(defmacro ffi/defbind-alias (defmacro ffi/defbind-alias :flycheck
"Generate bindings for native functions in a convenient manner. "Generate bindings for native functions in a convenient manner.
Similar to defbind but allows for the janet function name to be Similar to defbind but allows for the janet function name to be
different than the FFI function." different than the FFI function."
@@ -3978,6 +3994,8 @@
(def formal-args (map 0 arg-pairs)) (def formal-args (map 0 arg-pairs))
(def type-args (map 1 arg-pairs)) (def type-args (map 1 arg-pairs))
(def computed-type-args (eval ~[,;type-args])) (def computed-type-args (eval ~[,;type-args]))
(if (dyn *flychecking*)
(break ~(defn ,alias ,;meta [,;formal-args] nil)))
(def {:native lib (def {:native lib
:lazy lazy :lazy lazy
:native-lazy llib :native-lazy llib
@@ -3993,7 +4011,7 @@
~(defn ,alias ,;meta [,;formal-args] ~(defn ,alias ,;meta [,;formal-args]
(,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args)))) (,ffi/call ,(make-ptr) ,(make-sig) ,;formal-args))))
(defmacro ffi/defbind (defmacro ffi/defbind :flycheck
"Generate bindings for native functions in a convenient manner." "Generate bindings for native functions in a convenient manner."
[name ret-type & body] [name ret-type & body]
~(ffi/defbind-alias ,name ,name ,ret-type ,;body))) ~(ffi/defbind-alias ,name ,name ,ret-type ,;body)))
@@ -4004,6 +4022,51 @@
### ###
### ###
(def- flycheck-specials @{})
(defn- flycheck-evaluator
``
An evaluator function that is passed to `run-context` that lints
(flychecks) code for `flycheck`. This means code will be parsed,
compiled, and have macros expanded, but the code will not be
evaluated.
``
[thunk source env where]
(when (and (tuple? source) (= (tuple/type source) :parens))
(def head (source 0))
(def entry (get env head {}))
(def fc (get flycheck-specials head (get entry :flycheck)))
(cond
# Sometimes safe form
(function? fc)
(fc thunk source env where)
# Always safe form
fc
(thunk))))
(defn flycheck
```
Check a file for errors without running the file. Found errors
will be printed to stderr in the usual format. Top level functions
and macros that have the metadata `:flycheck` will also be evaluated
during flychecking. For full control, the `:flycheck` metadata can
also be a function that takes 4 arguments - `thunk`, `source`, `env`,
and `where`, the same as the `:evaluator` argument to `run-context`.
Other arguments to `flycheck` are the same as `dofile`. Returns nil.
```
[path &keys kwargs]
(def mc @{})
(def new-env (make-env (get kwargs :env)))
(put new-env *flychecking* true)
(put new-env *module-cache* @{})
(put new-env *module-loading* @{})
(put new-env *module-make-env* (fn :make-flycheck-env [&] (make-env new-env)))
(try
(dofile path :evaluator flycheck-evaluator ;(kvs kwargs) :env new-env)
([e f]
(debug/stacktrace f e "")))
nil)
(defn- no-side-effects (defn- no-side-effects
`Check if form may have side effects. If returns true, then the src `Check if form may have side effects. If returns true, then the src
must not have side effects, such as calling a C function.` must not have side effects, such as calling a C function.`
@@ -4019,59 +4082,29 @@
(all no-side-effects (values src))) (all no-side-effects (values src)))
true)) true))
(defn- is-safe-def [x] (no-side-effects (last x))) (defn- is-safe-def [thunk source env where]
(if (no-side-effects (last source))
(thunk)))
(def- safe-forms {'defn true 'varfn true 'defn- true 'defmacro true 'defmacro- true (defn- flycheck-importer
'def is-safe-def 'var is-safe-def 'def- is-safe-def 'var- 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]
(each a args (import* (string a) :prefix "" :evaluator evaluator)))
(defn- flycheck-evaluator
``An evaluator function that is passed to `run-context` that lints (flychecks) code.
This means code will parsed and compiled, macros executed, but the code will not be run.
Used by `flycheck`.``
[thunk source env where] [thunk source env where]
(when (tuple? source) (let [[l c] (tuple/sourcemap source)
(def head (source 0)) newtup (tuple/setmap (tuple ;source :evaluator flycheck-evaluator) l c)]
(def safe-check ((compile newtup env where))))
(or
(safe-forms head)
(if (symbol? head)
(if (string/has-prefix? "define-" head) is-safe-def))))
(cond
# Sometimes safe form
(function? safe-check)
(if (safe-check source) (thunk))
# Always safe form
safe-check
(thunk)
# Use
(= 'use head)
(use-2 flycheck-evaluator (tuple/slice source 1))
# Import-like form
(importers head)
(let [[l c] (tuple/sourcemap source)
newtup (tuple/setmap (tuple ;source :evaluator flycheck-evaluator) l c)]
((compile newtup env where))))))
(defn flycheck (defn- flycheck-use
``Check a file for errors without running the file. Found errors will be printed to stderr [thunk source env where]
in the usual format. Macros will still be executed, however, so (each a (drop 1 source) (import* (string a) :prefix "" :evaluator flycheck-evaluator)))
arbitrary execution is possible. Other arguments are the same as `dofile`. `path` can also be
a file value such as stdin. Returns nil.`` # Add metadata to defs and import macros for flychecking
[path &keys kwargs] (each sym ['def 'var]
(def old-modcache (table/clone module/cache)) (put flycheck-specials sym is-safe-def))
(table/clear module/cache) (each sym ['def- 'var- 'defglobal 'varglobal]
(try (put (dyn sym) :flycheck is-safe-def))
(dofile path :evaluator flycheck-evaluator ;(kvs kwargs)) (each sym ['import 'import* 'dofile 'require]
([e f] (put (dyn sym) :flycheck flycheck-importer))
(debug/stacktrace f e ""))) (each sym ['use]
(table/clear module/cache) (put (dyn sym) :flycheck flycheck-use))
(merge-into module/cache old-modcache)
nil)
### ###
### ###
@@ -4164,7 +4197,7 @@
(spit manifest-name b)) (spit manifest-name b))
(defn bundle/manifest (defn bundle/manifest
"Get the manifest for a give installed bundle" "Get the manifest for a given installed bundle."
[bundle-name] [bundle-name]
(def name (get-manifest-filename bundle-name)) (def name (get-manifest-filename bundle-name))
(assertf (fexists name) "no bundle %v found" bundle-name) (assertf (fexists name) "no bundle %v found" bundle-name)
@@ -4189,7 +4222,9 @@
(put new-env *syspath* fixed-syspath) (put new-env *syspath* fixed-syspath)
(with-env new-env (with-env new-env
(put new-env :bundle-dir (bundle-dir bundle-name)) # get the syspath right (put new-env :bundle-dir (bundle-dir bundle-name)) # get the syspath right
(require (string "@syspath/bundle/" bundle-name))))) (try
(require (string "@syspath/bundle/" bundle-name))
([_] (error "bundle must contain bundle.janet or bundle/init.janet"))))))
(defn- do-hook (defn- do-hook
[module bundle-name hook & args] [module bundle-name hook & args]
@@ -4226,7 +4261,9 @@
nil) nil)
(defn bundle/uninstall (defn bundle/uninstall
"Remove a bundle from the current syspath" ``Remove a bundle from the current syspath. There is 1 hook called during
uninstallation (uninstall). A user can register a hook by defining a
function with the same name in the bundle script.``
[bundle-name] [bundle-name]
(def breakage @{}) (def breakage @{})
(each b (bundle/list) (each b (bundle/list)
@@ -4262,8 +4299,8 @@
order) order)
(defn bundle/prune (defn bundle/prune
"Remove all orphaned bundles from the syspath. An orphaned bundle is a bundle that is ``Remove all orphaned bundles from the current syspath. An orphaned bundle is a
marked for :auto-remove and is not depended on by any other bundle." bundle that is marked for :auto-remove and is not depended on by any other bundle.``
[] []
(def topo (bundle/topolist)) (def topo (bundle/topolist))
(def rtopo (reverse topo)) (def rtopo (reverse topo))
@@ -4292,58 +4329,67 @@
(not (not (os/stat (bundle-dir bundle-name) :mode)))) (not (not (os/stat (bundle-dir bundle-name) :mode))))
(defn bundle/install (defn bundle/install
"Install a bundle from the local filesystem. The name of the bundle will be inferred from the bundle, or passed as a parameter :name in `config`." ``Install a bundle from the local filesystem. The name of the bundle is
the value mapped to :name in either `config` or the info file. There are
5 hooks called during installation (postdeps, clean, build, install and
check). A user can register a hook by defining a function with the same name
in the bundle script.``
[path &keys config] [path &keys config]
(def path (bundle-rpath path)) (def path (bundle-rpath path))
(def clean (get config :clean))
(def check (get config :check))
(def s (sep)) (def s (sep))
# Check meta file for dependencies and default name # Detect bundle name
(def infofile-pre-1 (string path s "bundle" s "info.jdn")) (def infofile-src1 (string path s "bundle" s "info.jdn"))
(def infofile-pre (if (fexists infofile-pre-1) infofile-pre-1 (string path s "info.jdn"))) # allow for alias (def infofile-src2 (string path s "info.jdn"))
(var default-bundle-name nil) (def infofile-src (cond
(when (os/stat infofile-pre :mode) (fexists infofile-src1) infofile-src1
(def info (-> infofile-pre slurp parse)) (fexists infofile-src2) infofile-src2))
(def deps (get info :dependencies @[])) (def info (-?> infofile-src slurp parse))
(set default-bundle-name (get info :name)) (def bundle-name (get config :name (get info :name)))
(def missing (seq [d :in deps :when (not (bundle/installed? d))] (string d))) (assertf bundle-name
(when (next missing) (errorf "missing dependencies %s" (string/join missing ", ")))) "unable to infer bundle name for %v, use :name argument or add :name to info file" path)
(def bundle-name (get config :name default-bundle-name))
(assertf bundle-name "unable to infer bundle name for %v, use :name argument" path)
(assertf (not (string/check-set "\\/" bundle-name)) (assertf (not (string/check-set "\\/" bundle-name))
"bundle name %v cannot contain path separators" bundle-name) "bundle name %v cannot contain path separators" bundle-name)
(assert (next bundle-name) "cannot use empty bundle-name") (assert (next bundle-name) "cannot use empty bundle-name")
(assertf (not (fexists (get-manifest-filename bundle-name))) (assertf (not (fexists (get-manifest-filename bundle-name)))
"bundle %v is already installed" bundle-name) "bundle %v is already installed" bundle-name)
# Check bscript
(def bscript-src1 (string path s "bundle" s "init.janet"))
(def bscript-src2 (string path s "bundle.janet"))
(def bscript-src (cond
(fexists bscript-src1) bscript-src1
(fexists bscript-src2) bscript-src2))
# Setup installed paths # Setup installed paths
(prime-bundle-paths) (prime-bundle-paths)
(os/mkdir (bundle-dir bundle-name)) (os/mkdir (bundle-dir bundle-name))
# Aliases for common bundle/ files # Copy aliased infofile
(def bundle.janet (string path s "bundle.janet")) (when (fexists infofile-src2)
(when (fexists bundle.janet) (copyfile bundle.janet (bundle-file bundle-name "init.janet"))) (copyfile infofile-src2 (bundle-file bundle-name "info.jdn")))
(when (fexists infofile-pre) (copyfile infofile-pre (bundle-file bundle-name "info.jdn"))) # Copy aliased bscript
(when (fexists bscript-src2)
(copyfile bscript-src2 (bundle-file bundle-name "init.janet")))
# Copy some files into the new location unconditionally # Copy some files into the new location unconditionally
(def implicit-sources (string path s "bundle")) (def implicit-sources (string path s "bundle"))
(when (= :directory (os/stat implicit-sources :mode)) (when (= :directory (os/stat implicit-sources :mode))
(copyrf implicit-sources (bundle-dir bundle-name))) (copyrf implicit-sources (bundle-dir bundle-name)))
(def man @{:name bundle-name :local-source path :files @[]}) (def man @{:name bundle-name :local-source path :files @[]})
(merge-into man config) (merge-into man config)
(def infofile (bundle-file bundle-name "info.jdn"))
(put man :auto-remove (get config :auto-remove))
(sync-manifest man) (sync-manifest man)
(edefer (do (print "installation error, uninstalling") (bundle/uninstall bundle-name)) (edefer (do (print "installation error, uninstalling") (bundle/uninstall bundle-name))
(when (os/stat infofile :mode) (when info
(def info (-> infofile slurp parse)) (def deps (seq [d :in (get info :dependencies @[])]
(def deps (get info :dependencies @[])) (string (if (dictionary? d) (get d :name) d))))
(def missing (filter (complement bundle/installed?) deps)) (def missing (filter (complement bundle/installed?) deps))
(when (next missing) (when (next missing)
(error (string "missing dependencies " (string/join missing ", ")))) (error (string "missing dependencies " (string/join missing ", "))))
(put man :dependencies deps) (put man :dependencies deps)
(put man :info info)) (put man :info info))
(def module (get-bundle-module bundle-name)) (def module (get-bundle-module bundle-name))
(def clean (get config :clean))
(def check (get config :check))
(def all-hooks (seq [[k v] :pairs module :when (symbol? k) :unless (get v :private)] (keyword k))) (def all-hooks (seq [[k v] :pairs module :when (symbol? k) :unless (get v :private)] (keyword k)))
(put man :hooks all-hooks) (put man :hooks all-hooks)
(do-hook module bundle-name :dependencies man) (do-hook module bundle-name :dependencies man) # deprecated, use :postdeps
(do-hook module bundle-name :postdeps man)
(when clean (when clean
(do-hook module bundle-name :clean man)) (do-hook module bundle-name :clean man))
(do-hook module bundle-name :build man) (do-hook module bundle-name :build man)
@@ -4353,15 +4399,21 @@
(when check (when check
(do-hook module bundle-name :check man))) (do-hook module bundle-name :check man)))
(print "installed " bundle-name) (print "installed " bundle-name)
(when (get man :has-bin-script) (when (or (get man :has-exe)
# remove eventually
(get man :has-bin-script))
(def binpath (string (dyn *syspath*) s "bin")) (def binpath (string (dyn *syspath*) s "bin"))
(eprintf "executable scripts have been installed to %s" binpath)) (eprintf "executable files have been installed to %s" binpath))
(when (get man :has-man)
(def manpath (string (dyn *syspath*) s "man"))
(eprintf "man pages have been installed to %s" manpath))
bundle-name) bundle-name)
(defn- bundle/pack (defn- bundle/pack
"Take an installed bundle and create a bundle source directory that can be used to ``Take an installed bundle and create a bundle source directory that can be
reinstall the bundle on a compatible system. This is used to create backups for installed used to reinstall the bundle on a compatible system. This is used to create
bundles without rebuilding, or make a prebuilt bundle for other systems." backups for installed bundles without rebuilding, or make a prebuilt bundle
for other systems.``
[bundle-name dest-dir &opt is-backup] [bundle-name dest-dir &opt is-backup]
(var i 0) (var i 0)
(def man (bundle/manifest bundle-name)) (def man (bundle/manifest bundle-name))
@@ -4391,9 +4443,9 @@
dest-dir) dest-dir)
(defn bundle/replace (defn bundle/replace
"Reinstall an existing bundle from a new directory. Similar to bundle/reinstall, ``Reinstall an existing bundle from a new directory. Similar to
but installs the replacement bundle from any directory. This is necesarry to replace a package without bundle/reinstall, but installs the replacement bundle from any directory.
breaking any dependencies." This is necessary to replace a package without breaking any dependencies.``
[bundle-name path &keys new-config] [bundle-name path &keys new-config]
(def manifest (bundle/manifest bundle-name)) (def manifest (bundle/manifest bundle-name))
(def config (get manifest :config @{})) (def config (get manifest :config @{}))
@@ -4420,7 +4472,7 @@
bundle-name) bundle-name)
(defn bundle/add-directory (defn bundle/add-directory
"Add a directory during the install process relative to `(dyn *syspath*)`" "Add a directory during an install relative to `(dyn *syspath*)`."
[manifest dest &opt chmod-mode] [manifest dest &opt chmod-mode]
(def files (get-files manifest)) (def files (get-files manifest))
(def s (sep)) (def s (sep))
@@ -4448,7 +4500,7 @@
ret) ret)
(defn bundle/add-file (defn bundle/add-file
"Add files during an install relative to `(dyn *syspath*)`" "Add a file during an install relative to `(dyn *syspath*)`."
[manifest src &opt dest chmod-mode] [manifest src &opt dest chmod-mode]
(default dest src) (default dest src)
(def files (get-files manifest)) (def files (get-files manifest))
@@ -4465,9 +4517,9 @@
absdest) absdest)
(defn bundle/add (defn bundle/add
"Add files and directories during a bundle install relative to `(dyn *syspath*)`. ``Add a file or directory during an install relative to `(dyn *syspath*)`.
Added files and directories 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
and removed during an upgrade or uninstall." that they are properly tracked and removed during an upgrade or uninstall.``
[manifest src &opt dest chmod-mode] [manifest src &opt dest chmod-mode]
(default dest src) (default dest src)
(def s (sep)) (def s (sep))
@@ -4482,18 +4534,31 @@
(errorf "bad path %s - file is a %s" src mode))) (errorf "bad path %s - file is a %s" src mode)))
(defn bundle/add-bin (defn bundle/add-bin
`Shorthand for adding scripts during an install. Scripts will be installed to ``Add a file to the "bin" subdirectory of the current syspath. By default,
(string (dyn *syspath*) "/bin") by default and will be set to be executable.` files will be set to be executable.``
[manifest src &opt dest chmod-mode] [manifest src &opt filename chmod-mode]
(def s (sep)) (def s (sep))
(default dest (last (string/split s src))) (default filename (last (string/split s src)))
(default chmod-mode 8r755) (default chmod-mode 8r755)
(os/mkdir (string (dyn *syspath*) s "bin")) (os/mkdir (string (dyn *syspath*) s "bin"))
(put manifest :has-bin-script true) (put manifest :has-exe true)
(bundle/add-file manifest src (string "bin" s dest) chmod-mode)) (put manifest :has-bin-script true) # remove eventually
(bundle/add-file manifest src (string "bin" s filename) chmod-mode))
(defn bundle/add-manpage
``Add a file to the man subdirectory of the current syspath. Files are
copied inside a directory `mansec`. By default, `mansec` is "man1".``
[manifest src &opt mansec]
(def s (sep))
(default mansec "man1")
(def filename (last (string/split s src)))
(os/mkdir (string (dyn *syspath*) s "man"))
(os/mkdir (string (dyn *syspath*) s "man" s mansec))
(put manifest :has-man true)
(bundle/add-file manifest src (string "man" s mansec s filename)))
(defn bundle/update-all (defn bundle/update-all
"Reinstall all bundles" "Reinstall all bundles."
[&keys configs] [&keys configs]
(each bundle (bundle/topolist) (each bundle (bundle/topolist)
(bundle/reinstall bundle ;(kvs configs))))) (bundle/reinstall bundle ;(kvs configs)))))
@@ -4505,7 +4570,10 @@
### ###
# conditional compilation for reduced os # conditional compilation for reduced os
(def- getenv-alias (if-let [entry (in root-env 'os/getenv)] (entry :value) (fn [&]))) (def- getenv-raw (if-let [entry (in root-env 'os/getenv)] (entry :value) (fn [&])))
(defn- getenv-alias [env-var &opt dflt]
(def x (getenv-raw env-var dflt))
(if (= x "") nil x)) # empty string is coerced to nil
(defn- run-main (defn- run-main
[env subargs arg] [env subargs arg]
@@ -4628,7 +4696,7 @@
--reinstall (-B) name : Reinstall a bundle by bundle name --reinstall (-B) name : Reinstall a bundle by bundle name
--uninstall (-u) name : Uninstall a bundle by bundle name --uninstall (-u) name : Uninstall a bundle by bundle name
--update-all (-U) : Reinstall all installed bundles --update-all (-U) : Reinstall all installed bundles
--prune (-P) : Uninstalled all bundles that are orphaned --prune (-P) : Uninstall all bundles that are orphaned
--list (-L) : List all installed bundles --list (-L) : List all installed bundles
-- : Stop handling options -- : Stop handling options
```) ```)
@@ -4880,14 +4948,15 @@
"src/core/wrap.c"]) "src/core/wrap.c"])
# Print janet.c to stdout # Print janet.c to stdout
(print "/* Amalgamated build - DO NOT EDIT */") (def image-only (has-value? boot/args "image-only"))
(print "/* " (if image-only "Image-only" "Amalgamated") " build - DO NOT EDIT */")
(print "/* Generated from janet version " janet/version "-" janet/build " */") (print "/* Generated from janet version " janet/version "-" janet/build " */")
(print "#define JANET_BUILD \"" janet/build "\"") (print "#define JANET_BUILD \"" janet/build "\"")
(print ```#define JANET_AMALG```) (print ```#define JANET_AMALG```)
(defn do-one-file (defn do-one-file
[fname] [fname]
(unless (has-value? boot/args "image-only") (unless image-only
(print "\n/* " fname " */") (print "\n/* " fname " */")
(print "#line 0 \"" fname "\"\n") (print "#line 0 \"" fname "\"\n")
(def source (slurp fname)) (def source (slurp fname))

View File

@@ -4,15 +4,16 @@
#define JANETCONF_H #define JANETCONF_H
#define JANET_VERSION_MAJOR 1 #define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 38 #define JANET_VERSION_MINOR 40
#define JANET_VERSION_PATCH 0 #define JANET_VERSION_PATCH 0
#define JANET_VERSION_EXTRA "" #define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.38.0" #define JANET_VERSION "1.40.0"
/* #define JANET_BUILD "local" */ /* #define JANET_BUILD "local" */
/* These settings all affect linking, so use cautiously. */ /* These settings all affect linking, so use cautiously. */
/* #define JANET_SINGLE_THREADED */ /* #define JANET_SINGLE_THREADED */
/* #define JANET_THREAD_LOCAL _Thread_local */
/* #define JANET_NO_DYNAMIC_MODULES */ /* #define JANET_NO_DYNAMIC_MODULES */
/* #define JANET_NO_NANBOX */ /* #define JANET_NO_NANBOX */
/* #define JANET_API __attribute__((visibility ("default"))) */ /* #define JANET_API __attribute__((visibility ("default"))) */

View File

@@ -589,6 +589,16 @@ JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
#endif #endif
} }
JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x) {
#ifdef _MSC_VER
return _InterlockedOr(x, 0);
#elif defined(JANET_USE_STDATOMIC)
return atomic_load_explicit(x, memory_order_relaxed);
#else
return __atomic_load_n(x, __ATOMIC_RELAXED);
#endif
}
/* Some definitions for function-like macros */ /* Some definitions for function-like macros */
JANET_API JanetStructHead *(janet_struct_head)(JanetStruct st) { JANET_API JanetStructHead *(janet_struct_head)(JanetStruct st) {

View File

@@ -66,7 +66,7 @@ JanetModule janet_native(const char *name, const uint8_t **error) {
JanetBuildConfig modconf = getter(); JanetBuildConfig modconf = getter();
JanetBuildConfig host = janet_config_current(); JanetBuildConfig host = janet_config_current();
if (host.major != modconf.major || if (host.major != modconf.major ||
host.minor < modconf.minor || host.minor != modconf.minor ||
host.bits != modconf.bits) { host.bits != modconf.bits) {
char errbuf[128]; char errbuf[128];
snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)",
@@ -653,22 +653,15 @@ JANET_CORE_FN(janet_core_check_int,
"(int? x)", "(int? x)",
"Check if x can be exactly represented as a 32 bit signed two's complement integer.") { "Check if x can be exactly represented as a 32 bit signed two's complement integer.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false; return janet_wrap_boolean(janet_checkint(argv[0]));
double num = janet_unwrap_number(argv[0]);
return janet_wrap_boolean(num == (double)((int32_t)num));
ret_false:
return janet_wrap_false();
} }
JANET_CORE_FN(janet_core_check_nat, JANET_CORE_FN(janet_core_check_nat,
"(nat? x)", "(nat? x)",
"Check if x can be exactly represented as a non-negative 32 bit signed two's complement integer.") { "Check if x can be exactly represented as a non-negative 32 bit signed two's complement integer.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false; if (!janet_checkint(argv[0])) return janet_wrap_false();
double num = janet_unwrap_number(argv[0]); return janet_wrap_boolean(janet_unwrap_integer(argv[0]) >= 0);
return janet_wrap_boolean(num >= 0 && (num == (double)((int32_t)num)));
ret_false:
return janet_wrap_false();
} }
JANET_CORE_FN(janet_core_is_bytes, JANET_CORE_FN(janet_core_is_bytes,
@@ -753,6 +746,7 @@ typedef struct SandboxOption {
static const SandboxOption sandbox_options[] = { static const SandboxOption sandbox_options[] = {
{"all", JANET_SANDBOX_ALL}, {"all", JANET_SANDBOX_ALL},
{"chroot", JANET_SANDBOX_CHROOT},
{"env", JANET_SANDBOX_ENV}, {"env", JANET_SANDBOX_ENV},
{"ffi", JANET_SANDBOX_FFI}, {"ffi", JANET_SANDBOX_FFI},
{"ffi-define", JANET_SANDBOX_FFI_DEFINE}, {"ffi-define", JANET_SANDBOX_FFI_DEFINE},
@@ -778,6 +772,7 @@ JANET_CORE_FN(janet_core_sandbox,
"Disable feature sets to prevent the interpreter from using certain system resources. " "Disable feature sets to prevent the interpreter from using certain system resources. "
"Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n" "Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n"
"* :all - disallow all (except IO to stdout, stderr, and stdin)\n" "* :all - disallow all (except IO to stdout, stderr, and stdin)\n"
"* :chroot - disallow calling `os/posix-chroot`\n"
"* :env - disallow reading and write env variables\n" "* :env - disallow reading and write env variables\n"
"* :ffi - disallow FFI (recommended if disabling anything else)\n" "* :ffi - disallow FFI (recommended if disabling anything else)\n"
"* :ffi-define - disallow loading new FFI modules and binding new functions\n" "* :ffi-define - disallow loading new FFI modules and binding new functions\n"

View File

@@ -117,6 +117,9 @@ typedef struct {
double sec; double sec;
JanetVM *vm; JanetVM *vm;
JanetFiber *fiber; JanetFiber *fiber;
#ifdef JANET_WINDOWS
HANDLE cancel_event;
#endif
} JanetThreadedTimeout; } JanetThreadedTimeout;
#define JANET_MAX_Q_CAPACITY 0x7FFFFFF #define JANET_MAX_Q_CAPACITY 0x7FFFFFF
@@ -353,21 +356,22 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod
static void janet_stream_close_impl(JanetStream *stream) { static void janet_stream_close_impl(JanetStream *stream) {
stream->flags |= JANET_STREAM_CLOSED; stream->flags |= JANET_STREAM_CLOSED;
int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE);
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
if (stream->handle != INVALID_HANDLE_VALUE) { if (stream->handle != INVALID_HANDLE_VALUE) {
#ifdef JANET_NET #ifdef JANET_NET
if (stream->flags & JANET_STREAM_SOCKET) { if (stream->flags & JANET_STREAM_SOCKET) {
closesocket((SOCKET) stream->handle); if (canclose) closesocket((SOCKET) stream->handle);
} else } else
#endif #endif
{ {
CloseHandle(stream->handle); if (canclose) CloseHandle(stream->handle);
} }
stream->handle = INVALID_HANDLE_VALUE; stream->handle = INVALID_HANDLE_VALUE;
} }
#else #else
if (stream->handle != -1) { if (stream->handle != -1) {
close(stream->handle); if (canclose) close(stream->handle);
stream->handle = -1; stream->handle = -1;
#ifdef JANET_EV_POLL #ifdef JANET_EV_POLL
uint32_t i = stream->index; uint32_t i = stream->index;
@@ -603,8 +607,43 @@ void janet_ev_init_common(void) {
#endif #endif
} }
#if JANET_ANDROID
static void janet_timeout_stop(int sig_num) {
if (sig_num == SIGUSR1) {
pthread_exit(0);
}
}
#endif
static void handle_timeout_worker(JanetTimeout to, int cancel) {
if (!to.has_worker) return;
#ifdef JANET_WINDOWS
if (cancel && to.worker_event) {
SetEvent(to.worker_event);
}
WaitForSingleObject(to.worker, INFINITE);
CloseHandle(to.worker);
if (to.worker_event) {
CloseHandle(to.worker_event);
}
#else
#ifdef JANET_ANDROID
if (cancel) janet_assert(!pthread_kill(to.worker, SIGUSR1), "pthread_kill");
#else
if (cancel) janet_assert(!pthread_cancel(to.worker), "pthread_cancel");
#endif
void *res = NULL;
janet_assert(!pthread_join(to.worker, &res), "pthread_join");
#endif
}
/* Common deinit code */ /* Common deinit code */
void janet_ev_deinit_common(void) { void janet_ev_deinit_common(void) {
JanetTimeout to;
while (peek_timeout(&to)) {
handle_timeout_worker(to, 1);
pop_timeout(0);
}
janet_q_deinit(&janet_vm.spawn); janet_q_deinit(&janet_vm.spawn);
janet_free(janet_vm.tq); janet_free(janet_vm.tq);
janet_table_deinit(&janet_vm.threaded_abstracts); janet_table_deinit(&janet_vm.threaded_abstracts);
@@ -647,13 +686,6 @@ void janet_addtimeout_nil(double sec) {
add_timeout(to); 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) { static void janet_timeout_cb(JanetEVGenericMessage msg) {
(void) msg; (void) msg;
janet_interpreter_interrupt_handled(&janet_vm); janet_interpreter_interrupt_handled(&janet_vm);
@@ -663,8 +695,16 @@ static void janet_timeout_cb(JanetEVGenericMessage msg) {
static DWORD WINAPI janet_timeout_body(LPVOID ptr) { static DWORD WINAPI janet_timeout_body(LPVOID ptr) {
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr; JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr); janet_free(ptr);
SleepEx((DWORD)(tto.sec * 1000), TRUE); JanetTimestamp wait_begin = ts_now();
if (janet_fiber_can_resume(tto.fiber)) { DWORD duration = (DWORD)round(tto.sec * 1000);
DWORD res = WAIT_TIMEOUT;
JanetTimestamp wait_end = ts_now();
for (DWORD i = 1; res == WAIT_TIMEOUT && (wait_end - wait_begin) < duration; i++) {
res = WaitForSingleObject(tto.cancel_event, (duration + i));
wait_end = ts_now();
}
/* only send interrupt message if result is WAIT_TIMEOUT */
if (res == WAIT_TIMEOUT) {
janet_interpreter_interrupt(tto.vm); janet_interpreter_interrupt(tto.vm);
JanetEVGenericMessage msg = {0}; JanetEVGenericMessage msg = {0};
janet_ev_post_event(tto.vm, janet_timeout_cb, msg); janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
@@ -673,6 +713,14 @@ static DWORD WINAPI janet_timeout_body(LPVOID ptr) {
} }
#else #else
static void *janet_timeout_body(void *ptr) { static void *janet_timeout_body(void *ptr) {
#ifdef JANET_ANDROID
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = &janet_timeout_stop;
sigaction(SIGUSR1, &action, NULL);
#endif
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr; JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr); janet_free(ptr);
struct timespec ts; struct timespec ts;
@@ -681,11 +729,9 @@ static void *janet_timeout_body(void *ptr) {
? (long)((tto.sec - ((uint32_t)tto.sec)) * 1000000000) ? (long)((tto.sec - ((uint32_t)tto.sec)) * 1000000000)
: 0; : 0;
nanosleep(&ts, &ts); nanosleep(&ts, &ts);
if (janet_fiber_can_resume(tto.fiber)) { janet_interpreter_interrupt(tto.vm);
janet_interpreter_interrupt(tto.vm); JanetEVGenericMessage msg = {0};
JanetEVGenericMessage msg = {0}; janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
}
return NULL; return NULL;
} }
#endif #endif
@@ -805,6 +851,34 @@ static int janet_chanat_gc(void *p, size_t s) {
return 0; return 0;
} }
static void janet_chanat_remove_vmref(JanetQueue *fq) {
JanetChannelPending *pending = fq->data;
if (fq->head <= fq->tail) {
for (int32_t i = fq->head; i < fq->tail; i++) {
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
}
} else {
for (int32_t i = fq->head; i < fq->capacity; i++) {
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
}
for (int32_t i = 0; i < fq->tail; i++) {
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
}
}
}
static int janet_chanat_gcperthread(void *p, size_t s) {
(void) s;
JanetChannel *chan = p;
janet_chan_lock(chan);
/* Make sure that the internals of the threaded channel no longer reference _this_ thread. Replace
* those references with NULL. */
janet_chanat_remove_vmref(&chan->read_pending);
janet_chanat_remove_vmref(&chan->write_pending);
janet_chan_unlock(chan);
return 0;
}
static void janet_chanat_mark_fq(JanetQueue *fq) { static void janet_chanat_mark_fq(JanetQueue *fq) {
JanetChannelPending *pending = fq->data; JanetChannelPending *pending = fq->data;
if (fq->head <= fq->tail) { if (fq->head <= fq->tail) {
@@ -887,8 +961,9 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ); int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ);
if (is_read) { if (is_read) {
JanetChannelPending reader; JanetChannelPending reader;
if (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) { while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
JanetVM *vm = reader.thread; JanetVM *vm = reader.thread;
if (!vm) continue;
JanetEVGenericMessage msg; JanetEVGenericMessage msg;
msg.tag = reader.mode; msg.tag = reader.mode;
msg.fiber = reader.fiber; msg.fiber = reader.fiber;
@@ -896,11 +971,13 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
msg.argp = channel; msg.argp = channel;
msg.argj = x; msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg); janet_ev_post_event(vm, janet_thread_chan_cb, msg);
break;
} }
} else { } else {
JanetChannelPending writer; JanetChannelPending writer;
if (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) { while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
JanetVM *vm = writer.thread; JanetVM *vm = writer.thread;
if (!vm) continue;
JanetEVGenericMessage msg; JanetEVGenericMessage msg;
msg.tag = writer.mode; msg.tag = writer.mode;
msg.fiber = writer.fiber; msg.fiber = writer.fiber;
@@ -908,6 +985,7 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
msg.argp = channel; msg.argp = channel;
msg.argj = janet_wrap_nil(); msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg); janet_ev_post_event(vm, janet_thread_chan_cb, msg);
break;
} }
} }
} }
@@ -971,7 +1049,9 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
msg.argi = (int32_t) reader.sched_id; msg.argi = (int32_t) reader.sched_id;
msg.argp = channel; msg.argp = channel;
msg.argj = x; msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg); if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else { } else {
if (reader.mode == JANET_CP_MODE_CHOICE_READ) { if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
janet_schedule(reader.fiber, make_read_result(channel, x)); janet_schedule(reader.fiber, make_read_result(channel, x));
@@ -1026,7 +1106,9 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i
msg.argi = (int32_t) writer.sched_id; msg.argi = (int32_t) writer.sched_id;
msg.argp = channel; msg.argp = channel;
msg.argj = janet_wrap_nil(); msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg); if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else { } else {
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) { if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
janet_schedule(writer.fiber, make_write_result(channel)); janet_schedule(writer.fiber, make_write_result(channel));
@@ -1290,7 +1372,9 @@ JANET_CORE_FN(cfun_channel_close,
msg.tag = JANET_CP_MODE_CLOSE; msg.tag = JANET_CP_MODE_CLOSE;
msg.argi = (int32_t) writer.sched_id; msg.argi = (int32_t) writer.sched_id;
msg.argj = janet_wrap_nil(); msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg); if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else { } else {
if (janet_fiber_can_resume(writer.fiber)) { if (janet_fiber_can_resume(writer.fiber)) {
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) { if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
@@ -1311,7 +1395,9 @@ JANET_CORE_FN(cfun_channel_close,
msg.tag = JANET_CP_MODE_CLOSE; msg.tag = JANET_CP_MODE_CLOSE;
msg.argi = (int32_t) reader.sched_id; msg.argi = (int32_t) reader.sched_id;
msg.argj = janet_wrap_nil(); msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg); if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else { } else {
if (janet_fiber_can_resume(reader.fiber)) { if (janet_fiber_can_resume(reader.fiber)) {
if (reader.mode == JANET_CP_MODE_CHOICE_READ) { if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
@@ -1404,7 +1490,10 @@ const JanetAbstractType janet_channel_type = {
NULL, /* compare */ NULL, /* compare */
NULL, /* hash */ NULL, /* hash */
janet_chanat_next, janet_chanat_next,
JANET_ATEND_NEXT NULL, /* call */
NULL, /* length */
NULL, /* bytes */
janet_chanat_gcperthread
}; };
/* Main event loop */ /* Main event loop */
@@ -1437,12 +1526,13 @@ JanetFiber *janet_loop1(void) {
} }
} }
} }
handle_timeout_worker(to, 0);
} }
/* Run scheduled fibers unless interrupts need to be handled. */ /* Run scheduled fibers unless interrupts need to be handled. */
while (janet_vm.spawn.head != janet_vm.spawn.tail) { while (janet_vm.spawn.head != janet_vm.spawn.tail) {
/* Don't run until all interrupts have been marked as handled by calling janet_interpreter_interrupt_handled */ /* Don't run until all interrupts have been marked as handled by calling janet_interpreter_interrupt_handled */
if (janet_vm.auto_suspend) break; if (janet_atomic_load_relaxed(&janet_vm.auto_suspend)) break;
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0}; JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0};
janet_q_pop(&janet_vm.spawn, &task, sizeof(task)); janet_q_pop(&janet_vm.spawn, &task, sizeof(task));
if (task.fiber->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) janet_ev_dec_refcount(); if (task.fiber->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) janet_ev_dec_refcount();
@@ -1484,23 +1574,14 @@ JanetFiber *janet_loop1(void) {
while ((has_timeout = peek_timeout(&to))) { while ((has_timeout = peek_timeout(&to))) {
if (to.curr_fiber != NULL) { if (to.curr_fiber != NULL) {
if (!janet_fiber_can_resume(to.curr_fiber)) { 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); pop_timeout(0);
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
handle_timeout_worker(to, 1);
continue; continue;
} }
} else if (to.fiber->sched_id != to.sched_id) { } else if (to.fiber->sched_id != to.sched_id) {
pop_timeout(0); pop_timeout(0);
handle_timeout_worker(to, 1);
continue; continue;
} }
break; break;
@@ -1665,7 +1746,7 @@ void janet_stream_level_triggered(JanetStream *stream) {
static JanetTimestamp ts_now(void) { static JanetTimestamp ts_now(void) {
struct timespec now; struct timespec now;
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time"); janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
uint64_t res = 1000 * now.tv_sec; uint64_t res = 1000 * now.tv_sec;
res += now.tv_nsec / 1000000; res += now.tv_nsec / 1000000;
return res; return res;
@@ -1823,7 +1904,7 @@ JanetTimestamp to_interval(const JanetTimestamp ts) {
static JanetTimestamp ts_now(void) { static JanetTimestamp ts_now(void) {
struct timespec now; struct timespec now;
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time"); janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
uint64_t res = 1000 * now.tv_sec; uint64_t res = 1000 * now.tv_sec;
res += now.tv_nsec / 1000000; res += now.tv_nsec / 1000000;
return res; return res;
@@ -1977,7 +2058,7 @@ void janet_ev_deinit(void) {
static JanetTimestamp ts_now(void) { static JanetTimestamp ts_now(void) {
struct timespec now; struct timespec now;
janet_assert(-1 != clock_gettime(CLOCK_REALTIME, &now), "failed to get time"); janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
uint64_t res = 1000 * now.tv_sec; uint64_t res = 1000 * now.tv_sec;
res += now.tv_nsec / 1000000; res += now.tv_nsec / 1000000;
return res; return res;
@@ -2149,7 +2230,7 @@ void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGenericMessage ms
event.cb = cb; event.cb = cb;
int fd = vm->selfpipe[1]; int fd = vm->selfpipe[1];
/* handle a bit of back pressure before giving up. */ /* handle a bit of back pressure before giving up. */
int tries = 4; int tries = 20;
while (tries > 0) { while (tries > 0) {
int status; int status;
do { do {
@@ -2996,7 +3077,8 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
uint32_t count1; uint32_t count1;
memcpy(&count1, nextbytes, sizeof(count1)); memcpy(&count1, nextbytes, sizeof(count1));
size_t count = (size_t) count1; size_t count = (size_t) count1;
if (count > (endbytes - nextbytes) * sizeof(JanetCFunRegistry)) { /* Use division to avoid overflowing size_t */
if (count > (endbytes - nextbytes - sizeof(count1)) / sizeof(JanetCFunRegistry)) {
janet_panic("thread message invalid"); janet_panic("thread message invalid");
} }
janet_vm.registry_count = count; janet_vm.registry_count = count;
@@ -3154,6 +3236,7 @@ JANET_NO_RETURN void janet_sleep_await(double sec) {
to.is_error = 0; to.is_error = 0;
to.sched_id = to.fiber->sched_id; to.sched_id = to.fiber->sched_id;
to.curr_fiber = NULL; to.curr_fiber = NULL;
to.has_worker = 0;
add_timeout(to); add_timeout(to);
janet_await(); janet_await();
} }
@@ -3188,6 +3271,9 @@ JANET_CORE_FN(cfun_ev_deadline,
to.is_error = 0; to.is_error = 0;
to.sched_id = to.fiber->sched_id; to.sched_id = to.fiber->sched_id;
if (use_interrupt) { if (use_interrupt) {
#ifdef JANET_ANDROID
janet_sandbox_assert(JANET_SANDBOX_SIGNAL);
#endif
JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout)); JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout));
if (NULL == tto) { if (NULL == tto) {
JANET_OUT_OF_MEMORY; JANET_OUT_OF_MEMORY;
@@ -3196,7 +3282,13 @@ JANET_CORE_FN(cfun_ev_deadline,
tto->vm = &janet_vm; tto->vm = &janet_vm;
tto->fiber = tocheck; tto->fiber = tocheck;
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
HANDLE worker = CreateThread(NULL, 0, janet_timeout_body, tto, 0, NULL); HANDLE cancel_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == cancel_event) {
janet_free(tto);
janet_panic("failed to create cancel event");
}
tto->cancel_event = cancel_event;
HANDLE worker = CreateThread(NULL, 0, janet_timeout_body, tto, CREATE_SUSPENDED, NULL);
if (NULL == worker) { if (NULL == worker) {
janet_free(tto); janet_free(tto);
janet_panic("failed to create thread"); janet_panic("failed to create thread");
@@ -3211,6 +3303,10 @@ JANET_CORE_FN(cfun_ev_deadline,
#endif #endif
to.has_worker = 1; to.has_worker = 1;
to.worker = worker; to.worker = worker;
#ifdef JANET_WINDOWS
to.worker_event = cancel_event;
ResumeThread(worker);
#endif
} else { } else {
to.has_worker = 0; to.has_worker = 0;
} }
@@ -3505,8 +3601,6 @@ void janet_lib_ev(JanetTable *env) {
janet_register_abstract_type(&janet_channel_type); janet_register_abstract_type(&janet_channel_type);
janet_register_abstract_type(&janet_mutex_type); janet_register_abstract_type(&janet_mutex_type);
janet_register_abstract_type(&janet_rwlock_type); janet_register_abstract_type(&janet_rwlock_type);
janet_lib_filewatch(env);
} }
#endif #endif

View File

@@ -599,33 +599,33 @@ JANET_CORE_FN(cfun_filewatch_make,
JANET_CORE_FN(cfun_filewatch_add, JANET_CORE_FN(cfun_filewatch_add,
"(filewatch/add watcher path &opt flags)", "(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" "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" "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" "* `:all` - trigger an event for all of the below triggers.\n\n"
"* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n" "* `:attributes` - `FILE_NOTIFY_CHANGE_ATTRIBUTES`\n\n"
"* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n" "* `:creation` - `FILE_NOTIFY_CHANGE_CREATION`\n\n"
"* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n" "* `:dir-name` - `FILE_NOTIFY_CHANGE_DIR_NAME`\n\n"
"* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n" "* `:last-access` - `FILE_NOTIFY_CHANGE_LAST_ACCESS`\n\n"
"* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n" "* `:last-write` - `FILE_NOTIFY_CHANGE_LAST_WRITE`\n\n"
"* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n" "* `:security` - `FILE_NOTIFY_CHANGE_SECURITY`\n\n"
"* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n" "* `:size` - `FILE_NOTIFY_CHANGE_SIZE`\n\n"
"* `:recursive` - watch subdirectories recursively\n\n" "* `:recursive` - watch subdirectories recursively\n\n"
"Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n" "Linux (flags correspond to `IN_*` flags from <sys/inotify.h>):\n\n"
"* `:access` - IN_ACCESS\n\n" "* `:access` - `IN_ACCESS`\n\n"
"* `:all` - IN_ALL_EVENTS\n\n" "* `:all` - `IN_ALL_EVENTS`\n\n"
"* `:attrib` - IN_ATTRIB\n\n" "* `:attrib` - `IN_ATTRIB`\n\n"
"* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n" "* `:close-nowrite` - `IN_CLOSE_NOWRITE`\n\n"
"* `:close-write` - IN_CLOSE_WRITE\n\n" "* `:close-write` - `IN_CLOSE_WRITE`\n\n"
"* `:create` - IN_CREATE\n\n" "* `:create` - `IN_CREATE`\n\n"
"* `:delete` - IN_DELETE\n\n" "* `:delete` - `IN_DELETE`\n\n"
"* `:delete-self` - IN_DELETE_SELF\n\n" "* `:delete-self` - `IN_DELETE_SELF`\n\n"
"* `:ignored` - IN_IGNORED\n\n" "* `:ignored` - `IN_IGNORED`\n\n"
"* `:modify` - IN_MODIFY\n\n" "* `:modify` - `IN_MODIFY`\n\n"
"* `:move-self` - IN_MOVE_SELF\n\n" "* `:move-self` - `IN_MOVE_SELF`\n\n"
"* `:moved-from` - IN_MOVED_FROM\n\n" "* `:moved-from` - `IN_MOVED_FROM`\n\n"
"* `:moved-to` - IN_MOVED_TO\n\n" "* `:moved-to` - `IN_MOVED_TO`\n\n"
"* `:open` - IN_OPEN\n\n" "* `:open` - `IN_OPEN`\n\n"
"* `:q-overflow` - IN_Q_OVERFLOW\n\n" "* `:q-overflow` - `IN_Q_OVERFLOW`\n\n"
"* `:unmount` - IN_UNMOUNT\n\n\n" "* `:unmount` - `IN_UNMOUNT`\n\n\n"
"On Windows, events will have the following possible types:\n\n" "On Windows, events will have the following possible types:\n\n"
"* `:unknown`\n\n" "* `:unknown`\n\n"
"* `:added`\n\n" "* `:added`\n\n"
@@ -633,7 +633,7 @@ JANET_CORE_FN(cfun_filewatch_add,
"* `:modified`\n\n" "* `:modified`\n\n"
"* `:renamed-old`\n\n" "* `:renamed-old`\n\n"
"* `:renamed-new`\n\n" "* `:renamed-new`\n\n"
"On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n" "On Linux, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n"
"") { "") {
janet_arity(argc, 2, -1); janet_arity(argc, 2, -1);
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);

View File

@@ -346,6 +346,9 @@ static void janet_deinit_block(JanetGCObject *mem) {
break; break;
case JANET_MEMORY_ABSTRACT: { case JANET_MEMORY_ABSTRACT: {
JanetAbstractHead *head = (JanetAbstractHead *)mem; JanetAbstractHead *head = (JanetAbstractHead *)mem;
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
if (head->type->gc) { if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
} }
@@ -497,9 +500,12 @@ void janet_sweep() {
/* If not visited... */ /* If not visited... */
if (!janet_truthy(items[i].value)) { if (!janet_truthy(items[i].value)) {
void *abst = janet_unwrap_abstract(items[i].key); void *abst = janet_unwrap_abstract(items[i].key);
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
if (0 == janet_abstract_decref(abst)) { if (0 == janet_abstract_decref(abst)) {
/* Run finalizer */ /* Run finalizer */
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) { if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
} }
@@ -672,8 +678,11 @@ void janet_clear_memory(void) {
for (int32_t i = 0; i < janet_vm.threaded_abstracts.capacity; i++) { for (int32_t i = 0; i < janet_vm.threaded_abstracts.capacity; i++) {
if (janet_checktype(items[i].key, JANET_ABSTRACT)) { if (janet_checktype(items[i].key, JANET_ABSTRACT)) {
void *abst = janet_unwrap_abstract(items[i].key); void *abst = janet_unwrap_abstract(items[i].key);
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
if (0 == janet_abstract_decref(abst)) { if (0 == janet_abstract_decref(abst)) {
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) { if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
} }

View File

@@ -341,7 +341,7 @@ static int janet_get_sockettype(Janet *argv, int32_t argc, int32_t n) {
/* Needs argc >= offset + 2 */ /* Needs argc >= offset + 2 */
/* For unix paths, just rertuns a single sockaddr and sets *is_unix to 1, /* For unix paths, just rertuns a single sockaddr and sets *is_unix to 1,
* otherwise 0. Also, ignores is_bind when is a unix socket. */ * otherwise 0. Also, ignores is_bind when is a unix socket. */
static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix) { static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix, socklen_t *sizeout) {
/* Unix socket support - not yet supported on windows. */ /* Unix socket support - not yet supported on windows. */
#ifndef JANET_WINDOWS #ifndef JANET_WINDOWS
if (janet_keyeq(argv[offset], "unix")) { if (janet_keyeq(argv[offset], "unix")) {
@@ -352,15 +352,14 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
} }
saddr->sun_family = AF_UNIX; saddr->sun_family = AF_UNIX;
size_t path_size = sizeof(saddr->sun_path); size_t path_size = sizeof(saddr->sun_path);
snprintf(saddr->sun_path, path_size, "%s", path);
*sizeout = sizeof(struct sockaddr_un);
#ifdef JANET_LINUX #ifdef JANET_LINUX
if (path[0] == '@') { if (path[0] == '@') {
saddr->sun_path[0] = '\0'; saddr->sun_path[0] = '\0';
snprintf(saddr->sun_path + 1, path_size - 1, "%s", path + 1); *sizeout = offsetof(struct sockaddr_un, sun_path) + janet_string_length(path);
} else
#endif
{
snprintf(saddr->sun_path, path_size, "%s", path);
} }
#endif
*is_unix = 1; *is_unix = 1;
return (struct addrinfo *) saddr; return (struct addrinfo *) saddr;
} }
@@ -385,6 +384,11 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
janet_panicf("could not get address info: %s", gai_strerror(status)); janet_panicf("could not get address info: %s", gai_strerror(status));
} }
*is_unix = 0; *is_unix = 0;
#ifdef JANET_WINDOWS
*sizeout = 0;
#else
*sizeout = sizeof(struct sockaddr_un);
#endif
return ai; return ai;
} }
@@ -405,12 +409,13 @@ JANET_CORE_FN(cfun_net_sockaddr,
int socktype = janet_get_sockettype(argv, argc, 2); int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0; int is_unix = 0;
int make_arr = (argc >= 3 && janet_truthy(argv[3])); int make_arr = (argc >= 3 && janet_truthy(argv[3]));
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix); socklen_t addrsize = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix, &addrsize);
#ifndef JANET_WINDOWS #ifndef JANET_WINDOWS
/* no unix domain socket support on windows yet */ /* no unix domain socket support on windows yet */
if (is_unix) { if (is_unix) {
void *abst = janet_abstract(&janet_address_type, sizeof(struct sockaddr_un)); void *abst = janet_abstract(&janet_address_type, addrsize);
memcpy(abst, ai, sizeof(struct sockaddr_un)); memcpy(abst, ai, addrsize);
Janet ret = janet_wrap_abstract(abst); Janet ret = janet_wrap_abstract(abst);
return make_arr ? janet_wrap_array(janet_array_n(&ret, 1)) : ret; return make_arr ? janet_wrap_array(janet_array_n(&ret, 1)) : ret;
} }
@@ -461,7 +466,8 @@ JANET_CORE_FN(cfun_net_connect,
} }
/* Where we're connecting to */ /* Where we're connecting to */
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix); socklen_t addrlen = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix, &addrlen);
/* Check if we're binding address */ /* Check if we're binding address */
struct addrinfo *binding = NULL; struct addrinfo *binding = NULL;
@@ -486,7 +492,6 @@ JANET_CORE_FN(cfun_net_connect,
/* Create socket */ /* Create socket */
JSock sock = JSOCKDEFAULT; JSock sock = JSOCKDEFAULT;
void *addr = NULL; void *addr = NULL;
socklen_t addrlen = 0;
#ifndef JANET_WINDOWS #ifndef JANET_WINDOWS
if (is_unix) { if (is_unix) {
sock = socket(AF_UNIX, socktype | JSOCKFLAGS, 0); sock = socket(AF_UNIX, socktype | JSOCKFLAGS, 0);
@@ -496,7 +501,6 @@ JANET_CORE_FN(cfun_net_connect,
janet_panicf("could not create socket: %V", v); janet_panicf("could not create socket: %V", v);
} }
addr = (void *) ai; addr = (void *) ai;
addrlen = sizeof(struct sockaddr_un);
} else } else
#endif #endif
{ {
@@ -543,7 +547,9 @@ JANET_CORE_FN(cfun_net_connect,
} }
/* Wrap socket in abstract type JanetStream */ /* Wrap socket in abstract type JanetStream */
JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); uint32_t udp_flag = 0;
if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER;
JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag);
/* Set up the socket for non-blocking IO before connecting */ /* Set up the socket for non-blocking IO before connecting */
janet_net_socknoblock(sock); janet_net_socknoblock(sock);
@@ -581,6 +587,56 @@ JANET_CORE_FN(cfun_net_connect,
net_sched_connect(stream); net_sched_connect(stream);
} }
JANET_CORE_FN(cfun_net_socket,
"(net/socket &opt type)",
"Creates a new unbound socket. Type is an optional keyword, "
"either a :stream (usually tcp), or :datagram (usually udp). The default is :stream.") {
janet_arity(argc, 0, 1);
int socktype = janet_get_sockettype(argv, argc, 0);
/* Create socket */
JSock sfd = JSOCKDEFAULT;
struct addrinfo *ai = NULL;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = socktype;
hints.ai_flags = 0;
int status = getaddrinfo(NULL, "0", &hints, &ai);
if (status) {
janet_panicf("could not get address info: %s", gai_strerror(status));
}
struct addrinfo *rp = NULL;
for (rp = ai; rp != NULL; rp = rp->ai_next) {
#ifdef JANET_WINDOWS
sfd = WSASocketW(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
#else
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
#endif
if (JSOCKVALID(sfd)) {
break;
}
}
freeaddrinfo(ai);
if (!JSOCKVALID(sfd)) {
Janet v = janet_ev_lasterr();
janet_panicf("could not create socket: %V", v);
}
/* Wrap socket in abstract type JanetStream */
uint32_t udp_flag = 0;
if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER;
JanetStream *stream = make_stream(sfd, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag);
/* Set up the socket for non-blocking IO */
janet_net_socknoblock(sfd);
return janet_wrap_abstract(stream);
}
static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) { static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) {
/* Set various socket options */ /* Set various socket options */
int enable = 1; int enable = 1;
@@ -657,14 +713,15 @@ JANET_CORE_FN(cfun_net_listen,
"The type parameter specifies the type of network connection, either " "The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is " "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. The last boolean parameter `no-reuse` will " ":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.") { "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_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
janet_arity(argc, 2, 4); janet_arity(argc, 2, 4);
/* Get host, port, and handler*/ /* Get host, port, and handler*/
int socktype = janet_get_sockettype(argv, argc, 2); int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0; int is_unix = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix); socklen_t addrlen = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix, &addrlen);
int reuse = !(argc >= 4 && janet_truthy(argv[3])); int reuse = !(argc >= 4 && janet_truthy(argv[3]));
JSock sfd = JSOCKDEFAULT; JSock sfd = JSOCKDEFAULT;
@@ -676,7 +733,7 @@ JANET_CORE_FN(cfun_net_listen,
janet_panicf("could not create socket: %V", janet_ev_lasterr()); janet_panicf("could not create socket: %V", janet_ev_lasterr());
} }
const char *err = serverify_socket(sfd, reuse, 0); const char *err = serverify_socket(sfd, reuse, 0);
if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) { if (NULL != err || bind(sfd, (struct sockaddr *)ai, addrlen)) {
JSOCKCLOSE(sfd); JSOCKCLOSE(sfd);
janet_free(ai); janet_free(ai);
if (err) { if (err) {
@@ -1078,6 +1135,7 @@ void janet_lib_net(JanetTable *env) {
JanetRegExt net_cfuns[] = { JanetRegExt net_cfuns[] = {
JANET_CORE_REG("net/address", cfun_net_sockaddr), JANET_CORE_REG("net/address", cfun_net_sockaddr),
JANET_CORE_REG("net/listen", cfun_net_listen), JANET_CORE_REG("net/listen", cfun_net_listen),
JANET_CORE_REG("net/socket", cfun_net_socket),
JANET_CORE_REG("net/accept", cfun_stream_accept), JANET_CORE_REG("net/accept", cfun_stream_accept),
JANET_CORE_REG("net/accept-loop", cfun_stream_accept_loop), JANET_CORE_REG("net/accept-loop", cfun_stream_accept_loop),
JANET_CORE_REG("net/read", cfun_stream_read), JANET_CORE_REG("net/read", cfun_stream_read),

View File

@@ -55,6 +55,7 @@
#include <sys/utime.h> #include <sys/utime.h>
#include <io.h> #include <io.h>
#include <process.h> #include <process.h>
#define JANET_SPAWN_CHDIR
#else #else
#include <spawn.h> #include <spawn.h>
#include <utime.h> #include <utime.h>
@@ -65,6 +66,8 @@
#ifdef JANET_APPLE #ifdef JANET_APPLE
#include <crt_externs.h> #include <crt_externs.h>
#define environ (*_NSGetEnviron()) #define environ (*_NSGetEnviron())
#include <AvailabilityMacros.h>
int chroot(const char *dirname);
#else #else
extern char **environ; extern char **environ;
#endif #endif
@@ -73,6 +76,26 @@ extern char **environ;
#endif #endif
#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)
/* The posix_spawn_file_actions_addchdir_np function
* has only been implemented since macOS 10.15 */
#if defined(MAC_OS_X_VERSION_10_15) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15)
#define JANET_SPAWN_CHDIR
#else
#define JANET_SPAWN_NO_CHDIR
#endif
#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. */ /* Not POSIX, but all Unixes but Solaris have this function. */
#if defined(JANET_POSIX) && !defined(__sun) #if defined(JANET_POSIX) && !defined(__sun)
time_t timegm(struct tm *tm); time_t timegm(struct tm *tm);
@@ -158,6 +181,8 @@ JANET_CORE_FN(os_which,
return janet_ckeywordv("dragonfly"); return janet_ckeywordv("dragonfly");
#elif defined(JANET_BSD) #elif defined(JANET_BSD)
return janet_ckeywordv("bsd"); return janet_ckeywordv("bsd");
#elif defined(JANET_ILLUMOS)
return janet_ckeywordv("illumos");
#else #else
return janet_ckeywordv("posix"); return janet_ckeywordv("posix");
#endif #endif
@@ -297,6 +322,13 @@ JANET_CORE_FN(os_cpu_count,
return dflt; return dflt;
} }
return janet_wrap_integer(result); return janet_wrap_integer(result);
#elif defined(JANET_ILLUMOS)
(void) dflt;
long result = sysconf(_SC_NPROCESSORS_CONF);
if (result < 0) {
return dflt;
}
return janet_wrap_integer(result);
#else #else
return dflt; return dflt;
#endif #endif
@@ -1182,6 +1214,21 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} }
} }
/* 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 */ /* Clean up if any of the pipes have any issues */
if (pipe_errflag) { if (pipe_errflag) {
if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in); if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in);
@@ -1196,6 +1243,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
SECURITY_ATTRIBUTES saAttr; SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION processInfo; PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo; STARTUPINFO startupInfo;
LPCSTR lpCurrentDirectory = NULL;
memset(&saAttr, 0, sizeof(saAttr)); memset(&saAttr, 0, sizeof(saAttr));
memset(&processInfo, 0, sizeof(processInfo)); memset(&processInfo, 0, sizeof(processInfo));
memset(&startupInfo, 0, sizeof(startupInfo)); memset(&startupInfo, 0, sizeof(startupInfo));
@@ -1212,6 +1260,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} }
const char *path = (const char *) janet_unwrap_string(exargs.items[0]); const char *path = (const char *) janet_unwrap_string(exargs.items[0]);
if (chdir_path != NULL) {
lpCurrentDirectory = chdir_path;
}
/* Do IO redirection */ /* Do IO redirection */
if (pipe_in != JANET_HANDLE_NONE) { if (pipe_in != JANET_HANDLE_NONE) {
@@ -1219,7 +1271,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_in != JANET_HANDLE_NONE) { } else if (new_in != JANET_HANDLE_NONE) {
startupInfo.hStdInput = new_in; startupInfo.hStdInput = new_in;
} else { } else {
startupInfo.hStdInput = (HANDLE) _get_osfhandle(0); startupInfo.hStdInput = (HANDLE) _get_osfhandle(_fileno(stdin));
} }
if (pipe_out != JANET_HANDLE_NONE) { if (pipe_out != JANET_HANDLE_NONE) {
@@ -1227,7 +1279,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_out != JANET_HANDLE_NONE) { } else if (new_out != JANET_HANDLE_NONE) {
startupInfo.hStdOutput = new_out; startupInfo.hStdOutput = new_out;
} else { } else {
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(1); startupInfo.hStdOutput = (HANDLE) _get_osfhandle(_fileno(stdout));
} }
if (pipe_err != JANET_HANDLE_NONE) { if (pipe_err != JANET_HANDLE_NONE) {
@@ -1237,7 +1289,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (stderr_is_stdout) { } else if (stderr_is_stdout) {
startupInfo.hStdError = startupInfo.hStdOutput; startupInfo.hStdError = startupInfo.hStdOutput;
} else { } else {
startupInfo.hStdError = (HANDLE) _get_osfhandle(2); startupInfo.hStdError = (HANDLE) _get_osfhandle(_fileno(stderr));
} }
int cp_failed = 0; int cp_failed = 0;
@@ -1248,7 +1300,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
TRUE, /* handle inheritance */ TRUE, /* handle inheritance */
0, /* flags */ 0, /* flags */
use_environ ? NULL : envp, /* pass in environment */ use_environ ? NULL : envp, /* pass in environment */
NULL, /* use parents starting directory */ lpCurrentDirectory,
&startupInfo, &startupInfo,
&processInfo)) { &processInfo)) {
cp_failed = 1; cp_failed = 1;
@@ -1305,6 +1357,15 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* Posix spawn setup */ /* Posix spawn setup */
posix_spawn_file_actions_t actions; posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&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) { if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0); posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
posix_spawn_file_actions_addclose(&actions, pipe_in); posix_spawn_file_actions_addclose(&actions, pipe_in);
@@ -1481,6 +1542,28 @@ JANET_CORE_FN(os_posix_fork,
#endif #endif
} }
JANET_CORE_FN(os_posix_chroot,
"(os/posix-chroot dirname)",
"Call `chroot` to change the root directory to `dirname`. "
"Not supported on all systems (POSIX only).") {
janet_sandbox_assert(JANET_SANDBOX_CHROOT);
janet_fixarity(argc, 1);
#ifdef JANET_WINDOWS
(void) argv;
janet_panic("not supported on Windows");
#else
const char *root = janet_getcstring(argv, 0);
int result;
do {
result = chroot(root);
} while (result == -1 && errno == EINTR);
if (result == -1) {
janet_panic(janet_strerror(errno));
}
return janet_wrap_nil();
#endif
}
#ifdef JANET_EV #ifdef JANET_EV
/* Runs in a separate thread */ /* Runs in a separate thread */
static JanetEVGenericMessage os_shell_subr(JanetEVGenericMessage args) { static JanetEVGenericMessage os_shell_subr(JanetEVGenericMessage args) {
@@ -2789,6 +2872,7 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/touch", os_touch), JANET_CORE_REG("os/touch", os_touch),
JANET_CORE_REG("os/realpath", os_realpath), JANET_CORE_REG("os/realpath", os_realpath),
JANET_CORE_REG("os/cd", os_cd), JANET_CORE_REG("os/cd", os_cd),
JANET_CORE_REG("os/posix-chroot", os_posix_chroot),
#ifndef JANET_NO_UMASK #ifndef JANET_NO_UMASK
JANET_CORE_REG("os/umask", os_umask), JANET_CORE_REG("os/umask", os_umask),
#endif #endif
@@ -2819,6 +2903,9 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/proc-kill", os_proc_kill), JANET_CORE_REG("os/proc-kill", os_proc_kill),
JANET_CORE_REG("os/proc-close", os_proc_close), JANET_CORE_REG("os/proc-close", os_proc_close),
JANET_CORE_REG("os/getpid", os_proc_getpid), JANET_CORE_REG("os/getpid", os_proc_getpid),
#ifdef JANET_EV
JANET_CORE_REG("os/sigaction", os_sigaction),
#endif
#endif #endif
/* high resolution timers */ /* high resolution timers */
@@ -2827,7 +2914,6 @@ void janet_lib_os(JanetTable *env) {
#ifdef JANET_EV #ifdef JANET_EV
JANET_CORE_REG("os/open", os_open), /* fs read and write */ JANET_CORE_REG("os/open", os_open), /* fs read and write */
JANET_CORE_REG("os/pipe", os_pipe), JANET_CORE_REG("os/pipe", os_pipe),
JANET_CORE_REG("os/sigaction", os_sigaction),
#endif #endif
#endif #endif
JANET_REG_END JANET_REG_END

View File

@@ -1060,19 +1060,11 @@ void janet_buffer_format(
break; break;
} }
case 's': { case 's': {
JanetByteView bytes = janet_getbytes(argv, arg); const char *s = janet_getcbytes(argv, arg);
const uint8_t *s = bytes.bytes;
int32_t l = bytes.len;
if (form[2] == '\0') if (form[2] == '\0')
janet_buffer_push_bytes(b, s, l); janet_buffer_push_cstring(b, s);
else { else {
if (l != (int32_t) strlen((const char *) s)) nb = snprintf(item, MAX_ITEM, form, s);
janet_panic("string contains zeros");
if (!strchr(form, '.') && l >= 100) {
janet_panic("no precision and string is too long to be formatted");
} else {
nb = snprintf(item, MAX_ITEM, form, s);
}
} }
break; break;
} }

View File

@@ -26,7 +26,8 @@
#include "state.h" #include "state.h"
#endif #endif
/* Run a string */ /* Run a string of code. The return value is a set of error flags, JANET_DO_ERROR_RUNTIME, JANET_DO_ERROR_COMPILE, and JANET_DOR_ERROR_PARSE if
* any errors were encountered in those phases. More information is printed to stderr. */
int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) { 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; int errflags = 0, done = 0;
@@ -55,11 +56,10 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
JanetSignal status = janet_continue(fiber, janet_wrap_nil(), &ret); JanetSignal status = janet_continue(fiber, janet_wrap_nil(), &ret);
if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) { if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) {
janet_stacktrace_ext(fiber, ret, ""); janet_stacktrace_ext(fiber, ret, "");
errflags |= 0x01; errflags |= JANET_DO_ERROR_RUNTIME;
done = 1; done = 1;
} }
} else { } else {
ret = janet_wrap_string(cres.error);
int32_t line = (int32_t) parser->line; int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser->column; int32_t col = (int32_t) parser->column;
if ((cres.error_mapping.line > 0) && if ((cres.error_mapping.line > 0) &&
@@ -67,15 +67,19 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
line = cres.error_mapping.line; line = cres.error_mapping.line;
col = cres.error_mapping.column; col = cres.error_mapping.column;
} }
JanetString ctx = janet_formatc("%s:%d:%d: compile error",
sourcePath, line, col);
JanetString errstr = janet_formatc("%s: %s",
(const char *)ctx,
(const char *)cres.error);
ret = janet_wrap_string(errstr);
if (cres.macrofiber) { if (cres.macrofiber) {
janet_eprintf("%s:%d:%d: compile error", sourcePath, janet_eprintf("%s", (const char *)ctx);
line, col);
janet_stacktrace_ext(cres.macrofiber, ret, ""); janet_stacktrace_ext(cres.macrofiber, ret, "");
} else { } else {
janet_eprintf("%s:%d:%d: compile error: %s\n", sourcePath, janet_eprintf("%s\n", (const char *)errstr);
line, col, (const char *)cres.error);
} }
errflags |= 0x02; errflags |= JANET_DO_ERROR_COMPILE;
done = 1; done = 1;
} }
} }
@@ -88,12 +92,14 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
done = 1; done = 1;
break; break;
case JANET_PARSE_ERROR: { case JANET_PARSE_ERROR: {
const char *e = janet_parser_error(parser); errflags |= JANET_DO_ERROR_PARSE;
errflags |= 0x04;
ret = janet_cstringv(e);
int32_t line = (int32_t) parser->line; int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser->column; int32_t col = (int32_t) parser->column;
janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e); JanetString errstr = janet_formatc("%s:%d:%d: parse error: %s",
sourcePath, line, col,
janet_parser_error(parser));
ret = janet_wrap_string(errstr);
janet_eprintf("%s\n", (const char *)errstr);
done = 1; done = 1;
break; break;
} }
@@ -121,7 +127,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
janet_loop(); janet_loop();
if (fiber) { if (fiber) {
janet_gcunroot(janet_wrap_fiber(fiber)); janet_gcunroot(janet_wrap_fiber(fiber));
ret = fiber->last_value; if (!errflags)
ret = fiber->last_value;
} }
} }
#endif #endif

View File

@@ -23,8 +23,11 @@
#ifndef JANET_STATE_H_defined #ifndef JANET_STATE_H_defined
#define JANET_STATE_H_defined #define JANET_STATE_H_defined
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h> #include <janet.h>
#include <stdint.h> #include <stdint.h>
#endif
#ifdef JANET_EV #ifdef JANET_EV
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
@@ -65,6 +68,7 @@ typedef struct {
int has_worker; int has_worker;
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
HANDLE worker; HANDLE worker;
HANDLE worker_event;
#else #else
pthread_t worker; pthread_t worker;
#endif #endif

View File

@@ -931,27 +931,24 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) {
#include <mach/clock.h> #include <mach/clock.h>
#include <mach/mach.h> #include <mach/mach.h>
int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { int janet_gettime(struct timespec *spec, enum JanetTimeSource source) {
if (source == JANET_TIME_REALTIME) { if (source == JANET_TIME_CPUTIME) {
clock_t tmp = clock();
spec->tv_sec = tmp / CLOCKS_PER_SEC;
spec->tv_nsec = ((tmp - (spec->tv_sec * CLOCKS_PER_SEC)) * 1000000000) / CLOCKS_PER_SEC;
} else {
clock_serv_t cclock; clock_serv_t cclock;
mach_timespec_t mts; mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); clock_id_t cid = CALENDAR_CLOCK;
if (source == JANET_TIME_REALTIME) {
cid = CALENDAR_CLOCK;
} else if (source == JANET_TIME_MONOTONIC) {
cid = SYSTEM_CLOCK;
}
host_get_clock_service(mach_host_self(), cid, &cclock);
clock_get_time(cclock, &mts); clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock); mach_port_deallocate(mach_task_self(), cclock);
spec->tv_sec = mts.tv_sec; spec->tv_sec = mts.tv_sec;
spec->tv_nsec = mts.tv_nsec; spec->tv_nsec = mts.tv_nsec;
} else if (source == JANET_TIME_MONOTONIC) {
clock_serv_t cclock;
int nsecs;
mach_msg_type_number_t count;
host_get_clock_service(mach_host_self(), clock, &cclock);
clock_get_attributes(cclock, CLOCK_GET_TIME_RES, (clock_attr_t)&nsecs, &count);
mach_port_deallocate(mach_task_self(), cclock);
clock_getres(CLOCK_MONOTONIC, spec);
}
if (source == JANET_TIME_CPUTIME) {
clock_t tmp = clock();
spec->tv_sec = tmp;
spec->tv_nsec = (tmp - spec->tv_sec) * 1.0e9;
} }
return 0; return 0;
} }

View File

@@ -115,7 +115,7 @@
#define vm_maybe_auto_suspend(COND) #define vm_maybe_auto_suspend(COND)
#else #else
#define vm_maybe_auto_suspend(COND) do { \ #define vm_maybe_auto_suspend(COND) do { \
if ((COND) && janet_vm.auto_suspend) { \ if ((COND) && janet_atomic_load_relaxed(&janet_vm.auto_suspend)) { \
fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \ fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \
vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \ vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \
} \ } \

View File

@@ -67,11 +67,21 @@ extern "C" {
#define JANET_LINUX 1 #define JANET_LINUX 1
#endif #endif
/* Check for Android */
#ifdef __ANDROID__
#define JANET_ANDROID 1
#endif
/* Check for Cygwin */ /* Check for Cygwin */
#if defined(__CYGWIN__) #if defined(__CYGWIN__)
#define JANET_CYGWIN 1 #define JANET_CYGWIN 1
#endif #endif
/* Check for Illumos */
#if defined(__illumos__)
#define JANET_ILLUMOS 1
#endif
/* Check Unix */ /* Check Unix */
#if defined(_AIX) \ #if defined(_AIX) \
|| defined(__APPLE__) /* Darwin */ \ || defined(__APPLE__) /* Darwin */ \
@@ -137,6 +147,7 @@ extern "C" {
|| defined(__s390x__) /* S390 64-bit */ \ || defined(__s390x__) /* S390 64-bit */ \
|| defined(__s390__) /* S390 32-bit */ \ || defined(__s390__) /* S390 32-bit */ \
|| defined(__ARMEB__) /* ARM big endian */ \ || defined(__ARMEB__) /* ARM big endian */ \
|| defined(__AARCH64EB__) /* ARM64 big endian */ \
|| ((defined(__CC_ARM) || defined(__ARMCC__)) /* ARM RealView compiler */ \ || ((defined(__CC_ARM) || defined(__ARMCC__)) /* ARM RealView compiler */ \
&& defined(__BIG_ENDIAN)) && defined(__BIG_ENDIAN))
#define JANET_BIG_ENDIAN 1 #define JANET_BIG_ENDIAN 1
@@ -157,7 +168,7 @@ extern "C" {
#endif #endif
/* Check sun */ /* Check sun */
#ifdef __sun #if defined(__sun) && !defined(JANET_ILLUMOS)
#define JANET_NO_UTC_MKTIME #define JANET_NO_UTC_MKTIME
#endif #endif
@@ -165,14 +176,12 @@ extern "C" {
/* Also enable the thread library only if not single-threaded */ /* Also enable the thread library only if not single-threaded */
#ifdef JANET_SINGLE_THREADED #ifdef JANET_SINGLE_THREADED
#define JANET_THREAD_LOCAL #define JANET_THREAD_LOCAL
#undef JANET_THREADS #elif !(defined(JANET_THREAD_LOCAL)) && defined(__GNUC__)
#elif defined(__GNUC__)
#define JANET_THREAD_LOCAL __thread #define JANET_THREAD_LOCAL __thread
#elif defined(_MSC_BUILD) #elif !(defined(JANET_THREAD_LOCAL)) && defined(_MSC_BUILD)
#define JANET_THREAD_LOCAL __declspec(thread) #define JANET_THREAD_LOCAL __declspec(thread)
#else #elif !(defined(JANET_THREAD_LOCAL))
#define JANET_THREAD_LOCAL #define JANET_THREAD_LOCAL
#undef JANET_THREADS
#endif #endif
/* Enable or disable dynamic module loading. Enabled by default. */ /* Enable or disable dynamic module loading. Enabled by default. */
@@ -591,6 +600,7 @@ typedef void *JanetAbstract;
#define JANET_STREAM_WRITABLE 0x400 #define JANET_STREAM_WRITABLE 0x400
#define JANET_STREAM_ACCEPTABLE 0x800 #define JANET_STREAM_ACCEPTABLE 0x800
#define JANET_STREAM_UDPSERVER 0x1000 #define JANET_STREAM_UDPSERVER 0x1000
#define JANET_STREAM_NOT_CLOSEABLE 0x2000
#define JANET_STREAM_TOCLOSE 0x10000 #define JANET_STREAM_TOCLOSE 0x10000
typedef enum { typedef enum {
@@ -663,6 +673,7 @@ typedef int32_t JanetAtomicInt;
JANET_API JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x); JANET_API JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x); JANET_API JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x); JANET_API JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x);
/* We provide three possible implementations of Janets. The preferred /* We provide three possible implementations of Janets. The preferred
* nanboxing approach, for 32 or 64 bits, and the standard C version. Code in the rest of the * nanboxing approach, for 32 or 64 bits, and the standard C version. Code in the rest of the
@@ -1178,6 +1189,7 @@ struct JanetAbstractType {
Janet(*call)(void *p, int32_t argc, Janet *argv); Janet(*call)(void *p, int32_t argc, Janet *argv);
size_t (*length)(void *p, size_t len); size_t (*length)(void *p, size_t len);
JanetByteView(*bytes)(void *p, size_t len); JanetByteView(*bytes)(void *p, size_t len);
int (*gcperthread)(void *data, size_t len);
}; };
/* Some macros to let us add extra types to JanetAbstract types without /* Some macros to let us add extra types to JanetAbstract types without
@@ -1197,7 +1209,8 @@ struct JanetAbstractType {
#define JANET_ATEND_NEXT NULL,JANET_ATEND_CALL #define JANET_ATEND_NEXT NULL,JANET_ATEND_CALL
#define JANET_ATEND_CALL NULL,JANET_ATEND_LENGTH #define JANET_ATEND_CALL NULL,JANET_ATEND_LENGTH
#define JANET_ATEND_LENGTH NULL,JANET_ATEND_BYTES #define JANET_ATEND_LENGTH NULL,JANET_ATEND_BYTES
#define JANET_ATEND_BYTES #define JANET_ATEND_BYTES NULL,JANET_ATEND_GCPERTHREAD
#define JANET_ATEND_GCPERTHREAD
struct JanetReg { struct JanetReg {
const char *name; const char *name;
@@ -1455,10 +1468,10 @@ JANET_API int32_t janet_abstract_incref(void *abst);
JANET_API int32_t janet_abstract_decref(void *abst); JANET_API int32_t janet_abstract_decref(void *abst);
/* Expose channel utilities */ /* Expose channel utilities */
JanetChannel *janet_channel_make(uint32_t limit); JANET_API JanetChannel *janet_channel_make(uint32_t limit);
JanetChannel *janet_channel_make_threaded(uint32_t limit); JANET_API JanetChannel *janet_channel_make_threaded(uint32_t limit);
JanetChannel *janet_getchannel(const Janet *argv, int32_t n); JANET_API JanetChannel *janet_getchannel(const Janet *argv, int32_t n);
JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, JanetChannel *dflt); JANET_API JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, JanetChannel *dflt);
JANET_API int janet_channel_give(JanetChannel *channel, Janet x); JANET_API int janet_channel_give(JanetChannel *channel, Janet x);
JANET_API int janet_channel_take(JanetChannel *channel, Janet *out); JANET_API int janet_channel_take(JanetChannel *channel, Janet *out);
@@ -1606,6 +1619,9 @@ JANET_API JanetTable *janet_core_env(JanetTable *replacements);
JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements); JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements);
/* Execute strings */ /* Execute strings */
#define JANET_DO_ERROR_RUNTIME 0x01
#define JANET_DO_ERROR_COMPILE 0x02
#define JANET_DO_ERROR_PARSE 0x04
JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out); JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out);
JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out); JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out);
@@ -1884,6 +1900,7 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr
#define JANET_SANDBOX_FFI_USE 2048 #define JANET_SANDBOX_FFI_USE 2048
#define JANET_SANDBOX_FFI_JIT 4096 #define JANET_SANDBOX_FFI_JIT 4096
#define JANET_SANDBOX_SIGNAL 8192 #define JANET_SANDBOX_SIGNAL 8192
#define JANET_SANDBOX_CHROOT 16384
#define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT) #define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT)
#define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP) #define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP)
#define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN) #define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN)

View File

@@ -50,6 +50,11 @@
(def errsym (keyword (gensym))) (def errsym (keyword (gensym)))
~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg)) ~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg))
(defmacro assert-error-value
[msg errval & forms]
(def e (gensym))
~(assert (= ,errval (try (do ,;forms) ([,e] ,e))) ,msg))
(defn check-compile-error (defn check-compile-error
[form] [form]
(def result (compile form)) (def result (compile form))

View File

@@ -865,6 +865,13 @@
(assert (deep= ~(,import* "a" :as "b" :fresh maybe) (assert (deep= ~(,import* "a" :as "b" :fresh maybe)
(macex '(import a :as b :fresh maybe))) "import macro 2") (macex '(import a :as b :fresh maybe))) "import macro 2")
# 2af3f21d
(assert-error "import macro 2" (macex '(import a :fresh)))
(assert-error "import macro 3" (macex '(import a :as b :fresh)))
(assert-error "import macro 4" (macex '(import b "notakeyword" value)))
(assert (deep= ~(,import* "a" :fresh nil)
(macex '(import a :fresh nil))) "import macro 5")
# #477 walk preserving bracket type # #477 walk preserving bracket type
# 0a1d902f4 # 0a1d902f4
(assert (= :brackets (tuple/type (postwalk identity '[]))) (assert (= :brackets (tuple/type (postwalk identity '[])))
@@ -1016,4 +1023,11 @@
(assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"} (assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"}
@{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys") @{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys")
# different try overloads
(assert (= (try (error :error) ([] :caught)) :caught))
(assert (= (try (error :error) ([e] e)) :error))
(assert (= (try (error :error) ([e fib] [e (fiber? fib)])) [:error true]))
# regression test for #1659
(assert (= (try (error :error) ([_ _] :caught)) :caught))
(end-suite) (end-suite)

View File

@@ -117,8 +117,17 @@
(assert (= 0 (length (bundle/list))) "bundles are listed correctly 7") (assert (= 0 (length (bundle/list))) "bundles are listed correctly 7")
(assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8") (assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8")
# Try installing a bundle that is missing bundle script
(assert-error-value "bundle missing bundle script"
"bundle must contain bundle.janet or bundle/init.janet"
(bundle/install "./examples/sample-bad-bundle1"))
(assert (= 0 (length (bundle/list))) "check failure 0")
(assert (= 0 (length (bundle/topolist))) "check failure 1")
# Try installing a bundle that fails check # Try installing a bundle that fails check
(assert-error "bad test" (bundle/install "./examples/sample-bad-bundle" :check true)) (assert-error-value "bundle check hook fails"
"Check failed!"
(bundle/install "./examples/sample-bad-bundle2" :check true))
(assert (= 0 (length (bundle/list))) "check failure 0") (assert (= 0 (length (bundle/list))) "check failure 0")
(assert (= 0 (length (bundle/topolist))) "check failure 1") (assert (= 0 (length (bundle/topolist))) "check failure 1")

View File

@@ -106,6 +106,8 @@
(calc-2 "(+ 9 10 11 12)")) (calc-2 "(+ 9 10 11 12)"))
@[10 26 42]) "parallel subprocesses 2") @[10 26 42]) "parallel subprocesses 2")
# (print "file piping")
# File piping # File piping
# a1cc5ca04 # a1cc5ca04
(assert-no-error "file writing 1" (assert-no-error "file writing 1"
@@ -225,6 +227,8 @@
(++ iterations) (++ iterations)
(ev/write stream " "))) (ev/write stream " ")))
# (print "local name / peer name testing")
# Test localname and peername # Test localname and peername
# 077bf5eba # 077bf5eba
(repeat 10 (repeat 10
@@ -407,6 +411,8 @@
(while (def msg (ev/read connection 100)) (while (def msg (ev/read connection 100))
(broadcast name (string msg))))))) (broadcast name (string msg)))))))
# (print "chat app testing")
# Now launch the chat server # Now launch the chat server
(def chat-server (net/listen test-host test-port)) (def chat-server (net/listen test-host test-port))
(ev/spawn (ev/spawn
@@ -500,6 +506,8 @@
(let [s (net/listen :unix uds-path :stream)] (let [s (net/listen :unix uds-path :stream)]
(:close s)))))) (:close s))))))
# (print "accept loop testing")
# net/accept-loop level triggering # net/accept-loop level triggering
(gccollect) (gccollect)
(def maxconn 50) (def maxconn 50)
@@ -522,6 +530,8 @@
(assert (= maxconn connect-count)) (assert (= maxconn connect-count))
(:close s) (:close s)
# (print "running deadline tests...")
# Cancel os/proc-wait with ev/deadline # Cancel os/proc-wait with ev/deadline
(let [p (os/spawn [;run janet "-e" "(os/sleep 4)"] :p)] (let [p (os/spawn [;run janet "-e" "(os/sleep 4)"] :p)]
(var terminated-normally false) (var terminated-normally false)
@@ -546,9 +556,35 @@
(ev/sleep 0.15) (ev/sleep 0.15)
(assert (not terminated-normally) "early termination failure 3")) (assert (not terminated-normally) "early termination failure 3"))
(let [f (coro (forever :foo))] # Deadline with interrupt
(ev/deadline 0.01 nil f true) (defmacro with-deadline2
(assert-error "deadline expired" (resume f))) ``
Create a fiber to execute `body`, schedule the event loop to cancel
the task (root fiber) associated with `body`'s fiber, and start
`body`'s fiber by resuming it.
The event loop will try to cancel the root fiber if `body`'s fiber
has not completed after at least `sec` seconds.
`sec` is a number that can have a fractional part.
``
[sec & body]
(with-syms [f]
~(let [,f (coro ,;body)]
(,ev/deadline ,sec nil ,f true)
(,resume ,f))))
(for i 0 10
# (print "deadline 1 iteration " i)
(assert (= :done (with-deadline2 10
(ev/sleep 0.01)
:done)) "deadline with interrupt exits normally"))
(for i 0 10
# (print "deadline 2 iteration " i)
(let [f (coro (forever :foo))]
(ev/deadline 0.01 nil f true)
(assert-error "deadline expired" (resume f))))
# Use :err :stdout # Use :err :stdout
(def- subproc-code '(do (eprint "hi") (eflush) (print "there") (flush))) (def- subproc-code '(do (eprint "hi") (eflush) (print "there") (flush)))

58
test/suite-ev2.janet Normal file
View File

@@ -0,0 +1,58 @@
# 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
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
(import ./helper :prefix "" :exit true)
(start-suite)
# Issue #1629
(def thread-channel (ev/thread-chan 100))
(def super (ev/thread-chan 10))
(defn worker []
(while true
(def item (ev/take thread-channel))
(when (= item :deadline)
(ev/deadline 0.1 nil (fiber/current) true))))
(ev/thread worker nil :n super)
(ev/give thread-channel :item)
(ev/sleep 0.05)
(ev/give thread-channel :item)
(ev/sleep 0.05)
(ev/give thread-channel :deadline)
(ev/sleep 0.05)
(ev/give thread-channel :item)
(ev/sleep 0.05)
(ev/give thread-channel :item)
(ev/sleep 0.15)
(assert (deep= '(:error "deadline expired" nil) (ev/take super)) "deadline expirataion")
# Another variant
(def thread-channel (ev/thread-chan 100))
(def super (ev/thread-chan 10))
(defn worker []
(while true
(def item (ev/take thread-channel))
(when (= item :deadline)
(ev/deadline 0.1))))
(ev/thread worker nil :n super)
(ev/give thread-channel :deadline)
(ev/sleep 0.2)
(assert (deep= '(:error "deadline expired" nil) (ev/take super)) "deadline expirataion")
(end-suite)

View File

@@ -136,5 +136,8 @@
"keyword slice") "keyword slice")
(assert (= 'symbol (symbol/slice "some_symbol_slice" 5 11)) "symbol slice") (assert (= 'symbol (symbol/slice "some_symbol_slice" 5 11)) "symbol slice")
# Check string formatting, #1600
(assert (= "" (string/format "%.99s" @"")) "string/format %s buffer")
(end-suite) (end-suite)

View File

@@ -37,6 +37,12 @@
Version="$(var.Version)" Version="$(var.Version)"
Manufacturer="$(var.Manufacturer)" Manufacturer="$(var.Manufacturer)"
UpgradeCode="$(var.UpgradeCode)"> UpgradeCode="$(var.UpgradeCode)">
<!--
perUser means destination will be under user's %AppData% directory,
not Program Files or similar.
see: https://learn.microsoft.com/en-us/windows/win32/msi/installation-context
-->
<Package Compressed="yes" <Package Compressed="yes"
InstallScope="perUser" InstallScope="perUser"
Manufacturer="$(var.Manufacturer)" Manufacturer="$(var.Manufacturer)"