1
0
mirror of https://github.com/janet-lang/janet synced 2026-04-01 20:41:27 +00:00

Compare commits

...

114 Commits

Author SHA1 Message Date
Calvin Rose
0a0453ff7f Fsync changes. 2026-03-07 07:14:10 -06:00
Calvin Rose
8f849cec55 Always 0-initialize EvGenericMessage and add plenty of padding for
OVERLAPPED structures.
2026-03-04 15:39:01 -06:00
Calvin Rose
7df23e8070 Add tentative fsync wrapper.
Fsync is a POSIX API that may not be available or useful on all systems.
2026-03-03 20:16:19 -06:00
Calvin Rose
aa63adccb4 Update CHANGELOG.md 2026-03-02 19:39:21 -06:00
Calvin Rose
7fc12ff167 Use ConnectEx for non-blocking connect on windows when available.
Still fallback to blocking connect with WSAConnect when ConnectEx is not
available or applicable, but ConnectEx is preferred and recommended by
Microsoft.

Also make some changes to our use of OVERLAPPED in various places in the
ev code, replacing all uses with JanetOverlapped. This also let's us
avoid reusing internal fields for OVERLAPPED which may or may not be
used in various places.
2026-03-02 19:39:21 -06:00
Calvin Rose
39f8cf207c Cast for warning on mingw DWORD printing. 2026-03-01 12:24:03 -06:00
Calvin Rose
95f2e233c5 Try io.h on windows 2026-03-01 12:20:15 -06:00
Calvin Rose
e8f9c12935 Fix regression where private main was not run. 2026-03-01 10:52:52 -06:00
Calvin Rose
32d75c9e49 Dup io file descriptors when marshalling closable files.
For unclosable files, no need to dup, but for closable files we can get
a resource leak. Valgrind and similar tools won't catch this but IO will
unexpectedly start going to the wrong descriptor if a file was
transferred to a new thread, closed, and then a new file was created.
2026-03-01 10:39:50 -06:00
Calvin Rose
5fec2aa9df Move some files around code more defensively for mitigation. 2026-02-27 18:19:40 -06:00
Brett
54fbd7607f Fix GC collecting active fiber during nested janet_continue (#1720)
janet_collect() marks janet_vm.root_fiber but not janet_vm.fiber.
When janet_pcall (or janet_continue) is called from a C function,
the inner fiber becomes janet_vm.fiber while root_fiber still points
to the outer fiber. If GC triggers inside the inner fiber's execution,
the inner fiber is not in any GC root set and can be collected —
including its stack memory — while actively running.

This also affects deeply nested cases: F1 -> C func -> janet_pcall ->
F2 -> C func -> janet_pcall -> F3, where F2 is saved only in a C
stack local (tstate.vm_fiber), invisible to GC.

Fix: in janet_continue_no_check, root the fiber with janet_gcroot
when this is a nested call (root_fiber already set). Each nesting
level roots its own fiber, handling arbitrary depth. Top-level calls
(event loop, REPL) skip the root/unroot entirely since root_fiber
is NULL.

Add test/test-gc-pcall.c: standalone C test covering both single
and deep nesting cases.

Co-authored-by: Brett Adams <brett@bletia-9.local>
2026-02-27 18:13:46 -06:00
sogaiu
019829fdf9 Tweak a docstring and a comment (#1718)
Co-authored-by: sogaiu <983021772@users.noreply.github.com>
2026-02-25 17:51:54 -06:00
Calvin Rose
2602bec017 Check stderr for redirection before turning on/off color. 2026-02-21 08:17:12 -06:00
Calvin Rose
403b2c704a Add sanitizer test to github actions and sr.ht builds.
This will run both clang and gcc sanitizers as part of ordinary testing.
2026-02-20 18:08:09 -06:00
Calvin Rose
ca9ffaa5bb Avoid memory leak when canceling fibers with threaded channels.
Objects in channels are sent as messages that need to be freed by the
consumer. However, in certain cases, no consumer is available and the
messages were being discarded without properly being freed. This should
also fix `-fsanitize=address` on GCC and CLANG with the default test
suite.
2026-02-20 14:47:00 -06:00
Calvin Rose
e61194a8d9 Remove older extra channel unlocks. 2026-02-20 08:17:25 -06:00
Calvin Rose
08e4030487 Add builders for issue #1716 2026-02-20 08:00:36 -06:00
Calvin Rose
56b5998553 Suspicious locking behavior with select.
This looks like it could cause deadlocks with threaded channels
(normal channels are unaffected, locking/unlocking is a no-op).
2026-02-20 07:35:18 -06:00
R Fisher
ea997d585b fix multicast on illumos (#1717)
illumos, like BSD, expects IP_MULTICAST_TTL to be
an unsigned char
2026-02-20 07:30:30 -06:00
Calvin Rose
fc725e2511 Prepare for next patch release. 2026-02-18 08:46:17 -06:00
Calvin Rose
d636502c32 Fix vestigial doc string. 2026-02-18 08:41:31 -06:00
Calvin Rose
0fea20c821 Prepare for v1.41.2 and indicate vm changes for stack correction. 2026-02-18 08:27:10 -06:00
Calvin Rose
91cc499e77 1.42.2 patch. 2026-02-18 08:19:55 -06:00
Calvin Rose
68850a0a05 Update for 1.41.2 patch. 2026-02-18 08:19:13 -06:00
Calvin Rose
d3d7c675a8 Update CHANGELOG.md 2026-02-17 18:56:21 -06:00
Calvin Rose
b2c9fc123c Generate JOP_PUT_INDEX in the compiler when possible. 2026-02-17 09:07:01 -06:00
Calvin Rose
fa0c039cd3 Add regression test for issue #1714 2026-02-17 07:59:08 -06:00
Evan Shaw
78ef9d1733 Initialize memory allocated by put (#1715) 2026-02-17 07:50:50 -06:00
sogaiu
b6676f350c Use snprintf instead of sprintf - sequel (#1713)
Co-authored-by: sogaiu <983021772@users.noreply.github.com>
2026-02-16 09:12:02 -06:00
Calvin Rose
0299620a2d Code defensively with regard to stack resizes from custom get and put
for abstract types.

Abstract types whose get/put/length/etc. implementations allocated fiber
slots could break the VM by invalidating the stack pointer in the
interpreter. This is admittedly a bit unusual but is something most
users would probably expect to work. Debugging this would be a real pain.
2026-02-16 09:02:07 -06:00
Calvin Rose
739d9d9fe3 Expose module/add-syspath and update CHANGELOG.md 2026-02-15 22:20:54 -06:00
Calvin Rose
1557f9da78 Don't reference argv after fiber may have been resized. 2026-02-15 21:54:42 -06:00
Calvin Rose
529d8c9e4a Improve ability to load modules by full path.
Be explicit when we are including this functionality. Add a function
module/add-file-extension that can do this programatically.
2026-02-15 21:10:26 -06:00
Calvin Rose
2df16e5a48 Allow garbage collection to be called inside the module entry.
This usually shouldn't be needed, but in the case that it is, or if
garbage collection is triggered manually, we can prevent use-after-free.
2026-02-15 18:46:21 -06:00
Calvin Rose
b0db2b22d6 Remove macos-13 2026-02-15 11:18:40 -06:00
Calvin Rose
8b6d56edae Patch release to 1.41.1 2026-02-15 10:36:46 -06:00
Calvin Rose
a2a7e9f01e Add explicit include of inttypes.h 2026-02-15 10:24:56 -06:00
Calvin Rose
4b078e7a45 Use correct format specifier on windows if missing message. 2026-02-15 10:17:16 -06:00
Calvin Rose
5c0bb4b385 Cosmo libc builds were not working. 2026-02-15 09:57:10 -06:00
Calvin Rose
2aaa7dfa10 Sort keys when compiling struct and table literals.
Order of evaluation becomes more clear in some cases.
2026-02-15 09:42:55 -06:00
Calvin Rose
10bb17199c Prepare for 1.41.0 release 2026-02-15 08:45:56 -06:00
Calvin Rose
0aa7dfeb9a Work on windows for WSAConnect not working.
For remote connections, if you did not manually wait for the connection
to settle, the programmer would see unspecified network errors. This is
is because the underlying TCP Connection had not been established yet.
The correct way to deal with this is to use ConnectEx if available
instead of WSAConnect.
2026-02-14 21:11:28 -06:00
Calvin Rose
8f7c32e5cb Update for msvc build. 2026-02-14 18:31:57 -06:00
sogaiu
abd7bb1110 Use snprintf instead of sprintf (#1711)
Co-authored-by: sogaiu <983021772@users.noreply.github.com>
2026-02-14 09:04:33 -06:00
Calvin Rose
d81512723b When pretty printing, don't sort keys for huge tables.
This was exposed when printing `(invert (range 200000))`, which
isn't so large that we shouldn't be able to sort it, but was taking
far too long to compute.
2026-02-14 08:57:27 -06:00
Calvin Rose
2a54154976 Don't use preload on absolute paths.
When importing full paths, the old preload code was preventing
(import <fullpath> :fresh true) from working as expected.
2026-02-13 19:36:09 -06:00
Calvin Rose
306ce892ea Merge branch 'make-modules-easier' 2026-02-06 00:31:08 -06:00
Calvin Rose
c7c3821aa6 Remove extra output from peg test. 2026-02-06 00:23:45 -06:00
Calvin Rose
d2685594f9 VERBOSE=1 caused tests to fail as we did more asserts inside the assert.
Thenc checked stderr for messages. Since the helper makes assert write
to stderr, this caused extra cruft in our test output.
2026-02-06 00:17:11 -06:00
Calvin Rose
ca5c617fba More tweaks to peg suite for arm32 failures. 2026-02-06 00:04:18 -06:00
Calvin Rose
16b449a137 Print "actual" output on verbose mode for suite-peg 2026-02-05 23:52:28 -06:00
Calvin Rose
2e8dd90a51 Line endings in tests. 2026-02-05 23:38:55 -06:00
Calvin Rose
196f27af3d Update CHANGELOG.md 2026-02-05 20:01:00 -06:00
Calvin Rose
42c0096ce7 Allow simpler loading of modules.
E.g.

(import project/file.janet :as file)
(import ./project/file.janet :as file)
(import /home/me/project/file.janet :as file)

Will now do what you expect. When loading with a file extension, this
will first check directly if the relative (or absolute) path exists.

Also expands `module/find` to be able to show all matches for a given
module name without needing to try and catch import errors. Lastly, one
can also override the module/find functionality by passing `:loader`
directly.

E.g.

(import ./macoslib.dylib :loader :native :as my-thing)

Which will allow easily bypassing all module/find logic;
"./macoslib.dylib" will be passed directly to the :native loader
`(get module/loaders :native)`.

The module system was previously left open to customization but with
defaults to encourage a more typical style. However, these qol
improvements are more than worth it and can in some cases let people
stop fighting the module system.
2026-02-05 19:45:30 -06:00
Calvin Rose
0194115412 Auto-format janet. 2026-02-05 00:33:52 -06:00
Calvin Rose
f33697d6a0 Show accumulation buffer and tagged capture in peg debug rule. 2026-02-04 21:17:08 -06:00
Calvin Rose
b2bf70eace Move peg debug output to stderr in line with other internal debug tools.
Also allow disabling color in the debug output.
2026-02-04 18:52:20 -06:00
Calvin Rose
855d1f2940 Update changelog. 2026-02-04 18:46:09 -06:00
0xbec163
416bba9bd9 Add ??/debug PEG rule (#1710)
* Add debug (short-form ??) PEG rule

* Display capture stack to debug PEG rule

* Revise spec_debug to emit just the rule

* Update to proper indentation

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add a marshal/unmarshal test for ??/debug

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 18:44:46 -06:00
Calvin Rose
517e40a17b Speed up symbol resolution.
In my testing of `janet -l spork -e '(os/exit)'`, this speeds up
compilation by ~5%. Allocating and interning symbols from C is often
slow, so avoid it.

Add utility function for looking up keywords in tables more quickly.

More measurement on compiling spork for compilation speed, win
over original code closer to between 1-2% improvement
2026-02-02 21:19:48 -06:00
Calvin Rose
4f9a2af357 Add unmarshal sandbox. 2026-02-02 18:28:08 -06:00
Calvin Rose
a37752708e Add :compile, :asm, and :threads to sandbox flags.
These can be used to limit programs in a very expansive way.
2026-02-02 18:13:46 -06:00
Calvin Rose
5042ad6d4b More tweaks to changelog. 2026-02-01 14:46:20 -06:00
Calvin Rose
643c0b4976 Update CHANGELOG.md 2026-02-01 10:54:31 -06:00
Calvin Rose
ecb72c9c9a Revert changes to peg grammar. 2026-02-01 08:08:50 -06:00
Calvin Rose
a95546ff16 Merge pull request #1703 from amano-kenji/peg
Make peg-grammar available in all threads for peg/compile
2026-02-01 07:42:28 -06:00
Calvin Rose
d47f82713b More tweaks for named arity linting.
Handle more edge cases where &named is combined with &opt
2026-01-31 23:57:08 -06:00
Calvin Rose
497e363401 Make sure named argumnets are compiled before destructured params.
This handles an edge-case when combing destructured and named
parameters that produced spurious and confusing compiler lints.
2026-01-31 19:34:26 -06:00
Calvin Rose
8481da18d0 Add another bounds check for updating labels. Related to #1700 2026-01-31 13:20:45 -06:00
Calvin Rose
8f8382eead Partially address issue #1654 - better linting for named arguments.
While there are still cases that we could improve here, this change will
help in most cases with minimal overhead.
2026-01-31 13:16:02 -06:00
Calvin Rose
8e2ec997f0 Update copyright. 2026-01-31 10:33:57 -06:00
Calvin Rose
ea271b6d6c Update README with more clarification on versions to use for building. 2026-01-30 19:55:39 -06:00
Calvin Rose
e1897e1865 More documentation. 2026-01-29 07:50:23 -06:00
Calvin Rose
0c1585fdfe Preserve tuple type to fix #1709
Macro expansion as done by `macex1` was incorrectly losing the semantic
tuple information inside the `set` form. Since macro expansion is
usually done by the compiler, and `macex1` is mainly used for debugging
and deep transformations inside other macros, this only surfaced with
certain usage of short-fn macro.
2026-01-29 06:52:43 -06:00
Calvin Rose
a5c4e929e8 Merge pull request #1704 from amano-kenji/os/date
Document how to set local timezone for os/date and os/strftime.
2026-01-28 21:37:43 -06:00
Calvin Rose
4c21dc3c06 Merge pull request #1708 from sogaiu/fiber-docstring-tweaks
Tweak some fiber-related docstrings
2026-01-28 21:28:50 -06:00
sogaiu
d67b462023 Tweak some fiber-related docstrings 2026-01-29 11:39:05 +09:00
Calvin Rose
24ca108288 Add some extra checks for marshalling code. 2026-01-28 10:55:00 -06:00
Calvin Rose
7366fbed1f Disallow scheduling already modified fibers.
While not strictly needed for correctness, this discourages misuse of
fibers and mixing both coroutine, error handling, and scheduling
purposes.
2026-01-28 10:55:00 -06:00
Calvin Rose
797643716b Address #1707 - do not schedule non-task fibers. 2026-01-28 10:55:00 -06:00
Calvin Rose
eda2e11d31 MSVC and windows clib does not like fopen(NULL, ...)
We were exploiting this behavior on posix when adding shell history for
the built-in readline functionality.
2026-01-28 10:51:18 -06:00
Calvin Rose
ae0afe6198 Add janet_decref_abstract_maybe_free 2026-01-27 13:28:52 -06:00
Calvin Rose
33f5a0b319 Add test case to help address #1705 2026-01-24 12:23:01 -06:00
Calvin Rose
3ecc9bc543 Small tweaks to boot.janet to improve stack traces. 2026-01-24 12:17:24 -06:00
Calvin Rose
339b0751c8 Address #1705 - ev/close affecting ev/select. 2026-01-24 12:17:02 -06:00
amano.kenji
87b1bf1a66 Document how to set local timezone for os/date and os/strftime 2026-01-24 14:21:33 +00:00
amano.kenji
41354ada96 Make peg-grammar available in all threads for peg/compile 2026-01-24 11:36:49 +00:00
Calvin Rose
ee8d816738 Fix test case. 2026-01-23 08:30:15 -06:00
Calvin Rose
0f285855f0 Address #170 - strchr was not properly checking for 0 byte. 2026-01-23 08:13:15 -06:00
Calvin Rose
c43e06672c Address #1700 and move test cases to suite-compile.janet 2026-01-23 08:01:22 -06:00
Calvin Rose
2fabc80151 Address #1699 and improve test case for #1702 2026-01-23 07:46:31 -06:00
Calvin Rose
4dd08a4cde Address #1702 with extra bounds check.
The buffer overflow happened because we were creating many upvalue
slots in the compiler without using them, along with some faulty logic
that used the wrong length to check for the bitmap's bounds.
2026-01-23 07:33:05 -06:00
Calvin Rose
883dde4fa5 Remove xprintf reference. 2026-01-22 20:14:46 -06:00
Calvin Rose
6111291ede Revert a number of minor, unneeded changes for persistent REPL history. 2026-01-22 20:13:49 -06:00
Calvin Rose
53b8bf2684 Disable persistent REPL history without JANET_HISTFILE. 2026-01-22 19:17:16 -06:00
Calvin Rose
0c402cf3d6 Add persistent repl history. 2026-01-22 19:17:16 -06:00
Calvin Rose
606a1fc11a Merge pull request #1696 from amano-kenji/master
Document empty? more clearly.
2026-01-20 07:59:00 -06:00
amano.kenji
a2db57b9dc Document empty? more clearly. 2026-01-20 12:31:28 +00:00
Calvin Rose
f021bb2839 janet-format boot.janet 2026-01-18 13:05:55 -06:00
Calvin Rose
979233dee5 Update changelog and spelling 2026-01-18 11:09:01 -06:00
Calvin Rose
78a785175a Allow redirecting :err to :out with os/execute, no just os/spawn. 2026-01-17 22:37:35 -06:00
Calvin Rose
268864b072 Don't use new flag on mingw test. 2026-01-17 22:15:03 -06:00
Calvin Rose
06f099d7f9 Make sure to run test with old flags as well. 2026-01-17 20:25:06 -06:00
Calvin Rose
6549903c51 Add option to explicity inherit handles rather than implicitly. 2026-01-17 20:07:12 -06:00
Calvin Rose
c1dff351d9 Improve stacktraces for macros that use defer.
Previously, they would show up as `defer` in stack traces.
2026-01-17 19:59:28 -06:00
Calvin Rose
4aa5615a37 On windows, streams created with os/open improved.
We now set bInheritHandles to true when creating them, which makes it
possible to pass them to subprocesses. Before, it would fail silently
in strange ways often simply losing data. Also added flags to disable
OVERLAPPED_IO on windows and O_NONBLOCK on posix.
2026-01-17 18:19:17 -06:00
Calvin Rose
67932bbaed On windows, make sure to use FILE_ATTRIBUTE_NORMAL by default. 2026-01-17 17:04:56 -06:00
Calvin Rose
4575cefb7e Add shorthand for os test. 2026-01-17 10:11:56 -06:00
Calvin Rose
d5a014baff Fix reference. 2026-01-17 07:43:43 -06:00
Calvin Rose
eb825772bb Add more information to process creation failure on windows. 2026-01-17 07:29:45 -06:00
Calvin Rose
ee2985f5e3 Merge pull request #1695 from nfgrusk/add-omitted-word-in-docstrings
Add omitted word "is" in docstrings
2026-01-16 21:29:34 -06:00
Nora Gruner
5819408715 Add omitted word "is" in docstrings. 2026-01-17 00:28:58 +01:00
Calvin Rose
8fe284b5eb Fix pathologically slow insertion pattern.
The correct resizing behavior for arrays and buffers was done for
`janet_putindex` but not for `janet_put`. This change copies the correct
behavior to `janet_put`.
2026-01-15 13:04:11 -06:00
Calvin Rose
19b5502f50 Simplify ev/gather and add ev/go-gather.
This allows using `ev/gather` semantics on a dynamic number of tasks and
not relying on the macro magic.
2026-01-14 17:33:10 -06:00
53 changed files with 1850 additions and 507 deletions

View File

@@ -10,3 +10,9 @@ tasks:
gmake test
sudo gmake install
sudo gmake uninstall
- build-sanitizers: |
cd janet
CFLAGS="-g -O2 -fsanitize=address,undefined" gmake
gmake test
sudo gmake install
sudo gmake uninstall

8
.github/cosmo/setup vendored
View File

@@ -1,19 +1,19 @@
#!/bin/sh
set -e
sudo apt update
sudo apt-get update
sudo apt-get install -y ca-certificates libssl-dev\
qemu qemu-utils qemu-user-static\
qemu-utils qemu-user-static\
texinfo groff\
cmake ninja-build bison zip\
pkg-config build-essential autoconf re2c
# download cosmocc
cd /sc
wget https://github.com/jart/cosmopolitan/releases/download/3.3.3/cosmocc-3.3.3.zip
wget https://github.com/jart/cosmopolitan/releases/download/4.0.2/cosmocc-4.0.2.zip
mkdir -p cosmocc
cd cosmocc
unzip ../cosmocc-3.3.3.zip
unzip ../cosmocc-4.0.2.zip
# register
cd /sc/cosmocc

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-13 ]
os: [ ubuntu-latest ]
steps:
- name: Checkout the repository
uses: actions/checkout@master
@@ -46,7 +46,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ macos-latest ]
os: [ macos-latest, macos-15-intel ]
steps:
- name: Checkout the repository
uses: actions/checkout@master

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, macos-14 ]
os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ]
steps:
- name: Checkout the repository
uses: actions/checkout@master
@@ -21,6 +21,20 @@ jobs:
- name: Test the project
run: make test
test-posix-sanitizers:
name: Build and test on POSIX systems with sanitizers turned on
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest ]
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Compile the project
run: make clean && CFLAGS="-g -O2 -fsanitize=address,undefined" make
- name: Test the project
run: make test
test-windows:
name: Build and test on Windows
strategy:
@@ -42,6 +56,27 @@ jobs:
shell: cmd
run: build_win dist
test-windows-sanitizers:
name: Build and test on Windows with sanitizers
strategy:
matrix:
os: [ windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Build the project
shell: cmd
run: set SANITIZE=1 & build_win
- name: Test the project
shell: cmd
run: set VERBOSE=1 & build_win test
- name: Test installer build
shell: cmd
run: build_win dist
test-windows-min:
name: Build and test on Windows Minimal build
strategy:
@@ -136,3 +171,22 @@ jobs:
run: docker run --privileged --rm tonistiigi/binfmt --install s390x
- name: Build and run on emulated architecture
run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test"
test-cosmo:
name: Test build for Cosmo
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: create build folder
run: |
sudo mkdir -p /sc
sudo chmod -R 0777 /sc
- name: setup Cosmopolitan Libc
run: bash ./.github/cosmo/setup
- name: Set the version
run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- name: Set the platform
run: echo "platform=cosmo" >> $GITHUB_ENV
- name: build Janet APE binary
run: bash ./.github/cosmo/build

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ janet
/src/include/generated/*.h
janet-*.tar.gz
dist
/tmp
# jpm lockfile
lockfile.janet

View File

@@ -2,9 +2,34 @@
All notable changes to this project will be documented in this file.
## Unreleased - ???
- Add `file/sync` as a wrapper around fsync.
- Documentation fixes
- ev/thread-chan deadlock bug fixed
- Re-add removed support for non-blocking net/connect on windows.
## 1.41.2 - 2026-02-18
- Fix regressions in `put` for arrays and buffers.
- Add `module/add-file-extension`
- Add `module/add-syspath`
- Fix issue with possible stack corrpution with abstract types that modify the current fiber.
- Allow use of the interpreter and garbage collection inside module entry for native modules.
## 1.41.1 - 2026-02-15
- Revert to blocking behaior of `net/connect` on windows to fix spurious errors.
- Allow overriding the loader when doing imports with the `:loader` argument.
- Allow importing modules with a path extension to do what one would expect.
- Add `find-all` argument to `module/find`
- Add :threads, :unmarshal, :compiler, and :asm sandbox flags.
- Add support for persistent REPL history with the environment variable `JANET_HISTFILE`
- Fix a number of fuzzer-found compiler bugs
- Fix windows processes launching bug with empty environment table that caused process-launch failures.
- Add `:I`, `:V`, and `:N` flags to `os/open` for more control when creating streams.
- Add `ev/go-gather` for a dynamic `ev/gather`.
- Use color in script output if color is being used in REPL output.
- Fix `varfn` macros handling of extra metadata.
- Disallow certain degenerate uses of fibers with the ev/ module.
- Add linting for unused bindings.
- Add linting for extra or wrong parameters to &named functions.
- Add `janet_optuinteger` and `janet_optuinteger64` to the C API.
- Add `cms` combinator to PEGs.
- Add `thaw-keep-keys` as a variant of thaw

View File

@@ -58,7 +58,6 @@ LDFLAGS?=-rdynamic
LIBJANET_LDFLAGS?=$(LDFLAGS)
RUN:=$(RUN)
COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC
BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 $(COMMON_CFLAGS) -g
BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS)

View File

@@ -148,8 +148,40 @@ You can get the source on [GitHub](https://github.com/janet-lang/janet) or
[SourceHut](https://git.sr.ht/~bakpakin/janet). While the GitHub repo is the official repo,
the SourceHut mirror is actively maintained.
## Spork and JPM
Spork and JPM are two companion projects to Janet. They are optional, especially in an embedding use case.
Spork is a collection of common utility modules, and several packaged scripts
like `janet-format` for code formatting, `janet-netrepl` for a socket-based
REPL, and `janet-pm` for a comprehensive Janet project manager tool. The
modules in `spork` are less stable than the interfaces in core Janet, although
we try to prevent breaking changes to existing modules, with a preference to
add new modules and functions. Spork requires a C compiler to build and install
various extenstion components such as miniz and JSON utilities. Many spork
sub-modules, for example spork/path, are independent and can be manually
vendored in programmer projects without fully installing spork.
When install Spork, scripts will be installed to $JANET_PATH/bin/ on POSIX systems by default.
This likely needs to be added to the path to use these scripts.
JPM is the older, more opinionated, project manager tool, which has it's pros
and cons. It does not require a C compiler to build and install, but is less
flexible and is not receiving many changes and improvements going forward. It
may also be harder to configure correctly on new systems. In that sense, it may
be more stable.
JPM will install to /usr/local/bin/ on posix systems by default, which may or
may not be on your PATH.
## Building
When building from source, for stability, please use the latest tagged release. For
example, run `git checkout $(git describe --tags --abbrev=0)` after cloning but
before building. For the latest development, build directly on the master
branch. The master branch is not-necessarily stable as most Janet development
happens directly on the master branch.
### macOS and Unix-like
The Makefile is non-portable and requires GNU-flavored make.
@@ -160,15 +192,18 @@ make
make test
make repl
make install
make install-jpm-git
make install-spork-git # optional
make install-jpm-git # optional
```
Find out more about the available make targets by running `make help`.
### Alpine Linux
To build a statically-linked build of Janet, Alpine Linux + MUSL is a good combination. Janet can also
be built inside a docker container or similar in this manner.
To build a statically-linked build of Janet, Alpine Linux + MUSL is a good
combination. Janet can also be built inside a docker container or similar in
this manner. This is a great way to try Janet without committing to a full
install or needing to customize the default install.
```sh
docker run -it --rm alpine /bin/ash
@@ -178,8 +213,13 @@ $ cd janet
$ make -j10
$ make test
$ make install
$ make install-spork-git # optional
$ make install-jpm-git # optional
```
Note that for a true statically-linked binary with MUSL, one needs to add `-static` to the Makefile flags. This
will also disable runtime loading of native modules (plugins) as well as the FFI.
### 32-bit Haiku
32-bit Haiku build instructions are the same as the UNIX-like build instructions,
@@ -191,7 +231,8 @@ make CC=gcc-x86
make test
make repl
make install
make install-jpm-git
make install-spork-git # optional
make install-jpm-git # optional
```
### FreeBSD
@@ -205,7 +246,8 @@ gmake
gmake test
gmake repl
gmake install
gmake install-jpm-git
gmake install-spork-git # optional
gmake install-jpm-git # optional
```
### NetBSD
@@ -320,8 +362,8 @@ If installed, you can also run `man janet` to get usage information.
## Embedding
Janet can be embedded in a host program very easily. The normal build
will create a file `build/janet.c`, which is a single C file
that contains all the source to Janet. This file, along with
will create a file `build/c/janet.c`, a C source code file that
that contains the amalgamated source to Janet. This file, along with
`src/include/janet.h` and `src/conf/janetconf.h`, can be dragged into any C
project and compiled into it. Janet should be compiled with `-std=c99`
on most compilers, and will need to be linked to the math library, `-lm`, and

View File

@@ -23,10 +23,22 @@
@rem set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD /fsanitize=address /Zi
@rem set JANET_LINK=link /nologo clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
@set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD
@set JANET_LINK=link /nologo
if DEFINED CLANG (
@set COMPILER=clang-cl.exe
) else (
@set COMPILER=cl.exe
)
if DEFINED SANITIZE (
@set "SANITIZERS=/fsanitize=address /Zi"
@set "LINK_SAN=/DEBUG"
) else (
@set "SANITIZERS="
@set "LINK_SAN=/DEBUG"
)
@set JANET_COMPILE=%COMPILER% /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD %SANITIZERS%
@set JANET_LINK=link /nologo %LINK_SAN%
@set JANET_LINK_STATIC=lib /nologo
@set JANET_LINK_STATIC=lib /nologo %LINK_SAN%
@rem Add janet build tag
if not "%JANET_BUILD%" == "" (

View File

@@ -156,7 +156,7 @@ Shows the version text and exits immediately.
.TP
.BR \-s
Read raw input from stdin and forgo prompt history and other readline-like features.
Read raw input from stdin and forgo fancy input, which includes prompt history and other readline-like features.
.TP
.BR \-e\ code
@@ -272,6 +272,12 @@ This variable does nothing in the default configuration of Janet, as PRF is disa
cannot be defined for this variable to have an effect.
.RE
.B JANET_HISTFILE
.RS
A file location to use for the default shell's REPL history when using fancy input. This relative path is where commands are persisted between sessions.
If unset, no repl history well be used. Does not work with the -s flag where fancy input is disabled.
.RE
.B NO_COLOR
.RS
Turn off color by default in the repl and in the error handler of scripts. This can be changed at runtime

View File

@@ -20,7 +20,7 @@
project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.41.0')
version : '1.41.3')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')

View File

View File

@@ -1,5 +1,5 @@
# The core janet library
# Copyright 2025 © Calvin Rose
# Copyright 2026 © Calvin Rose
###
###
@@ -105,9 +105,9 @@
(defn keyword? "Check if x is a keyword." [x] (= (type x) :keyword))
(defn buffer? "Check if x is a buffer." [x] (= (type x) :buffer))
(defn function? "Check if x is a function (not a cfunction)." [x] (= (type x) :function))
(defn cfunction? "Check if x a cfunction." [x] (= (type x) :cfunction))
(defn table? "Check if x a table." [x] (= (type x) :table))
(defn struct? "Check if x a struct." [x] (= (type x) :struct))
(defn cfunction? "Check if x is a cfunction." [x] (= (type x) :cfunction))
(defn table? "Check if x is a table." [x] (= (type x) :table))
(defn struct? "Check if x is a struct." [x] (= (type x) :struct))
(defn array? "Check if x is an array." [x] (= (type x) :array))
(defn tuple? "Check if x is a tuple." [x] (= (type x) :tuple))
(defn boolean? "Check if x is a boolean." [x] (= (type x) :boolean))
@@ -115,7 +115,7 @@
(defn true? "Check if x is true." [x] (= x true))
(defn false? "Check if x is false." [x] (= x false))
(defn nil? "Check if x is nil." [x] (= x nil))
(defn empty? "Check if xs is empty." [xs] (= nil (next xs nil)))
(defn empty? "Check if an iterable, `iter`, is empty." [iter] (= nil (next iter nil)))
# For macros, we define an incomplete odd? function that will be overridden.
(defn odd? [x] (= 1 (mod x 2)))
@@ -370,19 +370,24 @@
(++ i))
~(let (,;accum) ,;body))
(defmacro defer
``Run `form` unconditionally after `body`, even if the body throws an error.
Will also run `form` if a user signal 0-4 is received.``
[form & body]
(defn- defer-impl
"Defer but allow custom name for stack traces"
[name form body]
(with-syms [f r]
~(do
(def ,f (,fiber/new (fn :defer [] ,;body) :ti))
(def ,f (,fiber/new (fn ,name [] ,;body) :ti))
(def ,r (,resume ,f))
,form
(if (= (,fiber/status ,f) :dead)
,r
(,propagate ,r ,f)))))
(defmacro defer
``Run `form` unconditionally after `body`, even if the body throws an error.
Will also run `form` if a user signal 0-4 is received.``
[form & body]
(defer-impl :defer form body))
(defmacro edefer
``Run `form` after `body` in the case that body terminates abnormally (an error or user signal 0-4).
Otherwise, return last form in `body`.``
@@ -436,14 +441,14 @@
[[binding ctor dtor] & body]
~(do
(def ,binding ,ctor)
,(apply defer [(or dtor :close) binding] body)))
,(defer-impl :with [(or dtor :close) binding] body)))
(defmacro when-with
``Similar to with, but if binding is false or nil, returns
nil without evaluating the body. Otherwise, the same as `with`.``
[[binding ctor dtor] & body]
~(if-let [,binding ,ctor]
,(apply defer [(or dtor :close) binding] body)))
,(defer-impl :when-with [(or dtor :close) binding] body)))
(defmacro if-with
``Similar to `with`, but if binding is false or nil, evaluates
@@ -451,7 +456,7 @@
`ctor` is bound to binding.``
[[binding ctor dtor] truthy &opt falsey]
~(if-let [,binding ,ctor]
,(apply defer [(or dtor :close) binding] [truthy])
,(defer-impl :if-with [(or dtor :close) binding] [truthy])
,falsey))
(defn- for-var-template
@@ -2168,7 +2173,7 @@
(defn expand-bindings [x]
(case (type x)
:array (map expand-bindings x)
:tuple (tuple/slice (map expand-bindings x))
:tuple (keep-syntax! x (map expand-bindings x))
:table (dotable x expand-bindings)
:struct (table/to-struct (dotable x expand-bindings))
(recur x)))
@@ -2176,11 +2181,11 @@
(defn expanddef [t]
(def last (in t (- (length t) 1)))
(def bound (in t 1))
(tuple/slice
(array/concat
@[(in t 0) (expand-bindings bound)]
(tuple/slice t 2 -2)
@[(recur last)])))
(keep-syntax! t
(array/concat
@[(in t 0) (expand-bindings bound)]
(tuple/slice t 2 -2)
@[(recur last)])))
(defn expandall [t]
(def args (map recur (tuple/slice t 1)))
@@ -2191,10 +2196,10 @@
(if (symbol? t1)
(do
(def args (map recur (tuple/slice t 3)))
(tuple 'fn t1 (in t 2) ;args))
(keep-syntax t (tuple 'fn t1 (in t 2) ;args)))
(do
(def args (map recur (tuple/slice t 2)))
(tuple 'fn t1 ;args))))
(keep-syntax t (tuple 'fn t1 ;args)))))
(defn expandqq [t]
(defn qq [x]
@@ -2840,7 +2845,8 @@
(defmacro comptime
"Evals x at compile time and returns the result. Similar to a top level unquote."
[x]
(eval x))
(def y (eval x))
y)
(defmacro compif
"Check the condition `cnd` at compile time -- if truthy, compile `tru`, else compile `fals`."
@@ -2868,7 +2874,8 @@
(defn- check-dyn-relative [x] (if (string/has-prefix? "@" x) x))
(defn- check-relative [x] (if (string/has-prefix? "." x) x))
(defn- check-not-relative [x] (if-not (string/has-prefix? "." x) x))
# Don't try to preload absolute or relative paths
(defn- check-preloadable [x] (if-not (or (string/has-prefix? "/" x) (string/find "." x) (string/find "@" x)) x))
(defn- check-is-dep [x] (unless (or (string/has-prefix? "/" x) (string/has-prefix? "@" x) (string/has-prefix? "." x)) x))
(defn- check-project-relative [x] (if (string/has-prefix? "/" x) x))
@@ -2920,8 +2927,21 @@
(array/insert mp curall-index [(string ":cur:/:all:" ext) loader check-relative])
mp)
(defn module/add-file-extension
```
Add paths to `module/paths` for a given file extension such that
the programmer can import a module by relative or absolute path from
the current working directory.
Returns the modified `module/paths`.
```
[ext loader]
(assert (string/has-prefix? "." ext) "file extension must have . prefix")
(def mp (dyn *module-paths* module/paths))
(array/insert mp 0 [":all:" loader (fn :check-ext [x] (string/has-suffix? ext x))])
mp)
# Don't expose this externally yet - could break if custom module/paths is setup.
(defn- module/add-syspath
(defn module/add-syspath
```
Add a custom syspath to `module/paths` by duplicating all entries that being with `:sys:` and
adding duplicates with a specific path prefix instead.
@@ -2942,7 +2962,16 @@
(module/add-paths "/init.janet" :source)
(module/add-paths ".janet" :source)
(module/add-paths ".jimage" :image)
(array/insert module/paths 0 [(fn is-cached [path] (if (in (dyn *module-cache* module/cache) path) path)) :preload check-not-relative])
(module/add-file-extension ".janet" :source)
(module/add-file-extension ".jimage" :source)
# These obviously won't work on all platforms, but if a user explicitly
# tries to import them, we may as well try.
(module/add-file-extension ".so" :native)
(module/add-file-extension ".dll" :native)
(array/insert module/paths 0
[(fn is-cached [path] (if (in (dyn *module-cache* module/cache) path) path))
:preload
check-preloadable])
# Version of fexists that works even with a reduced OS
(defn- fexists
@@ -2970,20 +2999,22 @@
or :image if the module is found, otherwise a tuple with nil followed by
an error message.
```
[path]
[path &opt find-all]
(var ret nil)
(def mp (dyn *module-paths* module/paths))
(def all-matches (if find-all @[]))
(each [p mod-kind checker] mp
(when (mod-filter checker path)
(if (function? p)
(when-let [res (p path)]
(set ret [res mod-kind])
(break))
(if find-all (array/push all-matches ret) (break)))
(do
(def fullpath (string (module/expand-path path p)))
(when (fexists fullpath)
(set ret [fullpath mod-kind])
(break))))))
(if find-all (array/push all-matches ret) (break)))))))
(if find-all (break all-matches))
(if ret ret
(let [expander (fn :expander [[t _ chk]]
(when (string? t)
@@ -3158,17 +3189,20 @@
(defn- require-1
[path args kargs]
(def [fullpath mod-kind] (module/find path))
(def [fullpath mod-kind]
(if-let [loader (get kargs :loader)]
[path loader]
(module/find path)))
(unless fullpath (error mod-kind))
(def mc (dyn *module-cache* module/cache))
(def ml (dyn *module-loading* module/loading))
(def mls (dyn *module-loaders* module/loaders))
(if-let [check (if-not (kargs :fresh) (in mc fullpath))]
(if-let [check (if-not (get kargs :fresh) (in mc fullpath))]
check
(if (ml fullpath)
(if (get ml fullpath)
(error (string "circular dependency " fullpath " detected"))
(do
(def loader (if (keyword? mod-kind) (mls mod-kind) mod-kind))
(def loader (if (keyword? mod-kind) (get mls mod-kind) mod-kind))
(unless loader (error (string "module type " mod-kind " unknown")))
(def env (loader fullpath args))
(put mc fullpath env)
@@ -3207,7 +3241,7 @@
(def prefix (or
(and as (string as "/"))
prefix
(string (last (string/split "/" path)) "/")))
(string (first (string/split "." (last (string/split "/" path)))) "/")))
(merge-module env newenv prefix ep only))
(defmacro import
@@ -3268,7 +3302,6 @@
[&opt env local]
(env-walk keyword? env local))
(defdyn *doc-width*
"Width in columns to print documentation printed with `doc-format`.")
@@ -3722,7 +3755,7 @@
(def digits (inc (math/floor (math/log10 end))))
(def fmt-str (string "%" digits "d: %s"))
(for i beg end
(eprin " ") # breakpoint someday?
(eprin " ")
(eprin (if (= i cur) "> " " "))
(eprintf fmt-str i (get lines i))))
(let [[sl _] (sourcemap pc)]
@@ -3859,13 +3892,16 @@
(defn ev/call
```
Call a function asynchronously.
Returns a fiber that is scheduled to run the function.
Returns a task fiber that is scheduled to run the function.
```
[f & args]
(ev/go (fn :call [&] (f ;args))))
(defmacro ev/spawn
"Run some code in a new fiber. This is shorthand for `(ev/go (fn [] ;body))`."
``
Run some code in a new task fiber. This is shorthand for
`(ev/go (fn [] ;body))`."
``
[& body]
~(,ev/go (fn :spawn [&] ,;body)))
@@ -3938,23 +3974,33 @@
(cancel-all chan fibers "sibling canceled")
(propagate (fiber/last-value fiber) fiber))))))
(defn ev/go-gather
```
Run a dyanmic number of fibers in parallel and resume the current fiber after they complete. Takes
an array of functions or fibers, `thunks`, that will be run via `ev/go` in another task.
Returns the gathered results in an array.
```
[thunks]
(def fset @{})
(def chan (ev/chan))
(def results @[])
(each thunk thunks
(def ftemp (ev/go thunk nil chan))
(array/push results ftemp)
(put fset ftemp ftemp))
(wait-for-fibers chan fset)
(for i 0 (length results) # avoid extra copy from map
(set (results i) (fiber/last-value (in results i))))
results)
(defmacro ev/gather
``
Run a number of fibers in parallel on the event loop, and join when they complete.
Returns the gathered results in an array.
Create and run a number of fibers in parallel (created from `bodies`) and resume the
current fiber after they complete. Shorthand for `ev/go-gather`. Returns the gathered results in an
array.
``
[& bodies]
(with-syms [chan res fset ftemp]
~(do
(def ,fset @{})
(def ,chan (,ev/chan))
(def ,res @[])
,;(seq [[i body] :pairs bodies]
~(do
(def ,ftemp (,ev/go (fn :ev/gather [] (put ,res ,i ,body)) nil ,chan))
(,put ,fset ,ftemp ,ftemp)))
(,wait-for-fibers ,chan ,fset)
,res))))
~(,ev/go-gather ,(seq [body :in bodies] ~(fn :ev/gather [] ,body)))))
(compwhen (dyn 'net/listen)
(defn net/server
@@ -3968,7 +4014,7 @@
"handler not supported for :datagram servers")
(def s (net/listen host port type no-reuse))
(if handler
(ev/go (fn [] (net/accept-loop s handler))))
(ev/go (fn :net/server-handler [] (net/accept-loop s handler))))
s))
###
@@ -4266,12 +4312,12 @@
(try
(require (string "@syspath/bundle/" bundle-name))
([e f]
(def pfx "could not find module @syspath/bundle/")
(def msg (if (and (string? e)
(string/has-prefix? pfx e))
"bundle must contain bundle.janet or bundle/init.janet"
e))
(propagate msg f))))))
(def pfx "could not find module @syspath/bundle/")
(def msg (if (and (string? e)
(string/has-prefix? pfx e))
"bundle must contain bundle.janet or bundle/init.janet"
e))
(propagate msg f))))))
(defn- do-hook
[module bundle-name hook & args]
@@ -4403,8 +4449,8 @@
(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))
(fexists bscript-src1) bscript-src1
(fexists bscript-src2) bscript-src2))
# Setup installed paths
(prime-bundle-paths)
(os/mkdir (bundle-dir bundle-name))
@@ -4624,8 +4670,7 @@
(defn- run-main
[env subargs arg]
(when-let [entry (in env 'main)
main (or (get entry :value) (in (get entry :ref) 0))]
(when-let [main (module/value env 'main true)]
(def guard (if (get env :debug) :ydt :y))
(defn wrap-main [&]
(main ;subargs))
@@ -4714,7 +4759,8 @@
(apply-color
(and
(not (getenv-alias "NO_COLOR"))
(os/isatty stdout)))
(os/isatty stdout)
(os/isatty stderr)))
(defn- get-lint-level
[i]

View File

@@ -37,7 +37,7 @@ int system_test() {
/* Check the version defines are self consistent */
char version_combined[256];
sprintf(version_combined, "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA);
snprintf(version_combined, sizeof(version_combined), "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA);
assert(!strcmp(JANET_VERSION, version_combined));
/* Reflexive testing and nanbox testing */

View File

@@ -5,9 +5,9 @@
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 41
#define JANET_VERSION_PATCH 0
#define JANET_VERSION_PATCH 3
#define JANET_VERSION_EXTRA "-dev"
#define JANET_VERSION "1.41.0-dev"
#define JANET_VERSION "1.41.3-dev"
/* #define JANET_BUILD "local" */

View File

@@ -201,4 +201,17 @@ int32_t janet_abstract_decref(void *abst) {
return janet_atomic_dec(&janet_abstract_head(abst)->gc.data.refcount);
}
int32_t janet_abstract_decref_maybe_free(void *abst) {
int32_t result = janet_abstract_decref(abst);
if (0 == result) {
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
/* Free memory */
janet_free(head);
}
return result;
}
#endif

View File

@@ -567,6 +567,13 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
x = janet_get1(s, janet_ckeywordv("structarg"));
if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG;
/* Check namedarg */
x = janet_get1(s, janet_ckeywordv("namedargs"));
if (janet_checkint(x)) {
def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
def->named_args_count = janet_unwrap_integer(x);
}
/* Check source */
x = janet_get1(s, janet_ckeywordv("source"));
if (janet_checktype(x, JANET_STRING)) def->source = janet_unwrap_string(x);
@@ -982,6 +989,14 @@ static Janet janet_disasm_structarg(JanetFuncDef *def) {
return janet_wrap_boolean(def->flags & JANET_FUNCDEF_FLAG_STRUCTARG);
}
static Janet janet_disasm_namedargs(JanetFuncDef *def) {
if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS) {
return janet_wrap_integer(def->named_args_count);
} else {
return janet_wrap_nil();
}
}
static Janet janet_disasm_constants(JanetFuncDef *def) {
JanetArray *constants = janet_array(def->constants_length);
for (int32_t i = 0; i < def->constants_length; i++) {
@@ -1032,6 +1047,7 @@ Janet janet_disasm(JanetFuncDef *def) {
janet_table_put(ret, janet_ckeywordv("source"), janet_disasm_source(def));
janet_table_put(ret, janet_ckeywordv("vararg"), janet_disasm_vararg(def));
janet_table_put(ret, janet_ckeywordv("structarg"), janet_disasm_structarg(def));
janet_table_put(ret, janet_ckeywordv("namedargs"), janet_disasm_namedargs(def));
janet_table_put(ret, janet_ckeywordv("name"), janet_disasm_name(def));
janet_table_put(ret, janet_ckeywordv("slotcount"), janet_disasm_slotcount(def));
janet_table_put(ret, janet_ckeywordv("symbolmap"), janet_disasm_symbolslots(def));
@@ -1048,6 +1064,7 @@ JANET_CORE_FN(cfun_asm,
"The syntax for the assembly can be found on the Janet website, and should correspond\n"
"to the return value of disasm. Will throw an\n"
"error on invalid assembly.") {
janet_sandbox_assert(JANET_SANDBOX_ASM);
janet_fixarity(argc, 1);
JanetAssembleResult res;
res = janet_asm(argv[0], 0);
@@ -1067,6 +1084,8 @@ JANET_CORE_FN(cfun_disasm,
"* :min-arity - minimum number of arguments function can be called with.\n"
"* :max-arity - maximum number of arguments function can be called with.\n"
"* :vararg - true if function can take a variable number of arguments.\n"
"* :structarg - true if function can take a variable number of arguments using the &keys option.\n"
"* :namedargs - if function can take a variable number of arguments using the &named option, this will be the number of named arguments.\n"
"* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n"
"* :source - name of source file that this function was compiled from.\n"
"* :name - name of function.\n"
@@ -1076,6 +1095,7 @@ JANET_CORE_FN(cfun_disasm,
"* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n"
"* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n"
"* :defs - other function definitions that this function may instantiate.\n") {
janet_sandbox_assert(JANET_SANDBOX_ASM);
janet_arity(argc, 1, 2);
JanetFunction *f = janet_getfunction(argv, 0);
if (argc == 2) {
@@ -1088,6 +1108,7 @@ JANET_CORE_FN(cfun_disasm,
if (!janet_cstrcmp(kw, "name")) return janet_disasm_name(f->def);
if (!janet_cstrcmp(kw, "vararg")) return janet_disasm_vararg(f->def);
if (!janet_cstrcmp(kw, "structarg")) return janet_disasm_structarg(f->def);
if (!janet_cstrcmp(kw, "namedargs")) return janet_disasm_namedargs(f->def);
if (!janet_cstrcmp(kw, "slotcount")) return janet_disasm_slotcount(f->def);
if (!janet_cstrcmp(kw, "constants")) return janet_disasm_constants(f->def);
if (!janet_cstrcmp(kw, "sourcemap")) return janet_disasm_sourcemap(f->def);

View File

@@ -74,6 +74,7 @@ JanetBuffer *janet_pointer_buffer_unsafe(void *memory, int32_t capacity, int32_t
void janet_buffer_deinit(JanetBuffer *buffer) {
if (!(buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) {
janet_free(buffer->data);
buffer->data = NULL;
}
}

View File

@@ -522,6 +522,7 @@ JanetFuncDef *janet_funcdef_alloc(void) {
def->bytecode_length = 0;
def->environments_length = 0;
def->symbolmap_length = 0;
def->named_args_count = 0;
return def;
}

View File

@@ -460,7 +460,7 @@ Janet janet_dyn(const char *name) {
return janet_table_get(janet_vm.top_dyns, janet_ckeywordv(name));
}
if (janet_vm.fiber->env) {
return janet_table_get(janet_vm.fiber->env, janet_ckeywordv(name));
return janet_table_get_keyword(janet_vm.fiber->env, name);
} else {
return janet_wrap_nil();
}

View File

@@ -201,14 +201,29 @@ static JanetSlot do_cmp(JanetFopts opts, JanetSlot *args) {
return opreduce(opts, args, JOP_COMPARE, 0, janet_wrap_nil(), janet_wrap_nil());
}
static JanetSlot do_put(JanetFopts opts, JanetSlot *args) {
if (opts.flags & JANET_FOPTS_DROP) {
janetc_emit_sss(opts.compiler, JOP_PUT, args[0], args[1], args[2], 0);
return janetc_cslot(janet_wrap_nil());
int8_t inline_index = 0;
if (can_slot_be_imm(args[1], &inline_index)) {
/* Use JOP_PUT_INDEX */
if (opts.flags & JANET_FOPTS_DROP) {
janetc_emit_ssi(opts.compiler, JOP_PUT_INDEX, args[0], args[2], inline_index, 0);
return janetc_cslot(janet_wrap_nil());
} else {
JanetSlot t = janetc_gettarget(opts);
janetc_copy(opts.compiler, t, args[0]);
janetc_emit_ssi(opts.compiler, JOP_PUT_INDEX, t, args[2], inline_index, 0);
return t;
}
} else {
JanetSlot t = janetc_gettarget(opts);
janetc_copy(opts.compiler, t, args[0]);
janetc_emit_sss(opts.compiler, JOP_PUT, t, args[1], args[2], 0);
return t;
/* Use JOP_PUT */
if (opts.flags & JANET_FOPTS_DROP) {
janetc_emit_sss(opts.compiler, JOP_PUT, args[0], args[1], args[2], 0);
return janetc_cslot(janet_wrap_nil());
} else {
JanetSlot t = janetc_gettarget(opts);
janetc_copy(opts.compiler, t, args[0]);
janetc_emit_sss(opts.compiler, JOP_PUT, t, args[1], args[2], 0);
return t;
}
}
}
static JanetSlot do_length(JanetFopts opts, JanetSlot *args) {

View File

@@ -295,7 +295,7 @@ JanetSlot janetc_resolve(
{
JanetBinding binding = janet_resolve_ext(c->env, sym);
if (binding.type == JANET_BINDING_NONE) {
Janet handler = janet_table_get(c->env, janet_ckeywordv("missing-symbol"));
Janet handler = janet_table_get_keyword(c->env, "missing-symbol");
switch (janet_type(handler)) {
case JANET_NIL:
break;
@@ -459,11 +459,22 @@ JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) {
const JanetKV *kvs = NULL;
int32_t cap = 0, len = 0;
janet_dictionary_view(ds, &kvs, &len, &cap);
for (int32_t i = 0; i < cap; i++) {
if (janet_checktype(kvs[i].key, JANET_NIL)) continue;
janet_v_push(ret, janetc_value(subopts, kvs[i].key));
janet_v_push(ret, janetc_value(subopts, kvs[i].value));
/* Sort keys for stability of order? */
int32_t *index_buf;
int32_t index_buf_stack[32];
int32_t *index_buf_heap = NULL;
if (len < 32) {
index_buf = index_buf_stack;
} else {
index_buf_heap = janet_smalloc(sizeof(int32_t) * len);
index_buf = index_buf_heap;
}
if (len) janet_sorted_keys(kvs, cap, index_buf);
for (int32_t i = 0; i < len; i++) {
janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].key));
janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].value));
}
if (index_buf_heap) janet_sfree(index_buf_heap);
return ret;
}
@@ -536,7 +547,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
JanetScope unusedScope;
int32_t bufstart = janet_v_count(c->buffer);
int32_t mapbufstart = janet_v_count(c->mapbuffer);
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unusued");
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unused");
janetc_value(opts, x);
janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.4q", x);
janetc_popscope(c);
@@ -548,7 +559,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
}
/* Compile a call or tailcall instruction */
static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun, const Janet *form) {
JanetSlot retslot;
JanetCompiler *c = opts.compiler;
int specialized = 0;
@@ -574,6 +585,8 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
JanetFunction *f = janet_unwrap_function(fun.constant);
int32_t min = f->def->min_arity;
int32_t max = f->def->max_arity;
int structarg = f->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG;
int namedarg = f->def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS;
if (min_arity < 0) {
/* Call has splices */
min_arity = -1 - min_arity;
@@ -597,6 +610,47 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
fun.constant, min, min == 1 ? "" : "s", min_arity);
janetc_error(c, es);
}
if (structarg && (min_arity > f->def->arity) && ((min_arity - f->def->arity) & 1)) {
/* If we have an odd number of variadic arguments to a `&keys` function, that is almost certainly wrong. */
if (namedarg) {
janetc_lintf(c, JANET_C_LINT_NORMAL,
"odd number of named arguments to `&named` function %v", fun.constant);
} else {
janetc_lintf(c, JANET_C_LINT_NORMAL,
"odd number of named arguments to `&keys` function %v", fun.constant);
}
}
if (namedarg && f->def->named_args_count > 0) {
/* For each argument passed in, check if it is one of the used named arguments
* by checking the list defined in the function def. If not, raise a normal compiler
* lint. We can also do a strict lint for _missing_ named arguments, although in many
* cases those are assumed to have some kind of default, or we have dynamic keys. */
int32_t first_arg_key_index = f->def->arity + 1;
for (int32_t i = first_arg_key_index; i < janet_tuple_length(form); i += 2) {
Janet argkey = form[i];
/* Assumption: The first N constants of a function are its named argument keys. This
* may change if the compiler changes, but is true for all Janet generated functions. */
int found = 0;
if (janet_checktype(argkey, JANET_KEYWORD)) {
for (int32_t j = 0; j < f->def->named_args_count && j < f->def->constants_length; j++) {
if (janet_equals(argkey, f->def->constants[j])) {
found = 1;
break;
}
}
} else if (janet_checktype(argkey, JANET_TUPLE)) {
/* Possible lint : too dynamic, be dumber
* (defn f [&named x] [x])
* (f (if (coin-flip) :x :w) 10)
* A tuple could be a function call the evaluates to a valid key */
found = 1;
}
if (!found) {
janetc_lintf(c, JANET_C_LINT_NORMAL,
"unused named argument %v to function %v", argkey, fun.constant);
}
}
}
}
}
break;
@@ -831,14 +885,16 @@ JanetSlot janetc_value(JanetFopts opts, Janet x) {
} else if (janet_tuple_flag(tup) & JANET_TUPLE_FLAG_BRACKETCTOR) { /* [] tuples are not function call */
ret = janetc_tuple(opts, x);
} else {
/* Function calls */
JanetSlot head = janetc_value(subopts, tup[0]);
subopts.flags = JANET_FUNCTION | JANET_CFUNCTION;
ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head);
ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head, tup);
janetc_freeslot(c, head);
}
ret.flags &= ~JANET_SLOT_SPLICED;
}
break;
/* Data Constructors */
case JANET_SYMBOL:
ret = janetc_resolve(c, janet_unwrap_symbol(x));
break;
@@ -878,19 +934,21 @@ void janet_def_addflags(JanetFuncDef *def) {
int32_t set_flags = 0;
int32_t unset_flags = 0;
/* pos checks */
if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (def->named_args_count) set_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
/* negative checks */
if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (!def->named_args_count) unset_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
/* Update flags */
def->flags |= set_flags;
def->flags &= ~unset_flags;
@@ -961,8 +1019,9 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
JANET_OUT_OF_MEMORY;
}
memcpy(chunks, scope->ua.chunks, sizeof(uint32_t) * numchunks);
/* fprintf(stderr, "slot chunks: %d, scope->ua.count: %d, numchunks: %d\n", slotchunks, scope->ua.count, numchunks); */
/* Register allocator preallocates some registers [240-255, high 16 bits of chunk index 7], we can ignore those. */
if (scope->ua.count > 7) chunks[7] &= 0xFFFFU;
if (scope->ua.count > 7 && slotchunks > 7) chunks[7] &= 0xFFFFU;
def->closure_bitset = chunks;
}
@@ -1108,6 +1167,7 @@ JANET_CORE_FN(cfun_compile,
"struct with keys :line, :column, and :error if compilation fails. "
"If a `lints` array is given, linting messages will be appended to the array. "
"Each message will be a tuple of the form `(level line col message)`.") {
janet_sandbox_assert(JANET_SANDBOX_COMPILE);
janet_arity(argc, 1, 4);
JanetTable *env = (argc > 1 && !janet_checktype(argv[1], JANET_NIL))
? janet_gettable(argv, 1) : janet_vm.fiber->env;

View File

@@ -27,6 +27,7 @@
#include "compile.h"
#include "state.h"
#include "util.h"
#include "fiber.h"
#endif
/* Generated bytes */
@@ -294,6 +295,7 @@ JANET_CORE_FN(janet_core_native,
"from the native module.") {
JanetModule init;
janet_arity(argc, 1, 2);
Janet argv0 = argv[0];
const uint8_t *path = janet_getstring(argv, 0);
const uint8_t *error = NULL;
JanetTable *env;
@@ -306,8 +308,10 @@ JANET_CORE_FN(janet_core_native,
if (!init) {
janet_panicf("could not load native %S: %S", path, error);
}
/* GC root incase garbage collection called inside module entry */
janet_fiber_push(janet_vm.fiber, janet_wrap_table(env));
init(env);
janet_table_put(env, janet_ckeywordv("native"), argv[0]);
janet_table_put(env, janet_ckeywordv("native"), argv0);
return janet_wrap_table(env);
}
@@ -746,7 +750,9 @@ typedef struct SandboxOption {
static const SandboxOption sandbox_options[] = {
{"all", JANET_SANDBOX_ALL},
{"asm", JANET_SANDBOX_ASM},
{"chroot", JANET_SANDBOX_CHROOT},
{"compile", JANET_SANDBOX_COMPILE},
{"env", JANET_SANDBOX_ENV},
{"ffi", JANET_SANDBOX_FFI},
{"ffi-define", JANET_SANDBOX_FFI_DEFINE},
@@ -764,6 +770,8 @@ static const SandboxOption sandbox_options[] = {
{"sandbox", JANET_SANDBOX_SANDBOX},
{"signal", JANET_SANDBOX_SIGNAL},
{"subprocess", JANET_SANDBOX_SUBPROCESS},
{"threads", JANET_SANDBOX_THREADS},
{"unmarshal", JANET_SANDBOX_UNMARSHAL},
{NULL, 0}
};
@@ -772,7 +780,9 @@ JANET_CORE_FN(janet_core_sandbox,
"Disable feature sets to prevent the interpreter from using certain system resources. "
"Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n"
"* :all - disallow all (except IO to stdout, stderr, and stdin)\n"
"* :asm - disallow calling `asm` and `disasm` functions.\n"
"* :chroot - disallow calling `os/posix-chroot`\n"
"* :compile - disallow calling `compile`. This will disable a lot of functionality, such as `eval`.\n"
"* :env - disallow reading and write env variables\n"
"* :ffi - disallow FFI (recommended if disabling anything else)\n"
"* :ffi-define - disallow loading new FFI modules and binding new functions\n"
@@ -789,7 +799,9 @@ JANET_CORE_FN(janet_core_sandbox,
"* :net-listen - disallow accepting inbound network connections\n"
"* :sandbox - disallow calling this function\n"
"* :signal - disallow adding or removing signal handlers\n"
"* :subprocess - disallow running subprocesses") {
"* :subprocess - disallow running subprocesses\n"
"* :threads - disallow spawning threads with `ev/thread`. Certain helper threads may still be spawned.\n"
"* :unmarshal - disallow calling the unmarshal function.\n") {
uint32_t flags = 0;
for (int32_t i = 0; i < argc; i++) {
JanetKeyword kw = janet_getkeyword(argv, i);
@@ -1354,12 +1366,16 @@ JanetTable *janet_core_env(JanetTable *replacements) {
lidv = midv = janet_wrap_nil();
janet_resolve(env, janet_csymbol("load-image-dict"), &lidv);
janet_resolve(env, janet_csymbol("make-image-dict"), &midv);
JanetTable *lid = janet_unwrap_table(lidv);
JanetTable *mid = janet_unwrap_table(midv);
for (int32_t i = 0; i < lid->capacity; i++) {
const JanetKV *kv = lid->data + i;
if (!janet_checktype(kv->key, JANET_NIL)) {
janet_table_put(mid, kv->value, kv->key);
/* Check that we actually got tables - if we are using a smaller corelib, may not exist */
if (janet_checktype(lidv, JANET_TABLE) && janet_checktype(midv, JANET_TABLE)) {
JanetTable *lid = janet_unwrap_table(lidv);
JanetTable *mid = janet_unwrap_table(midv);
for (int32_t i = 0; i < lid->capacity; i++) {
const JanetKV *kv = lid->data + i;
if (!janet_checktype(kv->key, JANET_NIL)) {
janet_table_put(mid, kv->value, kv->key);
}
}
}

View File

@@ -524,9 +524,9 @@ static void janet_schedule_general(JanetFiber *fiber, Janet value, JanetSignal s
fiber->gc.flags |= JANET_FIBER_FLAG_ROOT;
if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_FLAG_CANCELED;
if (soon) {
janet_q_push_head(&janet_vm.spawn, &t, sizeof(t));
janet_assert(!janet_q_push_head(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow");
} else {
janet_q_push(&janet_vm.spawn, &t, sizeof(t));
janet_assert(!janet_q_push(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow");
}
}
@@ -539,6 +539,9 @@ void janet_schedule_soon(JanetFiber *fiber, Janet value, JanetSignal sig) {
}
void janet_cancel(JanetFiber *fiber, Janet value) {
if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) {
janet_panic("cannot cancel non-task fiber");
}
janet_schedule_signal(fiber, value, JANET_SIGNAL_ERROR);
}
@@ -956,29 +959,34 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
janet_schedule(fiber, janet_wrap_nil());
}
} else if (mode != JANET_CP_MODE_CLOSE) {
/* Fiber has already been cancelled or resumed. */
/* Fiber has already been canceled or resumed. */
/* Resend event to another waiting thread, depending on mode */
int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ);
if (is_read) {
JanetChannelPending reader;
int sent = 0;
while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
JanetVM *vm = reader.thread;
if (!vm) continue;
JanetEVGenericMessage msg;
JanetEVGenericMessage msg = {0};
msg.tag = reader.mode;
msg.fiber = reader.fiber;
msg.argi = (int32_t) reader.sched_id;
msg.argp = channel;
msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
sent = 1;
break;
}
if (!sent) {
janet_chan_unpack(channel, &x, 1);
}
} else {
JanetChannelPending writer;
while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
JanetVM *vm = writer.thread;
if (!vm) continue;
JanetEVGenericMessage msg;
JanetEVGenericMessage msg = {0};
msg.tag = writer.mode;
msg.fiber = writer.fiber;
msg.argi = (int32_t) writer.sched_id;
@@ -998,14 +1006,14 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode) {
JanetChannelPending reader;
int is_empty;
if (janet_chan_pack(channel, &x)) {
janet_chan_unlock(channel);
janet_panicf("failed to pack value for channel: %v", x);
}
if (channel->closed) {
janet_chan_unlock(channel);
janet_panic("cannot write to closed channel");
}
if (janet_chan_pack(channel, &x)) {
janet_chan_unlock(channel);
janet_panicf("failed to pack value for channel: %v", x);
}
int is_threaded = janet_chan_is_threaded(channel);
if (is_threaded) {
/* don't dereference fiber from another thread */
@@ -1018,6 +1026,7 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
if (is_empty) {
/* No pending reader */
if (janet_q_push(&channel->items, &x, sizeof(Janet))) {
janet_chan_unpack(channel, &x, 1);
janet_chan_unlock(channel);
janet_panicf("channel overflow: %v", x);
} else if (janet_q_count(&channel->items) > channel->limit) {
@@ -1043,7 +1052,7 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
/* Pending reader */
if (is_threaded) {
JanetVM *vm = reader.thread;
JanetEVGenericMessage msg;
JanetEVGenericMessage msg = {0};
msg.tag = reader.mode;
msg.fiber = reader.fiber;
msg.argi = (int32_t) reader.sched_id;
@@ -1051,6 +1060,9 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
msg.argj = x;
if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
} else {
/* If no vm to send to, we must clean up (unpack) the packed payload to avoid leak */
janet_chan_unpack(channel, &x, 1);
}
} else {
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
@@ -1100,7 +1112,7 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i
/* Pending writer */
if (is_threaded) {
JanetVM *vm = writer.thread;
JanetEVGenericMessage msg;
JanetEVGenericMessage msg = {0};
msg.tag = writer.mode;
msg.fiber = writer.fiber;
msg.argi = (int32_t) writer.sched_id;
@@ -1196,20 +1208,6 @@ JANET_CORE_FN(cfun_channel_pop,
janet_await();
}
static void chan_unlock_args(const Janet *argv, int32_t n) {
for (int32_t i = 0; i < n; i++) {
int32_t len;
const Janet *data;
JanetChannel *chan;
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
chan = janet_getchannel(data, 0);
} else {
chan = janet_getchannel(argv, i);
}
janet_chan_unlock(chan);
}
}
JANET_CORE_FN(cfun_channel_choice,
"(ev/select & clauses)",
"Block until the first of several channel operations occur. Returns a "
@@ -1238,29 +1236,27 @@ JANET_CORE_FN(cfun_channel_choice,
janet_chan_lock(chan);
if (chan->closed) {
janet_chan_unlock(chan);
chan_unlock_args(argv, i);
return make_close_result(chan);
}
if (janet_q_count(&chan->items) < chan->limit) {
janet_channel_push_with_lock(chan, data[1], 1);
chan_unlock_args(argv, i);
return make_write_result(chan);
}
janet_chan_unlock(chan);
} else {
/* Read */
JanetChannel *chan = janet_getchannel(argv, i);
janet_chan_lock(chan);
if (chan->closed) {
janet_chan_unlock(chan);
chan_unlock_args(argv, i);
return make_close_result(chan);
}
if (chan->items.head != chan->items.tail) {
Janet item;
janet_channel_pop_with_lock(chan, &item, 1);
chan_unlock_args(argv, i);
return make_read_result(chan, item);
}
janet_chan_unlock(chan);
}
}
@@ -1269,11 +1265,13 @@ JANET_CORE_FN(cfun_channel_choice,
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
/* Write */
JanetChannel *chan = janet_getchannel(data, 0);
janet_chan_lock(chan);
janet_channel_push_with_lock(chan, data[1], 1);
} else {
/* Read */
Janet item;
JanetChannel *chan = janet_getchannel(argv, i);
janet_chan_lock(chan);
janet_channel_pop_with_lock(chan, &item, 1);
}
}
@@ -1366,7 +1364,7 @@ JANET_CORE_FN(cfun_channel_close,
while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
if (writer.thread != &janet_vm) {
JanetVM *vm = writer.thread;
JanetEVGenericMessage msg;
JanetEVGenericMessage msg = {0};
msg.fiber = writer.fiber;
msg.argp = channel;
msg.tag = JANET_CP_MODE_CLOSE;
@@ -1376,7 +1374,7 @@ JANET_CORE_FN(cfun_channel_close,
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else {
if (janet_fiber_can_resume(writer.fiber)) {
if (janet_fiber_can_resume(writer.fiber) && writer.sched_id == writer.fiber->sched_id) {
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
janet_schedule(writer.fiber, make_close_result(channel));
} else {
@@ -1389,7 +1387,7 @@ JANET_CORE_FN(cfun_channel_close,
while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
if (reader.thread != &janet_vm) {
JanetVM *vm = reader.thread;
JanetEVGenericMessage msg;
JanetEVGenericMessage msg = {0};
msg.fiber = reader.fiber;
msg.argp = channel;
msg.tag = JANET_CP_MODE_CLOSE;
@@ -1399,7 +1397,7 @@ JANET_CORE_FN(cfun_channel_close,
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else {
if (janet_fiber_can_resume(reader.fiber)) {
if (janet_fiber_can_resume(reader.fiber) && reader.sched_id == reader.fiber->sched_id) {
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
janet_schedule(reader.fiber, make_close_result(channel));
} else {
@@ -1469,11 +1467,12 @@ static void *janet_chanat_unmarshal(JanetMarshalContext *ctx) {
int32_t limit = janet_unmarshal_int(ctx);
int32_t count = janet_unmarshal_int(ctx);
if (count < 0) janet_panic("invalid negative channel count");
if (count > limit) janet_panic("invalid channel count");
janet_chan_init(abst, limit, 0);
abst->closed = !!is_closed;
for (int32_t i = 0; i < count; i++) {
Janet item = janet_unmarshal_janet(ctx);
janet_q_push(&abst->items, &item, sizeof(item));
janet_assert(!janet_q_push(&abst->items, &item, sizeof(item)), "bad unmarshal channel");
}
return abst;
}
@@ -1713,20 +1712,20 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) {
janet_free(response);
} else {
/* Normal event */
JanetOverlapped *jo = (JanetOverlapped *) overlapped;
JanetStream *stream = (JanetStream *) completionKey;
JanetFiber *fiber = NULL;
if (stream->read_fiber && stream->read_fiber->ev_state == overlapped) {
if (stream->read_fiber && stream->read_fiber->ev_state == jo) {
fiber = stream->read_fiber;
} else if (stream->write_fiber && stream->write_fiber->ev_state == overlapped) {
} else if (stream->write_fiber && stream->write_fiber->ev_state == jo) {
fiber = stream->write_fiber;
}
if (fiber != NULL) {
fiber->flags &= ~JANET_FIBER_EV_FLAG_IN_FLIGHT;
/* System is done with this, we can reused this data */
overlapped->InternalHigh = (ULONG_PTR) num_bytes_transferred;
jo->bytes_transferred = (ULONG_PTR) num_bytes_transferred;
fiber->ev_callback(fiber, result ? JANET_ASYNC_EVENT_COMPLETE : JANET_ASYNC_EVENT_FAILED);
} else {
janet_free((void *) overlapped);
janet_free((void *) jo);
janet_ev_dec_refcount();
}
janet_stream_checktoclose(stream);
@@ -2258,11 +2257,14 @@ static DWORD WINAPI janet_thread_body(LPVOID ptr) {
/* Reuse memory from thread init for returning data */
init->msg = subr(msg);
init->cb = cb;
janet_assert(PostQueuedCompletionStatus(iocp,
BOOL result = PostQueuedCompletionStatus(iocp,
sizeof(JanetSelfPipeEvent),
0,
(LPOVERLAPPED) init),
"failed to post completion event");
(LPOVERLAPPED) init);
if (!result) {
JanetString x = janet_formatc("failed to post completion event: %V", janet_ev_lasterr());
janet_assert(0, (const char *)x);
}
return 0;
}
#else
@@ -2364,8 +2366,7 @@ void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) {
/* Convenience method for common case */
JANET_NO_RETURN
void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp) {
JanetEVGenericMessage arguments;
memset(&arguments, 0, sizeof(arguments));
JanetEVGenericMessage arguments = {0};
arguments.tag = tag;
arguments.argi = argi;
arguments.argp = argp;
@@ -2411,7 +2412,7 @@ Janet janet_ev_lasterr(void) {
msgbuf,
sizeof(msgbuf),
NULL);
if (!*msgbuf) sprintf(msgbuf, "%d", code);
if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%d", code);
char *c = msgbuf;
while (*c) {
if (*c == '\n' || *c == '\r') {
@@ -2438,7 +2439,7 @@ typedef enum {
typedef struct {
#ifdef JANET_WINDOWS
OVERLAPPED overlapped;
JanetOverlapped overlapped;
DWORD flags;
#ifdef JANET_NET
WSABUF wbuf;
@@ -2473,7 +2474,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
case JANET_ASYNC_EVENT_FAILED:
case JANET_ASYNC_EVENT_COMPLETE: {
/* Called when read finished */
uint32_t ev_bytes = (uint32_t) state->overlapped.InternalHigh;
uint32_t ev_bytes = (uint32_t) state->overlapped.bytes_transferred;
state->bytes_read += ev_bytes;
if (state->bytes_read == 0 && (state->mode != JANET_ASYNC_READMODE_RECVFROM)) {
janet_schedule(fiber, janet_wrap_nil());
@@ -2505,7 +2506,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
/* fallthrough */
case JANET_ASYNC_EVENT_INIT: {
int32_t chunk_size = state->bytes_left > JANET_EV_CHUNKSIZE ? JANET_EV_CHUNKSIZE : state->bytes_left;
memset(&(state->overlapped), 0, sizeof(OVERLAPPED));
memset(&(state->overlapped), 0, sizeof(JanetOverlapped));
int status;
#ifdef JANET_NET
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
@@ -2513,7 +2514,7 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
state->wbuf.buf = (char *) state->chunk_buf;
state->fromlen = sizeof(state->from);
status = WSARecvFrom((SOCKET) stream->handle, &state->wbuf, 1,
NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped, NULL);
NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped.as.wsaoverlapped, NULL);
if (status && (WSA_IO_PENDING != WSAGetLastError())) {
janet_cancel(fiber, janet_ev_lasterr());
janet_async_end(fiber);
@@ -2524,9 +2525,9 @@ void ev_callback_read(JanetFiber *fiber, JanetAsyncEvent event) {
{
/* Some handles (not all) read from the offset in lpOverlapped
* if its not set before calling `ReadFile` these streams will always read from offset 0 */
state->overlapped.Offset = (DWORD) state->bytes_read;
state->overlapped.as.overlapped.Offset = (DWORD) state->bytes_read;
status = ReadFile(stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped);
status = ReadFile(stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped.as.overlapped);
if (!status && (ERROR_IO_PENDING != GetLastError())) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
if (state->bytes_read) {
@@ -2682,7 +2683,7 @@ typedef enum {
typedef struct {
#ifdef JANET_WINDOWS
OVERLAPPED overlapped;
JanetOverlapped overlapped;
DWORD flags;
#ifdef JANET_NET
WSABUF wbuf;
@@ -2723,7 +2724,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
case JANET_ASYNC_EVENT_FAILED:
case JANET_ASYNC_EVENT_COMPLETE: {
/* Called when write finished */
uint32_t ev_bytes = (uint32_t) state->overlapped.InternalHigh;
uint32_t ev_bytes = (uint32_t) state->overlapped.bytes_transferred;
if (ev_bytes == 0 && (state->mode != JANET_ASYNC_WRITEMODE_SENDTO)) {
janet_cancel(fiber, janet_cstringv("disconnect"));
janet_async_end(fiber);
@@ -2752,7 +2753,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
bytes = state->src.str;
len = janet_string_length(bytes);
}
memset(&(state->overlapped), 0, sizeof(WSAOVERLAPPED));
memset(&(state->overlapped), 0, sizeof(JanetOverlapped));
int status;
#ifdef JANET_NET
@@ -2762,7 +2763,7 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
state->wbuf.len = len;
const struct sockaddr *to = state->dest_abst;
int tolen = (int) janet_abstract_size((void *) to);
status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped, NULL);
status = WSASendTo(sock, &state->wbuf, 1, NULL, state->flags, to, tolen, &state->overlapped.as.wsaoverlapped, NULL);
if (status) {
if (WSA_IO_PENDING == WSAGetLastError()) {
janet_async_in_flight(fiber);
@@ -2785,9 +2786,9 @@ void ev_callback_write(JanetFiber *fiber, JanetAsyncEvent event) {
* for more details see the lpOverlapped parameter in
* https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
*/
state->overlapped.Offset = (DWORD) 0xFFFFFFFF;
state->overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF;
status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped);
state->overlapped.as.overlapped.Offset = (DWORD) 0xFFFFFFFF;
state->overlapped.as.overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF;
status = WriteFile(stream->handle, bytes, len, NULL, &state->overlapped.as.overlapped);
if (!status) {
if (ERROR_IO_PENDING == GetLastError()) {
janet_async_in_flight(fiber);
@@ -2943,10 +2944,11 @@ int janet_make_pipe(JanetHandle handles[2], int mode) {
if (!CreatePipe(handles, handles + 1, &saAttr, 0)) return -1;
return 0;
}
sprintf(PipeNameBuffer,
"\\\\.\\Pipe\\JanetPipeFile.%08x.%08x",
(unsigned int) GetCurrentProcessId(),
(unsigned int) InterlockedIncrement(&PipeSerialNumber));
snprintf(PipeNameBuffer,
sizeof(PipeNameBuffer),
"\\\\.\\Pipe\\JanetPipeFile.%08x.%08x",
(unsigned int) GetCurrentProcessId(),
(unsigned int) InterlockedIncrement(&PipeSerialNumber));
/* server handle goes to subprocess */
shandle = CreateNamedPipeA(
@@ -3002,12 +3004,14 @@ error:
JANET_CORE_FN(cfun_ev_go,
"(ev/go fiber-or-fun &opt value supervisor)",
"Put a fiber on the event loop to be resumed later. If a function is used, it is wrapped "
"with `fiber/new` first. "
"Optionally pass a value to resume with, otherwise resumes with nil. Returns the fiber. "
"An optional `core/channel` can be provided as a supervisor. When various "
"events occur in the newly scheduled fiber, an event will be pushed to the supervisor. "
"If not provided, the new fiber will inherit the current supervisor.") {
"Put a fiber on the event loop to be resumed later. If a "
"function is used, it is wrapped with `fiber/new` first. "
"Returns a task fiber. Optionally pass a value to resume "
"with, otherwise resumes with nil. An optional `core/channel` "
"can be provided as a supervisor. When various events occur "
"in the newly scheduled fiber, an event will be pushed to the "
"supervisor. If not provided, the new fiber will inherit the "
"current supervisor.") {
janet_arity(argc, 1, 3);
Janet value = argc >= 2 ? argv[1] : janet_wrap_nil();
void *supervisor = janet_optabstract(argv, argc, 2, &janet_channel_type, janet_vm.root_fiber->supervisor_channel);
@@ -3033,6 +3037,9 @@ JANET_CORE_FN(cfun_ev_go,
fiber->env->proto = janet_vm.fiber->env;
} else {
fiber = janet_getfiber(argv, 0);
if (janet_fiber_status(fiber) != JANET_STATUS_NEW) {
janet_panic("can only schedule new fibers where (= (fiber/status f) :new)");
}
}
fiber->supervisor_channel = supervisor;
janet_schedule(fiber, value);
@@ -3168,6 +3175,7 @@ JANET_CORE_FN(cfun_ev_thread,
"* `:t` - set the task-id of the new thread to value. The task-id is passed in messages to the supervisor channel.\n"
"* `:a` - don't copy abstract registry to new thread (performance optimization)\n"
"* `:c` - don't copy cfunction registry to new thread (performance optimization)") {
janet_sandbox_assert(JANET_SANDBOX_THREADS);
janet_arity(argc, 1, 4);
Janet value = argc >= 2 ? argv[1] : janet_wrap_nil();
if (!janet_checktype(argv[0], JANET_FUNCTION)) janet_getfiber(argv, 0);
@@ -3200,8 +3208,7 @@ JANET_CORE_FN(cfun_ev_thread,
janet_marshal(buffer, value, NULL, JANET_MARSHAL_UNSAFE);
if (flags & 0x1) {
/* Return immediately */
JanetEVGenericMessage arguments;
memset(&arguments, 0, sizeof(arguments));
JanetEVGenericMessage arguments = {0};
arguments.tag = (uint32_t) flags;
arguments.argi = (uint32_t) janet_vm.sandbox_flags;
arguments.argp = buffer;
@@ -3316,7 +3323,8 @@ JANET_CORE_FN(cfun_ev_deadline,
JANET_CORE_FN(cfun_ev_cancel,
"(ev/cancel fiber err)",
"Cancel a suspended fiber in the event loop. Differs from cancel in that it returns the canceled fiber immediately.") {
"Cancel a suspended task fiber in the event loop. Differs from "
"`cancel` in that it returns the canceled fiber immediately.") {
janet_fixarity(argc, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
Janet err = argv[1];
@@ -3549,7 +3557,7 @@ JANET_CORE_FN(janet_cfun_to_file,
JANET_CORE_FN(janet_cfun_ev_all_tasks,
"(ev/all-tasks)",
"Get an array of all active fibers that are being used by the scheduler.") {
"Get an array of all active task fibers that are being used by the scheduler.") {
janet_fixarity(argc, 0);
(void) argv;
JanetArray *array = janet_array(janet_vm.active_tasks.count);

View File

@@ -592,8 +592,8 @@ JANET_CORE_FN(cfun_fiber_status,
"* :user(0-7) - the fiber is suspended by a user signal\n"
"* :interrupted - the fiber was interrupted\n"
"* :suspended - the fiber is waiting to be resumed by the scheduler\n"
"* :alive - the fiber is currently running and cannot be resumed\n"
"* :new - the fiber has just been created and not yet run") {
"* :new - the fiber has just been created and not yet run\n"
"* :alive - the fiber is currently running and cannot be resumed") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
uint32_t s = janet_fiber_status(fiber);
@@ -610,8 +610,9 @@ JANET_CORE_FN(cfun_fiber_current,
JANET_CORE_FN(cfun_fiber_root,
"(fiber/root)",
"Returns the current root fiber. The root fiber is the oldest ancestor "
"that does not have a parent.") {
"Returns the current root fiber. The root fiber is the oldest "
"ancestor that does not have a parent. Note that a root fiber "
"is also a task fiber.") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_fiber(janet_vm.root_fiber);

View File

@@ -326,7 +326,7 @@ static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uin
#define FILE_INFO_PADDING (4096 * 4)
typedef struct {
OVERLAPPED overlapped;
JanetOverlapped overlapped;
JanetStream *stream;
JanetWatcher *watcher;
JanetFiber *fiber;
@@ -456,7 +456,7 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t
Janet pathv = janet_wrap_string(ow->dir_path);
ow->flags = flags | watcher->default_flags;
ow->watcher = watcher;
ow->overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */
ow->overlapped.as.overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */
Janet streamv = janet_wrap_pointer(ow);
janet_table_put(watcher->watch_descriptors, pathv, streamv);
if (watcher->is_watching) {
@@ -521,23 +521,23 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t
(void) watcher;
(void) flags;
(void) path;
janet_panic("nyi");
janet_panic("filewatch not supported on this platform");
}
static void janet_watcher_remove(JanetWatcher *watcher, const char *path) {
(void) watcher;
(void) path;
janet_panic("nyi");
janet_panic("filewatch not supported on this platform");
}
static void janet_watcher_listen(JanetWatcher *watcher) {
(void) watcher;
janet_panic("nyi");
janet_panic("filewatch not supported on this platform");
}
static void janet_watcher_unlisten(JanetWatcher *watcher) {
(void) watcher;
janet_panic("nyi");
janet_panic("filewatch not supported on this platform");
}
#endif

View File

@@ -504,14 +504,7 @@ void janet_sweep() {
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
if (0 == janet_abstract_decref(abst)) {
/* Run finalizer */
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
/* Free memory */
janet_free(janet_abstract_head(abst));
}
janet_abstract_decref_maybe_free(abst);
/* Mark as tombstone in place */
items[i].key = janet_wrap_nil();
@@ -682,12 +675,7 @@ void janet_clear_memory(void) {
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
if (0 == janet_abstract_decref(abst)) {
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
janet_free(janet_abstract_head(abst));
}
janet_abstract_decref_maybe_free(abst);
}
}
#endif

View File

@@ -43,6 +43,7 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx);
static Janet io_file_next(void *p, Janet key);
#ifdef JANET_WINDOWS
#include <io.h>
#define ftell _ftelli64
#define fseek _fseeki64
#endif
@@ -109,10 +110,11 @@ static int32_t checkflags(const uint8_t *str) {
return flags;
}
static void *makef(FILE *f, int32_t flags) {
static void *makef(FILE *f, int32_t flags, size_t bufsize) {
JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile));
iof->file = f;
iof->flags = flags;
iof->vbufsize = bufsize;
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
/* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is
* not standard. */
@@ -164,6 +166,7 @@ JANET_CORE_FN(cfun_io_fopen,
flags = JANET_FILE_READ;
}
FILE *f = fopen((const char *)fname, (const char *)fmode);
size_t bufsize = BUFSIZ;
if (f != NULL) {
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
struct stat st;
@@ -173,7 +176,7 @@ JANET_CORE_FN(cfun_io_fopen,
janet_panicf("cannot open directory: %s", fname);
}
#endif
size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
if (bufsize != BUFSIZ) {
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);
if (result) {
@@ -181,7 +184,7 @@ JANET_CORE_FN(cfun_io_fopen,
}
}
}
return f ? janet_makefile(f, flags)
return f ? janet_wrap_abstract(makef(f, flags, bufsize))
: (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil())
: janet_wrap_nil();
}
@@ -317,6 +320,41 @@ static int cfun_io_gc(void *p, size_t len) {
return 0;
}
/* Cross-platform fsync binding for Janet */
JANET_CORE_FN(cfun_io_fsync,
"(file/sync f)",
"Flushes all operating system buffers to disk for file `f`. Guarantees data is physically "
"written to disk in a platform-dependent way. Returns the file handle if successful, raises error if not.") {
janet_fixarity(argc, 1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
janet_panic("file is closed");
#ifdef JANET_WINDOWS
{
int fd = _fileno(iof->file);
if (fd < 0)
janet_panic("invalid file descriptor");
HANDLE hFile = (HANDLE)_get_osfhandle(fd);
if (hFile == INVALID_HANDLE_VALUE)
janet_panic("invalid file handle");
if (!FlushFileBuffers(hFile))
janet_panic("could not flush file buffers");
}
#elif defined(_POSIX_VERSION)
{
int fd = fileno(iof->file);
if (fd < 0)
janet_panic("invalid file descriptor");
if (fsync(fd) != 0)
janet_panic("could not fsync file");
}
#else
janet_panic("fsync not supported on this platform");
#endif
return argv[0];
}
/* Close a file */
JANET_CORE_FN(cfun_io_fclose,
"(file/close f)",
@@ -391,6 +429,7 @@ static JanetMethod io_file_methods[] = {
{"seek", cfun_io_fseek},
{"tell", cfun_io_ftell},
{"write", cfun_io_fwrite},
{"sync", cfun_io_fsync},
{NULL, NULL}
};
@@ -410,12 +449,23 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) {
JanetFile *iof = (JanetFile *)p;
if (ctx->flags & JANET_MARSHAL_UNSAFE) {
janet_marshal_abstract(ctx, p);
int fno = -1;
#ifdef JANET_WINDOWS
janet_marshal_int(ctx, _fileno(iof->file));
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
fno = _fileno(iof->file);
} else {
fno = _dup(_fileno(iof->file));
}
#else
janet_marshal_int(ctx, fileno(iof->file));
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
fno = fileno(iof->file);
} else {
fno = dup(fileno(iof->file));
}
#endif
janet_marshal_int(ctx, fno);
janet_marshal_int(ctx, iof->flags);
janet_marshal_size(ctx, iof->vbufsize);
} else {
janet_panic("cannot marshal file in safe mode");
}
@@ -444,6 +494,11 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx) {
} else {
iof->flags = flags;
}
iof->vbufsize = janet_unmarshal_size(ctx);
if (iof->vbufsize != BUFSIZ) {
int result = setvbuf(iof->file, NULL, iof->vbufsize ? _IOFBF : _IONBF, iof->vbufsize);
janet_assert(!result, "unmarshal setvbuf");
}
return iof;
} else {
janet_panic("cannot unmarshal file in safe mode");
@@ -721,8 +776,15 @@ JANET_CORE_FN(cfun_io_eflush,
void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) {
va_list args;
va_start(args, format);
Janet x = janet_dyn(name);
JanetType xtype = janet_type(x);
JanetType xtype;
Janet x;
if (!name || name[0] == '\0') { /* Allow NULL or empty string to just use dflt_file directly */
x = janet_wrap_nil();
xtype = JANET_NIL;
} else {
x = janet_dyn(name);
xtype = janet_type(x);
}
switch (xtype) {
default:
/* Other values simply do nothing */
@@ -778,11 +840,11 @@ FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) {
}
JanetFile *janet_makejfile(FILE *f, int32_t flags) {
return makef(f, flags);
return makef(f, flags, BUFSIZ);
}
Janet janet_makefile(FILE *f, int32_t flags) {
return janet_wrap_abstract(makef(f, flags));
return janet_wrap_abstract(makef(f, flags, BUFSIZ));
}
JanetAbstract janet_checkfile(Janet j) {
@@ -820,6 +882,7 @@ void janet_lib_io(JanetTable *env) {
JANET_CORE_REG("file/flush", cfun_io_fflush),
JANET_CORE_REG("file/seek", cfun_io_fseek),
JANET_CORE_REG("file/tell", cfun_io_ftell),
JANET_CORE_REG("file/sync", cfun_io_fsync),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, io_cfuns);

View File

@@ -276,6 +276,8 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
pushint(st, def->max_arity);
pushint(st, def->constants_length);
pushint(st, def->bytecode_length);
if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS)
pushint(st, def->named_args_count);
if (def->flags & JANET_FUNCDEF_FLAG_HASENVS)
pushint(st, def->environments_length);
if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS)
@@ -914,6 +916,7 @@ static const uint8_t *unmarshal_one_def(
def->sourcemap = NULL;
def->symbolmap = NULL;
def->symbolmap_length = 0;
def->named_args_count = 0;
janet_v_push(st->lookup_defs, def);
/* Set default lengths to zero */
@@ -933,6 +936,8 @@ static const uint8_t *unmarshal_one_def(
/* Read some lengths */
constants_length = readnat(st, &data);
bytecode_length = readnat(st, &data);
if (def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS)
def->named_args_count = readnat(st, &data);
if (def->flags & JANET_FUNCDEF_FLAG_HASENVS)
environments_length = readnat(st, &data);
if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS)
@@ -1693,6 +1698,7 @@ JANET_CORE_FN(cfun_unmarshal,
"Unmarshal a value from a buffer. An optional lookup table "
"can be provided to allow for aliases to be resolved. Returns the value "
"unmarshalled from the buffer.") {
janet_sandbox_assert(JANET_SANDBOX_UNMARSHAL);
janet_arity(argc, 1, 2);
JanetByteView view = janet_getbytes(argv, 0);
JanetTable *reg = NULL;

View File

@@ -140,6 +140,35 @@ static int net_get_address_family(Janet x) {
}
/* State machine for async connect */
#ifdef JANET_WINDOWS
typedef struct NetStateConnect {
/* Only used for ConnectEx */
JanetOverlapped overlapped;
} NetStateConnect;
static LPFN_CONNECTEX lazy_get_connectex(JSock sock) {
/* Get ConnectEx */
if (janet_vm.connect_ex_loaded) {
return janet_vm.connect_ex;
}
GUID guid = WSAID_CONNECTEX;
LPFN_CONNECTEX connect_ex_ptr = NULL;
DWORD byte_len = 0;
int success = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
(void*)&guid, sizeof(guid),
(void*)&connect_ex_ptr, sizeof(connect_ex_ptr),
&byte_len, NULL, NULL);
if (success) {
janet_vm.connect_ex = connect_ex_ptr;
} else {
janet_vm.connect_ex = NULL;
}
janet_vm.connect_ex_loaded = 1;
return janet_vm.connect_ex;
}
#endif
void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
JanetStream *stream = fiber->ev_stream;
@@ -159,15 +188,21 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
return;
}
#ifdef JANET_WINDOWS
/* We should be using ConnectEx here */
int res = 0;
int size = sizeof(res);
int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_ERROR, (char *)&res, &size);
int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_CONNECT_TIME, (char *)&res, &size);
if (r == NO_ERROR && res == 0xFFFFFFFF) {
return; /* This apparently indicates we haven't yet gotten a connection */
}
const int no_error = NO_ERROR;
#else
int res = 0;
socklen_t size = sizeof res;
socklen_t size = sizeof(res);
int r = getsockopt(stream->handle, SOL_SOCKET, SO_ERROR, &res, &size);
const int no_error = 0;
#endif
if (r == 0) {
if (r == no_error) {
if (res == 0) {
janet_schedule(fiber, janet_wrap_abstract(stream));
} else {
@@ -181,8 +216,8 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
janet_async_end(fiber);
}
static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) {
janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, NULL);
static JANET_NO_RETURN void net_sched_connect(JanetStream *stream, void *state) {
janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, state);
}
/* State machine for accepting connections. */
@@ -190,7 +225,7 @@ static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) {
#ifdef JANET_WINDOWS
typedef struct {
WSAOVERLAPPED overlapped;
JanetOverlapped overlapped;
JanetFunction *function;
JanetStream *lstream;
JanetStream *astream;
@@ -253,7 +288,7 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) {
JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) {
Janet err;
NetStateAccept *state = janet_malloc(sizeof(NetStateAccept));
memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED));
memset(&state->overlapped, 0, sizeof(JanetOverlapped));
memset(&state->buf, 0, 1024);
state->function = fun;
state->lstream = stream;
@@ -274,7 +309,7 @@ static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet
JanetStream *astream = make_stream(asock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE);
state->astream = astream;
int socksize = sizeof(SOCKADDR_STORAGE) + 16;
if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped)) {
if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped.as.wsaoverlapped)) {
int code = WSAGetLastError();
if (code == WSA_IO_PENDING) {
/* indicates io is happening async */
@@ -570,15 +605,44 @@ JANET_CORE_FN(cfun_net_connect,
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 */
janet_net_socknoblock(sock);
/* Connect to socket */
#ifdef JANET_WINDOWS
int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
int err = WSAGetLastError();
freeaddrinfo(ai);
int status = 0;
int err = 0;
LPFN_CONNECTEX connect_ex = NULL;
if (socktype == SOCK_STREAM && ((connect_ex = lazy_get_connectex(sock)))) {
/* Prefer ConnecEx as it works well with overlapped IO. */
janet_net_socknoblock(sock);
NetStateConnect *state = janet_malloc(sizeof(NetStateConnect));
memset(state, 0, sizeof(NetStateConnect));
BOOL success = connect_ex(sock, addr, addrlen, NULL, 0, NULL, &state->overlapped.as.overlapped);
freeaddrinfo(ai);
if (success) {
/* Did not fail */
} else {
int err = WSAGetLastError();
if (err == ERROR_IO_PENDING) {
/* Did not actually fail yet */
} else {
janet_free(state);
Janet lasterr = janet_ev_lasterr();
janet_panicf("could not connect socket (ConnectEx): %V", lasterr);
}
}
net_sched_connect(stream, state);
} else {
/* Default to blocking connect if ConnectEx not available */
status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
err = WSAGetLastError();
freeaddrinfo(ai);
/* Set up the socket for non-blocking IO after connecting on windows by default */
janet_net_socknoblock(sock);
}
#else
/* Set up the socket for non-blocking IO before connecting */
janet_net_socknoblock(sock);
int status;
do {
status = connect(sock, addr, addrlen);
@@ -599,10 +663,11 @@ JANET_CORE_FN(cfun_net_connect,
return janet_wrap_abstract(stream);
}
if (status == -1) {
#ifdef JANET_WINDOWS
if (status == SOCKET_ERROR) {
if (err != WSAEWOULDBLOCK) {
#else
if (status == -1) {
if (err != EINPROGRESS) {
#endif
JSOCKCLOSE(sock);
@@ -611,7 +676,7 @@ JANET_CORE_FN(cfun_net_connect,
}
}
net_sched_connect(stream);
net_sched_connect(stream, NULL);
}
JANET_CORE_FN(cfun_net_socket,
@@ -1120,7 +1185,7 @@ JANET_CORE_FN(cfun_net_setsockopt,
val.v_int = janet_getboolean(argv, 2);
optlen = sizeof(val.v_int);
} else if (st->type == JANET_NUMBER) {
#ifdef JANET_BSD
#if defined(JANET_BSD) || defined(JANET_ILLUMOS)
int v_int = janet_getinteger(argv, 2);
if (st->optname == IP_MULTICAST_TTL) {
val.v_uchar = v_int;
@@ -1211,6 +1276,8 @@ void janet_net_init(void) {
#ifdef JANET_WINDOWS
WSADATA wsaData;
janet_assert(!WSAStartup(MAKEWORD(2, 2), &wsaData), "could not start winsock");
janet_vm.connect_ex_loaded = 0;
janet_vm.connect_ex = NULL;
#endif
}

View File

@@ -40,6 +40,7 @@
#include <sys/stat.h>
#include <signal.h>
#include <locale.h>
#include <inttypes.h>
#ifdef JANET_BSD
#include <sys/sysctl.h>
@@ -142,8 +143,8 @@ static void janet_unlock_environ(void) {
#define janet_stringify(x) janet_stringify1(x)
JANET_CORE_FN(os_which,
"(os/which)",
"Check the current operating system. Returns one of:\n\n"
"(os/which &opt test)",
"Check the current operating system. If `test` is nil or unset, Returns one of:\n\n"
"* :windows\n\n"
"* :mingw\n\n"
"* :cygwin\n\n"
@@ -156,9 +157,12 @@ JANET_CORE_FN(os_which,
"* :dragonfly\n\n"
"* :bsd\n\n"
"* :posix - A POSIX compatible system (default)\n\n"
"May also return a custom keyword specified at build time.") {
janet_fixarity(argc, 0);
(void) argv;
"May also return a custom keyword specified at build time. Is `test` is truthy, will check if the current operating system equals `test` and return true if they are the same, false otherwise.") {
janet_arity(argc, 0, 1);
if (argc == 1 && janet_truthy(argv[0])) {
janet_getkeyword(argv, 0); /* Constrain to keywords */
return janet_wrap_boolean(janet_equals(argv[0], os_which(0, NULL)));
}
#if defined(JANET_OS_NAME)
return janet_ckeywordv(janet_stringify(JANET_OS_NAME));
#elif defined(JANET_MINGW)
@@ -1211,7 +1215,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) {
new_err = make_pipes(&pipe_err, 0, &pipe_errflag);
pipe_owner_flags |= JANET_PROC_OWNS_STDERR;
} else if (is_spawn && janet_keyeq(maybe_stderr, "out")) {
} else if (janet_keyeq(maybe_stderr, "out")) {
stderr_is_stdout = 1;
} else if (!janet_checktype(maybe_stderr, JANET_NIL)) {
new_err = janet_getjstream(&maybe_stderr, 0, &orig_err);
@@ -1297,6 +1301,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
}
int cp_failed = 0;
DWORD cp_error_code = 0;
if (!CreateProcess(janet_flag_at(flags, 1) ? NULL : path,
(char *) buf->data, /* Single CLI argument */
&saAttr, /* no proc inheritance */
@@ -1308,6 +1313,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
&startupInfo,
&processInfo)) {
cp_failed = 1;
cp_error_code = GetLastError();
}
if (pipe_in != JANET_HANDLE_NONE) CloseHandle(pipe_in);
@@ -1317,7 +1323,25 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
os_execute_cleanup(envp, NULL);
if (cp_failed) {
janet_panic("failed to create process");
char msgbuf[256];
msgbuf[0] = '\0';
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
cp_error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
msgbuf,
sizeof(msgbuf),
NULL);
if (!*msgbuf) snprintf(msgbuf, sizeof(msgbuf), "%" PRIu32, (uint32_t) cp_error_code);
char *c = msgbuf;
while (*c) {
if (*c == '\n' || *c == '\r') {
*c = '\0';
break;
}
c++;
}
janet_panicf("failed to create process: %s", janet_cstringv(msgbuf));
}
pHandle = processInfo.hProcess;
@@ -1870,9 +1894,8 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct
JANET_CORE_FN(os_date,
"(os/date &opt time local)",
"Returns the given time as a date struct, or the current time if `time` is not given. "
"Returns a struct with following key values. Note that all numbers are 0-indexed. "
"Date is given in UTC unless `local` is truthy, in which case the date is formatted for "
"the local timezone.\n\n"
"the local timezone. Returns a struct with following key values. Note that all numbers are 0-indexed.\n\n"
"* :seconds - number of seconds [0-61]\n\n"
"* :minutes - number of minutes [0-59]\n\n"
"* :hours - number of hours [0-23]\n\n"
@@ -1881,7 +1904,9 @@ JANET_CORE_FN(os_date,
"* :year - years since year 0 (e.g. 2019)\n\n"
"* :week-day - day of the week [0-6]\n\n"
"* :year-day - day of the year [0-365]\n\n"
"* :dst - if Day Light Savings is in effect") {
"* :dst - if Day Light Savings is in effect\n\n"
"You can set local timezone by setting TZ environment variable. "
"See tzset(<time.h>) or _tzset(<time.h>) for further details.") {
janet_arity(argc, 0, 2);
(void) argv;
struct tm t_infos;
@@ -1899,14 +1924,15 @@ JANET_CORE_FN(os_date,
return janet_wrap_struct(janet_struct_end(st));
}
#define SIZETIMEFMT 250
#define SIZETIMEFMT 250
JANET_CORE_FN(os_strftime,
"(os/strftime fmt &opt time local)",
"Format the given time as a string, or the current time if `time` is not given. "
"The time is formatted according to the same rules as the ISO C89 function strftime(). "
"The time is formatted in UTC unless `local` is truthy, in which case the date is formatted for "
"the local timezone.") {
"the local timezone. You can set local timezone by setting TZ environment variable. "
"See tzset(<time.h>) or _tzset(<time.h>) for further details.") {
janet_arity(argc, 1, 3);
const char *fmt = janet_getcstring(argv, 0);
/* ANSI X3.159-1989, section 4.12.3.5 "The strftime function" */
@@ -1914,6 +1940,9 @@ JANET_CORE_FN(os_strftime,
const char *p = fmt;
while (*p) {
if (*p++ == '%') {
if (!*p) {
janet_panic("invalid conversion specifier");
}
if (!strchr(valid, *p)) {
janet_panicf("invalid conversion specifier '%%%c'", *p);
}
@@ -1923,7 +1952,7 @@ JANET_CORE_FN(os_strftime,
struct tm t_infos;
struct tm *t_info = time_to_tm(argv, argc, 1, &t_infos);
char buf[SIZETIMEFMT];
(void)strftime(buf, SIZETIMEFMT, fmt, t_info);
(void)strftime(buf, sizeof(buf), fmt, t_info);
return janet_cstringv(buf);
}
@@ -1931,7 +1960,7 @@ static int entry_getdst(Janet env_entry) {
Janet v;
if (janet_checktype(env_entry, JANET_TABLE)) {
JanetTable *entry = janet_unwrap_table(env_entry);
v = janet_table_get(entry, janet_ckeywordv("dst"));
v = janet_table_get_keyword(entry, "dst");
} else if (janet_checktype(env_entry, JANET_STRUCT)) {
const JanetKV *entry = janet_unwrap_struct(env_entry);
v = janet_struct_get(entry, janet_ckeywordv("dst"));
@@ -1955,7 +1984,7 @@ static timeint_t entry_getint(Janet env_entry, char *field) {
Janet i;
if (janet_checktype(env_entry, JANET_TABLE)) {
JanetTable *entry = janet_unwrap_table(env_entry);
i = janet_table_get(entry, janet_ckeywordv(field));
i = janet_table_get_keyword(entry, field);
} else if (janet_checktype(env_entry, JANET_STRUCT)) {
const JanetKV *entry = janet_unwrap_struct(env_entry);
i = janet_struct_get(entry, janet_ckeywordv(field));
@@ -2550,7 +2579,7 @@ JANET_CORE_FN(os_dir,
char pattern[MAX_PATH + 1];
if (strlen(dir) > (sizeof(pattern) - 3))
janet_panicf("path too long: %s", dir);
sprintf(pattern, "%s/*", dir);
snprintf(pattern, sizeof(pattern), "%s/*", dir);
intptr_t res = _findfirst(pattern, &afile);
if (-1 == res) janet_panicv(janet_cstringv(janet_strerror(errno)));
do {
@@ -2668,10 +2697,11 @@ JANET_CORE_FN(os_open,
" * :c - create a new file (O\\_CREATE)\n"
" * :e - fail if the file exists (O\\_EXCL)\n"
" * :t - shorten an existing file to length 0 (O\\_TRUNC)\n\n"
" * :a - append to a file (O\\_APPEND on posix, FILE_APPEND_DATA on windows)\n"
"Posix-only flags:\n\n"
" * :a - append to a file (O\\_APPEND)\n"
" * :x - O\\_SYNC\n"
" * :C - O\\_NOCTTY\n\n"
" * :N - Turn off O\\_NONBLOCK and disable ev reading/writing\n\n"
"Windows-only flags:\n\n"
" * :R - share reads (FILE\\_SHARE\\_READ)\n"
" * :W - share writes (FILE\\_SHARE\\_WRITE)\n"
@@ -2681,19 +2711,24 @@ JANET_CORE_FN(os_open,
" * :F - FILE\\_ATTRIBUTE\\_OFFLINE\n"
" * :T - FILE\\_ATTRIBUTE\\_TEMPORARY\n"
" * :d - FILE\\_FLAG\\_DELETE\\_ON\\_CLOSE\n"
" * :V - Turn off FILE\\_FLAG\\_OVERLAPPED and disable ev reading/writing\n"
" * :I - set bInheritHandle on the created file so it can be passed to other processes.\n"
" * :b - FILE\\_FLAG\\_NO\\_BUFFERING\n") {
janet_arity(argc, 1, 3);
const char *path = janet_getcstring(argv, 0);
const uint8_t *opt_flags = janet_optkeyword(argv, argc, 1, (const uint8_t *) "r");
jmode_t mode = os_optmode(argc, argv, 2, 0666);
uint32_t stream_flags = 0;
int disable_stream_mode = 0;
JanetHandle fd;
#ifdef JANET_WINDOWS
(void) mode;
int inherited_handle = 0;
DWORD desiredAccess = 0;
DWORD shareMode = 0;
DWORD creationDisp = 0;
DWORD flagsAndAttributes = FILE_FLAG_OVERLAPPED;
DWORD fileFlags = FILE_FLAG_OVERLAPPED;
DWORD fileAttributes = 0;
/* We map unix-like open flags to the creationDisp parameter */
int creatUnix = 0;
#define OCREAT 1
@@ -2713,6 +2748,11 @@ JANET_CORE_FN(os_open,
stream_flags |= JANET_STREAM_WRITABLE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'a':
desiredAccess |= FILE_APPEND_DATA;
stream_flags |= JANET_STREAM_WRITABLE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'c':
creatUnix |= OCREAT;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
@@ -2735,22 +2775,29 @@ JANET_CORE_FN(os_open,
shareMode |= FILE_SHARE_WRITE;
break;
case 'H':
flagsAndAttributes |= FILE_ATTRIBUTE_HIDDEN;
fileAttributes |= FILE_ATTRIBUTE_HIDDEN;
break;
case 'O':
flagsAndAttributes |= FILE_ATTRIBUTE_READONLY;
fileAttributes |= FILE_ATTRIBUTE_READONLY;
break;
case 'F':
flagsAndAttributes |= FILE_ATTRIBUTE_OFFLINE;
fileAttributes |= FILE_ATTRIBUTE_OFFLINE;
break;
case 'T':
flagsAndAttributes |= FILE_ATTRIBUTE_TEMPORARY;
fileAttributes |= FILE_ATTRIBUTE_TEMPORARY;
break;
case 'd':
flagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE;
fileFlags |= FILE_FLAG_DELETE_ON_CLOSE;
break;
case 'b':
flagsAndAttributes |= FILE_FLAG_NO_BUFFERING;
fileFlags |= FILE_FLAG_NO_BUFFERING;
break;
case 'I':
inherited_handle = 1;
break;
case 'V':
fileFlags &= ~FILE_FLAG_OVERLAPPED;
disable_stream_mode = 1;
break;
/* we could potentially add more here -
* https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
@@ -2776,7 +2823,16 @@ JANET_CORE_FN(os_open,
creationDisp = TRUNCATE_EXISTING;
break;
}
fd = CreateFileA(path, desiredAccess, shareMode, NULL, creationDisp, flagsAndAttributes, NULL);
if (fileAttributes == 0) {
fileAttributes = FILE_ATTRIBUTE_NORMAL;
}
SECURITY_ATTRIBUTES saAttr;
memset(&saAttr, 0, sizeof(saAttr));
saAttr.nLength = sizeof(saAttr);
if (inherited_handle) {
saAttr.bInheritHandle = TRUE; /* Needed to do interesting things with file */
}
fd = CreateFileA(path, desiredAccess, shareMode, &saAttr, creationDisp, fileFlags | fileAttributes, NULL);
if (fd == INVALID_HANDLE_VALUE) janet_panicv(janet_ev_lasterr());
#else
int open_flags = O_NONBLOCK;
@@ -2820,6 +2876,10 @@ JANET_CORE_FN(os_open,
case 'a':
open_flags |= O_APPEND;
break;
case 'N':
open_flags &= ~O_NONBLOCK;
disable_stream_mode = 1;
break;
}
}
/* If both read and write, fix up to O_RDWR */
@@ -2836,7 +2896,7 @@ JANET_CORE_FN(os_open,
} while (fd == -1 && errno == EINTR);
if (fd == -1) janet_panicv(janet_ev_lasterr());
#endif
return janet_wrap_abstract(janet_stream(fd, stream_flags, NULL));
return janet_wrap_abstract(janet_stream(fd, disable_stream_mode ? 0 : stream_flags, NULL));
}
JANET_CORE_FN(os_pipe,

View File

@@ -194,6 +194,41 @@ tail:
return memcmp(text, rule + 2, len) ? NULL : text + len;
}
case RULE_DEBUG: {
char buffer[32] = {0};
size_t len = (size_t)(s->outer_text_end - text);
memcpy(buffer, text, (len > 31 ? 31 : len));
janet_eprintf("?? at [%s] (index %d)\n", buffer, (int32_t)(text - s->text_start));
int has_color = janet_truthy(janet_dyn("err-color"));
/* Accumulate buffer */
if (s->scratch->count) {
janet_eprintf("accumulate buffer: %v\n", janet_wrap_buffer(s->scratch));
}
/* Normal captures */
if (s->captures->count) {
janet_eprintf("stack [%d]:\n", s->captures->count);
for (int32_t i = 0; i < s->captures->count; i++) {
if (has_color) {
janet_eprintf(" [%d]: %M\n", i, s->captures->data[i]);
} else {
janet_eprintf(" [%d]: %m\n", i, s->captures->data[i]);
}
}
}
/* Tagged captures */
if (s->tagged_captures->count) {
janet_eprintf("tag stack [%d]:\n", s->tagged_captures->count);
for (int32_t i = 0; i < s->tagged_captures->count; i++) {
if (has_color) {
janet_eprintf(" [%d] tag=%d: %M\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]);
} else {
janet_eprintf(" [%d] tag=%d: %m\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]);
}
}
}
return text;
}
case RULE_NCHAR: {
uint32_t n = rule[1];
return (text + n > s->text_end) ? NULL : text + n;
@@ -1245,6 +1280,14 @@ static void spec_constant(Builder *b, int32_t argc, const Janet *argv) {
emit_2(r, RULE_CONSTANT, emit_constant(b, argv[0]), tag);
}
static void spec_debug(Builder *b, int32_t argc, const Janet *argv) {
peg_arity(b, argc, 0, 0);
Reserve r = reserve(b, 1);
uint32_t empty = 0;
(void) argv;
emit_rule(r, RULE_DEBUG, 0, &empty);
}
static void spec_replace(Builder *b, int32_t argc, const Janet *argv) {
peg_arity(b, argc, 2, 3);
Reserve r = reserve(b, 4);
@@ -1349,6 +1392,7 @@ static const SpecialPair peg_specials[] = {
{"<-", spec_capture},
{">", spec_look},
{"?", spec_opt},
{"??", spec_debug},
{"accumulate", spec_accumulate},
{"any", spec_any},
{"argument", spec_argument},
@@ -1363,6 +1407,7 @@ static const SpecialPair peg_specials[] = {
{"cmt", spec_matchtime},
{"column", spec_column},
{"constant", spec_constant},
{"debug", spec_debug},
{"drop", spec_drop},
{"error", spec_error},
{"group", spec_group},
@@ -1639,6 +1684,10 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
case RULE_LITERAL:
i += 2 + ((rule[1] + 3) >> 2);
break;
case RULE_DEBUG:
/* [0 words] */
i += 1;
break;
case RULE_NCHAR:
case RULE_NOTNCHAR:
case RULE_RANGE:
@@ -1854,8 +1903,8 @@ static JanetPeg *compile_peg(Janet x) {
JANET_CORE_FN(cfun_peg_compile,
"(peg/compile peg)",
"Compiles a peg source data structure into a <core/peg>. This will speed up matching "
"if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to supplement "
"the grammar of the peg for otherwise undefined peg keywords.") {
"if the same peg will be used multiple times. `(dyn :peg-grammar)` replaces "
"`default-peg-grammar` for the grammar of the peg.") {
janet_fixarity(argc, 1);
JanetPeg *peg = compile_peg(argv[0]);
return janet_wrap_abstract(peg);

View File

@@ -487,6 +487,7 @@ static const char *janet_pretty_colors[] = {
#define JANET_PRETTY_DICT_ONELINE 4
#define JANET_PRETTY_IND_ONELINE 10
#define JANET_PRETTY_DICT_LIMIT 30
#define JANET_PRETTY_DICT_KEYSORT_LIMIT 2000
#define JANET_PRETTY_ARRAY_LIMIT 160
/* Helper for pretty printing */
@@ -625,55 +626,78 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
if (S->depth == 0) {
janet_buffer_push_cstring(S->buffer, "...");
} else {
int32_t i = 0, len = 0, cap = 0;
int32_t len = 0, cap = 0;
const JanetKV *kvs = NULL;
janet_dictionary_view(x, &kvs, &len, &cap);
if (!istable && !(S->flags & JANET_PRETTY_ONELINE) && len >= JANET_PRETTY_DICT_ONELINE)
janet_buffer_push_u8(S->buffer, ' ');
if (is_dict_value && len >= JANET_PRETTY_DICT_ONELINE) print_newline(S, 0);
int32_t ks_start = S->keysort_start;
/* Ensure buffer is large enough to sort keys. */
int truncated = 0;
int64_t mincap = (int64_t) len + (int64_t) ks_start;
if (mincap > INT32_MAX) {
truncated = 1;
len = 0;
mincap = ks_start;
}
if (S->keysort_capacity < mincap) {
if (mincap >= INT32_MAX / 2) {
S->keysort_capacity = INT32_MAX;
} else {
S->keysort_capacity = (int32_t)(mincap * 2);
/* Shortcut for huge dictionaries, don't bother sorting keys */
if (len > JANET_PRETTY_DICT_KEYSORT_LIMIT) {
if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) {
len = JANET_PRETTY_DICT_LIMIT;
truncated = 1;
}
S->keysort_buffer = janet_srealloc(S->keysort_buffer, sizeof(int32_t) * S->keysort_capacity);
if (NULL == S->keysort_buffer) {
JANET_OUT_OF_MEMORY;
int32_t j = 0;
for (int32_t i = 0; i < len; i++) {
while (janet_checktype(kvs[j].key, JANET_NIL)) j++;
if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE);
janet_pretty_one(S, kvs[j].key, 0);
janet_buffer_push_u8(S->buffer, ' ');
janet_pretty_one(S, kvs[j].value, 1);
j++;
}
}
if (truncated) {
print_newline(S, 0);
janet_buffer_push_cstring(S->buffer, "...");
}
} else {
/* Sorted keys dictionaries */
janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start);
S->keysort_start += len;
if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) {
len = JANET_PRETTY_DICT_LIMIT;
truncated = 1;
}
/* Ensure buffer is large enough to sort keys. */
int64_t mincap = (int64_t) len + (int64_t) ks_start;
if (mincap > INT32_MAX) {
truncated = 1;
len = 0;
mincap = ks_start;
}
for (i = 0; i < len; i++) {
if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE);
int32_t j = S->keysort_buffer[i + ks_start];
janet_pretty_one(S, kvs[j].key, 0);
janet_buffer_push_u8(S->buffer, ' ');
janet_pretty_one(S, kvs[j].value, 1);
}
if (S->keysort_capacity < mincap) {
if (mincap >= INT32_MAX / 2) {
S->keysort_capacity = INT32_MAX;
} else {
S->keysort_capacity = (int32_t)(mincap * 2);
}
S->keysort_buffer = janet_srealloc(S->keysort_buffer, sizeof(int32_t) * S->keysort_capacity);
if (NULL == S->keysort_buffer) {
JANET_OUT_OF_MEMORY;
}
}
if (truncated) {
print_newline(S, 0);
janet_buffer_push_cstring(S->buffer, "...");
}
janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start);
S->keysort_start += len;
if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) {
len = JANET_PRETTY_DICT_LIMIT;
truncated = 1;
}
for (int32_t i = 0; i < len; i++) {
if (i) print_newline(S, len < JANET_PRETTY_DICT_ONELINE);
int32_t j = S->keysort_buffer[i + ks_start];
janet_pretty_one(S, kvs[j].key, 0);
janet_buffer_push_u8(S->buffer, ' ');
janet_pretty_one(S, kvs[j].value, 1);
}
if (truncated) {
print_newline(S, 0);
janet_buffer_push_cstring(S->buffer, "...");
}
}
S->keysort_start = ks_start;
}
S->indent -= 2;
@@ -897,7 +921,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) {
case 's':
case 'S': {
const char *str = va_arg(args, const char *);
int32_t len = c[-1] == 's'
int32_t len = (c[-1] == 's')
? (int32_t) strlen(str)
: janet_string_length((JanetString) str);
if (form[2] == '\0')

View File

@@ -307,14 +307,14 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv)
/* Add attributes to a global def or var table */
static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) {
int32_t i;
JanetTable *tab = janet_table(2);
const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL
? ((const char *)janet_unwrap_symbol(argv[0]))
: "<multiple bindings>";
if (argn < 2) {
janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind));
return NULL;
}
JanetTable *tab = janet_table(2);
const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL
? ((const char *)janet_unwrap_symbol(argv[0]))
: "<multiple bindings>";
for (i = 1; i < argn - 1; i++) {
Janet attr = argv[i];
switch (janet_type(attr)) {
@@ -443,8 +443,7 @@ static int varleaf(
JanetSlot refslot;
JanetTable *entry = janet_table_clone(reftab);
Janet redef_kw = janet_ckeywordv("redef");
int is_redef = janet_truthy(janet_table_get(c->env, redef_kw));
int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef"));
JanetArray *ref;
JanetBinding old_binding;
@@ -464,7 +463,7 @@ static int varleaf(
janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0);
return 1;
} else {
int no_unused = reftab && reftab->count && janet_truthy(janet_table_get(reftab, janet_ckeywordv("unused")));
int no_unused = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "unused"));
return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused);
}
}
@@ -472,7 +471,7 @@ static int varleaf(
static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) {
if (!(c->scope->flags & JANET_SCOPE_TOP) && attr_table && attr_table->count) {
/* A macro is a normal lint, other metadata is a strict lint */
if (janet_truthy(janet_table_get(attr_table, janet_ckeywordv("macro")))) {
if (janet_truthy(janet_table_get_keyword(attr_table, "macro"))) {
janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes");
}
}
@@ -511,9 +510,8 @@ static int defleaf(
janet_table_put(entry, janet_ckeywordv("source-map"),
janet_wrap_tuple(janetc_make_sourcemap(c)));
Janet redef_kw = janet_ckeywordv("redef");
int is_redef = janet_truthy(janet_table_get(c->env, redef_kw));
if (is_redef) janet_table_put(entry, redef_kw, janet_wrap_true());
int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef"));
if (is_redef) janet_table_put(entry, janet_ckeywordv("redef"), janet_wrap_true());
if (is_redef) {
JanetBinding binding = janet_resolve_ext(c->env, sym);
@@ -536,7 +534,7 @@ static int defleaf(
/* Add env entry to env */
janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry));
}
int no_unused = tab && tab->count && janet_truthy(janet_table_get(tab, janet_ckeywordv("unused")));
int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused"));
return namelocal(c, sym, 0, s, no_unused);
}
@@ -686,8 +684,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) {
/* Write jumps - only add jump lengths if jump actually emitted */
labeld = janet_v_count(c->buffer);
c->buffer[labeljr] |= (labelr - labeljr) << 16;
if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8;
if (labeljr < labeld) {
c->buffer[labeljr] |= (labelr - labeljr) << 16;
if (!tail && labeljd < labeld) c->buffer[labeljd] |= (labeld - labeljd) << 8;
}
if (tail) target.flags |= JANET_SLOT_RETURNED;
return target;
@@ -909,7 +909,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv)
janetc_regalloc_freetemp(&c->scope->ra, tempself, JANETC_REGTEMP_0);
/* Compile function */
JanetFuncDef *def = janetc_pop_funcdef(c);
def->name = janet_cstring("_while");
def->name = janet_cstring("while");
janet_def_addflags(def);
int32_t defindex = janetc_addfuncdef(c, def);
/* And then load the closure and call it. */
@@ -1076,6 +1076,14 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
}
}
/* Compile named arguments */
if (namedargs) {
Janet param = janet_wrap_table(named_table);
destructure(c, param, named_slot, defleaf, NULL);
janetc_freeslot(c, named_slot);
janet_v_free(named_params);
}
/* Compile destructed params */
int32_t j = 0;
for (i = 0; i < paramcount; i++) {
@@ -1089,14 +1097,6 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
}
janet_v_free(destructed_params);
/* Compile named arguments */
if (namedargs) {
Janet param = janet_wrap_table(named_table);
destructure(c, param, named_slot, defleaf, NULL);
janetc_freeslot(c, named_slot);
janet_v_free(named_params);
}
max_arity = (vararg || allow_extra) ? INT32_MAX : arity;
if (!seenopt) min_arity = arity;
@@ -1139,8 +1139,12 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
def->arity = arity;
def->min_arity = min_arity;
def->max_arity = max_arity;
if (named_table != NULL) {
def->named_args_count = named_table->count;
}
if (vararg) def->flags |= JANET_FUNCDEF_FLAG_VARARG;
if (structarg) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG;
if (namedargs) def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */
janet_def_addflags(def);

View File

@@ -182,6 +182,8 @@ struct JanetVM {
JanetTable signal_handlers;
#ifdef JANET_WINDOWS
void **iocp;
void *connect_ex; /* MSWsock extension if available */
int connect_ex_loaded;
#elif defined(JANET_EV_EPOLL)
pthread_attr_t new_thread_attr;
JanetHandle selfpipe[2];

View File

@@ -155,6 +155,17 @@ Janet janet_table_get(JanetTable *t, Janet key) {
return janet_wrap_nil();
}
/* Used internally for compiler stuff */
Janet janet_table_get_keyword(JanetTable *t, const char *keyword) {
int32_t keyword_len = (int32_t) strlen(keyword);
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
JanetKV *bucket = (JanetKV *) janet_dict_find_keyword(t->data, t->capacity, (const uint8_t *) keyword, keyword_len);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
return bucket->value;
}
return janet_wrap_nil();
}
/* Get a value out of the table, and record which prototype it was from. */
Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which) {
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {

View File

@@ -268,7 +268,7 @@ int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len) {
return (int32_t) hash;
}
/* Calculate next power of 2. May overflow. If n is 0,
/* Calculate next power of 2. May overflow. If n < 0,
* will return 0. */
int32_t janet_tablen(int32_t n) {
if (n < 0) return 0;
@@ -321,6 +321,54 @@ const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) {
return first_bucket;
}
/* Helper to find a keyword, symbol, or string in a Janet struct or table without allocating
* memory or needing to find interned symbols */
const JanetKV *janet_dict_find_keyword(
const JanetKV *buckets, int32_t cap,
const uint8_t *cstr, int32_t cstr_len) {
int32_t hash = janet_string_calchash(cstr, cstr_len);
int32_t index = janet_maphash(cap, hash);
int32_t i;
const JanetKV *first_bucket = NULL;
/* Higher half */
for (i = index; i < cap; i++) {
const JanetKV *kv = buckets + i;
if (janet_checktype(kv->key, JANET_NIL)) {
if (janet_checktype(kv->value, JANET_NIL)) {
return kv;
} else if (NULL == first_bucket) {
first_bucket = kv;
}
} else if (janet_checktype(kv->key, JANET_KEYWORD)) {
/* Works for symbol and keyword, too */
JanetString str = janet_unwrap_string(kv->key);
int32_t len = janet_string_length(str);
if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) {
return buckets + i;
}
}
}
/* Lower half */
for (i = 0; i < index; i++) {
const JanetKV *kv = buckets + i;
if (janet_checktype(kv->key, JANET_NIL)) {
if (janet_checktype(kv->value, JANET_NIL)) {
return kv;
} else if (NULL == first_bucket) {
first_bucket = kv;
}
} else if (janet_checktype(kv->key, JANET_KEYWORD)) {
/* Works for symbol and keyword, too */
JanetString str = janet_unwrap_string(kv->key);
int32_t len = janet_string_length(str);
if (hash == janet_string_hash(str) && len == cstr_len && !memcmp(str, cstr, len)) {
return buckets + i;
}
}
}
return first_bucket;
}
/* Get a value from a janet struct or table. */
Janet janet_dictionary_get(const JanetKV *data, int32_t cap, Janet key) {
const JanetKV *kv = janet_dict_find(data, cap, key);
@@ -628,8 +676,11 @@ JanetBinding janet_binding_from_entry(Janet entry) {
return binding;
entry_table = janet_unwrap_table(entry);
/* deprecation check */
Janet deprecate = janet_table_get(entry_table, janet_ckeywordv("deprecated"));
Janet deprecate = janet_table_get_keyword(entry_table, "deprecated");
int macro = janet_truthy(janet_table_get_keyword(entry_table, "macro"));
Janet value = janet_table_get_keyword(entry_table, "value");
Janet ref = janet_table_get_keyword(entry_table, "ref");
if (janet_checktype(deprecate, JANET_KEYWORD)) {
JanetKeyword depkw = janet_unwrap_keyword(deprecate);
if (!janet_cstrcmp(depkw, "relaxed")) {
@@ -643,11 +694,8 @@ JanetBinding janet_binding_from_entry(Janet entry) {
binding.deprecation = JANET_BINDING_DEP_NORMAL;
}
int macro = janet_truthy(janet_table_get(entry_table, janet_ckeywordv("macro")));
Janet value = janet_table_get(entry_table, janet_ckeywordv("value"));
Janet ref = janet_table_get(entry_table, janet_ckeywordv("ref"));
int ref_is_valid = janet_checktype(ref, JANET_ARRAY);
int redef = ref_is_valid && janet_truthy(janet_table_get(entry_table, janet_ckeywordv("redef")));
int redef = ref_is_valid && janet_truthy(janet_table_get_keyword(entry_table, "redef"));
if (macro) {
binding.value = redef ? ref : value;

View File

@@ -66,42 +66,72 @@
/* Utils */
uint32_t janet_hash_mix(uint32_t input, uint32_t more);
#define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1))
int janet_valid_utf8(const uint8_t *str, int32_t len);
int janet_is_symbol_char(uint8_t c);
extern const char janet_base64[65];
int32_t janet_array_calchash(const Janet *array, int32_t len);
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len);
int32_t janet_string_calchash(const uint8_t *str, int32_t len);
int32_t janet_tablen(int32_t n);
void safe_memcpy(void *dest, const void *src, size_t len);
void janet_buffer_push_types(JanetBuffer *buffer, int types);
const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key);
void janet_memempty(JanetKV *mem, int32_t count);
void *janet_memalloc_empty(int32_t count);
JanetTable *janet_get_core_table(const char *name);
void janet_def_addflags(JanetFuncDef *def);
void janet_buffer_dtostr(JanetBuffer *buffer, double x);
const char *janet_strerror(int e);
const void *janet_strbinsearch(
const void *tab,
size_t tabcount,
size_t itemsize,
const uint8_t *key);
void janet_buffer_format(
JanetBuffer *b,
const char *strfrmt,
int32_t argstart,
int32_t argc,
Janet *argv);
Janet janet_next_impl(Janet ds, Janet key, int is_interpreter);
JanetBinding janet_binding_from_entry(Janet entry);
JanetByteView janet_text_substitution(
Janet *subst,
const uint8_t *bytes,
uint32_t len,
JanetArray *extra_args);
const JanetKV *janet_dict_find_keyword(
const JanetKV *buckets,
int32_t cap,
const uint8_t *cstr,
int32_t cstr_len);
Janet janet_table_get_keyword(JanetTable *t, const char *keyword);
/* Registry functions */
void janet_registry_put(
JanetCFunction key,
@@ -173,6 +203,21 @@ char *get_processed_name(const char *name);
#define RETRY_EINTR(RC, CALL) do { (RC) = CALL; } while((RC) < 0 && errno == EINTR)
#endif
#ifdef JANET_EV
#ifdef JANET_WINDOWS
#include <winsock2.h>
#include <windows.h>
#include <io.h>
typedef struct {
union {
OVERLAPPED overlapped;
WSAOVERLAPPED wsaoverlapped;
} as;
uint32_t bytes_transferred;
} JanetOverlapped;
#endif
#endif
/* Initialize builtin libraries */
void janet_lib_io(JanetTable *env);
void janet_lib_math(JanetTable *env);

View File

@@ -335,10 +335,9 @@ int32_t janet_hash(Janet x) {
} as;
as.d = janet_unwrap_number(x);
as.d += 0.0; /* normalize negative 0 */
uint32_t lo = (uint32_t)(as.u & 0xFFFFFFFF);
as.u = murmur64(as.u);
uint32_t hi = (uint32_t)(as.u >> 32);
uint32_t hilo = (hi ^ lo) * 2654435769u;
hash = (int32_t)((hilo << 16) | (hilo >> 16));
hash = (int32_t)hi;
break;
}
case JANET_ABSTRACT: {
@@ -496,7 +495,7 @@ Janet janet_in(Janet ds, Janet key) {
if (!(type->get)(janet_unwrap_abstract(ds), key, &value))
janet_panicf("key %v not found in %v ", key, ds);
} else {
janet_panicf("no getter for %v ", ds);
janet_panicf("no getter for %v", ds);
}
break;
}
@@ -623,7 +622,7 @@ Janet janet_getindex(Janet ds, int32_t index) {
if (!(type->get)(janet_unwrap_abstract(ds), janet_wrap_integer(index), &value))
value = janet_wrap_nil();
} else {
janet_panicf("no getter for %v ", ds);
janet_panicf("no getter for %v", ds);
}
break;
}
@@ -725,6 +724,9 @@ void janet_putindex(Janet ds, int32_t index, Janet value) {
JanetArray *array = janet_unwrap_array(ds);
if (index >= array->count) {
janet_array_ensure(array, index + 1, 2);
for (int32_t i = array->count; i < index + 1; i++) {
array->data[i] = janet_wrap_nil();
}
array->count = index + 1;
}
array->data[index] = value;
@@ -736,6 +738,7 @@ void janet_putindex(Janet ds, int32_t index, Janet value) {
janet_panicf("can only put integers in buffers, got %v", value);
if (index >= buffer->count) {
janet_buffer_ensure(buffer, index + 1, 2);
memset(buffer->data + buffer->count, 0, index + 1 - buffer->count);
buffer->count = index + 1;
}
buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF);
@@ -768,7 +771,11 @@ void janet_put(Janet ds, Janet key, Janet value) {
JanetArray *array = janet_unwrap_array(ds);
int32_t index = getter_checkint(type, key, INT32_MAX - 1);
if (index >= array->count) {
janet_array_setcount(array, index + 1);
janet_array_ensure(array, index + 1, 2);
for (int32_t i = array->count; i < index + 1; i++) {
array->data[i] = janet_wrap_nil();
}
array->count = index + 1;
}
array->data[index] = value;
break;
@@ -779,7 +786,9 @@ void janet_put(Janet ds, Janet key, Janet value) {
if (!janet_checkint(value))
janet_panicf("can only put integers in buffers, got %v", value);
if (index >= buffer->count) {
janet_buffer_setcount(buffer, index + 1);
janet_buffer_ensure(buffer, index + 1, 2);
memset(buffer->data + buffer->count, 0, index + 1 - buffer->count);
buffer->count = index + 1;
}
buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF);
break;

View File

@@ -129,7 +129,9 @@
if (!janet_checktype(op1, JANET_NUMBER)) {\
vm_commit();\
Janet _argv[2] = { op1, janet_wrap_number(CS) };\
stack[A] = janet_mcall(#op, 2, _argv);\
Janet a = janet_mcall(#op, 2, _argv);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
} else {\
double x1 = janet_unwrap_number(op1);\
@@ -143,7 +145,9 @@
if (!janet_checktype(op1, JANET_NUMBER)) {\
vm_commit();\
Janet _argv[2] = { op1, janet_wrap_number(CS) };\
stack[A] = janet_mcall(#op, 2, _argv);\
Janet a = janet_mcall(#op, 2, _argv);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
} else {\
double y1 = janet_unwrap_number(op1);\
@@ -166,7 +170,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\
Janet a = janet_binop_call(#op, "r" #op, op1, op2);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -186,7 +192,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\
Janet a = janet_binop_call(#op, "r" #op, op1, op2);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -203,7 +211,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_wrap_boolean(janet_compare(op1, op2) op 0);\
Janet a = janet_wrap_boolean(janet_compare(op1, op2) op 0);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -217,7 +227,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\
Janet a = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -710,7 +722,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_binop_call("div", "rdiv", op1, op2);
Janet a = janet_binop_call("div", "rdiv", op1, op2);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -730,7 +744,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_binop_call("mod", "rmod", op1, op2);
Janet a = janet_binop_call("mod", "rmod", op1, op2);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -745,7 +761,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_binop_call("%", "r%", op1, op2);
Janet a = janet_binop_call("%", "r%", op1, op2);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -766,7 +784,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_unary_call("~", op);
Janet a = janet_unary_call("~", op);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -872,8 +892,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
stack[A] = janet_wrap_boolean(!janet_checktype(stack[B], JANET_NUMBER) || (janet_unwrap_number(stack[B]) != (double) CS));
vm_pcnext();
VM_OP(JOP_COMPARE)
stack[A] = janet_wrap_integer(janet_compare(stack[B], stack[C]));
VM_OP(JOP_COMPARE) {
Janet a = janet_wrap_integer(janet_compare(stack[B], stack[C]));
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_NEXT)
@@ -1104,11 +1127,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
}
fiber->child = child;
JanetSignal sig = janet_continue_no_check(child, stack[C], &retreg);
stack = fiber->data + fiber->frame;
if (sig != JANET_SIGNAL_OK && !(child->flags & (1 << sig))) {
vm_return(sig, retreg);
}
fiber->child = NULL;
stack = fiber->data + fiber->frame;
stack[A] = retreg;
vm_checkgc_pcnext();
}
@@ -1157,6 +1180,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_commit();
fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL;
janet_put(stack[A], stack[B], stack[C]);
stack = fiber->data + fiber->frame;
fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL;
vm_checkgc_pcnext();
@@ -1164,27 +1188,44 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_commit();
fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL;
janet_putindex(stack[A], C, stack[B]);
stack = fiber->data + fiber->frame;
fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL;
vm_checkgc_pcnext();
VM_OP(JOP_IN)
vm_commit();
stack[A] = janet_in(stack[B], stack[C]);
{
Janet a = janet_in(stack[B], stack[C]);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_GET)
vm_commit();
stack[A] = janet_get(stack[B], stack[C]);
{
Janet a = janet_get(stack[B], stack[C]);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_GET_INDEX)
vm_commit();
stack[A] = janet_getindex(stack[B], C);
{
Janet a = janet_getindex(stack[B], C);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_LENGTH)
vm_commit();
stack[A] = janet_lengthv(stack[E]);
{
Janet a = janet_lengthv(stack[E]);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_MAKE_ARRAY) {
@@ -1518,6 +1559,15 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
}
}
/* If this is a nested continue (root_fiber already set), root the fiber
* so it survives GC. janet_collect only marks root_fiber, so without
* this a nested fiber (e.g., from janet_pcall in a C function) would be
* invisible to GC and could be collected while actively running. */
int fiber_rooted = (janet_vm.root_fiber != NULL);
if (fiber_rooted) {
janet_gcroot(janet_wrap_fiber(fiber));
}
/* Save global state */
JanetTryState tstate;
JanetSignal sig = janet_try(&tstate);
@@ -1533,6 +1583,9 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
if (janet_vm.root_fiber == fiber) janet_vm.root_fiber = NULL;
janet_fiber_set_status(fiber, sig);
janet_restore(&tstate);
if (fiber_rooted) {
janet_gcunroot(janet_wrap_fiber(fiber));
}
fiber->last_value = tstate.payload;
*out = tstate.payload;

View File

@@ -1074,6 +1074,7 @@ struct JanetAbstractHead {
#define JANET_FUNCDEF_FLAG_HASSOURCEMAP 0x800000
#define JANET_FUNCDEF_FLAG_STRUCTARG 0x1000000
#define JANET_FUNCDEF_FLAG_HASCLOBITSET 0x2000000
#define JANET_FUNCDEF_FLAG_NAMEDARGS 0x4000000
#define JANET_FUNCDEF_FLAG_TAG 0xFFFF
/* Source mapping structure for a bytecode instruction */
@@ -1115,6 +1116,7 @@ struct JanetFuncDef {
int32_t environments_length;
int32_t defs_length;
int32_t symbolmap_length;
int32_t named_args_count;
};
/* A function environment */
@@ -1138,6 +1140,7 @@ struct JanetFunction {
JanetFuncEnv *envs[];
};
/* Use to read Janet data structures into memory from source code */
typedef struct JanetParseState JanetParseState;
typedef struct JanetParser JanetParser;
@@ -1187,7 +1190,10 @@ typedef struct {
const JanetAbstractType *at;
} JanetMarshalContext;
/* Defines an abstract type */
/* Defines an abstract type. Use a const pointer to one of these structures
* when creating abstract types. The memory for this pointer should not be free
* until after janet_deinit is called. Usually, this means declaring JanetAbstractType's
* as const data at file scope, and creating instances with janet_abstract(&MyType, sizeof(MyTypeStruct)); */
struct JanetAbstractType {
const char *name;
int (*gc)(void *data, size_t len);
@@ -1275,6 +1281,7 @@ typedef struct JanetFile JanetFile;
struct JanetFile {
FILE *file;
int32_t flags;
size_t vbufsize;
};
/* For janet_try and janet_restore */
@@ -1439,6 +1446,7 @@ JANET_API void janet_loop(void);
* } else {
* janet_schedule(interrupted_fiber, janet_wrap_nil());
* }
* janet_interpreter_interrupt_handled(NULL);
* }
* }
*
@@ -1478,9 +1486,18 @@ JANET_API void janet_ev_dec_refcount(void);
JANET_API void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size);
JANET_API void *janet_abstract_end_threaded(void *x);
JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size);
/* Allow reference counting on threaded abstract types. This is useful when external code , either
* in the current OS thread or in a different OS thread, takes a pointer to this abstract type. The programmer
* should tncrement the reference count when taking the pointer, and then decrement and possibly cleanup and free
* if the reference count is 0. */
JANET_API int32_t janet_abstract_incref(void *abst);
JANET_API int32_t janet_abstract_decref(void *abst);
/* If this returns 0, *abst will be deinitialized and freed. Useful shorthand if there is no other cleanup for
* this abstract type before calling `janet_free` on it's backing memory. */
JANET_API int32_t janet_abstract_decref_maybe_free(void *abst);
/* Expose channel utilities */
JANET_API JanetChannel *janet_channel_make(uint32_t limit);
JANET_API JanetChannel *janet_channel_make_threaded(uint32_t limit);
@@ -1489,7 +1506,7 @@ JANET_API JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_
JANET_API int janet_channel_give(JanetChannel *channel, Janet x);
JANET_API int janet_channel_take(JanetChannel *channel, Janet *out);
/* Expose some OS sync primitives */
/* Expose some OS sync primitives - mutexes and reader-writer locks */
JANET_API size_t janet_os_mutex_size(void);
JANET_API size_t janet_os_rwlock_size(void);
JANET_API void janet_os_mutex_init(JanetOSMutex *mutex);
@@ -1514,6 +1531,9 @@ JANET_API Janet janet_ev_lasterr(void);
* We could just use a pointer but this prevents malloc/free in the common case
* of only a handful of arguments. */
typedef struct {
#ifdef JANET_WINDOWS
char padding[48]; /* On windows, used for OVERLAPPED storage */
#endif
int tag;
int argi;
void *argp;
@@ -1557,7 +1577,8 @@ JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGeneric
/* Callback used by janet_ev_threaded_await */
JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value);
/* Read async from a stream */
/* Read async from a stream. These function yield to the event-loop with janet_await(), and so do not return.
* When the fiber is resumed, the fiber will simply continue to the next Janet abstract machine instruction. */
JANET_NO_RETURN JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes);
JANET_NO_RETURN JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes);
#ifdef JANET_NET
@@ -1566,7 +1587,8 @@ JANET_NO_RETURN JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuff
JANET_NO_RETURN JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags);
#endif
/* Write async to a stream */
/* Write async to a stream. These function yield to the event-loop with janet_await(), and so do not return.
* When the fiber is resumed, the fiber will simply continue to the next Janet abstract machine instruction. */
JANET_NO_RETURN JANET_API void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf);
JANET_NO_RETURN JANET_API void janet_ev_write_string(JanetStream *stream, JanetString str);
#ifdef JANET_NET
@@ -1578,17 +1600,63 @@ JANET_NO_RETURN JANET_API void janet_ev_sendto_string(JanetStream *stream, Janet
#endif
/* Parsing */
/* Parsing.
*
* E.g.
*
* JanetParser parser;
* janet_parser_init(&parser);
* for (int i = 0; i < source_code_length + 1; i++) {
* if (i >= source_code_length) {
* janet_parser_eof(&parser);
* } else {
* janet_parser_consume(&parser, source_code[i]);
* }
* while (janet_parser_has_more(&parser)) {
* Janet x = janet_parser_produce(&parser);
* janet_printf("got value: %v\n", x);
* }
* switch (janet_parser_status(&parser)) {
* case JANET_PARSE_PENDING: break;
* case JANET_PARSE_ERROR: janet_eprintf("error: %s\n", janet_parser_error(&parser)); break;
* case JANET_PARSE_ROOT: break;
* case JANET_PARSE_DEAD: break;
* }
* }
* janet_parser_deinit(&parser);
*
* */
extern JANET_API const JanetAbstractType janet_parser_type;
/* Construct/destruct a parser. Parsers can be allocated on the stack or the heap. */
JANET_API void janet_parser_init(JanetParser *parser);
JANET_API void janet_parser_deinit(JanetParser *parser);
/* Feed bytes into the parser. Check the parser state after every byte to handle errors. */
JANET_API void janet_parser_consume(JanetParser *parser, uint8_t c);
/* Check the current status of the parser */
JANET_API enum JanetParserStatus janet_parser_status(JanetParser *parser);
/* Produce a value from the parser. Call this when janet_parser_has_more(&parser) is non-zero. */
JANET_API Janet janet_parser_produce(JanetParser *parser);
/* Produce a value from the parser, wrapped in a tuple. The tuple is used to carry the source mapping information of the
* top level form, such as a line number or symbol. */
JANET_API Janet janet_parser_produce_wrapped(JanetParser *parser);
/* When there is an error while parsing (janet_parser_status(&parser) == JANET_PARSE_ERROR), get a nice error string.
* Calling this will also flush the parser. */
JANET_API const char *janet_parser_error(JanetParser *parser);
/* If there is a parsing error, flush the parser to set the state back to empty.
* This allows for better error recover and less confusing error messages on bad syntax deep inside nested data structures. */
JANET_API void janet_parser_flush(JanetParser *parser);
/* Indicate that there is no more source code */
JANET_API void janet_parser_eof(JanetParser *parser);
/* If non-zero, the parser has values ready to be produced. */
JANET_API int janet_parser_has_more(JanetParser *parser);
/* Assembly */
@@ -1632,7 +1700,10 @@ JANET_API JanetCompileResult janet_compile_lint(
JANET_API JanetTable *janet_core_env(JanetTable *replacements);
JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements);
/* Execute strings */
/* Execute strings.
*
* These functions wrap parsing, compilation, and evalutation into convenient functions.
* */
#define JANET_DO_ERROR_RUNTIME 0x01
#define JANET_DO_ERROR_COMPILE 0x02
#define JANET_DO_ERROR_PARSE 0x04
@@ -1826,21 +1897,41 @@ JANET_API JanetTable *janet_env_lookup(JanetTable *env);
JANET_API void janet_env_lookup_into(JanetTable *renv, JanetTable *env, const char *prefix, int recurse);
/* GC */
JANET_API void janet_mark(Janet x);
JANET_API void janet_sweep(void);
/* The main interface to garbage collection. Call this to do a full mark and sweep cleanup. */
JANET_API void janet_collect(void);
JANET_API void janet_clear_memory(void);
/* Add "roots" to the garbage collector to prevent the runtime from freeing objects.
* This is only needed if code outside of Janet keeps references to Janet values */
JANET_API void janet_gcroot(Janet root);
JANET_API int janet_gcunroot(Janet root);
JANET_API int janet_gcunrootall(Janet root);
/* Allow disabling garbage collection temporarily or for certain sections of code.
* this is a very cheap operation. */
JANET_API int janet_gclock(void);
JANET_API void janet_gcunlock(int handle);
/* The mark and sweep components of the mark and sweep collector. Prefer using janet_collect directly. */
JANET_API void janet_mark(Janet x);
JANET_API void janet_sweep(void);
/* Clear all gced memory and call all destructors. Used as part of the standard cleanup routune, most programmers will not need this. */
JANET_API void janet_clear_memory(void);
/* Remove all GC roots. Used as part of the standard cleanup routine, most programmers will not need this. */
JANET_API int janet_gcunrootall(Janet root);
/* Hint to the collector that memory of size s was just allocated to help it better understand when to free memory. */
JANET_API void janet_gcpressure(size_t s);
/* Functions */
JANET_API JanetFuncDef *janet_funcdef_alloc(void);
JANET_API JanetFunction *janet_thunk(JanetFuncDef *def);
/* Get a function that when called with no args, will return x. */
JANET_API JanetFunction *janet_thunk_delay(Janet x);
/* Do some simple verfification on constructed bytecode to disallow any trivial incorrect bytecode. */
JANET_API int janet_verify(JanetFuncDef *def);
/* Pretty printing */
@@ -1889,7 +1980,7 @@ JANET_API void janet_vm_free(JanetVM *vm);
JANET_API void janet_vm_save(JanetVM *into);
JANET_API void janet_vm_load(JanetVM *from);
JANET_API void janet_interpreter_interrupt(JanetVM *vm);
JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm);
JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); /* Call this after running interrupt handler */
JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out);
JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig);
JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f);
@@ -1918,6 +2009,10 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr
#define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT)
#define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP)
#define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN)
#define JANET_SANDBOX_COMPILE 32768
#define JANET_SANDBOX_ASM 65536
#define JANET_SANDBOX_THREADS 131072
#define JANET_SANDBOX_UNMARSHAL 262144
#define JANET_SANDBOX_ALL (UINT32_MAX)
JANET_API void janet_sandbox(uint32_t flags);
JANET_API void janet_sandbox_assert(uint32_t forbidden_flags);
@@ -1962,7 +2057,14 @@ JANET_API JanetBinding janet_resolve_ext(JanetTable *env, JanetSymbol sym);
/* Get values from the core environment. */
JANET_API Janet janet_resolve_core(const char *name);
/* New C API */
/* New C API
*
* The "New" C API is intended to make constructing good documentation and source maps
* much more straightforward. This not only ensures doc strings for functions in native
* modules, it also add source code mapping for C functions so that programmers can see which
* file and line a native function that calls janet_panic came from.
*
* */
/* Shorthand for janet C function declarations */
#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv)
@@ -2220,6 +2322,7 @@ typedef enum {
RULE_NTH, /* [nth, rule, tag] */
RULE_ONLY_TAGS, /* [rule] */
RULE_MATCHSPLICE, /* [rule, constant, tag] */
RULE_DEBUG, /* [] */
} JanetPegOpcode;
typedef struct {

View File

@@ -112,6 +112,8 @@ static JANET_THREAD_LOCAL int gbl_historyi = 0;
static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
static JANET_THREAD_LOCAL int gbl_match_count = 0;
static JANET_THREAD_LOCAL int gbl_lines_below = 0;
static JANET_THREAD_LOCAL int gbl_history_loaded = 0;
static JANET_THREAD_LOCAL char *gbl_history_file = NULL;
#endif
/* Fallback */
@@ -430,6 +432,63 @@ static int insert(char c, int draw) {
return 0;
}
static void calc_history_file(void) {
char *hist = getenv("JANET_HISTFILE");
if (hist != NULL) {
gbl_history_file = sdup(hist);
} else {
gbl_history_file = NULL;
}
}
static void loadhistory(void) {
if (gbl_history_loaded) return;
calc_history_file();
gbl_history_loaded = 1;
if (NULL == gbl_history_file) return;
FILE *history_file = fopen(gbl_history_file, "rb");
if (NULL == history_file) return;
JanetParser p;
janet_parser_init(&p);
int c = 0;
while ((c = fgetc(history_file))) {
if (c == EOF) {
janet_parser_eof(&p);
} else {
janet_parser_consume(&p, c);
}
while (janet_parser_has_more(&p) && gbl_history_count < JANET_HISTORY_MAX) {
if (janet_parser_status(&p) == JANET_PARSE_ERROR) {
janet_eprintf("bad history file: %s\n", janet_parser_error(&p));
goto parsing_done;
}
Janet x = janet_parser_produce(&p);
const char *cstr = (const char *) janet_to_string(x);
if (cstr[0]) { /* Drop empty strings */
gbl_history[gbl_history_count++] = sdup(cstr);
}
}
if (c == EOF) break;
}
parsing_done:
janet_parser_deinit(&p);
gbl_historyi = 0;
fclose(history_file);
}
static void savehistory(void) {
if (gbl_history_count < 1 || (gbl_history_file == NULL)) return;
FILE *history_file = fopen(gbl_history_file, "wb");
for (int i = 0; i < gbl_history_count; i++) {
if (gbl_history[i][0]) { /* Drop empty strings */
janet_dynprintf(NULL, history_file, "%j\n", janet_cstringv(gbl_history[i]));
}
}
fclose(history_file);
}
static void historymove(int delta) {
if (gbl_history_count > 1) {
janet_free(gbl_history[gbl_historyi]);
@@ -896,6 +955,7 @@ static int line() {
case 3: /* ctrl-c */
clearlines();
norawmode();
savehistory();
#ifdef _WIN32
ExitProcess(1);
#else
@@ -1089,17 +1149,21 @@ void janet_line_init() {
}
void janet_line_deinit() {
int i;
norawmode();
for (i = 0; i < gbl_history_count; i++)
for (int i = 0; i < gbl_history_count; i++)
janet_free(gbl_history[i]);
gbl_historyi = 0;
if (gbl_history_file) {
janet_free(gbl_history_file);
gbl_history_file = NULL;
}
}
void janet_line_get(const char *p, JanetBuffer *buffer) {
gbl_prompt = p;
buffer->count = 0;
gbl_historyi = 0;
loadhistory();
if (check_simpleline(buffer)) return;
FILE *out = janet_dynfile("err", stderr);
if (line()) {
@@ -1194,6 +1258,10 @@ int main(int argc, char **argv) {
status = janet_loop_fiber(fiber);
/* Deinitialize vm */
#if !defined(JANET_SIMPLE_GETLINE)
savehistory();
#endif
janet_deinit();
janet_line_deinit();

143
test/c/test-gc-pcall.c Normal file
View File

@@ -0,0 +1,143 @@
/*
* Test that GC does not collect fibers during janet_pcall.
*
* Bug: janet_collect() marks janet_vm.root_fiber but not janet_vm.fiber.
* When janet_pcall is called from a C function, the inner fiber becomes
* janet_vm.fiber while root_fiber still points to the outer fiber. If GC
* triggers inside the inner fiber's execution, the inner fiber is not in
* any GC root set and can be collected — including its stack memory —
* while it is actively running.
*
* Two tests:
* 1. Single nesting: F1 -> C func -> janet_pcall -> F2
* F2 is not marked (it's janet_vm.fiber but not root_fiber)
* 2. Deep nesting: F1 -> C func -> janet_pcall -> F2 -> C func -> janet_pcall -> F3
* F2 is not marked (saved only in a C stack local tstate.vm_fiber)
*
* Build (after building janet):
* cc -o build/test-gc-pcall test/test-gc-pcall.c \
* -Isrc/include -Isrc/conf build/libjanet.a -lm -lpthread -ldl
*
* Run:
* ./build/test-gc-pcall
*/
#include "janet.h"
#include <stdio.h>
/* C function that calls a Janet callback via janet_pcall. */
static Janet cfun_call_via_pcall(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
JanetFunction *fn = janet_getfunction(argv, 0);
Janet result;
JanetFiber *fiber = NULL;
JanetSignal sig = janet_pcall(fn, 0, NULL, &result, &fiber);
if (sig != JANET_SIGNAL_OK) {
janet_panicv(result);
}
return result;
}
static int run_test(JanetTable *env, const char *name, const char *source) {
printf(" %s... ", name);
fflush(stdout);
Janet result;
int status = janet_dostring(env, source, name, &result);
if (status != 0) {
printf("FAIL (crashed or errored)\n");
return 1;
}
printf("PASS\n");
return 0;
}
/* Test 1: Single nesting.
* F1 -> cfun_call_via_pcall -> janet_pcall -> F2
* F2 is janet_vm.fiber but not root_fiber, so GC can collect it.
*
* All allocations are done in Janet code so GC checks trigger in the
* VM loop (janet_gcalloc does NOT call janet_collect — only the VM's
* vm_checkgc_next does). */
static const char test_single[] =
"(gcsetinterval 1024)\n"
"(def cb\n"
" (do\n"
" (def captured @{:key \"value\" :nested @[1 2 3 4 5]})\n"
" (fn []\n"
" (var result nil)\n"
" (for i 0 500\n"
" (def t @{:i i :s (string \"iter-\" i) :arr @[i (+ i 1) (+ i 2)]})\n"
" (set result (get captured :key)))\n"
" result)))\n"
"(for round 0 200\n"
" (def result (call-via-pcall cb))\n"
" (assert (= result \"value\")\n"
" (string \"round \" round \": expected 'value', got \" (describe result))))\n";
/* Test 2: Deep nesting.
* F1 -> cfun_call_via_pcall -> janet_pcall -> F2 -> cfun_call_via_pcall -> janet_pcall -> F3
* F2 is saved only in C stack local tstate.vm_fiber, invisible to GC.
* F2's stack data can be freed if F2 is collected during F3's execution.
*
* The inner callback allocates in Janet code (not C) to ensure the
* VM loop triggers GC checks during F3's execution. */
static const char test_deep[] =
"(gcsetinterval 1024)\n"
"(def inner-cb\n"
" (do\n"
" (def captured @{:key \"deep\" :nested @[10 20 30]})\n"
" (fn []\n"
" (var result nil)\n"
" (for i 0 500\n"
" (def t @{:i i :s (string \"iter-\" i) :arr @[i (+ i 1) (+ i 2)]})\n"
" (set result (get captured :key)))\n"
" result)))\n"
"\n"
"(def outer-cb\n"
" (do\n"
" (def state @{:count 0 :data @[\"a\" \"b\" \"c\" \"d\" \"e\"]})\n"
" (fn []\n"
" # This runs on F2. Calling call-via-pcall here creates F3.\n"
" # F2 becomes unreachable: it's not root_fiber (that's F1)\n"
" # and it's no longer janet_vm.fiber (that's now F3).\n"
" (def inner-result (call-via-pcall inner-cb))\n"
" # If F2 was collected during F3's execution, accessing\n"
" # state here reads freed memory.\n"
" (put state :count (+ (state :count) 1))\n"
" (string inner-result \"-\" (state :count)))))\n"
"\n"
"(for round 0 200\n"
" (def result (call-via-pcall outer-cb))\n"
" (def expected (string \"deep-\" (+ round 1)))\n"
" (assert (= result expected)\n"
" (string \"round \" round \": expected '\" expected \"', got '\" (describe result) \"'\")))\n";
int main(int argc, char **argv) {
(void)argc;
(void)argv;
int failures = 0;
janet_init();
JanetTable *env = janet_core_env(NULL);
janet_def(env, "call-via-pcall",
janet_wrap_cfunction(cfun_call_via_pcall),
"Call a function via janet_pcall from C.");
printf("Testing janet_pcall GC safety:\n");
failures += run_test(env, "single-nesting", test_single);
failures += run_test(env, "deep-nesting", test_deep);
janet_deinit();
if (failures > 0) {
printf("\n%d test(s) FAILED\n", failures);
return 1;
}
printf("\nAll tests passed.\n");
return 0;
}

View File

@@ -86,5 +86,10 @@
(assert-error "array/join error 4" (array/join @[] "abc123"))
(assert-error "array/join error 5" (array/join @[] "abc123"))
# Regression 1714
(repeat 10
(assert (deep= (put @[] 100 10) (put (seq [_ :range [0 101]] nil) 100 10)) "regression 1714")
(assert (deep= (put @[] 200 10) (put (seq [_ :range [0 201]] nil) 200 10)) "regression 1714"))
(end-suite)

View File

@@ -179,5 +179,10 @@
(assert (= (string buf) "xxxxxx") "buffer/format-at negative index")
(assert-error "expected index at to be in range [0, 0), got 1" (buffer/format-at @"" 1 "abc"))
# Regression 1714
(repeat 10
(assert (deep= (put @"" 100 10) (put (buffer (string/repeat "\0" 101)) 100 10)) "regression 1714")
(assert (deep= (put @"" 200 10) (put (buffer (string/repeat "\0" 201)) 200 10)) "regression 1714"))
(end-suite)

View File

@@ -75,5 +75,80 @@
(foo 0)
10)
# Issue #1699 - fuzz case with bad def
(def result
(compile '(defn sum3
"Solve the 3SUM problem in O(n^2) time."
[s]
(def)tab @{})))
(assert (get result :error) "bad sum3 fuzz issue valgrind")
# Issue #1700
(def result
(compile
'(defn fuzz-case-1
[start end &]
(if end
(if e start (lazy-range (+ 1 start) end)))
1)))
(assert (get result :error) "fuzz case issue #1700")
# Issue #1702 - fuzz case with upvalues
(def result
(compile
'(each item [1 2 3]
# Generate a lot of upvalues (more than 224)
(def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"")
(with-dyns [:out out-buf] 1))))
(assert result "bad upvalues fuzz case")
# Named argument linting
# Enhancement for #1654
(defn fnamed [&named x y z] [x y z])
(defn fkeys [&keys ks] ks)
(defn fnamed2 [_a _b _c &named x y z] [x y z])
(defn fkeys2 [_a _b _c &keys ks] ks)
(defn fnamed3 [{:x x} &named a b c] [x a b c])
(defn fnamed4 [_y &opt _z &named a b c] [a b c])
(defn fnamed5 [&opt _z &named a b c] [a b c])
(defn g [x &opt y &named z] [x y z])
(defn check-good-compile
[code msg]
(def lints @[])
(def result (compile code (curenv) "suite-compile.janet" lints))
(assert (and (function? result) (empty? lints)) msg))
(defn check-lint-compile
[code msg]
(def lints @[])
(def result (compile code (curenv) "suite-compile.janet" lints))
(assert (and (function? result) (next lints)) msg))
(check-good-compile '(fnamed) "named no args")
(check-good-compile '(fnamed :x 1 :y 2 :z 3) "named full args")
(check-lint-compile '(fnamed :x) "named odd args")
(check-lint-compile '(fnamed :w 0) "named wrong key args")
(check-good-compile '(fkeys :a 1) "keys even args")
(check-lint-compile '(fkeys :a 1 :b) "keys odd args")
(check-good-compile '(fnamed2 nil nil nil) "named 2 no args")
(check-good-compile '(fnamed2 nil nil nil :x 1 :y 2 :z 3) "named 2 full args")
(check-lint-compile '(fnamed2 nil nil nil :x) "named 2 odd args")
(check-lint-compile '(fnamed2 nil nil nil :w 0) "named 2 wrong key args")
(check-good-compile '(fkeys2 nil nil nil :a 1) "keys 2 even args")
(check-lint-compile '(fkeys2 nil nil nil :a 1 :b) "keys 2 odd args")
(check-good-compile '(fnamed3 {:x 1} :a 1 :b 2 :c 3) "named 3 good")
(check-lint-compile '(fnamed3 {:x 1} :a 1 :b 2 :d 3) "named 3 lint")
(check-good-compile '(fnamed4 10 20 :a 1 :b 2 :c 3) "named 4 good")
(check-lint-compile '(fnamed4 10 20 :a 1 :b 2 :d 3) "named 4 lint")
(check-good-compile '(fnamed5 10 :a 1 :b 2 :c 3) "named 5 good")
(check-lint-compile '(fnamed5 10 :a 1 :b 2 :d 3) "named 5 lint")
(check-good-compile '(g 1) "g good 1")
(check-good-compile '(g 1 2) "g good 2")
(check-good-compile '(g 1 2 :z 10) "g good 3")
(check-lint-compile '(g 1 2 :z) "g lint 1")
(check-lint-compile '(g 1 2 :z 4 5) "g lint 2")
(end-suite)

View File

@@ -202,5 +202,9 @@
(assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n))
(assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a))
(end-suite)
# Issue #1709
(assert (= (macex1 '|(set (my-table [2 1]) 'foo))
'(fn :short-fn [] (set (my-table [2 1]) (quote foo))))
"Macro expand inside set preserves tuple type correctly")
(end-suite)

View File

@@ -55,4 +55,50 @@
(ev/sleep 0.2)
(assert (deep= '(:error "deadline expired" nil) (ev/take super)) "deadline expirataion")
# Issue #1705 - ev select
(def supervisor (ev/chan 10))
(def ch (ev/chan))
(def ch2 (ev/chan))
(ev/go |(do
(ev/select ch ch2)
(:close ch)
"close ch...")
nil supervisor)
(ev/go |(do
(ev/sleep 0.05)
(:close ch2)
"close ch2...")
nil supervisor)
(assert (let [[status] (ev/take supervisor)] (= status :ok)) "status 1 ev/select")
(assert (let [[status] (ev/take supervisor)] (= status :ok)) "status 2 ev/select")
(ev/sleep 0.1) # can we do better?
(assert (= 0 (ev/count supervisor)) "empty supervisor")
# Issue #1707
(def f (coro (repeat 10 (yield 1))))
(resume f)
(assert-error "cannot schedule non-new fiber"
(ev/go f))
# IO file copying
(os/mkdir "tmp")
(def f-original (file/open "tmp/out.txt" :wb))
(xprin f-original "hello\n")
(file/flush f-original)
(ev/do-thread
# Closes a COPY of the original file, otherwise we get a user-after-close file descriptor
(:close f-original))
(def g-original (file/open "tmp/out2.txt" :wb))
(xprin g-original "world1\n")
(xprin f-original "world2\n")
(:close f-original)
(xprin g-original "abc\n")
(:close g-original)
(assert (deep= @"hello\nworld2\n" (slurp "tmp/out.txt")) "file threading 1")
(assert (deep= @"world1\nabc\n" (slurp "tmp/out2.txt")) "file threading 2")
(end-suite)

View File

@@ -55,7 +55,8 @@
(file/flush f)
(file/seek f :set 0)
(assert (= 0 (file/tell f)) "start of file again")
(assert (= (string (file/read f :all)) "foo\n") "temp files work"))
(assert (= (string (file/read f :all)) "foo\n") "temp files work")
(assert-no-error "fsync" (file/sync f)))
# issue #1055 - 2c927ea76
(let [b @""]
@@ -74,9 +75,13 @@
(defn to-b [a] (buffer/push b a))
(xprintf to-b "123")
(assert (deep= b @"123\n") "xprintf to buffer")
(assert-error "cannot print to 3" (xprintf 3 "123"))
# file/sync
(with [f (file/temp)]
(file/write f "123abc")
(file/flush f)
(file/sync f))
(end-suite)

View File

@@ -63,7 +63,10 @@
"strftime january 2014")
(assert (= (try (os/strftime "%%%d%t") ([err] err))
"invalid conversion specifier '%t'")
"invalid conversion specifier")
"invalid conversion specifier 1")
(assert (= (try (os/strftime "%H:%M:%") ([err] err))
"invalid conversion specifier")
"invalid conversion specifier 2")
# 07db4c530
(os/setenv "TESTENV1" "v1")
@@ -145,11 +148,10 @@
# os/execute with empty environment
# pr #1686
# native MinGW can't find system DLLs without PATH and so fails
(assert (= (if (and (= :mingw (os/which))
(nil? (os/stat "C:\\windows\\system32\\wineboot.exe")))
-1073741515 0)
(os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {}))
# native MinGW can't find system DLLs without PATH, SystemRoot, etc. and so fails
# Also fails for address sanitizer builds on windows.
(def result (os/execute [;run janet "-e" "(+ 1 2 3)"] :pe {}))
(assert (or (= result -1073741515) (= result 0))
"os/execute with minimal env")
# os/execute regressions
@@ -174,8 +176,27 @@
:px
{:out dn :err dn})))
# os/execute IO redirection with more windows flags
(assert-no-error "IO redirection more windows flags"
(defn devnull []
(def os (os/which))
(def path (if (or (= os :mingw) (= os :windows))
"NUL"
"/dev/null"))
(os/open path (if (= os :windows) :wWI :wW)))
(with [dn (devnull)]
(os/execute [;run janet
"-e"
"(print :foo) (eprint :bar)"]
:px
{:out dn :err dn})))
# Issue 16922
(assert-error "os/realpath errors when path does not exist"
(os/realpath "abc123def456"))
# os/which changes
(assert (os/which (os/which)) "os/which 1 arg")
(assert (not (os/which :gobbledegook)) "os/which 2")
(end-suite)

View File

@@ -101,9 +101,9 @@
# 798c88b4c
(def csv
'{:field (+
(* `"` (% (any (+ (<- (if-not `"` 1))
(* (constant `"`) `""`)))) `"`)
(<- (any (if-not (set ",\n") 1))))
(* `"` (% (any (+ (<- (if-not `"` 1))
(* (constant `"`) `""`)))) `"`)
(<- (any (if-not (set ",\n") 1))))
:main (* :field (any (* "," :field)) (+ "\n" -1))})
(defn check-csv
@@ -266,6 +266,12 @@
(marshpeg '(sub "abcdf" "abc"))
(marshpeg '(* (sub 1 1)))
(marshpeg '(split "," (+ "a" "b" "c")))
(marshpeg "")
(marshpeg 1)
(marshpeg 0)
(marshpeg -1)
(marshpeg '(drop 1))
(marshpeg '(accumulate 1))
# Peg swallowing errors
# 159651117
@@ -345,16 +351,16 @@
# Using a large test grammar
# cf05ff610
(def- specials {'fn true
'var true
'do true
'while true
'def true
'splice true
'set true
'unquote true
'quasiquote true
'quote true
'if true})
'var true
'do true
'while true
'def true
'splice true
'set true
'unquote true
'quasiquote true
'quote true
'if true})
(defn- check-number [text] (and (scan-number text) text))
@@ -399,7 +405,7 @@
:struct (* '"{" :root2 (+ '"}" (error "")))
:parray (* '"@" :ptuple)
:barray (* '"@" :btuple)
:dict (* '"@" :struct)
:dict (* '"@" :struct)
:main (+ :root (error ""))})
(def p (peg/compile grammar))
@@ -563,18 +569,18 @@
(assert (= (string (f peg subst text)) expected) name))
(peg-test "peg/replace has access to captures"
peg/replace
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b.c")
peg/replace
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b.c")
(peg-test "peg/replace-all has access to captures"
peg/replace-all
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b -> B, .c")
peg/replace-all
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b -> B, .c")
# Peg bug
# eab5f67c5
@@ -648,20 +654,20 @@
# issue #1026 - 9341081a4
(assert (deep=
(peg/match '(not (* (constant 7) "a")) "hello")
@[]) "peg not")
(peg/match '(not (* (constant 7) "a")) "hello")
@[]) "peg not")
(assert (deep=
(peg/match '(if-not (* (constant 7) "a") "hello") "hello")
@[]) "peg if-not")
(peg/match '(if-not (* (constant 7) "a") "hello") "hello")
@[]) "peg if-not")
(assert (deep=
(peg/match '(if-not (drop (* (constant 7) "a")) "hello") "hello")
@[]) "peg if-not drop")
(peg/match '(if-not (drop (* (constant 7) "a")) "hello") "hello")
@[]) "peg if-not drop")
(assert (deep=
(peg/match '(if (not (* (constant 7) "a")) "hello") "hello")
@[]) "peg if not")
(peg/match '(if (not (* (constant 7) "a")) "hello") "hello")
@[]) "peg if not")
(defn test [name peg input expected]
(assert-no-error "compile peg" (peg/compile peg))
@@ -669,143 +675,143 @@
(assert (deep= (peg/match peg input) expected) name))
(test "sub: matches the same input twice"
~(sub "abcd" "abc")
"abcdef"
@[])
~(sub "abcd" "abc")
"abcdef"
@[])
(test "sub: second pattern cannot match more than the first pattern"
~(sub "abcd" "abcde")
"abcdef"
nil)
~(sub "abcd" "abcde")
"abcdef"
nil)
(test "sub: fails if first pattern fails"
~(sub "x" "abc")
"abcdef"
nil)
~(sub "x" "abc")
"abcdef"
nil)
(test "sub: fails if second pattern fails"
~(sub "abc" "x")
"abcdef"
nil)
~(sub "abc" "x")
"abcdef"
nil)
(test "sub: keeps captures from both patterns"
~(sub '"abcd" '"abc")
"abcdef"
@["abcd" "abc"])
~(sub '"abcd" '"abc")
"abcdef"
@["abcd" "abc"])
(test "sub: second pattern can reference captures from first"
~(* (constant 5 :tag) (sub (capture "abc" :tag) (backref :tag)))
"abcdef"
@[5 "abc" "abc"])
~(* (constant 5 :tag) (sub (capture "abc" :tag) (backref :tag)))
"abcdef"
@[5 "abc" "abc"])
(test "sub: second pattern can't see past what the first pattern matches"
~(sub "abc" (* "abc" -1))
"abcdef"
@[])
~(sub "abc" (* "abc" -1))
"abcdef"
@[])
(test "sub: positions inside second match are still relative to the entire input"
~(* "one\ntw" (sub "o" (* ($) (line) (column))))
"one\ntwo\nthree\n"
@[6 2 3])
~(* "one\ntw" (sub "o" (* ($) (line) (column))))
"one\ntwo\nthree\n"
@[6 2 3])
(test "sub: advances to the end of the first pattern's match"
~(* (sub "abc" "ab") "d")
"abcdef"
@[])
~(* (sub "abc" "ab") "d")
"abcdef"
@[])
(test "til: basic matching"
~(til "d" "abc")
"abcdef"
@[])
~(til "d" "abc")
"abcdef"
@[])
(test "til: second pattern can't see past the first occurrence of first pattern"
~(til "d" (* "abc" -1))
"abcdef"
@[])
~(til "d" (* "abc" -1))
"abcdef"
@[])
(test "til: fails if first pattern fails"
~(til "x" "abc")
"abcdef"
nil)
~(til "x" "abc")
"abcdef"
nil)
(test "til: fails if second pattern fails"
~(til "abc" "x")
"abcdef"
nil)
~(til "abc" "x")
"abcdef"
nil)
(test "til: discards captures from initial pattern"
~(til '"d" '"abc")
"abcdef"
@["abc"])
~(til '"d" '"abc")
"abcdef"
@["abc"])
(test "til: positions inside second match are still relative to the entire input"
~(* "one\ntw" (til 0 (* ($) (line) (column))))
"one\ntwo\nthree\n"
@[6 2 3])
~(* "one\ntw" (til 0 (* ($) (line) (column))))
"one\ntwo\nthree\n"
@[6 2 3])
(test "til: advances to the end of the first pattern's first occurrence"
~(* (til "d" "ab") "e")
"abcdef"
@[])
~(* (til "d" "ab") "e")
"abcdef"
@[])
(test "split: basic functionality"
~(split "," '1)
"a,b,c"
@["a" "b" "c"])
~(split "," '1)
"a,b,c"
@["a" "b" "c"])
(test "split: drops captures from separator pattern"
~(split '"," '1)
"a,b,c"
@["a" "b" "c"])
~(split '"," '1)
"a,b,c"
@["a" "b" "c"])
(test "split: can match empty subpatterns"
~(split "," ':w*)
",a,,bar,,,c,,"
@["" "a" "" "bar" "" "" "c" "" ""])
~(split "," ':w*)
",a,,bar,,,c,,"
@["" "a" "" "bar" "" "" "c" "" ""])
(test "split: subpattern is limited to only text before the separator"
~(split "," '(to -1))
"a,,bar,c"
@["a" "" "bar" "c"])
~(split "," '(to -1))
"a,,bar,c"
@["a" "" "bar" "c"])
(test "split: fails if any subpattern fails"
~(split "," '"a")
"a,a,b"
nil)
~(split "," '"a")
"a,a,b"
nil)
(test "split: separator does not have to match anything"
~(split "x" '(to -1))
"a,a,b"
@["a,a,b"])
~(split "x" '(to -1))
"a,a,b"
@["a,a,b"])
(test "split: always consumes entire input"
~(split 1 '"")
"abc"
@["" "" "" ""])
~(split 1 '"")
"abc"
@["" "" "" ""])
(test "split: separator can be an arbitrary PEG"
~(split :s+ '(to -1))
"a b c"
@["a" "b" "c"])
~(split :s+ '(to -1))
"a b c"
@["a" "b" "c"])
(test "split: does not advance past the end of the input"
~(* (split "," ':w+) 0)
"a,b,c"
@["a" "b" "c"])
~(* (split "," ':w+) 0)
"a,b,c"
@["a" "b" "c"])
(test "nth 1"
~{:prefix (number :d+ nil :n)
:word '(lenprefix (-> :n) :w)
:main (some (nth 1 (* :prefix ":" :word)))}
"5:apple6:banana6:cherry"
@["apple" "banana" "cherry"])
~{:prefix (number :d+ nil :n)
:word '(lenprefix (-> :n) :w)
:main (some (nth 1 (* :prefix ":" :word)))}
"5:apple6:banana6:cherry"
@["apple" "banana" "cherry"])
(test "only-tags 1"
~{:prefix (number :d+ nil :n)
:word (capture (lenprefix (-> :n) :w) :W)
:main (some (* (only-tags (* :prefix ":" :word)) (-> :W)))}
"5:apple6:banana6:cherry"
@["apple" "banana" "cherry"])
~{:prefix (number :d+ nil :n)
:word (capture (lenprefix (-> :n) :w) :W)
:main (some (* (only-tags (* :prefix ":" :word)) (-> :W)))}
"5:apple6:banana6:cherry"
@["apple" "banana" "cherry"])
# Issue #1539 - make sure split with "" doesn't infinite loop/oom
(test "issue 1539"
@@ -814,9 +820,9 @@
nil)
(test "issue 1539 pt. 2"
~(split "," (capture 0))
"abc123,,,,"
@["" "" "" "" ""])
~(split "," (capture 0))
"abc123,,,,"
@["" "" "" "" ""])
# Issue #1549 - allow buffers as peg literals
(test "issue 1549"
@@ -845,4 +851,106 @@
"abc"
@[["b" "b" "b"]])
# Debug and ?? tests.
(defn test-stderr [name peg input expected-matches expected-stderr]
(with-dyns [:err @""]
(test name peg input expected-matches))
(def actual @"")
(with-dyns [:err actual *err-color* true]
(peg/match peg input))
(assert (deep= (string actual) expected-stderr)))
(defn test-stderr-no-color [name peg input expected-matches expected-stderr]
(with-dyns [:err @""]
(test name peg input expected-matches))
(def actual @"")
(with-dyns [:err actual *err-color* false]
(peg/match peg input))
(assert (deep= (string actual) expected-stderr)))
(test-stderr
"?? long form"
'(* (debug) "abc")
"abc"
@[]
"?? at [abc] (index 0)\n")
(test-stderr
"?? short form"
'(* (??) "abc")
"abc"
@[]
"?? at [abc] (index 0)\n")
(test-stderr
"?? end of text"
'(* "abc" (??))
"abc"
@[]
"?? at [] (index 3)\n")
(test-stderr
"?? between rules"
'(* "a" (??) "bc")
"abc"
@[]
"?? at [bc] (index 1)\n")
(test-stderr
"?? stack display, string"
'(* (<- "a") (??) "bc")
"abc"
@["a"]
(string/format "?? at [bc] (index 1)\nstack [1]:\n [0]: %M\n" "a"))
(test-stderr
"?? stack display, multiple types"
'(* (<- "a") (number :d) (constant true) (constant {}) (constant @[]) (??) "bc")
"a1bc"
@["a" 1 true {} @[]]
(string/format "?? at [bc] (index 2)\nstack [5]:\n [0]: %M\n [1]: %M\n [2]: %M\n [3]: %M\n [4]: %M\n" "a" 1 true {} @[]))
(marshpeg '(* (??) "abc"))
(marshpeg '(* (some (debug)) (??) "abc"))
(test-stderr
"?? displays when capture fails"
'(* '1 (??) "x")
"abc"
nil
(string/format "?? at [bc] (index 1)\nstack [1]:\n [0]: %M\n" "a"))
(test-stderr-no-color
"?? displays accumuate and tagged captures"
'(* '1 '2 (% (* '1 (??) (<- 2 :tag) '3 (backref :tag) (??))))
"aksjndkajsnd"
@["a" "ks" "jndkajnd"]
(string/replace-all
# In case on windows someone messes with line endings.
"\r" ""
```
?? at [ndkajsnd] (index 4)
accumulate buffer: @"j"
stack [2]:
[0]: "a"
[1]: "ks"
tag stack [3]:
[0] tag=0: "a"
[1] tag=0: "ks"
[2] tag=0: "j"
?? at [snd] (index 9)
accumulate buffer: @"jndkajnd"
stack [2]:
[0]: "a"
[1]: "ks"
tag stack [6]:
[0] tag=0: "a"
[1] tag=0: "ks"
[2] tag=0: "j"
[3] tag=1: "nd"
[4] tag=0: "kaj"
[5] tag=0: "nd"
```))
(end-suite)

Binary file not shown.

View File

@@ -87,7 +87,9 @@
<Directory Id="BinDir" Name="bin"/>
<Directory Id="CDir" Name="C"/>
<Directory Id="DocsDir" Name="docs"/>
<Directory Id="LibraryDir" Name="Library"/>
<Directory Id="LibraryDir" Name="Library">
<Directory Id="LibBinDir" Name="bin"/>
</Directory>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
@@ -169,6 +171,7 @@
<Component Id="SetEnvVarsPerMachine" Directory="ApplicationProgramsFolder" Guid="57b1e1ef-89c8-4ce4-9f0f-37618677c5a4" KeyPath="yes">
<Condition>ALLUSERS=1</Condition>
<Environment Id="PATH_PERMACHINE" Name="PATH" Value="[BinDir]" Action="set" Permanent="no" System="yes" Part="last"/>
<Environment Id="PATH2_PERMACHINE" Name="PATH" Value="[LibBinDir]" Action="set" Permanent="no" System="yes" Part="last"/>
<Environment Id="JANET_BINPATH_PERMACHINE" Name="JANET_BINPATH" Value="[BinDir]" Action="set" Permanent="no" System="yes"/>
<Environment Id="JANET_MANPATH_PERMACHINE" Name="JANET_MANPATH" Value="[DocsDir]" Action="set" Permanent="no" System="yes"/>
<Environment Id="JANET_PATH_PERMACHINE" Name="JANET_PATH" Value="[LibraryDir]" Action="set" Permanent="no" System="yes" />
@@ -178,6 +181,7 @@
<Component Id="SetEnvVarsPerUser" Directory="ApplicationProgramsFolder" Guid="128be307-488b-49aa-971a-d2ae00a1a584" KeyPath="yes">
<Condition>NOT ALLUSERS=1</Condition>
<Environment Id="PATH_PERUSER" Name="PATH" Value="[BinDir]" Action="set" Permanent="no" System="no" Part="last"/>
<Environment Id="PATH2_PERUSER" Name="PATH" Value="[LibBinDir]" Action="set" Permanent="no" System="no" Part="last"/>
<Environment Id="JANET_BINPATH_PERUSER" Name="JANET_BINPATH" Value="[BinDir]" Action="set" Permanent="no" System="no"/>
<Environment Id="JANET_MANPATH_PERUSER" Name="JANET_MANPATH" Value="[DocsDir]" Action="set" Permanent="no" System="no"/>
<Environment Id="JANET_PATH_PERUSER" Name="JANET_PATH" Value="[LibraryDir]" Action="set" Permanent="no" System="no" />