1
0
mirror of https://github.com/janet-lang/janet synced 2026-04-02 04:51:26 +00:00

Compare commits

...

436 Commits

Author SHA1 Message Date
Calvin Rose
d816011ec1 Remove word. 2026-03-14 23:12:47 -05:00
Calvin Rose
934cf9d5b8 Remove wiggle room for any AI code in the Janet runtime.
Still leave open the possibity for AI / tool usage for static analysis
and bug repots. However, the 5-15 lines of code limitation is fuzzy and
arbitrary. We can just say no.
2026-03-14 23:05:24 -05:00
Calvin Rose
9880475262 Add suggestions.
- No bot PRs
- Define "Large" code contribution

Also try to disuade users from using AI for one-line or simple changes, instead
preferring to treat that as "feedback" and rewrite instead.
2026-03-13 17:27:32 -05:00
Calvin Rose
bab71feadb Word order and punctuation. 2026-03-13 08:30:46 -05:00
Calvin Rose
8eae8984b1 Add LLM, AI and tool usage section to contribution guide. 2026-03-13 08:28:19 -05:00
PinieP
df56efbae0 Fix: add missing :symbolmap keyword to disasm function (#1729) 2026-03-12 19:25:51 -05:00
sogaiu
b160f4f5c0 Macro hygiene tweaks (#1727)
* Tweak hygiene for when-with, if-with, when-let

* Tweak hygiene for loop1 helper

* Tweak hygiene for repeat

* Tweak hygiene for seq and catseq

* Tweak hygiene for -?> and -?>>

* Tweak hygiene for varfn

* Tweak hygiene for ev/with-deadline

* Tweak hygiene for ffi/defbind-alias + ffi/defbind

---------

Co-authored-by: sogaiu <983021772@users.noreply.github.com>
2026-03-11 19:06:43 -05:00
Calvin Rose
a0cc867f14 Improve macro hygiene in boot.janet. 2026-03-09 19:46:55 -05:00
Calvin Rose
8f446736ed Fix #1723 - handle prompt longer than terminal is wide.
When this happened, we tried to print a buffer with a negative length
that resulted in invalid memmove call. This fix both checks that we
don't tried to append a negative buffer, and truncates a prompt that is
too long for the termimal so that one can still enter text with at least
16 visible characters for data entry.
2026-03-07 15:28:59 -06:00
PinieP
decd7078af fix janet_make_threaded to return threaded channel (#1724)
janet_make_threaded previously created a channel with threaded set to 0
2026-03-07 15:01:03 -06:00
McSinyx
b96350f132 Align items in multiline pretty format (%m and %p) (#1721)
Indentation levels are not sufficiently clear for deeply nested
data structures.  Instead, align items in a collection
with its opening bracket, also known as hanging indentation.

To avoid guessing the length of a "short" collection to print it
on one line, items are now always printed on separate lines.
2026-03-05 19:34:05 -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
Calvin Rose
0a5ff208a8 Disable ipv6 for now in main test. 2026-01-12 08:00:11 -06:00
Calvin Rose
d35f189446 Turn strict linting back on. 2026-01-12 07:57:41 -06:00
Calvin Rose
216c9799f5 Some more work on socket testing and allow setting ipv4 vs. ipv6 on raw
sockets.
2026-01-12 07:41:14 -06:00
Peter Rippe
5966017232 use u_char for IP_MULTICAST_TTL sockopt on BSD 2026-01-12 06:06:27 -06:00
Calvin Rose
f80690e4c9 Version bump for wip 1.41.x 2026-01-10 15:40:04 -06:00
Calvin Rose
9bc308532f Make varfn correctly handle verbose metadata.
The macro would previously choke on `(varfn abc {:abc 123} ...)` due
to mishandling of structs and tables as metadata. This caused issues
when running `janet -d` with spork.
2026-01-10 14:04:48 -06:00
Calvin Rose
7a8d8444fe Simplify dynamic binding setup in cli-main.
Instead of manually putting bindings into environment tables, just use
`setdyn` to set the bindings based on the command line flags. This
results in easier to understand behavior and prevents "swallowing" of
options. For example, linting and debug flags should be set in the
root-env by default so that they are used in all loaded modules.
2026-01-10 13:37:42 -06:00
Calvin Rose
15cea60589 Revert "Reworking coloring logic for repl and CLI interface."
This reverts commit 10954fe0d7.
2026-01-10 13:20:02 -06:00
Calvin Rose
10954fe0d7 Reworking coloring logic for repl and CLI interface. 2026-01-10 13:15:46 -06:00
Calvin Rose
70fb13eb48 Fix -w flag with repl. 2026-01-10 12:35:41 -06:00
Calvin Rose
cb355815ee Turn on strict linting in test suite. 2026-01-10 08:37:09 -06:00
Calvin Rose
ddc7cc5ae4 Warn on unused bindings (strict lint).
This does create a lot of warnings, especially in the test suite, but
should improve code and point out real issues.

To disable individual messages, either disable linting, add the metadata :unused to a
binding, or add the prefix "_" to a symbol.
2026-01-10 07:59:11 -06:00
Calvin Rose
84ced08810 Add test case. 2026-01-09 08:15:18 -06:00
Calvin Rose
7b0b9f57e8 Make os/realpath error on windows if path does not exist. 2026-01-09 08:04:07 -06:00
Calvin Rose
80da028aeb Add linting to address #1688
Warn when defining macros on inner scopes, and also point out unused
metadata as well.
2026-01-04 08:19:18 -06:00
Calvin Rose
f881f4dba9 Merge pull request #1690 from pyrmont/bugfix.copyright-2026
Update year in copyright disclaimer to 2026
2026-01-03 10:47:57 -06:00
Michael Camilleri
6e4b0b9259 Update year in copyright disclaimer to 2026 2026-01-02 14:14:53 +09:00
Calvin Rose
d5a56caa33 Add fix for issue #1687 - destrucutre + splice
An optimization to ellude creation of intermediate tuples was
erroneously flagging a splice as invalid, even though it was valid.
Instead, if we see splice on the rhs, bail out of the optimization.
2025-12-22 07:40:29 -06:00
Calvin Rose
7d672f43fc Merge pull request #1686 from pyrmont/bugfix.windows-empty-env 2025-12-14 22:05:03 -06:00
Michael Camilleri
7810724ed2 Exclude MinGW on WINE from special case 2025-12-15 12:04:14 +09:00
Michael Camilleri
1e73fa7ec7 Add special case for expected error on MinGW 2025-12-15 11:27:46 +09:00
Michael Camilleri
9b1194b08a Use double-NULL byte for empty process environments on Windows 2025-12-15 10:00:03 +09:00
Calvin Rose
d3f5b541ee Merge pull request #1683 from llmII/fix-unix-sockets
Fix unix sockets issue on FreeBSD
2025-12-14 13:23:33 -06:00
Calvin Rose
3a3c0dec7a Merge pull request #1685 from sogaiu/tweak-int-max-min-docstrings
Fix math/int-{max,min} docstrings
2025-12-14 13:21:39 -06:00
Calvin Rose
bb3cf7d83b Add missing uinteger janet_opt* funcitons. 2025-12-14 11:46:57 -06:00
sogaiu
9bc47e766e Fix math/int-{max,min} docstrings 2025-12-14 13:41:00 +09:00
llmII
978c4e8b69 Fix unix sockets issue on FreeBSD
Sometimes a unix socket has a 0 status return which indicates the connection
immediately succeeded, at which point entering the event loop waiting on the
connection to complete actually breaks things.

It seems on FreeBSD with events being edge triggered, we're awaiting a
connection to signal it's writeable (to complete the connection) but that
never occurs (the event already took place before we registered for the
event). Going by status alone to determine if we should enter into the event
loop to await the complete connection seems sensible here.
2025-12-13 16:56:56 -06:00
Calvin Rose
2544c4ae1a Add os/cpu-count get defaults. 2025-12-13 06:20:50 -06:00
Calvin Rose
7f9d92a73b Formatting 2025-12-13 06:00:16 -06:00
Calvin Rose
898e93bc4a Merge pull request #1679 from pixelherodev/master
Take 2: 9front port, using 9front's Native Porting/Posix Environment
2025-12-13 05:57:17 -06:00
Noam Preil
715dd413cc plan9: support %r format to read errstr 2025-12-12 10:32:43 +00:00
Noam Preil
1971d7df0a plan9: use _Exit 2025-12-12 09:52:30 +00:00
Noam Preil
0d7afc9419 clean up headers 2025-12-12 09:45:39 +00:00
Noam Preil
7e2a57687a clean test 2025-12-12 09:42:55 +00:00
Noam Preil
2de6d26a46 plan9: restore CLOCK_PROCESS_CPUTIME_ID support 2025-12-12 09:35:37 +00:00
Noam Preil
4a9c9ebefc plan9: restore sys/wait usage (patched in NPE) 2025-12-12 09:29:23 +00:00
Noam Preil
8887075a8c plan9: working process support 2025-12-12 09:21:19 +00:00
Calvin Rose
01ac5c32f3 Merge pull request #1681 from tw4452852/push-somyznopprwr
Apply :flycheck on def as well
2025-12-11 22:05:06 -06:00
Calvin Rose
b980c2ae17 Merge pull request #1682 from sogaiu/update-macos-runner
Update to macos-14 in ci
2025-12-11 21:54:30 -06:00
Noam Preil
7d06faca66 9front: expanded OS support 2025-12-11 21:36:29 -06:00
Noam Preil
352935596a 9front: support (os/cpu-count) 2025-12-11 21:36:13 -06:00
Noam Preil
dc52242d36 add plan9 support to changelog 2025-12-12 07:44:25 +00:00
Noam Preil
dde1a67b77 plan9: 32-bit support 2025-12-12 07:41:05 +00:00
Noam Preil
7c31110195 9front: allow file/open of directories 2025-12-12 04:32:19 +00:00
Noam Preil
d41eeaf7fa 9front: detect compiler as kencc 2025-12-12 04:14:46 +00:00
Noam Preil
53fd2185fc fix mkfile, rename port to plan9 2025-12-12 04:25:41 +00:00
Noam Preil
2937125633 9front: clean up mkfile 2025-12-12 04:08:00 +00:00
Noam Preil
8284b1f5f2 mkfile: standalone build in place, fix clean target 2025-12-12 03:00:44 +00:00
Noam Preil
04c96296b5 drop fixme, can worry about it later 2025-12-12 02:54:07 +00:00
Noam Preil
d888b1c530 plan9: silence first mk, redisable EV for now 2025-12-12 02:51:22 +00:00
sogaiu
7777a017e2 Update to macos-14 in ci 2025-12-12 11:13:27 +09:00
Tw
d8a074731e Apply :flycheck on def as well
Signed-off-by: Tw <tw19881113@gmail.com>
Change-Id: I7bd10cbaa838b44794f8a881b0deedd06a6a6964
2025-12-12 08:40:18 +08:00
Noam Preil
b723c00b98 mkfile: fix cleanfiles list, drop debugging target 2025-12-11 07:54:39 +00:00
Noam Preil
98bbe9f474 expose os/isatty, fix floating point math 2025-12-10 23:35:24 +00:00
Noam Preil
2f69678f2f 9front port 2025-12-10 12:18:17 +00:00
Calvin Rose
efe66f207f Merge pull request #1678 from tw4452852/push-swmvxuxkrvsx
shell: Prevent buggy moving zero column behavior
2025-12-07 08:02:32 -06:00
Calvin Rose
d6803f7a17 Formatting. 2025-12-04 08:48:12 -06:00
Tw
6e883b8972 shell: Prevent buggy moving zero column behavior
According to https://unix.stackexchange.com/a/559331,
moving zero column is implemented inconsistently between
different terminal emulators.

In order to fix this inconsistency,
we avoid using this escape sequence entirely.

Signed-off-by: Tw <tw19881113@gmail.com>
Change-Id: I73d4252f8472c769f0cf98b6bbdf2b3d6a6a6964
2025-12-04 08:51:15 +08:00
Calvin Rose
c2cbfa4d5d Merge pull request #1676 from tsujamin/msvc-runtime-stack-check-ffi-crash
Disable MSVC runtime stack checks in janet_ffi_win64
2025-12-02 09:10:50 -06:00
Benjamin Roberts
a85689312a Disable MSVC runtime stack checks in janet_ffi_win64
The MSVC /RTCs argument instrument alloca() to prepend stack allocations
with metadata used to check for memory corruption during the function
prologue. This causes stack-based FFI arguments in janet_ffi_win64 to
become mis-aligned, and also for the alloca()-prepended header to be
corrupted leading to a fatal exception.
2025-12-01 15:37:09 -05:00
Calvin Rose
b9bc89a38c Fix very old typo in janet.h 2025-11-30 14:22:17 -06:00
Calvin Rose
f79e4d6249 Address #1674 - Add cms combinator to peg.
It behaves like `cmt`, but flattens the result and can collect
multiple captures if an indexed data structure is returned.
2025-11-30 14:19:51 -06:00
Calvin Rose
238d6c2e13 Remove type constraint on doc for +.
This constraint is no longer true in all cases.
2025-11-30 14:19:51 -06:00
Calvin Rose
be2a1ddf96 Add test case for dollar suffix match. 2025-11-30 14:19:51 -06:00
Calvin Rose
d9105299f1 Allow matching tuples and arrays exactly via a dollar suffix. 2025-11-30 14:19:51 -06:00
Calvin Rose
8efeeaec95 Allow matching tuples and arrays exactly via a dollar suffix. 2025-11-30 14:19:51 -06:00
Calvin Rose
b92992c862 Make additions to core library
* Add `thaw-keep-keys` variant of `thaw`.
* Add `walk2` to traverse key-value pairs and fibers. Also add
  corresponding `postwalk2` and `prewalk2`
* Built-in `repl` function now respects custom repl prompt.
2025-11-30 14:19:51 -06:00
Calvin Rose
996ad64d01 Merge pull request #1673 from sogaiu/remove-stale-item
Remove unused def
2025-11-30 10:17:30 -06:00
sogaiu
bd1641b936 Remove unused def 2025-11-29 23:38:07 +09:00
Calvin Rose
a9e6766958 Merge pull request #1672 from pyrmont/bugfix.bundle-script-loading
Improve messages on errors when loading the bundle script
2025-11-23 07:28:44 -06:00
Michael Camilleri
ee46315e1d Improve messages on errors when loading the bundle script 2025-11-21 22:45:36 +09:00
Calvin Rose
7c1db67749 Merge pull request #1668 from sogaiu/tweak-file-write-docstring
Update file/write docstring
2025-11-17 05:27:57 -06:00
sogaiu
4fa1a6947c Update file/write docstring 2025-11-17 18:51:55 +09:00
Calvin Rose
1449ad8b31 Update changelog. 2025-11-16 17:39:19 -06:00
Calvin Rose
c44592c84d Prepare for 1.40.1 patch release. 2025-11-16 16:55:28 -06:00
Calvin Rose
37e084969a Merge pull request #1665 from sogaiu/fix-comment
Fix comment about syspath
2025-11-16 08:15:57 -06:00
Calvin Rose
b389f01005 Fix undefined behavior (integer overflow) in hash function.
If an intermediate hash value was 0xFFFFFFFF and we tried to add 1 as a
signed addition, this technically triggers undefined behavior.
2025-11-16 08:14:19 -06:00
sogaiu
a886a93d2f Fix comment about syspath 2025-11-16 15:14:11 +09:00
Calvin Rose
f7d0d065c8 Add sr.ht build for meson reduced builds.
This should help better catch configs that break our website emscripten
build.
2025-11-15 09:46:35 -06:00
Calvin Rose
8c2a517cd7 Formatting and fix chroot build. 2025-11-15 09:43:15 -06:00
Calvin Rose
7e0c692d4e Remove test-install target from sr.ht 2025-11-15 08:39:47 -06:00
Calvin Rose
732fe0ad03 Try new openbsd. 2025-11-15 08:33:41 -06:00
Calvin Rose
0c8622c803 posix_chroot warning 2025-11-15 08:15:26 -06:00
Calvin Rose
94f2494f8d MSVC warning 2025-11-15 08:14:28 -06:00
Calvin Rose
0f9ecc2da5 Prepare for 1.40.0 release 2025-11-15 07:31:23 -06:00
Calvin Rose
83f5da3b8f Merge pull request #1662 from McSinyx/dobytes-err-ctx
Include context in dobytes' error string
2025-11-09 09:20:23 -06:00
Calvin Rose
9b9f2a1713 Merge pull request #1660 from pyrmont/bugfix.confirm-bundle-files
Clarify error message for missing bundle script during bundle installation
2025-11-09 09:06:06 -06:00
Calvin Rose
8df4d47ede Merge pull request #1663 from McSinyx/dobytes-err-keep
Keep dobytes' error string
2025-11-09 08:40:07 -06:00
Nguyễn Gia Phong
1c372fbf32 Keep dobytes' error string
The reason for failure would be more useful
than the most recently evaluated value.
2025-11-05 17:00:04 +09:00
Nguyễn Gia Phong
8ace580498 Include context in dobytes' error string 2025-11-05 16:53:19 +09:00
Michael Camilleri
8241d9cbb4 Revert support for bundles without an info file 2025-10-27 09:32:38 +09:00
Michael Camilleri
6bd02bb5b6 Confirm necessary files during bundle installation 2025-10-26 06:53:27 +09:00
Calvin Rose
2a3308005d Merge pull request #1659 from ianthehenry/try-macro-hygiene
fix (try) macro hygiene
2025-10-25 12:15:12 -05:00
Ian Henry
0c34033b72 add some tests for the (try) macro 2025-10-24 21:56:07 -07:00
Ian Henry
f1ec0cc48b fix try macro hygiene
Allow re-using the same symbol for the fiber and error. This allows you to
write code like (try (print "hi") ([_ _] (print "oh no"))), fixing a regression
introduced in #1605.
2025-10-24 21:33:15 -07:00
Calvin Rose
98265f0637 Merge pull request #1658 from sogaiu/tweak-has-bin-script 2025-10-17 15:44:50 -05:00
sogaiu
1018cb9cca Support :has-exe as well
This is a companion change to spork's issue

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

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

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

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

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

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

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

    Inter
2025-05-16 18:49:45 -05:00
Calvin Rose
a8e2c8e5b8 Address #1596 - Use atomic intrinsics to check allow_interrupt flag.
Use a relaxed memory order if possible to mitigate performance issues as
much as possible. relaxed memory order should be sufficient.
2025-05-16 18:26:43 -05:00
Calvin Rose
3d3e880f52 Allow configuration of JANET_THREAD_LOCAL. Address #1595
This is to better allow configuration on various, unknown compilers.
Previously, we hardcoded how thread local storage was specified for a
few different compilers, but we were not following and C standard. In
C11, there is a standardized storage specifier _Thread_local for this
storage class, however this is now deprecated in various C++ compilers
for a new keyword, confusingly. Janet also does not claim to require the
C11 standard, so for maximum flexibilty, the storage specifier must be
specified at configure time.
2025-05-11 08:37:15 -05:00
Calvin Rose
bef8ba5e06 Merge pull request #1594 from sogaiu/tweak-docstrings
Tweak docstrings for better handling
2025-05-08 17:00:32 -05:00
Calvin Rose
523639362c Merge pull request #1593 from agent-kilo/thread-message-size-check
Try to fix cfun registry size check for cross-thread messages
2025-05-08 17:00:22 -05:00
sogaiu
4b6d5e5671 Tweak docstrings for better handling 2025-05-02 18:54:18 +09:00
Agent Kilo
a695454dae Try to fix cfun registry size check for cross-thread messages 2025-04-28 17:00:23 +08:00
Calvin Rose
f2eaa5dee8 Remove ev/to-stream.
The function has more problems than initially expected, both on Posix
systems and on Windows. Given all the caveats, it is probably best not
to include. Any function that can obtain files can use os/open instead.
The standard FILE objects also will not work anyway, and different
operating systems have different work arounds.
2025-04-18 18:20:27 -07:00
Calvin Rose
b27c830d90 Use win32 DuplicatHandle instead of _dup
There are constraints when using the posix API on win32
that aren't present with normal win32.
2025-04-11 21:36:08 -05:00
Calvin Rose
92a852f2df Add ev/to-stream.
This function is symmetrical to ev/to-file and can convert a blocking
file to a stream (with caveats).
2025-04-11 21:10:49 -05:00
Calvin Rose
647e218bed make format 2025-04-09 20:39:23 -05:00
Calvin Rose
5ebe945ffd Merge pull request #1582 from pyrmont/bugfix.termux-pthread_cancel
Fix absence of pthread_cancel in Android
2025-04-09 20:16:57 -05:00
Michael Camilleri
6254fffad0 Update changelog to include change to ev/deadline 2025-04-09 10:40:19 +09:00
Michael Camilleri
5705b2f6c7 Fix absence of pthread_cancel in Android 2025-04-09 10:37:46 +09:00
Calvin Rose
90a33bc88a Add some systems that support addchdir. 2025-04-07 21:12:28 -05:00
Calvin Rose
1ba077c87d Add ifdefs for various platforms to check for the support of addchdir.
This will need to be expanded but should support most modern systems.
2025-04-07 21:12:28 -05:00
Calvin Rose
34629ae314 Fix signature on windows. 2025-04-07 21:12:28 -05:00
Calvin Rose
3edc4f35b2 Add :cd argument to os/execute and os/spawn. 2025-04-07 21:12:28 -05:00
Calvin Rose
06d01c099f Merge pull request #1581 from sogaiu/fix-mapcat-and-keep-docstrings
Fix mapcat and keep docstrings
2025-04-07 21:11:14 -05:00
sogaiu
d493eaf485 Fix mapcat and keep docstrings 2025-04-06 20:48:52 +09:00
Calvin Rose
332f123abe Replace if not errorlevel 0 with if errorlevel 1
The former is just bad syntax.
2025-04-05 20:59:45 -05:00
Calvin Rose
38e841fc5c Get rid of test error. 2025-04-05 20:52:47 -05:00
Calvin Rose
e8187fdee5 Fix windows build 2025-04-05 20:26:57 -05:00
Calvin Rose
2fedb67cb3 formatting 2025-04-05 17:32:07 -05:00
Calvin Rose
bdab93c999 Add {:err :out} option to os/spawn.
This special case in the os/spawn interface allows easily
redirecting both stderr and stdout to the same pipe.
2025-04-05 17:30:46 -05:00
Calvin Rose
a9ff8b388f Add os/getpid 2025-04-05 16:20:17 -05:00
Calvin Rose
b12dfd784e Merge pull request #1578 from sogaiu/tweak-some-docstrings
Tweak doc for mapcat, count, keep, all, some
2025-04-04 21:45:09 -05:00
sogaiu
e2cc8f2965 Tweak doc for mapcat, count, keep, all, some 2025-04-05 10:42:04 +09:00
Calvin Rose
17524d2ed3 Merge pull request #1577 from sogaiu/cleanup-unix-domain-socket
Remove unix domain socket after test
2025-04-04 07:11:42 -05:00
sogaiu
d2ee4aa074 Remove unix domain socket after test 2025-04-04 20:22:10 +09:00
Calvin Rose
363e32d455 Adopt docstring from #1574 2025-04-03 20:52:40 -05:00
Calvin Rose
31920e574d Add explicit Alpine Linux directions.
Statically linking Janet is quite useful, especially for users who want
to use jpm and janet-pm to build standalone binaries.
2025-04-01 19:47:03 -05:00
Calvin Rose
cf714ed591 Notify user when script is installed during a bundle installation.
Lets user know what to add to PATH.
2025-04-01 18:52:23 -05:00
Calvin Rose
b458404b41 Update ffi/write to append to a buffer by default. 2025-03-28 10:00:40 -05:00
Calvin Rose
707463a645 Merge pull request #1575 from pyrmont/feature.ev-interrupt2
Expand scope of code that works with `ev/deadline` again
2025-03-26 09:42:51 -05:00
Michael Camilleri
eac37ab869 Avoid leaking memory used for JanetThreadedTimeout struct 2025-03-25 22:02:53 +09:00
Michael Camilleri
a24e5b1eaa Prevent deadline timeout being less than zero 2025-03-25 21:41:59 +09:00
Michael Camilleri
09ac85b1b9 Move janet_timeout_* function definitions before first use 2025-03-25 21:15:28 +09:00
Michael Camilleri
87c1eab7d4 Define JanetTimeout only if event loop enabled 2025-03-25 21:14:21 +09:00
Michael Camilleri
5a29a28c11 Include <windows.h> in state.h 2025-03-25 20:34:45 +09:00
Michael Camilleri
2ed186664f Support using a background thread to monitor deadlines 2025-03-25 20:27:44 +09:00
Calvin Rose
73334f3485 Prepare for 1.38.0 release. 2025-03-18 21:14:45 -05:00
Calvin Rose
a5b8da8d67 Merge pull request #1572 from tsujamin/janet-ffi-define-fix
Remove janet_lib_ffi() dependency on JANET_EV
2025-03-18 19:12:44 -07:00
Benjamin Roberts
e8cccfced5 Remove janet_lib_ffi() dependency on JANET_EV 2025-03-18 10:54:18 -04:00
Calvin Rose
88984f7ffb Add bundle/replace
Slight generalization of bundle/reinstall to allow inplace upgrades
of a bundle without reinstalling all dependent bundles as well.
2025-03-17 17:58:40 -05:00
Calvin Rose
182170b3be Make installed manifests nicer to work with. 2025-03-16 20:39:16 -05:00
Calvin Rose
f92412841b Merge pull request #1569 from sogaiu/vm-bug-1566 2025-03-12 21:13:49 -07:00
sogaiu
18c00e89da Fix vm bugs from #1566 2025-03-13 11:43:43 +09:00
Calvin Rose
7c38a55a9a Merge pull request #1568 from rwtolbert/msys2_mingw_build_fix
Update Makefile and meson to build with msys2 gcc and clang
2025-03-10 17:56:00 -07:00
Bob Tolbert
a15916ec9c Update Makefile and meson to build with msys2 gcc and clang 2025-03-10 10:13:36 -05:00
Calvin Rose
3583d4c92f Fix defdyn in flychecking. 2025-03-08 20:03:02 -06:00
Calvin Rose
a456c67a7b Merge pull request #1564 from pyrmont/bugfix.qemu-segfault
Use Alpine Linux as base image for s390x testing
2025-03-05 05:52:20 -08:00
Michael Camilleri
6e226e4073 Use sh rather than bash on Alpine Linux 2025-03-04 15:19:44 +09:00
Michael Camilleri
d23e6614b1 Use Alpine Linux as the base image rather than Ubuntu 2025-03-04 15:17:07 +09:00
Michael Camilleri
ab6afa72fd Use simpler base image 2025-03-04 15:01:25 +09:00
Calvin Rose
9538b8a77c Merge pull request #1562 from pyrmont/bugfix.subprocess-cleanup
Improve subprocess-related docstrings and functions
2025-03-03 18:47:59 -08:00
Calvin Rose
36d3804dd2 Merge pull request #1563 from pyrmont/bugfix.copyright-dates
Add script to update copyright dates
2025-03-03 18:47:38 -08:00
Michael Camilleri
a34b8ea68f Update older dates to 2025 2025-03-04 11:32:15 +09:00
Michael Camilleri
55c10f98bb Check more file extensions 2025-03-04 11:31:56 +09:00
Michael Camilleri
4b5a2a14c0 Update year in copyright disclaimer to 2025 2025-03-04 11:24:09 +09:00
Michael Camilleri
665705d06d Add Janet script to update copyright disclaimer 2025-03-04 11:23:26 +09:00
Michael Camilleri
cc8cd4bace Use uraimo GitHub action for s390x testing 2025-03-04 09:14:05 +09:00
Michael Camilleri
8f8b6ed001 Remove note on cancellation and tidy up wording 2025-03-03 23:28:37 +09:00
Calvin Rose
aa9efee868 Expose bundle operations in the CLI.
Bundle installation is a fairly natural command on the command line.
2025-03-03 08:14:09 -06:00
Calvin Rose
e0a0e2ed42 Prevent multi-scheduling with os/proc-wait. (address #1562) 2025-03-02 10:44:15 -06:00
Michael Camilleri
39f5c539d7 Clarify wording of docstrings further 2025-03-02 13:25:14 +09:00
Michael Camilleri
1b6437a4f8 Remove unnecessary returns 2025-03-02 08:30:51 +09:00
Michael Camilleri
c62c1a58f0 Reword documentation relating to subprocess creation 2025-03-02 08:29:32 +09:00
Calvin Rose
3441bcbd69 Add more cases for checking coerce error.
Channel operations inside a janet_call could wreak more
havoc later, when scheduled fibers are shooting off when they shouldn't
be. The easiest way to prevent this is simply check that we are not
inside janet_call before doing channel operations. We also get nicer
error messages this way.
2025-02-16 10:40:38 -06:00
Calvin Rose
2e6001316a Work on bug #1556 2025-02-16 09:09:33 -06:00
Calvin Rose
53bcc15207 Merge pull request #1557 from sogaiu/update-changelog-2025-02
Update CHANGELOG.md
2025-02-09 23:22:45 -08:00
sogaiu
dd609bb1bb Update CHANGELOG.md 2025-02-08 14:29:22 +09:00
Calvin Rose
5c56c7fa91 Formatting in peg.c 2025-02-07 21:05:28 -06:00
Calvin Rose
9a892363a3 Address issue #1558
Don't attempt SO_REUSEPORT on unix domain sockets since it doesn't make
sense and may cause a failure to bind without extra effort by the
programmer.
2025-02-07 21:05:07 -06:00
Calvin Rose
5f550ea5d4 Merge pull request #1555 from sogaiu/tweak-apply-short-fn-docstrings
Tweak apply and short-fn docstrings
2025-02-06 16:44:45 -08:00
sogaiu
1b6cc023a5 Tweak apply and short-fn docstrings 2025-02-06 22:35:59 +09:00
Calvin Rose
410f8d69bc Merge pull request #1528 from ianthehenry/til-peg-special
add (til) PEG special
2025-02-03 19:07:08 -08:00
Calvin Rose
6da44bdb6a Get rid of early termination rule in all finite loops. 2025-02-03 07:36:00 -06:00
Calvin Rose
d30fd27575 Address #1554 - capture first 0-width match in repeating rules and
optionals.

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

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

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

Simplifying the implementation to not resole mutable keys is much
simpler, faster, and has the benefit that deep= and deep-not= do not
need to allocate.
2024-12-21 09:03:01 -06:00
Calvin Rose
1b49934e4f Allow table/to-struct to take a prototype.
Use this prototype struct in freeze.
2024-12-19 19:41:19 -06:00
Calvin Rose
682f0f584f freeze with mutable keys should be determinsic help address #1535 2024-12-19 19:31:01 -06:00
Calvin Rose
611b2a6c3a Add more test cases for #1535 2024-12-19 18:37:51 -06:00
Calvin Rose
8043caf581 Update CHANGELOG. 2024-12-19 18:31:05 -06:00
Calvin Rose
b2d2690eb9 Merge pull request #1534 from pyrmont/bugfix.windows-longstrings
Support dedenting long-strings with Windows EOLs
2024-12-19 16:24:21 -08:00
Calvin Rose
7f745a34c3 Allow for mutable keys correctly in deep= 2024-12-19 18:20:05 -06:00
Calvin Rose
b16cf17246 Merge pull request #1533 from pyrmont/feature.file-socket
Add `ev/to-file` for synchronous resource operations
2024-12-17 20:50:47 -08:00
Michael Camilleri
67e8518ba6 Support dedenting longstrings with Windows EOLs 2024-12-17 05:14:59 +09:00
Michael Camilleri
e94e8dc484 Remove special casing for MinGW 2024-12-16 08:12:14 +09:00
Michael Camilleri
1a24d4fc86 Raise error if using ev/to-file on MinGW 2024-12-15 21:00:52 +09:00
Michael Camilleri
6ee05785d1 Disable buffering for files created with ev/to-file 2024-12-15 20:37:58 +09:00
Michael Camilleri
268ff666d2 Move <fcntl.h> header to general imports 2024-12-15 20:02:41 +09:00
Michael Camilleri
91bb34c3bf Add missing <io.h> header for Windows 2024-12-15 19:17:48 +09:00
Michael Camilleri
17d5fb3210 Fix ev/to-file on Windows 2024-12-15 18:56:35 +09:00
Michael Camilleri
687b987f7e Add ev/to-file for synchronous resource operations 2024-12-15 17:38:01 +09:00
Calvin Rose
4daecc9a41 Prevent await inside janet_call - address #1531
This was partially implemented before, but not in the case where the
await or other signal itself was created by a C function. We have to
separate code paths for generating signals - one via normal returns in
janet_vm_continue, and the other via longjump. This adds handling for
the longjump case, as well as improved messaging.
2024-12-14 10:34:36 -06:00
Calvin Rose
a85eacadda Merge pull request #1532 from strangepete/fstat-directory-test
file/open: check if directory
2024-12-14 06:00:41 -08:00
peteee
ed63987fd1 use fileno()
Should use fileno() instead of direct
2024-12-14 07:17:28 -05:00
peteee
ff173047f4 file/open: check if directory
Adds fstat() directory test after fopen(), which can return non-NULL when passed a directory name on Linux
2024-12-13 00:20:44 -05:00
Ian Henry
952906279c add (til) PEG special
(til sep subpattern) is a specialized (sub) that behaves like
(sub (to sep) subpattern), but advances over the input like (thru sep).
2024-12-04 21:17:10 -08:00
127 changed files with 4531 additions and 1353 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

View File

@@ -1,4 +1,4 @@
image: openbsd/7.4
image: openbsd/7.7
sources:
- https://git.sr.ht/~bakpakin/janet
packages:
@@ -10,13 +10,17 @@ tasks:
gmake
gmake test
doas gmake install
gmake test-install
doas gmake uninstall
- meson_min: |
cd janet
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
cd build_meson_min
ninja
- meson_reduced: |
cd janet
meson setup build_meson_reduced --buildtype=release -Dreduced_os=true
cd build_meson_reduced
ninja
- meson_prf: |
cd janet
meson setup build_meson_prf --buildtype=release -Dprf=true

8
.github/cosmo/setup vendored
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

@@ -27,15 +27,16 @@ jobs:
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
tools: linked
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"

View File

@@ -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-13 ]
os: [ ubuntu-latest, macos-latest, macos-14, macos-15-intel ]
steps:
- name: Checkout the repository
uses: actions/checkout@master
@@ -21,11 +21,25 @@ 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:
matrix:
os: [ windows-latest, windows-2019 ]
os: [ windows-latest, windows-2022 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout the repository
@@ -42,11 +56,32 @@ 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:
matrix:
os: [ windows-2019 ]
os: [ windows-2022 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout the repository
@@ -132,7 +167,26 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Do Qemu build and test
run: |
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --rm -v .:/janet --platform linux/s390x ubuntu bash -c "apt-get -y update && apt-get -y install git build-essential && cd /janet && make -j3 && make test"
- name: Enable qemu
run: docker run --privileged --rm tonistiigi/binfmt --install s390x
- name: Build and run on emulated architecture
run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test"
test-cosmo:
name: Test build for Cosmo
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: create build folder
run: |
sudo mkdir -p /sc
sudo chmod -R 0777 /sc
- name: setup Cosmopolitan Libc
run: bash ./.github/cosmo/setup
- name: Set the version
run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- name: Set the platform
run: echo "platform=cosmo" >> $GITHUB_ENV
- name: build Janet APE binary
run: bash ./.github/cosmo/build

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,84 @@
# Changelog
All notable changes to this project will be documented in this file.
## Unreleased - ???
- Documentation fixes
- ev/thread-chan deadlock bug fixed
- Re-add removed support for non-blocking net/connect on windows.
## 1.41.2 - 2026-02-18
- Fix regressions in `put` for arrays and buffers.
- Add `module/add-file-extension`
- Add `module/add-syspath`
- Fix issue with possible stack corrpution with abstract types that modify the current fiber.
- Allow use of the interpreter and garbage collection inside module entry for native modules.
## 1.41.1 - 2026-02-15
- Revert to blocking behaior of `net/connect` on windows to fix spurious errors.
- Allow overriding the loader when doing imports with the `:loader` argument.
- Allow importing modules with a path extension to do what one would expect.
- Add `find-all` argument to `module/find`
- Add :threads, :unmarshal, :compiler, and :asm sandbox flags.
- Add support for persistent REPL history with the environment variable `JANET_HISTFILE`
- Fix a number of fuzzer-found compiler bugs
- Fix windows processes launching bug with empty environment table that caused process-launch failures.
- Add `:I`, `:V`, and `:N` flags to `os/open` for more control when creating streams.
- Add `ev/go-gather` for a dynamic `ev/gather`.
- Use color in script output if color is being used in REPL output.
- Fix `varfn` macros handling of extra metadata.
- Disallow certain degenerate uses of fibers with the ev/ module.
- Add linting for unused bindings.
- Add linting for extra or wrong parameters to &named functions.
- Add `janet_optuinteger` and `janet_optuinteger64` to the C API.
- Add `cms` combinator to PEGs.
- Add `thaw-keep-keys` as a variant of thaw
- The `repl` function now respects the `*repl-prompt*` dynamic binding by default.
- Allow matching exact lengths of datastructures with the `match` macro using a dollar suffix.
- Add initial support for Plan 9. Some modules (e.g. ev) are not yet enabled.
- Allow specifying `:flycheck` for individual defines to annotate that they are safe to evaluate for flychecking.
## 1.40.1 - 2025-11-16
- Fix `JANET_REDUCED_OS` build regression caused by `os/posix-chroot`.
- Code formatting
## 1.40.0 - 2025-11-15
- Add `os/posix-chroot`
- Fix `ev/deadline` with interrupt race condition bug on Windows.
- Improve `flycheck` by allowing functions and macros to define their own flycheck behavior via the metadata `:flycheck`.
- Add `*flychecking*` dynamic binding to check if inside flycheck evalutation
- Add `gcperthread` callback for abstract types. This lets threaded abstracts have a finalizer that is called per thread, as well as a global finalizer.
- Add `JANET_DO_ERROR_*` flags to describe the return value of `janet_dobytes` and `janet_dostring`.
## 1.39.1 - 2025-08-30
- Add support for chdir in os/spawn on older macOS versions
- Expose channels properly in C API
## 1.39.0 - 2025-08-24
- Various bug fixes
- Add `net/socket`
- Add support for illumos OS
- Raise helpful errors for incorrect arguments to `import`.
- Allow configuring `JANET_THREAD_LOCAL` during builds to allow multi-threading on unknown compilers.
- Make `ffi/write` append to a buffer instead of insert at 0 by default.
- Add `os/getpid` to get the current process id.
- Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes.
Add `interrupt?` argument to `ev/deadline` to use VM interruptions.
## 1.38.0 - 2025-03-18
- Add `bundle/replace`
- Add CLI flags for the `bundle/` module to install and manage bundles.
- Improve `?` peg special termination behavior
- Add IEEE hex floats to grammar.
- Add buffer peg literal support
- Improve `split` peg special edge case behavior
- Add Arm64 .msi support
- Add `no-reuse` argument to `net/listen` to disable reusing server sockets
- Add `struct/rawget`
- Fix `deep=` and `deep-not=` to better handle degenerate cases with mutable table keys
- Long strings will now dedent on `\r\n` instead of just `\n`.
- Add `ev/to-file` for synchronous resource operations
- Improve `file/open` error message by including path
## 1.37.1 - 2024-12-05
- Fix meson cross compilation
- Update timeout documentation for networking APIs: timeouts raise errors and do not return nil.

View File

@@ -37,6 +37,12 @@ may require changes before being merged.
do this indentation, or approximate as close as possible. There is a janet formatter
in [spork](https://github.com/janet-lang/spork.git) that can be used to format code as well.
Bot pull requests will not be accepted, and anonymous submissions, including
new accounts, unknown emails, and first time contributors will be subjected
to greater scrutiny and code reivew. Automatically generated and filed bug
reports MAY be ok, if they are of consistent and good quality, such as
OSSFuzz or well constructed CI pipelines.
## C style
For changes to the VM and Core code, you will probably need to know C. Janet is programmed with
@@ -61,7 +67,7 @@ ensure a consistent code style for C.
## Janet style
All janet code in the project should be formatted similar to the code in core.janet.
All janet code in the project should be formatted similar to the code in src/boot/boot.janet.
The auto formatting from janet.vim will work well.
## Typo Fixing and One-Line changes
@@ -90,3 +96,18 @@ timely manner. In short, if you want extra functionality now, then build it.
* Include a good description of the problem that is being solved
* Include descriptions of potential solutions if you have some in mind.
## LLMs, Tool Usage, and Transparency
All usage of Large Language Models (LLMs), Neural Networks, "AI" tools, and
other tools such as software fuzzers or static analyzers must be disclosed.
This applies to pull requests, email patches, bug reports, and any other
meaningful contribution to Janet's source code. Please also refrain from using
generative AI for code that will be embedded in the Janet runtime, which include
all C source files as well as boot.janet. All code should be well
and completely understood by the human author, including test cases. Large and
obviously AI-driven changes will be rejected. Be mindful and transparent on the
copyright implications of any submitted code. We will use discretion when
accepting generated test cases for bug reproductions, one-line bug
fixes, or typo fixes. Often, these can be trivially rewritten by a human to
avoid the problem.

View File

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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -47,6 +47,7 @@ SPORK_TAG?=master
HAS_SHARED?=1
DEBUGGER=gdb
SONAME_SETTER=-Wl,-soname,
STRIPFLAGS=-x -S
# For cross compilation
HOSTCC?=$(CC)
@@ -54,7 +55,7 @@ HOSTAR?=$(AR)
# Symbols are (optionally) removed later, keep -g as default!
CFLAGS?=-O2 -g
LDFLAGS?=-rdynamic
LIBJANET_LDFLAGS?=$(LD_FLAGS)
LIBJANET_LDFLAGS?=$(LDFLAGS)
RUN:=$(RUN)
COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC
@@ -79,6 +80,12 @@ ifeq ($(UNAME), Darwin)
LDCONFIG:=true
else ifeq ($(UNAME), Linux)
CLIBS:=$(CLIBS) -lrt -ldl
else ifeq ($(UNAME), SunOS)
BUILD_CFLAGS+=-D__EXTENSIONS__ -DJANET_NO_NANBOX
BOOT_CFLAGS+=-D__EXTENSIONS__ -DJANET_NO_NANBOX
CLIBS:=-lsocket -lm
STRIPFLAGS=-x
LDCONFIG:=false
endif
# For other unix likes, add flags here!
@@ -94,12 +101,18 @@ endif
endif
# Mingw
MINGW_COMPILER=
ifeq ($(findstring MINGW,$(UNAME)), MINGW)
MINGW_COMPILER=gcc
CLIBS:=-lws2_32 -lpsapi -lwsock32
LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB)
LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB)
JANET_TARGET:=$(JANET_TARGET).exe
JANET_BOOT:=$(JANET_BOOT).exe
COMPILER_VERSION:=$(shell $(CC) --version)
ifeq ($(findstring clang,$(COMPILER_VERSION)), clang)
MINGW_COMPILER=clang
endif
endif
@@ -206,9 +219,14 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile
########################
ifeq ($(UNAME), Darwin)
SONAME=libjanet.1.37.dylib
SONAME=libjanet.1.41.dylib
else
SONAME=libjanet.so.1.37
SONAME=libjanet.so.1.41
endif
ifeq ($(MINGW_COMPILER), clang)
SONAME=
SONAME_SETTER=
endif
build/c/shell.c: src/mainclient/shell.c
@@ -242,6 +260,7 @@ $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS)
# Testing assumes HOSTCC=CC
TEST_SCRIPTS=$(wildcard test/suite*.janet)
EXAMPLE_SCRIPTS=$(wildcard examples/*.janet)
repl: $(JANET_TARGET)
$(RUN) ./$(JANET_TARGET)
@@ -249,21 +268,26 @@ repl: $(JANET_TARGET)
debug: $(JANET_TARGET)
$(DEBUGGER) ./$(JANET_TARGET)
VALGRIND_COMMAND=valgrind --leak-check=full --quiet
VALGRIND_COMMAND=$(RUN) valgrind --leak-check=full --quiet
CALLGRIND_COMMAND=$(RUN) valgrind --tool=callgrind
valgrind: $(JANET_TARGET)
$(VALGRIND_COMMAND) ./$(JANET_TARGET)
test: $(JANET_TARGET) $(TEST_PROGRAMS)
test: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done
valtest: $(JANET_TARGET) $(TEST_PROGRAMS)
valtest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done
for f in examples/*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done
callgrind: $(JANET_TARGET)
for f in test/suite*.janet; do valgrind --tool=callgrind ./$(JANET_TARGET) "$$f" || exit; done
$(CALLGRIND_COMMAND) ./$(JANET_TARGET)
calltest: $(JANET_TARGET) $(TEST_SCRIPTS) $(EXAMPLE_SCRIPTS)
for f in test/suite*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do $(CALLGRIND_COMMAND) ./$(JANET_TARGET) -k "$$f"; done
########################
##### Distribution #####
@@ -277,7 +301,7 @@ build/janet-%.tar.gz: $(JANET_TARGET) \
README.md build/c/janet.c build/c/shell.c
mkdir -p build/$(JANET_DIST_DIR)/bin
cp $(JANET_TARGET) build/$(JANET_DIST_DIR)/bin/
strip -x -S 'build/$(JANET_DIST_DIR)/bin/janet'
strip $(STRIPFLAGS) 'build/$(JANET_DIST_DIR)/bin/janet'
mkdir -p build/$(JANET_DIST_DIR)/include
cp build/janet.h build/$(JANET_DIST_DIR)/include/
mkdir -p build/$(JANET_DIST_DIR)/lib/
@@ -322,22 +346,23 @@ build/janet.pc: $(JANET_TARGET)
echo 'Libs.private: $(CLIBS)' >> $@
install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h
$(eval JANET_VERSION := $(shell $(JANET_TARGET) -e '(print janet/version)'))
mkdir -p '$(DESTDIR)$(BINDIR)'
cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet'
strip -x -S '$(DESTDIR)$(BINDIR)/janet'
strip $(STRIPFLAGS) '$(DESTDIR)$(BINDIR)/janet'
mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet'
cp -r build/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet'
ln -sf ./janet/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet.h'
mkdir -p '$(DESTDIR)$(JANET_PATH)'
mkdir -p '$(DESTDIR)$(LIBDIR)'
if test $(UNAME) = Darwin ; then \
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib' ; \
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(JANET_VERSION).dylib' ; \
ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.dylib' ; \
ln -sf libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
ln -sf libjanet.$(JANET_VERSION).dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
else \
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)')' ; \
cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(JANET_VERSION)' ; \
ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.so' ; \
ln -sf libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)') $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
ln -sf libjanet.so.$(JANET_VERSION) $(DESTDIR)$(LIBDIR)/$(SONAME) ; \
fi
cp $(JANET_STATIC_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.a'
mkdir -p '$(DESTDIR)$(JANET_MANPATH)'
@@ -394,9 +419,6 @@ clean:
-rm -rf build vgcore.* callgrind.*
-rm -rf test/install/build test/install/modpath
test-install:
echo "JPM has been removed from default install."
help:
@echo
@echo 'Janet: A Dynamic Language & Bytecode VM'
@@ -408,7 +430,8 @@ help:
@echo ' make test Test a built Janet'
@echo ' make valgrind Assess Janet with Valgrind'
@echo ' make callgrind Assess Janet with Valgrind, using Callgrind'
@echo ' make valtest Run the test suite with Valgrind to check for memory leaks'
@echo ' make valtest Run the test suite and examples with Valgrind to check for memory leaks'
@echo ' make calltest Run the test suite and examples with Callgrind'
@echo ' make dist Create a distribution tarball'
@echo ' make docs Generate documentation'
@echo ' make debug Run janet with GDB or LLDB'
@@ -418,6 +441,9 @@ help:
@echo " make format Format Janet's own source files"
@echo ' make grammar Generate a TextMate language grammar'
@echo
@echo ' make install-jpm-git Install jpm into the current filesystem'
@echo ' make install-spork-git Install spork into the current filesystem'
@echo
.PHONY: clean install repl debug valgrind test \
valtest dist uninstall docs grammar format help compile-commands
.PHONY: clean install install-jpm-git install-spork-git repl debug valgrind test \
valtest callgrind callgrind-test dist uninstall docs grammar format help compile-commands

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,11 +192,34 @@ make
make test
make repl
make install
make install-jpm-git
make install-spork-git # optional
make install-jpm-git # optional
```
Find out more about the available make targets by running `make help`.
### Alpine Linux
To build a statically-linked build of Janet, Alpine Linux + MUSL is a good
combination. Janet can also be built inside a docker container or similar in
this manner. This is a great way to try Janet without committing to a full
install or needing to customize the default install.
```sh
docker run -it --rm alpine /bin/ash
$ apk add make gcc musl-dev git
$ git clone https://github.com/janet-lang/janet.git
$ cd janet
$ make -j10
$ make test
$ make install
$ make install-spork-git # optional
$ make install-jpm-git # optional
```
Note that for a true statically-linked binary with MUSL, one needs to add `-static` to the Makefile flags. This
will also disable runtime loading of native modules (plugins) as well as the FFI.
### 32-bit Haiku
32-bit Haiku build instructions are the same as the UNIX-like build instructions,
@@ -176,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
@@ -190,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
@@ -198,6 +255,10 @@ gmake install-jpm-git
NetBSD build instructions are the same as the FreeBSD build instructions.
Alternatively, install the package directly with `pkgin install janet`.
### illumos
Building on illumos is exactly the same as building on FreeBSD.
### Windows
1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15#) or [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15#).
@@ -207,7 +268,7 @@ Alternatively, install the package directly with `pkgin install janet`.
To build an `.msi` installer executable, in addition to the above steps, you will have to:
5. Install, or otherwise add to your PATH the [WiX 3.11 Toolset](https://github.com/wixtoolset/wix3/releases).
5. Install, or otherwise add to your PATH the [WiX 3.14 Toolset](https://github.com/wixtoolset/wix3/releases).
6. Run `build_win dist`.
Now you should have an `.msi`. You can run `build_win install` to install the `.msi`, or execute the file itself.
@@ -301,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,7 +23,17 @@
@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
if DEFINED CLANG (
@set COMPILER=clang-cl.exe
) else (
@set COMPILER=cl.exe
)
if DEFINED SANITIZE (
@set "SANITIZERS=/fsanitize=address"
) else (
@set "SANITIZERS="
)
@set JANET_COMPILE=%COMPILER% /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD %SANITIZERS%
@set JANET_LINK=link /nologo
@set JANET_LINK_STATIC=lib /nologo
@@ -41,34 +51,35 @@ if not exist build\boot mkdir build\boot
@rem Build the bootstrap interpreter
for %%f in (src\core\*.c) do (
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
)
for %%f in (src\boot\*.c) do (
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
)
%JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem note that there is no default syspath being baked in
build\janet_boot . > build\c\janet.c
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Build the sources
%JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
%JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Build the resources
rc /nologo /fobuild\janet_win.res janet_win.rc
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Link everything to main client
%JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Build static library (libjanet.lib)
%JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
echo === Successfully built janet.exe for Windows ===
echo === Run 'build_win test' to run tests. ==
@@ -91,7 +102,7 @@ exit /b 0
@rem Clean build artifacts
:CLEAN
del *.exe *.lib *.exp
del *.exe *.lib *.exp *.msi *.wixpdb
rd /s /q build
if exist dist (
rd /s /q dist
@@ -102,7 +113,7 @@ exit /b 0
:TEST
for %%f in (test/suite*.janet) do (
janet.exe test\%%f
@if not errorlevel 0 goto TESTFAIL
@if errorlevel 1 goto TESTFAIL
)
exit /b 0
@@ -143,7 +154,13 @@ if defined CI (
) else (
set WIXBIN=
)
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %BUILDARCH% -out build\
set WIXARCH=%BUILDARCH%
if "%WIXARCH%"=="aarch64" (
set WIXARCH=arm64
)
%WIXBIN%candle.exe tools\msi\janet.wxs -arch %WIXARCH% -out build\
%WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi
exit /b 0

View File

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

View File

@@ -26,7 +26,7 @@
(broadcast name (string msg)))
(print name " disconnected")))))
(defn main [& args]
(defn main [&]
(printf "STARTING SERVER...")
(flush)
(def my-server (net/listen "127.0.0.1" "8000"))

View File

@@ -132,7 +132,7 @@
"Go to the next breakpoint."
[&opt n]
(var res nil)
(for i 0 (or n 1)
(repeat (or n 1)
(set res (resume (.fiber))))
res)
@@ -146,6 +146,6 @@
"Execute the next n instructions."
[&opt n]
(var res nil)
(for i 0 (or n 1)
(repeat (or n 1)
(set res (debug/step (.fiber))))
res)

View File

@@ -2,7 +2,7 @@
(def counts (seq [_ :range [0 100]] 0))
(for i 0 1000000
(repeat 1000000
(let [x (math/random)
intrange (math/floor (* 100 x))
oldcount (counts intrange)]

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
(ev/give chan (math/random))
(ev/give chan (math/random))
(ev/sleep 0.5)
(for i 0 10
(repeat 10
(print "giving to channel...")
(ev/give chan (math/random))
(ev/sleep 1))

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

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose and contributors
# Copyright (c) 2026 Calvin Rose and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -20,7 +20,7 @@
project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.37.1')
version : '1.41.3')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
@@ -39,6 +39,15 @@ native_thread_dep = dependency('threads', native : true)
# Deps
m_dep = cc.find_library('m', required : false)
dl_dep = cc.find_library('dl', required : false)
# for MINGW/MSYS2
native_ws2_dep = native_cc.find_library('ws2_32', required: false)
native_psapi_dep = native_cc.find_library('psapi', required: false)
native_wsock_dep = native_cc.find_library('wsock32', required: false)
ws2_dep = cc.find_library('ws2_32', required: false)
psapi_dep = cc.find_library('psapi', required: false)
wsock_dep = cc.find_library('wsock32', required: false)
android_spawn_dep = cc.find_library('android-spawn', required : false)
thread_dep = dependency('threads')
@@ -96,6 +105,9 @@ endif
if get_option('arch_name') != ''
conf.set('JANET_ARCH_NAME', get_option('arch_name'))
endif
if get_option('thread_local_prefix') != ''
conf.set('JANET_THREAD_LOCAL', get_option('thread_local_prefix'))
endif
jconf = configure_file(output : 'janetconf.h',
configuration : conf)
@@ -173,8 +185,8 @@ mainclient_src = [
'src/mainclient/shell.c'
]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep, ws2_dep, psapi_dep, wsock_dep]
janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep, native_ws2_dep, native_psapi_dep, native_wsock_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
janet_native_dependencies += native_thread_dep
@@ -269,12 +281,14 @@ test_files = [
'test/suite-corelib.janet',
'test/suite-debug.janet',
'test/suite-ev.janet',
'test/suite-ev2.janet',
'test/suite-ffi.janet',
'test/suite-filewatch.janet',
'test/suite-inttypes.janet',
'test/suite-io.janet',
'test/suite-marsh.janet',
'test/suite-math.janet',
'test/suite-net.janet',
'test/suite-os.janet',
'test/suite-parse.janet',
'test/suite-peg.janet',

View File

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

49
plan9.mk Executable file
View File

@@ -0,0 +1,49 @@
</$objtype/mkfile
TARG=janet
HFILES=src/include/janet.h src/conf/janetconf.h
JANET_PATH=/sys/lib/janet
BIN=/$objtype/bin/
DISABLED=EV NET ASSEMBLER FFI UTC_MKTIME REALPATH DYNAMIC_MODULES THREADS SYMLINKS LOCALES UMASK SPAWN
JANET_CONFIG=JANET_SINGLE_THREADED JANET_OS_NAME=plan9 JANET_ARCH_NAME=$objtype JANET_API='' JANET_NO_RETURN='' JANET_SIMPLE_GETLINE `{echo JANET_NO_^$DISABLED} PLAN9_`{echo -n $objtype}
CFLAGS=-FTVBNcwp -D _POSIX_SOURCE -DJANET_PLAN9 -D_BSD_EXTENSION -D_LIMITS_EXTENSION -Isrc/include -Isrc/conf -I/sys/include/npe -Dtypestr=janettypestr -DJANET_API `{echo '-D'^$JANET_CONFIG}
BOOT_CFLAGS=$CFLAGS -DJANET_BOOTSTRAP
JANET_CORE_HEADERS=`{ls src/core/*.h}
JANET_CORE_SOURCES=`{ls src/core/*.c}
JANET_BOOT_SOURCES=src/boot/array_test.c \
src/boot/boot.c \
src/boot/buffer_test.c \
src/boot/number_test.c \
src/boot/system_test.c \
src/boot/table_test.c
JANET_BOOT_HEADERS=src/boot/tests.h
JANET_BOOT_OBJECTS=`{echo $JANET_CORE_SOURCES $JANET_BOOT_SOURCES | sed -e 's/\.c/.boot.'$O'/g'}
OFILES=janet.$O src/mainclient/shell.$O
default:V:all
src/%.boot.$O: src/%.c $JANET_HEADERS $JANET_CORE_HEADERS $JANET_BOOT_HEADERS
$CC $BOOT_CFLAGS -o $target $prereq(1)
src/mainclient/shell.$O: src/mainclient/shell.c
$CC $BOOT_CFLAGS -o $target $prereq(1)
$O.janetboot: $JANET_BOOT_OBJECTS
$LD $LDFLAGS -o $target $prereq
janet.c: $O.janetboot src/boot/boot.janet
$prereq(1) . JANET_PATH $JANET_PATH >$target
build/janet.$O: build/c/janet.c src/conf/janetconf.h src/include/janet.h
$CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1)
build/shell.$O: src/mainclient/shell.c src/conf/janetconf.h src/include/janet.h
$CC $CFLAGS -D^$JANET_CONFIG -o $target $prereq(1)
</sys/src/cmd/mkone
clean:V:
rm -f src/core/*.[$OS] src/boot/*.[$OS] *.a[$OS] y.tab.? lex.yy.c y.debug y.output [$OS].??* $TARG janet.[$OS] janet.c src/mainclient/shell.[$OS]

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -44,7 +44,9 @@ static void test_valid_str(const char *str) {
}
int number_test() {
#ifdef JANET_PLAN9
return 0;
#endif
test_valid_str("1.0");
test_valid_str("1");
test_valid_str("2.1");

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -37,7 +37,7 @@ int system_test() {
/* Check the version defines are self consistent */
char version_combined[256];
sprintf(version_combined, "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA);
snprintf(version_combined, sizeof(version_combined), "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA);
assert(!strcmp(JANET_VERSION, version_combined));
/* Reflexive testing and nanbox testing */
@@ -51,6 +51,10 @@ int system_test() {
assert(janet_equals(janet_wrap_number(1.4), janet_wrap_number(1.4)));
assert(janet_equals(janet_wrap_number(3.14159265), janet_wrap_number(3.14159265)));
#ifdef NAN
#ifdef JANET_PLAN9
// Plan 9 traps NaNs by default; disable that.
setfcr(0);
#endif
assert(janet_checktype(janet_wrap_number(NAN), JANET_NUMBER));
#else
assert(janet_checktype(janet_wrap_number(0.0 / 0.0), JANET_NUMBER));

View File

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

View File

@@ -4,15 +4,16 @@
#define JANETCONF_H
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 37
#define JANET_VERSION_PATCH 1
#define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.37.1"
#define JANET_VERSION_MINOR 41
#define JANET_VERSION_PATCH 3
#define JANET_VERSION_EXTRA "-dev"
#define JANET_VERSION "1.41.3-dev"
/* #define JANET_BUILD "local" */
/* These settings all affect linking, so use cautiously. */
/* #define JANET_SINGLE_THREADED */
/* #define JANET_THREAD_LOCAL _Thread_local */
/* #define JANET_NO_DYNAMIC_MODULES */
/* #define JANET_NO_NANBOX */
/* #define JANET_API __attribute__((visibility ("default"))) */

View File

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

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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,7 +1108,9 @@ 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, "symbolmap")) return janet_disasm_symbolslots(f->def);
if (!janet_cstrcmp(kw, "constants")) return janet_disasm_constants(f->def);
if (!janet_cstrcmp(kw, "sourcemap")) return janet_disasm_sourcemap(f->def);
if (!janet_cstrcmp(kw, "environments")) return janet_disasm_environments(f->def);

View File

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

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

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -62,6 +62,18 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) {
void janet_signalv(JanetSignal sig, Janet message) {
if (janet_vm.return_reg != NULL) {
/* Should match logic in janet_call for coercing everything not ok to an error (no awaits, yields, etc.) */
if (janet_vm.coerce_error && sig != JANET_SIGNAL_OK) {
#ifdef JANET_EV
if (NULL != janet_vm.root_fiber && sig == JANET_SIGNAL_EVENT) {
janet_vm.root_fiber->sched_id++;
}
#endif
if (sig != JANET_SIGNAL_ERROR) {
message = janet_wrap_string(janet_formatc("%v coerced from %s to error", message, janet_signal_names[sig]));
}
sig = JANET_SIGNAL_ERROR;
}
*janet_vm.return_reg = message;
if (NULL != janet_vm.fiber) {
janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP;
@@ -448,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();
}
@@ -545,6 +557,18 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA
return janet_getabstract(argv, n, at);
}
uint32_t janet_optuinteger(const Janet *argv, int32_t argc, int32_t n, uint32_t dflt) {
if (argc <= n) return dflt;
if (janet_checktype(argv[n], JANET_NIL)) return dflt;
return janet_getuinteger(argv, n);
}
uint64_t janet_optuinteger64(const Janet *argv, int32_t argc, int32_t n, uint64_t dflt) {
if (argc <= n) return dflt;
if (janet_checktype(argv[n], JANET_NIL)) return dflt;
return janet_getuinteger64(argv, n);
}
/* Atomic refcounts */
JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
@@ -552,6 +576,8 @@ JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) {
return _InterlockedIncrement(x);
#elif defined(JANET_USE_STDATOMIC)
return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1;
#elif defined(JANET_PLAN9)
return aincl((void*)x, 1);
#else
return __atomic_add_fetch(x, 1, __ATOMIC_RELAXED);
#endif
@@ -562,6 +588,8 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
return _InterlockedDecrement(x);
#elif defined(JANET_USE_STDATOMIC)
return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1;
#elif defined(JANET_PLAN9)
return aincl((void*)x, -1);
#else
return __atomic_add_fetch(x, -1, __ATOMIC_ACQ_REL);
#endif
@@ -570,6 +598,8 @@ JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) {
JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
#ifdef _MSC_VER
return _InterlockedOr(x, 0);
#elif defined(JANET_PLAN9)
return agetl((void*)x);
#elif defined(JANET_USE_STDATOMIC)
return atomic_load_explicit(x, memory_order_acquire);
#else
@@ -577,6 +607,18 @@ JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
#endif
}
JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x) {
#ifdef _MSC_VER
return _InterlockedOr(x, 0);
#elif defined(JANET_PLAN9)
return agetl((void*)x);
#elif defined(JANET_USE_STDATOMIC)
return atomic_load_explicit(x, memory_order_relaxed);
#else
return __atomic_load_n(x, __ATOMIC_RELAXED);
#endif
}
/* Some definitions for function-like macros */
JANET_API JanetStructHead *(janet_struct_head)(JanetStruct st) {

View File

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

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -98,6 +98,22 @@ void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s) {
sp.sym2 = sym;
sp.slot = s;
sp.keep = 0;
sp.referenced = sym[0] == '_'; /* Fake ref if symbol is _ to avoid lints */
sp.slot.flags |= JANET_SLOT_NAMED;
sp.birth_pc = cnt ? cnt - 1 : 0;
sp.death_pc = UINT32_MAX;
janet_v_push(c->scope->syms, sp);
}
/* Same as janetc_nameslot, but don't have a lint for unused bindings. */
void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s) {
SymPair sp;
int32_t cnt = janet_v_count(c->buffer);
sp.sym = sym;
sp.sym2 = sym;
sp.slot = s;
sp.keep = 0;
sp.referenced = 1;
sp.slot.flags |= JANET_SLOT_NAMED;
sp.birth_pc = cnt ? cnt - 1 : 0;
sp.death_pc = UINT32_MAX;
@@ -170,6 +186,10 @@ void janetc_popscope(JanetCompiler *c) {
/* Keep upvalue slots and symbols for debugging. */
for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) {
SymPair pair = oldscope->syms[i];
/* Check for unused symbols */
if (pair.referenced == 0 && pair.sym) {
janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is unused", janet_wrap_symbol(pair.sym));
}
/* The variable should not be lexically accessible */
pair.sym = NULL;
if (pair.death_pc == UINT32_MAX) {
@@ -262,6 +282,7 @@ JanetSlot janetc_resolve(
pair = scope->syms + i;
if (pair->sym == sym) {
ret = pair->slot;
pair->referenced = 1;
goto found;
}
}
@@ -274,7 +295,7 @@ JanetSlot janetc_resolve(
{
JanetBinding binding = janet_resolve_ext(c->env, sym);
if (binding.type == JANET_BINDING_NONE) {
Janet handler = janet_table_get(c->env, janet_ckeywordv("missing-symbol"));
Janet handler = janet_table_get_keyword(c->env, "missing-symbol");
switch (janet_type(handler)) {
case JANET_NIL:
break;
@@ -346,6 +367,7 @@ found:
/* non-local scope needs to expose its environment */
JanetScope *original_scope = scope;
pair->keep = 1;
pair->referenced = 1;
while (scope && !(scope->flags & JANET_SCOPE_FUNCTION))
scope = scope->parent;
janet_assert(scope, "invalid scopes");
@@ -437,11 +459,22 @@ JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) {
const JanetKV *kvs = NULL;
int32_t cap = 0, len = 0;
janet_dictionary_view(ds, &kvs, &len, &cap);
for (int32_t i = 0; i < cap; i++) {
if (janet_checktype(kvs[i].key, JANET_NIL)) continue;
janet_v_push(ret, janetc_value(subopts, kvs[i].key));
janet_v_push(ret, janetc_value(subopts, kvs[i].value));
/* Sort keys for stability of order? */
int32_t *index_buf;
int32_t index_buf_stack[32];
int32_t *index_buf_heap = NULL;
if (len < 32) {
index_buf = index_buf_stack;
} else {
index_buf_heap = janet_smalloc(sizeof(int32_t) * len);
index_buf = index_buf_heap;
}
if (len) janet_sorted_keys(kvs, cap, index_buf);
for (int32_t i = 0; i < len; i++) {
janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].key));
janet_v_push(ret, janetc_value(subopts, kvs[index_buf[i]].value));
}
if (index_buf_heap) janet_sfree(index_buf_heap);
return ret;
}
@@ -514,9 +547,9 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
JanetScope unusedScope;
int32_t bufstart = janet_v_count(c->buffer);
int32_t mapbufstart = janet_v_count(c->mapbuffer);
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unusued");
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unused");
janetc_value(opts, x);
janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.2q", x);
janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.4q", x);
janetc_popscope(c);
if (c->buffer) {
janet_v__cnt(c->buffer) = bufstart;
@@ -526,7 +559,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
}
/* Compile a call or tailcall instruction */
static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun, const Janet *form) {
JanetSlot retslot;
JanetCompiler *c = opts.compiler;
int specialized = 0;
@@ -552,6 +585,8 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
JanetFunction *f = janet_unwrap_function(fun.constant);
int32_t min = f->def->min_arity;
int32_t max = f->def->max_arity;
int structarg = f->def->flags & JANET_FUNCDEF_FLAG_STRUCTARG;
int namedarg = f->def->flags & JANET_FUNCDEF_FLAG_NAMEDARGS;
if (min_arity < 0) {
/* Call has splices */
min_arity = -1 - min_arity;
@@ -575,6 +610,47 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
fun.constant, min, min == 1 ? "" : "s", min_arity);
janetc_error(c, es);
}
if (structarg && (min_arity > f->def->arity) && ((min_arity - f->def->arity) & 1)) {
/* If we have an odd number of variadic arguments to a `&keys` function, that is almost certainly wrong. */
if (namedarg) {
janetc_lintf(c, JANET_C_LINT_NORMAL,
"odd number of named arguments to `&named` function %v", fun.constant);
} else {
janetc_lintf(c, JANET_C_LINT_NORMAL,
"odd number of named arguments to `&keys` function %v", fun.constant);
}
}
if (namedarg && f->def->named_args_count > 0) {
/* For each argument passed in, check if it is one of the used named arguments
* by checking the list defined in the function def. If not, raise a normal compiler
* lint. We can also do a strict lint for _missing_ named arguments, although in many
* cases those are assumed to have some kind of default, or we have dynamic keys. */
int32_t first_arg_key_index = f->def->arity + 1;
for (int32_t i = first_arg_key_index; i < janet_tuple_length(form); i += 2) {
Janet argkey = form[i];
/* Assumption: The first N constants of a function are its named argument keys. This
* may change if the compiler changes, but is true for all Janet generated functions. */
int found = 0;
if (janet_checktype(argkey, JANET_KEYWORD)) {
for (int32_t j = 0; j < f->def->named_args_count && j < f->def->constants_length; j++) {
if (janet_equals(argkey, f->def->constants[j])) {
found = 1;
break;
}
}
} else if (janet_checktype(argkey, JANET_TUPLE)) {
/* Possible lint : too dynamic, be dumber
* (defn f [&named x] [x])
* (f (if (coin-flip) :x :w) 10)
* A tuple could be a function call the evaluates to a valid key */
found = 1;
}
if (!found) {
janetc_lintf(c, JANET_C_LINT_NORMAL,
"unused named argument %v to function %v", argkey, fun.constant);
}
}
}
}
}
break;
@@ -809,14 +885,16 @@ JanetSlot janetc_value(JanetFopts opts, Janet x) {
} else if (janet_tuple_flag(tup) & JANET_TUPLE_FLAG_BRACKETCTOR) { /* [] tuples are not function call */
ret = janetc_tuple(opts, x);
} else {
/* Function calls */
JanetSlot head = janetc_value(subopts, tup[0]);
subopts.flags = JANET_FUNCTION | JANET_CFUNCTION;
ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head);
ret = janetc_call(opts, janetc_toslots(c, tup + 1, janet_tuple_length(tup) - 1), head, tup);
janetc_freeslot(c, head);
}
ret.flags &= ~JANET_SLOT_SPLICED;
}
break;
/* Data Constructors */
case JANET_SYMBOL:
ret = janetc_resolve(c, janet_unwrap_symbol(x));
break;
@@ -856,19 +934,21 @@ void janet_def_addflags(JanetFuncDef *def) {
int32_t set_flags = 0;
int32_t unset_flags = 0;
/* pos checks */
if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (def->name) set_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (def->source) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (def->defs) set_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (def->environments) set_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (def->sourcemap) set_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (def->closure_bitset) set_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (def->named_args_count) set_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
/* negative checks */
if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (!def->name) unset_flags |= JANET_FUNCDEF_FLAG_HASNAME;
if (!def->source) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCE;
if (!def->defs) unset_flags |= JANET_FUNCDEF_FLAG_HASDEFS;
if (!def->environments) unset_flags |= JANET_FUNCDEF_FLAG_HASENVS;
if (!def->sourcemap) unset_flags |= JANET_FUNCDEF_FLAG_HASSOURCEMAP;
if (!def->closure_bitset) unset_flags |= JANET_FUNCDEF_FLAG_HASCLOBITSET;
if (!def->named_args_count) unset_flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
/* Update flags */
def->flags |= set_flags;
def->flags &= ~unset_flags;
@@ -939,8 +1019,9 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
JANET_OUT_OF_MEMORY;
}
memcpy(chunks, scope->ua.chunks, sizeof(uint32_t) * numchunks);
/* fprintf(stderr, "slot chunks: %d, scope->ua.count: %d, numchunks: %d\n", slotchunks, scope->ua.count, numchunks); */
/* Register allocator preallocates some registers [240-255, high 16 bits of chunk index 7], we can ignore those. */
if (scope->ua.count > 7) chunks[7] &= 0xFFFFU;
if (scope->ua.count > 7 && slotchunks > 7) chunks[7] &= 0xFFFFU;
def->closure_bitset = chunks;
}
@@ -974,6 +1055,10 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
SymPair pair = scope->syms[i];
if (pair.sym2) {
JanetSymbolMap jsm;
/* Check for unused symbols */
if (pair.referenced == 0 && pair.sym) {
janetc_lintf(c, JANET_C_LINT_STRICT, "binding %q is unused", janet_wrap_symbol(pair.sym));
}
if (pair.death_pc == UINT32_MAX) {
jsm.death_pc = def->bytecode_length;
} else {
@@ -1082,6 +1167,7 @@ JANET_CORE_FN(cfun_compile,
"struct with keys :line, :column, and :error if compilation fails. "
"If a `lints` array is given, linting messages will be appended to the array. "
"Each message will be a tuple of the form `(level line col message)`.") {
janet_sandbox_assert(JANET_SANDBOX_COMPILE);
janet_arity(argc, 1, 4);
JanetTable *env = (argc > 1 && !janet_checktype(argv[1], JANET_NIL))
? janet_gettable(argv, 1) : janet_vm.fiber->env;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -114,6 +114,7 @@ typedef struct SymPair {
const uint8_t *sym;
const uint8_t *sym2;
int keep;
int referenced; /* Has this value been used */
uint32_t birth_pc;
uint32_t death_pc;
} SymPair;
@@ -222,6 +223,7 @@ const JanetSpecial *janetc_special(const uint8_t *name);
void janetc_freeslot(JanetCompiler *c, JanetSlot s);
void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s);
void janetc_nameslot_no_unused(JanetCompiler *c, const uint8_t *sym, JanetSlot s);
JanetSlot janetc_farslot(JanetCompiler *c);
/* Throw away some code after checking that it is well formed. */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,6 +27,7 @@
#include "compile.h"
#include "state.h"
#include "util.h"
#include "fiber.h"
#endif
/* Generated bytes */
@@ -66,7 +67,7 @@ JanetModule janet_native(const char *name, const uint8_t **error) {
JanetBuildConfig modconf = getter();
JanetBuildConfig host = janet_config_current();
if (host.major != modconf.major ||
host.minor < modconf.minor ||
host.minor != modconf.minor ||
host.bits != modconf.bits) {
char errbuf[128];
snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)",
@@ -294,6 +295,7 @@ JANET_CORE_FN(janet_core_native,
"from the native module.") {
JanetModule init;
janet_arity(argc, 1, 2);
Janet argv0 = argv[0];
const uint8_t *path = janet_getstring(argv, 0);
const uint8_t *error = NULL;
JanetTable *env;
@@ -306,8 +308,10 @@ JANET_CORE_FN(janet_core_native,
if (!init) {
janet_panicf("could not load native %S: %S", path, error);
}
/* GC root incase garbage collection called inside module entry */
janet_fiber_push(janet_vm.fiber, janet_wrap_table(env));
init(env);
janet_table_put(env, janet_ckeywordv("native"), argv[0]);
janet_table_put(env, janet_ckeywordv("native"), argv0);
return janet_wrap_table(env);
}
@@ -449,8 +453,9 @@ JANET_CORE_FN(janet_core_range,
}
count = (count > 0) ? count : 0;
int32_t int_count;
janet_assert(count >= 0, "bad range code");
if (count > (double) INT32_MAX) {
int_count = INT32_MAX;
janet_panicf("range is too large, %f elements", count);
} else {
int_count = (int32_t) ceil(count);
}
@@ -652,22 +657,15 @@ JANET_CORE_FN(janet_core_check_int,
"(int? x)",
"Check if x can be exactly represented as a 32 bit signed two's complement integer.") {
janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false;
double num = janet_unwrap_number(argv[0]);
return janet_wrap_boolean(num == (double)((int32_t)num));
ret_false:
return janet_wrap_false();
return janet_wrap_boolean(janet_checkint(argv[0]));
}
JANET_CORE_FN(janet_core_check_nat,
"(nat? x)",
"Check if x can be exactly represented as a non-negative 32 bit signed two's complement integer.") {
janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false;
double num = janet_unwrap_number(argv[0]);
return janet_wrap_boolean(num >= 0 && (num == (double)((int32_t)num)));
ret_false:
return janet_wrap_false();
if (!janet_checkint(argv[0])) return janet_wrap_false();
return janet_wrap_boolean(janet_unwrap_integer(argv[0]) >= 0);
}
JANET_CORE_FN(janet_core_is_bytes,
@@ -752,6 +750,9 @@ typedef struct SandboxOption {
static const SandboxOption sandbox_options[] = {
{"all", JANET_SANDBOX_ALL},
{"asm", JANET_SANDBOX_ASM},
{"chroot", JANET_SANDBOX_CHROOT},
{"compile", JANET_SANDBOX_COMPILE},
{"env", JANET_SANDBOX_ENV},
{"ffi", JANET_SANDBOX_FFI},
{"ffi-define", JANET_SANDBOX_FFI_DEFINE},
@@ -769,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}
};
@@ -777,6 +780,9 @@ JANET_CORE_FN(janet_core_sandbox,
"Disable feature sets to prevent the interpreter from using certain system resources. "
"Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n"
"* :all - disallow all (except IO to stdout, stderr, and stdin)\n"
"* :asm - disallow calling `asm` and `disasm` functions.\n"
"* :chroot - disallow calling `os/posix-chroot`\n"
"* :compile - disallow calling `compile`. This will disable a lot of functionality, such as `eval`.\n"
"* :env - disallow reading and write env variables\n"
"* :ffi - disallow FFI (recommended if disabling anything else)\n"
"* :ffi-define - disallow loading new FFI modules and binding new functions\n"
@@ -793,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);
@@ -1001,12 +1009,11 @@ static void make_apply(JanetTable *env) {
janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG,
"apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm),
JDOC("(apply f & args)\n\n"
"Applies a function to a variable number of arguments. Each element in args "
"is used as an argument to f, except the last element in args, which is expected to "
"be an array-like. Each element in this last argument is then also pushed as an argument to "
"f. For example:\n\n"
"\t(apply + 1000 (range 10))\n\n"
"sums the first 10 integers and 1000."));
"Applies a function f to a variable number of arguments. Each "
"element in args is used as an argument to f, except the last "
"element in args, which is expected to be an array or a tuple. "
"Each element in this last argument is then also pushed as an "
"argument to f."));
}
static const uint32_t error_asm[] = {
@@ -1243,7 +1250,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
/* Variadic ops */
templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD,
JDOC("(+ & xs)\n\n"
"Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0."));
"Returns the sum of all xs. If xs is empty, return 0."));
templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT,
JDOC("(- & xs)\n\n"
"Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the "
@@ -1277,7 +1284,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
"Returns the bit-wise or of all values in xs. Each x in xs must be an integer."));
templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR,
JDOC("(bxor & xs)\n\n"
"Returns the bit-wise xor of all values in xs. Each in xs must be an integer."));
"Returns the bit-wise xor of all values in xs. Each x in xs must be an integer."));
templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT,
JDOC("(blshift x & shifts)\n\n"
"Returns the value of x bit shifted left by the sum of all values in shifts. x "
@@ -1359,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

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -32,9 +32,11 @@
#ifdef JANET_EV
#include <math.h>
#include <fcntl.h>
#ifdef JANET_WINDOWS
#include <winsock2.h>
#include <windows.h>
#include <io.h>
#else
#include <pthread.h>
#include <limits.h>
@@ -43,7 +45,6 @@
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
@@ -111,6 +112,16 @@ typedef struct {
JanetHandle write_pipe;
} JanetEVThreadInit;
/* Structure used to initialize threads that run timeouts */
typedef struct {
double sec;
JanetVM *vm;
JanetFiber *fiber;
#ifdef JANET_WINDOWS
HANDLE cancel_event;
#endif
} JanetThreadedTimeout;
#define JANET_MAX_Q_CAPACITY 0x7FFFFFF
static void janet_q_init(JanetQueue *q) {
@@ -345,21 +356,22 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod
static void janet_stream_close_impl(JanetStream *stream) {
stream->flags |= JANET_STREAM_CLOSED;
int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE);
#ifdef JANET_WINDOWS
if (stream->handle != INVALID_HANDLE_VALUE) {
#ifdef JANET_NET
if (stream->flags & JANET_STREAM_SOCKET) {
closesocket((SOCKET) stream->handle);
if (canclose) closesocket((SOCKET) stream->handle);
} else
#endif
{
CloseHandle(stream->handle);
if (canclose) CloseHandle(stream->handle);
}
stream->handle = INVALID_HANDLE_VALUE;
}
#else
if (stream->handle != -1) {
close(stream->handle);
if (canclose) close(stream->handle);
stream->handle = -1;
#ifdef JANET_EV_POLL
uint32_t i = stream->index;
@@ -512,9 +524,9 @@ static void janet_schedule_general(JanetFiber *fiber, Janet value, JanetSignal s
fiber->gc.flags |= JANET_FIBER_FLAG_ROOT;
if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_FLAG_CANCELED;
if (soon) {
janet_q_push_head(&janet_vm.spawn, &t, sizeof(t));
janet_assert(!janet_q_push_head(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow");
} else {
janet_q_push(&janet_vm.spawn, &t, sizeof(t));
janet_assert(!janet_q_push(&janet_vm.spawn, &t, sizeof(t)), "schedule queue overflow");
}
}
@@ -527,6 +539,9 @@ void janet_schedule_soon(JanetFiber *fiber, Janet value, JanetSignal sig) {
}
void janet_cancel(JanetFiber *fiber, Janet value) {
if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) {
janet_panic("cannot cancel non-task fiber");
}
janet_schedule_signal(fiber, value, JANET_SIGNAL_ERROR);
}
@@ -595,8 +610,43 @@ void janet_ev_init_common(void) {
#endif
}
#if JANET_ANDROID
static void janet_timeout_stop(int sig_num) {
if (sig_num == SIGUSR1) {
pthread_exit(0);
}
}
#endif
static void handle_timeout_worker(JanetTimeout to, int cancel) {
if (!to.has_worker) return;
#ifdef JANET_WINDOWS
if (cancel && to.worker_event) {
SetEvent(to.worker_event);
}
WaitForSingleObject(to.worker, INFINITE);
CloseHandle(to.worker);
if (to.worker_event) {
CloseHandle(to.worker_event);
}
#else
#ifdef JANET_ANDROID
if (cancel) janet_assert(!pthread_kill(to.worker, SIGUSR1), "pthread_kill");
#else
if (cancel) janet_assert(!pthread_cancel(to.worker), "pthread_cancel");
#endif
void *res = NULL;
janet_assert(!pthread_join(to.worker, &res), "pthread_join");
#endif
}
/* Common deinit code */
void janet_ev_deinit_common(void) {
JanetTimeout to;
while (peek_timeout(&to)) {
handle_timeout_worker(to, 1);
pop_timeout(0);
}
janet_q_deinit(&janet_vm.spawn);
janet_free(janet_vm.tq);
janet_table_deinit(&janet_vm.threaded_abstracts);
@@ -622,6 +672,7 @@ void janet_addtimeout(double sec) {
to.curr_fiber = NULL;
to.sched_id = fiber->sched_id;
to.is_error = 1;
to.has_worker = 0;
add_timeout(to);
}
@@ -634,9 +685,61 @@ void janet_addtimeout_nil(double sec) {
to.curr_fiber = NULL;
to.sched_id = fiber->sched_id;
to.is_error = 0;
to.has_worker = 0;
add_timeout(to);
}
static void janet_timeout_cb(JanetEVGenericMessage msg) {
(void) msg;
janet_interpreter_interrupt_handled(&janet_vm);
}
#ifdef JANET_WINDOWS
static DWORD WINAPI janet_timeout_body(LPVOID ptr) {
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr);
JanetTimestamp wait_begin = ts_now();
DWORD duration = (DWORD)round(tto.sec * 1000);
DWORD res = WAIT_TIMEOUT;
JanetTimestamp wait_end = ts_now();
for (DWORD i = 1; res == WAIT_TIMEOUT && (wait_end - wait_begin) < duration; i++) {
res = WaitForSingleObject(tto.cancel_event, (duration + i));
wait_end = ts_now();
}
/* only send interrupt message if result is WAIT_TIMEOUT */
if (res == WAIT_TIMEOUT) {
janet_interpreter_interrupt(tto.vm);
JanetEVGenericMessage msg = {0};
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
}
return 0;
}
#else
static void *janet_timeout_body(void *ptr) {
#ifdef JANET_ANDROID
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = &janet_timeout_stop;
sigaction(SIGUSR1, &action, NULL);
#endif
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr);
struct timespec ts;
ts.tv_sec = (time_t) tto.sec;
ts.tv_nsec = (tto.sec <= UINT32_MAX)
? (long)((tto.sec - ((uint32_t)tto.sec)) * 1000000000)
: 0;
nanosleep(&ts, &ts);
janet_interpreter_interrupt(tto.vm);
JanetEVGenericMessage msg = {0};
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
return NULL;
}
#endif
void janet_ev_inc_refcount(void) {
janet_atomic_inc(&janet_vm.listener_count);
}
@@ -751,6 +854,34 @@ static int janet_chanat_gc(void *p, size_t s) {
return 0;
}
static void janet_chanat_remove_vmref(JanetQueue *fq) {
JanetChannelPending *pending = fq->data;
if (fq->head <= fq->tail) {
for (int32_t i = fq->head; i < fq->tail; i++) {
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
}
} else {
for (int32_t i = fq->head; i < fq->capacity; i++) {
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
}
for (int32_t i = 0; i < fq->tail; i++) {
if (pending[i].thread == &janet_vm) pending[i].thread = NULL;
}
}
}
static int janet_chanat_gcperthread(void *p, size_t s) {
(void) s;
JanetChannel *chan = p;
janet_chan_lock(chan);
/* Make sure that the internals of the threaded channel no longer reference _this_ thread. Replace
* those references with NULL. */
janet_chanat_remove_vmref(&chan->read_pending);
janet_chanat_remove_vmref(&chan->write_pending);
janet_chan_unlock(chan);
return 0;
}
static void janet_chanat_mark_fq(JanetQueue *fq) {
JanetChannelPending *pending = fq->data;
if (fq->head <= fq->tail) {
@@ -828,13 +959,15 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
janet_schedule(fiber, janet_wrap_nil());
}
} else if (mode != JANET_CP_MODE_CLOSE) {
/* Fiber has already been cancelled or resumed. */
/* Fiber has already been canceled or resumed. */
/* Resend event to another waiting thread, depending on mode */
int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ);
if (is_read) {
JanetChannelPending reader;
if (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
int sent = 0;
while (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
JanetVM *vm = reader.thread;
if (!vm) continue;
JanetEVGenericMessage msg;
msg.tag = reader.mode;
msg.fiber = reader.fiber;
@@ -842,11 +975,17 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
msg.argp = channel;
msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
sent = 1;
break;
}
if (!sent) {
janet_chan_unpack(channel, &x, 1);
}
} else {
JanetChannelPending writer;
if (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
while (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
JanetVM *vm = writer.thread;
if (!vm) continue;
JanetEVGenericMessage msg;
msg.tag = writer.mode;
msg.fiber = writer.fiber;
@@ -854,6 +993,7 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
msg.argp = channel;
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
break;
}
}
}
@@ -866,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 */
@@ -886,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) {
@@ -917,7 +1058,12 @@ static int janet_channel_push_with_lock(JanetChannel *channel, Janet x, int mode
msg.argi = (int32_t) reader.sched_id;
msg.argp = channel;
msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
} else {
/* If no vm to send to, we must clean up (unpack) the packed payload to avoid leak */
janet_chan_unpack(channel, &x, 1);
}
} else {
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
janet_schedule(reader.fiber, make_read_result(channel, x));
@@ -948,7 +1094,7 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i
int is_threaded = janet_chan_is_threaded(channel);
if (janet_q_pop(&channel->items, item, sizeof(Janet))) {
/* Queue empty */
if (is_choice == 2) return 0; // Skip pending read
if (is_choice == 2) return 0; /* Skip pending read */
JanetChannelPending pending;
pending.thread = &janet_vm;
pending.fiber = janet_vm.root_fiber,
@@ -972,7 +1118,9 @@ static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int i
msg.argi = (int32_t) writer.sched_id;
msg.argp = channel;
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else {
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
janet_schedule(writer.fiber, make_write_result(channel));
@@ -1024,7 +1172,7 @@ JanetChannel *janet_channel_make(uint32_t limit) {
JanetChannel *janet_channel_make_threaded(uint32_t limit) {
janet_assert(limit <= INT32_MAX, "bad limit");
JanetChannel *channel = janet_abstract_threaded(&janet_channel_type, sizeof(JanetChannel));
janet_chan_init(channel, (int32_t) limit, 0);
janet_chan_init(channel, (int32_t) limit, 1);
return channel;
}
@@ -1036,6 +1184,9 @@ JANET_CORE_FN(cfun_channel_push,
"Returns the channel if the write succeeded, nil otherwise.") {
janet_fixarity(argc, 2);
JanetChannel *channel = janet_getchannel(argv, 0);
if (janet_vm.coerce_error) {
janet_panic("cannot give to channel inside janet_call");
}
if (janet_channel_push(channel, argv[1], 0)) {
janet_await();
}
@@ -1048,26 +1199,15 @@ JANET_CORE_FN(cfun_channel_pop,
janet_fixarity(argc, 1);
JanetChannel *channel = janet_getchannel(argv, 0);
Janet item;
if (janet_vm.coerce_error) {
janet_panic("cannot take from channel inside janet_call");
}
if (janet_channel_pop(channel, &item, 0)) {
janet_schedule(janet_vm.root_fiber, item);
}
janet_await();
}
static void chan_unlock_args(const Janet *argv, int32_t n) {
for (int32_t i = 0; i < n; i++) {
int32_t len;
const Janet *data;
JanetChannel *chan;
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
chan = janet_getchannel(data, 0);
} else {
chan = janet_getchannel(argv, i);
}
janet_chan_unlock(chan);
}
}
JANET_CORE_FN(cfun_channel_choice,
"(ev/select & clauses)",
"Block until the first of several channel operations occur. Returns a "
@@ -1084,6 +1224,10 @@ JANET_CORE_FN(cfun_channel_choice,
int32_t len;
const Janet *data;
if (janet_vm.coerce_error) {
janet_panic("cannot select from channel inside janet_call");
}
/* Check channels for immediate reads and writes */
for (int32_t i = 0; i < argc; i++) {
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
@@ -1092,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);
}
}
@@ -1123,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);
}
}
@@ -1226,9 +1370,11 @@ JANET_CORE_FN(cfun_channel_close,
msg.tag = JANET_CP_MODE_CLOSE;
msg.argi = (int32_t) writer.sched_id;
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else {
if (janet_fiber_can_resume(writer.fiber)) {
if (janet_fiber_can_resume(writer.fiber) && writer.sched_id == writer.fiber->sched_id) {
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
janet_schedule(writer.fiber, make_close_result(channel));
} else {
@@ -1247,9 +1393,11 @@ JANET_CORE_FN(cfun_channel_close,
msg.tag = JANET_CP_MODE_CLOSE;
msg.argi = (int32_t) reader.sched_id;
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
if (vm) {
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
} else {
if (janet_fiber_can_resume(reader.fiber)) {
if (janet_fiber_can_resume(reader.fiber) && reader.sched_id == reader.fiber->sched_id) {
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
janet_schedule(reader.fiber, make_close_result(channel));
} else {
@@ -1319,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;
}
@@ -1340,7 +1489,10 @@ const JanetAbstractType janet_channel_type = {
NULL, /* compare */
NULL, /* hash */
janet_chanat_next,
JANET_ATEND_NEXT
NULL, /* call */
NULL, /* length */
NULL, /* bytes */
janet_chanat_gcperthread
};
/* Main event loop */
@@ -1373,12 +1525,13 @@ JanetFiber *janet_loop1(void) {
}
}
}
handle_timeout_worker(to, 0);
}
/* Run scheduled fibers unless interrupts need to be handled. */
while (janet_vm.spawn.head != janet_vm.spawn.tail) {
/* Don't run until all interrupts have been marked as handled by calling janet_interpreter_interrupt_handled */
if (janet_vm.auto_suspend) break;
if (janet_atomic_load_relaxed(&janet_vm.auto_suspend)) break;
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0};
janet_q_pop(&janet_vm.spawn, &task, sizeof(task));
if (task.fiber->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) janet_ev_dec_refcount();
@@ -1420,12 +1573,14 @@ JanetFiber *janet_loop1(void) {
while ((has_timeout = peek_timeout(&to))) {
if (to.curr_fiber != NULL) {
if (!janet_fiber_can_resume(to.curr_fiber)) {
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
pop_timeout(0);
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
handle_timeout_worker(to, 1);
continue;
}
} else if (to.fiber->sched_id != to.sched_id) {
pop_timeout(0);
handle_timeout_worker(to, 1);
continue;
}
break;
@@ -1557,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_transfered = (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);
@@ -1590,7 +1745,7 @@ void janet_stream_level_triggered(JanetStream *stream) {
static JanetTimestamp ts_now(void) {
struct timespec now;
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time");
janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
uint64_t res = 1000 * now.tv_sec;
res += now.tv_nsec / 1000000;
return res;
@@ -1748,7 +1903,7 @@ JanetTimestamp to_interval(const JanetTimestamp ts) {
static JanetTimestamp ts_now(void) {
struct timespec now;
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time");
janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
uint64_t res = 1000 * now.tv_sec;
res += now.tv_nsec / 1000000;
return res;
@@ -1788,6 +1943,22 @@ void janet_stream_edge_triggered(JanetStream *stream) {
}
void janet_stream_level_triggered(JanetStream *stream) {
/* On macos, we seem to need to delete any registered events before re-registering without
* EV_CLEAR, otherwise the new event will still have EV_CLEAR set erroneously. This could be a
* kernel bug, but unfortunately the specification is vague here, esp. in regards to where and when
* EV_CLEAR is set automatically. */
struct kevent kevs[2];
int length = 0;
if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) {
EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_DELETE, 0, 0, stream);
}
if (stream->flags & JANET_STREAM_WRITABLE) {
EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_DELETE, 0, 0, stream);
}
int status;
do {
status = kevent(janet_vm.kq, kevs, length, NULL, 0, NULL);
} while (status == -1 && errno == EINTR);
janet_register_stream_impl(stream, 0);
}
@@ -1886,7 +2057,7 @@ void janet_ev_deinit(void) {
static JanetTimestamp ts_now(void) {
struct timespec now;
janet_assert(-1 != clock_gettime(CLOCK_REALTIME, &now), "failed to get time");
janet_assert(-1 != janet_gettime(&now, JANET_TIME_MONOTONIC), "failed to get time");
uint64_t res = 1000 * now.tv_sec;
res += now.tv_nsec / 1000000;
return res;
@@ -2058,7 +2229,7 @@ void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGenericMessage ms
event.cb = cb;
int fd = vm->selfpipe[1];
/* handle a bit of back pressure before giving up. */
int tries = 4;
int tries = 20;
while (tries > 0) {
int status;
do {
@@ -2239,7 +2410,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') {
@@ -2266,7 +2437,7 @@ typedef enum {
typedef struct {
#ifdef JANET_WINDOWS
OVERLAPPED overlapped;
JanetOverlapped overlapped;
DWORD flags;
#ifdef JANET_NET
WSABUF wbuf;
@@ -2301,7 +2472,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_transfered;
state->bytes_read += ev_bytes;
if (state->bytes_read == 0 && (state->mode != JANET_ASYNC_READMODE_RECVFROM)) {
janet_schedule(fiber, janet_wrap_nil());
@@ -2333,7 +2504,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) {
@@ -2341,7 +2512,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);
@@ -2352,9 +2523,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) {
@@ -2510,7 +2681,7 @@ typedef enum {
typedef struct {
#ifdef JANET_WINDOWS
OVERLAPPED overlapped;
JanetOverlapped overlapped;
DWORD flags;
#ifdef JANET_NET
WSABUF wbuf;
@@ -2551,7 +2722,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_transfered;
if (ev_bytes == 0 && (state->mode != JANET_ASYNC_WRITEMODE_SENDTO)) {
janet_cancel(fiber, janet_cstringv("disconnect"));
janet_async_end(fiber);
@@ -2580,7 +2751,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
@@ -2590,7 +2761,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);
@@ -2613,9 +2784,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);
@@ -2771,10 +2942,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(
@@ -2830,12 +3002,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);
@@ -2861,6 +3035,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);
@@ -2905,7 +3082,8 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
uint32_t count1;
memcpy(&count1, nextbytes, sizeof(count1));
size_t count = (size_t) count1;
if (count > (endbytes - nextbytes) * sizeof(JanetCFunRegistry)) {
/* Use division to avoid overflowing size_t */
if (count > (endbytes - nextbytes - sizeof(count1)) / sizeof(JanetCFunRegistry)) {
janet_panic("thread message invalid");
}
janet_vm.registry_count = count;
@@ -2995,6 +3173,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);
@@ -3063,6 +3242,7 @@ JANET_NO_RETURN void janet_sleep_await(double sec) {
to.is_error = 0;
to.sched_id = to.fiber->sched_id;
to.curr_fiber = NULL;
to.has_worker = 0;
add_timeout(to);
janet_await();
}
@@ -3076,33 +3256,74 @@ JANET_CORE_FN(cfun_ev_sleep,
}
JANET_CORE_FN(cfun_ev_deadline,
"(ev/deadline sec &opt tocancel tocheck)",
"Schedules the event loop to try to cancel the `tocancel` "
"task as with `ev/cancel`. After `sec` seconds, the event "
"loop will attempt cancellation of `tocancel` if the "
"`tocheck` fiber is resumable. `sec` is a number that can "
"have a fractional part. `tocancel` defaults to "
"`(fiber/root)`, but if specified, must be a task (root "
"fiber). `tocheck` defaults to `(fiber/current)`, but if "
"specified, should be a fiber. Returns `tocancel` "
"immediately.") {
janet_arity(argc, 1, 3);
"(ev/deadline sec &opt tocancel tocheck intr?)",
"Schedules the event loop to try to cancel the `tocancel` task as with `ev/cancel`. "
"After `sec` seconds, the event loop will attempt cancellation of `tocancel` if the "
"`tocheck` fiber is resumable. `sec` is a number that can have a fractional part. "
"`tocancel` defaults to `(fiber/root)`, but if specified, must be a task (root "
"fiber). `tocheck` defaults to `(fiber/current)`, but if specified, must be a fiber. "
"Returns `tocancel` immediately. If `interrupt?` is set to true, will create a "
"background thread to try to interrupt the VM if the timeout expires.") {
janet_arity(argc, 1, 4);
double sec = janet_getnumber(argv, 0);
sec = (sec < 0) ? 0 : sec;
JanetFiber *tocancel = janet_optfiber(argv, argc, 1, janet_vm.root_fiber);
JanetFiber *tocheck = janet_optfiber(argv, argc, 2, janet_vm.fiber);
int use_interrupt = janet_optboolean(argv, argc, 3, 0);
JanetTimeout to;
to.when = ts_delta(ts_now(), sec);
to.fiber = tocancel;
to.curr_fiber = tocheck;
to.is_error = 0;
to.sched_id = to.fiber->sched_id;
if (use_interrupt) {
#ifdef JANET_ANDROID
janet_sandbox_assert(JANET_SANDBOX_SIGNAL);
#endif
JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout));
if (NULL == tto) {
JANET_OUT_OF_MEMORY;
}
tto->sec = sec;
tto->vm = &janet_vm;
tto->fiber = tocheck;
#ifdef JANET_WINDOWS
HANDLE cancel_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == cancel_event) {
janet_free(tto);
janet_panic("failed to create cancel event");
}
tto->cancel_event = cancel_event;
HANDLE worker = CreateThread(NULL, 0, janet_timeout_body, tto, CREATE_SUSPENDED, NULL);
if (NULL == worker) {
janet_free(tto);
janet_panic("failed to create thread");
}
#else
pthread_t worker;
int err = pthread_create(&worker, NULL, janet_timeout_body, tto);
if (err) {
janet_free(tto);
janet_panicf("%s", janet_strerror(err));
}
#endif
to.has_worker = 1;
to.worker = worker;
#ifdef JANET_WINDOWS
to.worker_event = cancel_event;
ResumeThread(worker);
#endif
} else {
to.has_worker = 0;
}
add_timeout(to);
return janet_wrap_fiber(tocancel);
}
JANET_CORE_FN(cfun_ev_cancel,
"(ev/cancel fiber err)",
"Cancel a suspended fiber in the event loop. Differs from cancel in that it returns the canceled fiber immediately.") {
"Cancel a suspended task fiber in the event loop. Differs from "
"`cancel` in that it returns the canceled fiber immediately.") {
janet_fixarity(argc, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
Janet err = argv[1];
@@ -3275,9 +3496,67 @@ JANET_CORE_FN(janet_cfun_rwlock_write_release,
return argv[0];
}
static JanetFile *get_file_for_stream(JanetStream *stream) {
int32_t flags = 0;
char fmt[4] = {0};
int index = 0;
if (stream->flags & JANET_STREAM_READABLE) {
flags |= JANET_FILE_READ;
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
fmt[index++] = 'r';
}
if (stream->flags & JANET_STREAM_WRITABLE) {
flags |= JANET_FILE_WRITE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
int currindex = index;
fmt[index++] = (currindex == 0) ? 'w' : '+';
}
if (index == 0) return NULL;
/* duplicate handle when converting stream to file */
#ifdef JANET_WINDOWS
int htype = 0;
if (fmt[0] == 'r' && fmt[1] == '+') {
htype = _O_RDWR;
} else if (fmt[0] == 'r') {
htype = _O_RDONLY;
} else if (fmt[0] == 'w') {
htype = _O_WRONLY;
}
int fd = _open_osfhandle((intptr_t) stream->handle, htype);
if (fd < 0) return NULL;
int fd_dup = _dup(fd);
if (fd_dup < 0) return NULL;
FILE *f = _fdopen(fd_dup, fmt);
if (NULL == f) {
_close(fd_dup);
return NULL;
}
#else
int fd_dup = dup(stream->handle);
if (fd_dup < 0) return NULL;
FILE *f = fdopen(fd_dup, fmt);
if (NULL == f) {
close(fd_dup);
return NULL;
}
#endif
return janet_makejfile(f, flags);
}
JANET_CORE_FN(janet_cfun_to_file,
"(ev/to-file)",
"Create core/file copy of the stream. This value can be used "
"when blocking IO behavior is needed.") {
janet_fixarity(argc, 1);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
JanetFile *iof = get_file_for_stream(stream);
if (iof == NULL) janet_panic("cannot make file from stream");
return janet_wrap_abstract(iof);
}
JANET_CORE_FN(janet_cfun_ev_all_tasks,
"(ev/all-tasks)",
"Get an array of all active fibers that are being used by the scheduler.") {
"Get an array of all active task fibers that are being used by the scheduler.") {
janet_fixarity(argc, 0);
(void) argv;
JanetArray *array = janet_array(janet_vm.active_tasks.count);
@@ -3319,6 +3598,7 @@ void janet_lib_ev(JanetTable *env) {
JANET_CORE_REG("ev/acquire-wlock", janet_cfun_rwlock_write_lock),
JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release),
JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release),
JANET_CORE_REG("ev/to-file", janet_cfun_to_file),
JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks),
JANET_REG_END
};
@@ -3328,8 +3608,6 @@ void janet_lib_ev(JanetTable *env) {
janet_register_abstract_type(&janet_channel_type);
janet_register_abstract_type(&janet_mutex_type);
janet_register_abstract_type(&janet_rwlock_type);
janet_lib_filewatch(env);
}
#endif

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -1344,6 +1344,15 @@ typedef double (win64_variant_f_ffif)(double, double, uint64_t, double);
typedef double (win64_variant_f_fffi)(double, double, double, uint64_t);
typedef double (win64_variant_f_ffff)(double, double, double, double);
/* MSVC stack frame runtime error checking (/RTCs) prepends alloca() allocations with an _RTC_ALLOCA_NODE
* header; misalligning stack-based FFI arguments and causing the memmove() (by stack_shift) to corrupt
* the _RTC_ALLOCA_NODE header.
*
* We turn off the RTC-instrumented alloca() and adding of _RTC_CheckStackVars to function prologue just
* for janet_ffi_win64() */
#ifdef __MSVC_RUNTIME_CHECKS
#pragma runtime_checks( "s", off )
#endif
static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) {
union {
uint64_t integer;
@@ -1493,6 +1502,10 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR);
}
#ifdef __MSVC_RUNTIME_CHECKS
// Restore stack frame runtime error checking (/RTCs) if it was enabled.
#pragma runtime_checks ( "s", restore )
#endif
#endif
@@ -1686,7 +1699,7 @@ JANET_CORE_FN(cfun_ffi_buffer_write,
JanetFFIType type = decode_ffi_type(argv[0]);
uint32_t el_size = (uint32_t) type_size(type);
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, el_size);
int32_t index = janet_optnat(argv, argc, 3, 0);
int32_t index = janet_optnat(argv, argc, 3, buffer->count);
int32_t old_count = buffer->count;
if (index > old_count) janet_panic("index out of bounds");
buffer->count = index;

View File

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

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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
@@ -573,7 +573,7 @@ static const JanetAbstractType janet_filewatch_at = {
};
JANET_CORE_FN(cfun_filewatch_make,
"(filewatch/new channel &opt default-flags)",
"(filewatch/new channel & default-flags)",
"Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n"
"When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. "
"The contents of the channel depend on the OS, but will contain some common keys:\n\n"
@@ -597,35 +597,35 @@ JANET_CORE_FN(cfun_filewatch_make,
}
JANET_CORE_FN(cfun_filewatch_add,
"(filewatch/add watcher path &opt flags)",
"(filewatch/add watcher path flag & more-flags)",
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
"Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n"
"Windows/MINGW (flags correspond to `FILE_NOTIFY_CHANGE_*` flags in win32 documentation):\n\n"
"* `:all` - trigger an event for all of the below triggers.\n\n"
"* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n"
"* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n"
"* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n"
"* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n"
"* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n"
"* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n"
"* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n"
"* `:attributes` - `FILE_NOTIFY_CHANGE_ATTRIBUTES`\n\n"
"* `:creation` - `FILE_NOTIFY_CHANGE_CREATION`\n\n"
"* `:dir-name` - `FILE_NOTIFY_CHANGE_DIR_NAME`\n\n"
"* `:last-access` - `FILE_NOTIFY_CHANGE_LAST_ACCESS`\n\n"
"* `:last-write` - `FILE_NOTIFY_CHANGE_LAST_WRITE`\n\n"
"* `:security` - `FILE_NOTIFY_CHANGE_SECURITY`\n\n"
"* `:size` - `FILE_NOTIFY_CHANGE_SIZE`\n\n"
"* `:recursive` - watch subdirectories recursively\n\n"
"Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n"
"* `:access` - IN_ACCESS\n\n"
"* `:all` - IN_ALL_EVENTS\n\n"
"* `:attrib` - IN_ATTRIB\n\n"
"* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n"
"* `:close-write` - IN_CLOSE_WRITE\n\n"
"* `:create` - IN_CREATE\n\n"
"* `:delete` - IN_DELETE\n\n"
"* `:delete-self` - IN_DELETE_SELF\n\n"
"* `:ignored` - IN_IGNORED\n\n"
"* `:modify` - IN_MODIFY\n\n"
"* `:move-self` - IN_MOVE_SELF\n\n"
"* `:moved-from` - IN_MOVED_FROM\n\n"
"* `:moved-to` - IN_MOVED_TO\n\n"
"* `:open` - IN_OPEN\n\n"
"* `:q-overflow` - IN_Q_OVERFLOW\n\n"
"* `:unmount` - IN_UNMOUNT\n\n\n"
"Linux (flags correspond to `IN_*` flags from <sys/inotify.h>):\n\n"
"* `:access` - `IN_ACCESS`\n\n"
"* `:all` - `IN_ALL_EVENTS`\n\n"
"* `:attrib` - `IN_ATTRIB`\n\n"
"* `:close-nowrite` - `IN_CLOSE_NOWRITE`\n\n"
"* `:close-write` - `IN_CLOSE_WRITE`\n\n"
"* `:create` - `IN_CREATE`\n\n"
"* `:delete` - `IN_DELETE`\n\n"
"* `:delete-self` - `IN_DELETE_SELF`\n\n"
"* `:ignored` - `IN_IGNORED`\n\n"
"* `:modify` - `IN_MODIFY`\n\n"
"* `:move-self` - `IN_MOVE_SELF`\n\n"
"* `:moved-from` - `IN_MOVED_FROM`\n\n"
"* `:moved-to` - `IN_MOVED_TO`\n\n"
"* `:open` - `IN_OPEN`\n\n"
"* `:q-overflow` - `IN_Q_OVERFLOW`\n\n"
"* `:unmount` - `IN_UNMOUNT`\n\n\n"
"On Windows, events will have the following possible types:\n\n"
"* `:unknown`\n\n"
"* `:added`\n\n"
@@ -633,7 +633,7 @@ JANET_CORE_FN(cfun_filewatch_add,
"* `:modified`\n\n"
"* `:renamed-old`\n\n"
"* `:renamed-new`\n\n"
"On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n"
"On Linux, events will have a `:type` corresponding to the possible flags, excluding `:all`.\n"
"") {
janet_arity(argc, 2, -1);
JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -346,6 +346,9 @@ static void janet_deinit_block(JanetGCObject *mem) {
break;
case JANET_MEMORY_ABSTRACT: {
JanetAbstractHead *head = (JanetAbstractHead *)mem;
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
@@ -497,15 +500,11 @@ void janet_sweep() {
/* If not visited... */
if (!janet_truthy(items[i].value)) {
void *abst = janet_unwrap_abstract(items[i].key);
if (0 == janet_abstract_decref(abst)) {
/* Run finalizer */
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
/* Free memory */
janet_free(janet_abstract_head(abst));
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
janet_abstract_decref_maybe_free(abst);
/* Mark as tombstone in place */
items[i].key = janet_wrap_nil();
@@ -672,13 +671,11 @@ void janet_clear_memory(void) {
for (int32_t i = 0; i < janet_vm.threaded_abstracts.capacity; i++) {
if (janet_checktype(items[i].key, JANET_ABSTRACT)) {
void *abst = janet_unwrap_abstract(items[i].key);
if (0 == janet_abstract_decref(abst)) {
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
janet_free(janet_abstract_head(abst));
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gcperthread) {
janet_assert(!head->type->gcperthread(head->data, head->size), "per-thread finalizer failed");
}
janet_abstract_decref_maybe_free(abst);
}
}
#endif

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose & contributors
* Copyright (c) 2026 Calvin Rose & contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -191,21 +191,21 @@ Janet janet_wrap_u64(uint64_t x) {
JANET_CORE_FN(cfun_it_s64_new,
"(int/s64 value)",
"Create a boxed signed 64 bit integer from a string value.") {
"Create a boxed signed 64 bit integer from a string value or a number.") {
janet_fixarity(argc, 1);
return janet_wrap_s64(janet_unwrap_s64(argv[0]));
}
JANET_CORE_FN(cfun_it_u64_new,
"(int/u64 value)",
"Create a boxed unsigned 64 bit integer from a string value.") {
"Create a boxed unsigned 64 bit integer from a string value or a number.") {
janet_fixarity(argc, 1);
return janet_wrap_u64(janet_unwrap_u64(argv[0]));
}
JANET_CORE_FN(cfun_to_number,
"(int/to-number value)",
"Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int32.") {
"Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int64.") {
janet_fixarity(argc, 1);
if (janet_type(argv[0]) == JANET_ABSTRACT) {
void *abst = janet_unwrap_abstract(argv[0]);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -31,6 +31,7 @@
#ifndef JANET_WINDOWS
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
@@ -42,6 +43,7 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx);
static Janet io_file_next(void *p, Janet key);
#ifdef JANET_WINDOWS
#include <io.h>
#define ftell _ftelli64
#define fseek _fseeki64
#endif
@@ -108,11 +110,12 @@ static int32_t checkflags(const uint8_t *str) {
return flags;
}
static void *makef(FILE *f, int32_t flags) {
static void *makef(FILE *f, int32_t flags, size_t bufsize) {
JanetFile *iof = (JanetFile *) janet_abstract(&janet_file_type, sizeof(JanetFile));
iof->file = f;
iof->flags = flags;
#ifndef JANET_WINDOWS
iof->vbufsize = bufsize;
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
/* While we would like fopen to set cloexec by default (like O_CLOEXEC) with the e flag, that is
* not standard. */
if (!(flags & JANET_FILE_NOT_CLOSEABLE))
@@ -163,8 +166,17 @@ JANET_CORE_FN(cfun_io_fopen,
flags = JANET_FILE_READ;
}
FILE *f = fopen((const char *)fname, (const char *)fmode);
size_t bufsize = BUFSIZ;
if (f != NULL) {
size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
#if !(defined(JANET_WINDOWS) || defined(JANET_PLAN9))
struct stat st;
fstat(fileno(f), &st);
if (S_ISDIR(st.st_mode)) {
fclose(f);
janet_panicf("cannot open directory: %s", fname);
}
#endif
bufsize = janet_optsize(argv, argc, 2, BUFSIZ);
if (bufsize != BUFSIZ) {
int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize);
if (result) {
@@ -172,7 +184,7 @@ JANET_CORE_FN(cfun_io_fopen,
}
}
}
return f ? janet_makefile(f, flags)
return f ? janet_wrap_abstract(makef(f, flags, bufsize))
: (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil())
: janet_wrap_nil();
}
@@ -240,9 +252,9 @@ JANET_CORE_FN(cfun_io_fread,
/* Write bytes to a file */
JANET_CORE_FN(cfun_io_fwrite,
"(file/write f bytes)",
"Writes to a file. 'bytes' must be string, buffer, or symbol. Returns the "
"file.") {
"(file/write f & bytes)",
"Writes to a file `f`. Each value of `bytes` must be a "
"string, buffer, symbol, or keyword. Returns the file.") {
janet_arity(argc, 1, -1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
@@ -401,12 +413,23 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx) {
JanetFile *iof = (JanetFile *)p;
if (ctx->flags & JANET_MARSHAL_UNSAFE) {
janet_marshal_abstract(ctx, p);
int fno = -1;
#ifdef JANET_WINDOWS
janet_marshal_int(ctx, _fileno(iof->file));
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
fno = _fileno(iof->file);
} else {
fno = _dup(_fileno(iof->file));
}
#else
janet_marshal_int(ctx, fileno(iof->file));
if (iof->flags & JANET_FILE_NOT_CLOSEABLE) {
fno = fileno(iof->file);
} else {
fno = dup(fileno(iof->file));
}
#endif
janet_marshal_int(ctx, fno);
janet_marshal_int(ctx, iof->flags);
janet_marshal_size(ctx, iof->vbufsize);
} else {
janet_panic("cannot marshal file in safe mode");
}
@@ -435,6 +458,11 @@ static void *io_file_unmarshal(JanetMarshalContext *ctx) {
} else {
iof->flags = flags;
}
iof->vbufsize = janet_unmarshal_size(ctx);
if (iof->vbufsize != BUFSIZ) {
int result = setvbuf(iof->file, NULL, iof->vbufsize ? _IOFBF : _IONBF, iof->vbufsize);
janet_assert(!result, "unmarshal setvbuf");
}
return iof;
} else {
janet_panic("cannot unmarshal file in safe mode");
@@ -712,8 +740,15 @@ JANET_CORE_FN(cfun_io_eflush,
void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) {
va_list args;
va_start(args, format);
Janet x = janet_dyn(name);
JanetType xtype = janet_type(x);
JanetType xtype;
Janet x;
if (!name || name[0] == '\0') { /* Allow NULL or empty string to just use dflt_file directly */
x = janet_wrap_nil();
xtype = JANET_NIL;
} else {
x = janet_dyn(name);
xtype = janet_type(x);
}
switch (xtype) {
default:
/* Other values simply do nothing */
@@ -769,11 +804,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) {

View File

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

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -271,28 +271,30 @@ JANET_DEFINE_MATHOP(cosh, "Returns the hyperbolic cosine of x.")
JANET_DEFINE_MATHOP(acosh, "Returns the hyperbolic arccosine of x.")
JANET_DEFINE_MATHOP(sin, "Returns the sine of x.")
JANET_DEFINE_MATHOP(sinh, "Returns the hyperbolic sine of x.")
JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.")
JANET_DEFINE_MATHOP(tan, "Returns the tangent of x.")
JANET_DEFINE_MATHOP(tanh, "Returns the hyperbolic tangent of x.")
JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.")
JANET_DEFINE_MATHOP(exp, "Returns e to the power of x.")
JANET_DEFINE_MATHOP(exp2, "Returns 2 to the power of x.")
JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
#ifndef JANET_PLAN9
JANET_DEFINE_MATHOP(expm1, "Returns e to the power of x minus 1.")
JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.")
JANET_DEFINE_MATHOP(erf, "Returns the error function of x.")
JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.")
JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).")
JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).")
JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.")
JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.")
#endif
JANET_DEFINE_MATHOP(log, "Returns the natural logarithm of x.")
JANET_DEFINE_MATHOP(log10, "Returns the log base 10 of x.")
JANET_DEFINE_MATHOP(log2, "Returns the log base 2 of x.")
JANET_DEFINE_MATHOP(sqrt, "Returns the square root of x.")
JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.")
JANET_DEFINE_MATHOP(ceil, "Returns the smallest integer value number that is not less than x.")
JANET_DEFINE_MATHOP(floor, "Returns the largest integer value number that is not greater than x.")
JANET_DEFINE_MATHOP(trunc, "Returns the integer between x and 0 nearest to x.")
JANET_DEFINE_MATHOP(round, "Returns the integer nearest to x.")
JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
JANET_DEFINE_MATHOP(erf, "Returns the error function of x.")
JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.")
JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).")
JANET_DEFINE_NAMED_MATHOP("abs", fabs, "Return the absolute value of x.")
JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).")
#define JANET_DEFINE_MATH2OP(name, fop, signature, doc)\
JANET_CORE_FN(janet_##name, signature, doc) {\
@@ -305,7 +307,9 @@ JANET_CORE_FN(janet_##name, signature, doc) {\
JANET_DEFINE_MATH2OP(atan2, atan2, "(math/atan2 y x)", "Returns the arctangent of y/x. Works even when x is 0.")
JANET_DEFINE_MATH2OP(pow, pow, "(math/pow a x)", "Returns a to the power of x.")
JANET_DEFINE_MATH2OP(hypot, hypot, "(math/hypot a b)", "Returns c from the equation c^2 = a^2 + b^2.")
#ifndef JANET_PLAN9
JANET_DEFINE_MATH2OP(nextafter, nextafter, "(math/next x y)", "Returns the next representable floating point value after x in the direction of y.")
#endif
JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") {
janet_fixarity(argc, 1);
@@ -386,7 +390,17 @@ void janet_lib_math(JanetTable *env) {
JANET_CORE_REG("math/log10", janet_log10),
JANET_CORE_REG("math/log2", janet_log2),
JANET_CORE_REG("math/sqrt", janet_sqrt),
#ifndef JANET_PLAN9
JANET_CORE_REG("math/cbrt", janet_cbrt),
JANET_CORE_REG("math/gamma", janet_tgamma),
JANET_CORE_REG("math/log-gamma", janet_lgamma),
JANET_CORE_REG("math/erfc", janet_erfc),
JANET_CORE_REG("math/erf", janet_erf),
JANET_CORE_REG("math/expm1", janet_expm1),
JANET_CORE_REG("math/atanh", janet_atanh),
JANET_CORE_REG("math/asinh", janet_asinh),
JANET_CORE_REG("math/next", janet_nextafter),
#endif
JANET_CORE_REG("math/floor", janet_floor),
JANET_CORE_REG("math/ceil", janet_ceil),
JANET_CORE_REG("math/pow", janet_pow),
@@ -394,8 +408,6 @@ void janet_lib_math(JanetTable *env) {
JANET_CORE_REG("math/sinh", janet_sinh),
JANET_CORE_REG("math/cosh", janet_cosh),
JANET_CORE_REG("math/tanh", janet_tanh),
JANET_CORE_REG("math/atanh", janet_atanh),
JANET_CORE_REG("math/asinh", janet_asinh),
JANET_CORE_REG("math/acosh", janet_acosh),
JANET_CORE_REG("math/atan2", janet_atan2),
JANET_CORE_REG("math/rng", cfun_rng_make),
@@ -405,14 +417,8 @@ void janet_lib_math(JanetTable *env) {
JANET_CORE_REG("math/hypot", janet_hypot),
JANET_CORE_REG("math/exp2", janet_exp2),
JANET_CORE_REG("math/log1p", janet_log1p),
JANET_CORE_REG("math/gamma", janet_tgamma),
JANET_CORE_REG("math/log-gamma", janet_lgamma),
JANET_CORE_REG("math/erfc", janet_erfc),
JANET_CORE_REG("math/erf", janet_erf),
JANET_CORE_REG("math/expm1", janet_expm1),
JANET_CORE_REG("math/trunc", janet_trunc),
JANET_CORE_REG("math/round", janet_round),
JANET_CORE_REG("math/next", janet_nextafter),
JANET_CORE_REG("math/gcd", janet_cfun_gcd),
JANET_CORE_REG("math/lcm", janet_cfun_lcm),
JANET_CORE_REG("math/frexp", janet_cfun_frexp),
@@ -435,9 +441,9 @@ void janet_lib_math(JanetTable *env) {
JANET_CORE_DEF(env, "math/int32-max", janet_wrap_number(INT32_MAX),
"The maximum contiguous integer representable by a 32 bit signed integer");
JANET_CORE_DEF(env, "math/int-min", janet_wrap_number(JANET_INTMIN_DOUBLE),
"The minimum contiguous integer representable by a double (2^53)");
"The minimum contiguous integer representable by a double (-(2^53))");
JANET_CORE_DEF(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE),
"The maximum contiguous integer representable by a double (-(2^53))");
"The maximum contiguous integer representable by a double (2^53)");
#ifdef NAN
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN)");
#else

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose and contributors.
* Copyright (c) 2026 Calvin Rose and contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -120,7 +120,55 @@ static void janet_net_socknoblock(JSock s) {
#endif
}
/* Allow specifying IPV6 vs. IPV4 (or unix domain socket) */
static int net_get_address_family(Janet x) {
if (janet_checktype(x, JANET_NIL)) {
return AF_UNSPEC;
}
if (janet_keyeq(x, "ipv4")) {
return AF_INET;
}
if (janet_keyeq(x, "ipv6")) {
return AF_INET6;
}
#ifndef JANET_WINDOWS
if (janet_keyeq(x, "unix")) {
return AF_UNIX;
}
#endif
return AF_UNSPEC;
}
/* State machine for async connect */
#ifdef JANET_WINDOWS
typedef struct NetStateConnect {
/* Only used for ConnectEx */
JanetOverlapped overlapped;
} NetStateConnect;
static LPFN_CONNECTEX lazy_get_connectex(JSock sock) {
/* Get ConnectEx */
if (janet_vm.connect_ex_loaded) {
return janet_vm.connect_ex;
}
GUID guid = WSAID_CONNECTEX;
LPFN_CONNECTEX connect_ex_ptr = NULL;
DWORD byte_len = 0;
int success = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
(void*)&guid, sizeof(guid),
(void*)&connect_ex_ptr, sizeof(connect_ex_ptr),
&byte_len, NULL, NULL);
if (success) {
janet_vm.connect_ex = connect_ex_ptr;
} else {
janet_vm.connect_ex = NULL;
}
janet_vm.connect_ex_loaded = 1;
return janet_vm.connect_ex;
}
#endif
void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
JanetStream *stream = fiber->ev_stream;
@@ -140,15 +188,21 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
return;
}
#ifdef JANET_WINDOWS
/* We should be using ConnectEx here */
int res = 0;
int size = sizeof(res);
int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_ERROR, (char *)&res, &size);
int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_CONNECT_TIME, (char *)&res, &size);
if (r == NO_ERROR && res == 0xFFFFFFFF) {
return; /* This apparently indicates we haven't yet gotten a connection */
}
const int no_error = NO_ERROR;
#else
int res = 0;
socklen_t size = sizeof res;
socklen_t size = sizeof(res);
int r = getsockopt(stream->handle, SOL_SOCKET, SO_ERROR, &res, &size);
const int no_error = 0;
#endif
if (r == 0) {
if (r == no_error) {
if (res == 0) {
janet_schedule(fiber, janet_wrap_abstract(stream));
} else {
@@ -162,8 +216,8 @@ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) {
janet_async_end(fiber);
}
static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) {
janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, NULL);
static JANET_NO_RETURN void net_sched_connect(JanetStream *stream, void *state) {
janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, state);
}
/* State machine for accepting connections. */
@@ -171,7 +225,7 @@ static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) {
#ifdef JANET_WINDOWS
typedef struct {
WSAOVERLAPPED overlapped;
JanetOverlapped overlapped;
JanetFunction *function;
JanetStream *lstream;
JanetStream *astream;
@@ -234,7 +288,7 @@ void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) {
JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) {
Janet err;
NetStateAccept *state = janet_malloc(sizeof(NetStateAccept));
memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED));
memset(&state->overlapped, 0, sizeof(JanetOverlapped));
memset(&state->buf, 0, 1024);
state->function = fun;
state->lstream = stream;
@@ -255,7 +309,7 @@ static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet
JanetStream *astream = make_stream(asock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE);
state->astream = astream;
int socksize = sizeof(SOCKADDR_STORAGE) + 16;
if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped)) {
if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped.as.wsaoverlapped)) {
int code = WSAGetLastError();
if (code == WSA_IO_PENDING) {
/* indicates io is happening async */
@@ -341,7 +395,7 @@ static int janet_get_sockettype(Janet *argv, int32_t argc, int32_t n) {
/* Needs argc >= offset + 2 */
/* For unix paths, just rertuns a single sockaddr and sets *is_unix to 1,
* otherwise 0. Also, ignores is_bind when is a unix socket. */
static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix) {
static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix, socklen_t *sizeout) {
/* Unix socket support - not yet supported on windows. */
#ifndef JANET_WINDOWS
if (janet_keyeq(argv[offset], "unix")) {
@@ -352,15 +406,14 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
}
saddr->sun_family = AF_UNIX;
size_t path_size = sizeof(saddr->sun_path);
snprintf(saddr->sun_path, path_size, "%s", path);
*sizeout = sizeof(struct sockaddr_un);
#ifdef JANET_LINUX
if (path[0] == '@') {
saddr->sun_path[0] = '\0';
snprintf(saddr->sun_path + 1, path_size - 1, "%s", path + 1);
} else
#endif
{
snprintf(saddr->sun_path, path_size, "%s", path);
*sizeout = offsetof(struct sockaddr_un, sun_path) + janet_string_length(path);
}
#endif
*is_unix = 1;
return (struct addrinfo *) saddr;
}
@@ -385,6 +438,11 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
janet_panicf("could not get address info: %s", gai_strerror(status));
}
*is_unix = 0;
#ifdef JANET_WINDOWS
*sizeout = 0;
#else
*sizeout = sizeof(struct sockaddr_un);
#endif
return ai;
}
@@ -405,12 +463,13 @@ JANET_CORE_FN(cfun_net_sockaddr,
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
int make_arr = (argc >= 3 && janet_truthy(argv[3]));
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix);
socklen_t addrsize = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix, &addrsize);
#ifndef JANET_WINDOWS
/* no unix domain socket support on windows yet */
if (is_unix) {
void *abst = janet_abstract(&janet_address_type, sizeof(struct sockaddr_un));
memcpy(abst, ai, sizeof(struct sockaddr_un));
void *abst = janet_abstract(&janet_address_type, addrsize);
memcpy(abst, ai, addrsize);
Janet ret = janet_wrap_abstract(abst);
return make_arr ? janet_wrap_array(janet_array_n(&ret, 1)) : ret;
}
@@ -461,7 +520,8 @@ JANET_CORE_FN(cfun_net_connect,
}
/* Where we're connecting to */
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix);
socklen_t addrlen = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix, &addrlen);
/* Check if we're binding address */
struct addrinfo *binding = NULL;
@@ -486,7 +546,6 @@ JANET_CORE_FN(cfun_net_connect,
/* Create socket */
JSock sock = JSOCKDEFAULT;
void *addr = NULL;
socklen_t addrlen = 0;
#ifndef JANET_WINDOWS
if (is_unix) {
sock = socket(AF_UNIX, socktype | JSOCKFLAGS, 0);
@@ -496,7 +555,6 @@ JANET_CORE_FN(cfun_net_connect,
janet_panicf("could not create socket: %V", v);
}
addr = (void *) ai;
addrlen = sizeof(struct sockaddr_un);
} else
#endif
{
@@ -543,18 +601,52 @@ JANET_CORE_FN(cfun_net_connect,
}
/* Wrap socket in abstract type JanetStream */
JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE);
/* Set up the socket for non-blocking IO before connecting */
janet_net_socknoblock(sock);
uint32_t udp_flag = 0;
if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER;
JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag);
/* Connect to socket */
#ifdef JANET_WINDOWS
int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
int err = WSAGetLastError();
freeaddrinfo(ai);
int status = 0;
int err = 0;
LPFN_CONNECTEX connect_ex = NULL;
if (socktype == SOCK_STREAM && ((connect_ex = lazy_get_connectex(sock)))) {
/* Prefer ConnecEx as it works well with overlapped IO. */
janet_net_socknoblock(sock);
NetStateConnect *state = janet_malloc(sizeof(NetStateConnect));
memset(state, 0, sizeof(NetStateConnect));
BOOL success = connect_ex(sock, addr, addrlen, NULL, 0, NULL, &state->overlapped.as.overlapped);
freeaddrinfo(ai);
if (success) {
/* Did not fail */
} else {
int err = WSAGetLastError();
if (err == ERROR_IO_PENDING) {
/* Did not actually fail yet */
} else {
janet_free(state);
Janet lasterr = janet_ev_lasterr();
janet_panicf("could not connect socket (ConnectEx): %V", lasterr);
}
}
net_sched_connect(stream, state);
} else {
/* Default to blocking connect if ConnectEx not available */
status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
err = WSAGetLastError();
freeaddrinfo(ai);
/* Set up the socket for non-blocking IO after connecting on windows by default */
janet_net_socknoblock(sock);
}
#else
int status = connect(sock, addr, addrlen);
/* Set up the socket for non-blocking IO before connecting */
janet_net_socknoblock(sock);
int status;
do {
status = connect(sock, addr, addrlen);
} while (status == -1 && errno == EINTR);
int err = errno;
if (is_unix) {
janet_free(ai);
@@ -563,10 +655,19 @@ JANET_CORE_FN(cfun_net_connect,
}
#endif
if (status) {
if (status == 0) {
/* Connect completed synchronously (common for unix domain sockets).
* Return the stream directly without scheduling an async wait,
* as edge-triggered kqueue may not signal EVFILT_WRITE if the socket
* is already connected when registered. */
return janet_wrap_abstract(stream);
}
#ifdef JANET_WINDOWS
if (status == SOCKET_ERROR) {
if (err != WSAEWOULDBLOCK) {
#else
if (status == -1) {
if (err != EINPROGRESS) {
#endif
JSOCKCLOSE(sock);
@@ -575,20 +676,84 @@ JANET_CORE_FN(cfun_net_connect,
}
}
net_sched_connect(stream);
net_sched_connect(stream, NULL);
}
static const char *serverify_socket(JSock sfd) {
JANET_CORE_FN(cfun_net_socket,
"(net/socket &opt type address-family)",
"Creates a new unbound socket. Type is an optional keyword, "
"either a :stream (usually tcp), or :datagram (usually udp). The default is :stream. "
"`address-family` should be one of :ipv4 or :ipv6.") {
janet_arity(argc, 0, 2);
int socktype = janet_get_sockettype(argv, argc, 0);
/* Create socket */
JSock sfd = JSOCKDEFAULT;
struct addrinfo *ai = NULL;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = socktype;
#ifdef AI_NUMERICSERV
hints.ai_flags = AI_NUMERICSERV; /* Explicitly prevent name resolution */
#else
hints.ai_flags = 0;
#endif
if (argc >= 2) {
hints.ai_family = net_get_address_family(argv[1]);
}
int status = getaddrinfo(NULL, "0", &hints, &ai);
if (status) {
janet_panicf("could not get address info: %s", gai_strerror(status));
}
struct addrinfo *rp = NULL;
for (rp = ai; rp != NULL; rp = rp->ai_next) {
#ifdef JANET_WINDOWS
sfd = WSASocketW(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
#else
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
#endif
if (JSOCKVALID(sfd)) {
break;
}
}
freeaddrinfo(ai);
if (!JSOCKVALID(sfd)) {
Janet v = janet_ev_lasterr();
janet_panicf("could not create socket: %V", v);
}
/* Wrap socket in abstract type JanetStream */
uint32_t udp_flag = 0;
if (socktype == SOCK_DGRAM) udp_flag = JANET_STREAM_UDPSERVER;
JanetStream *stream = make_stream(sfd, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE | udp_flag);
/* Set up the socket for non-blocking IO */
janet_net_socknoblock(sfd);
return janet_wrap_abstract(stream);
}
static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) {
/* Set various socket options */
int enable = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEADDR) failed";
if (reuse_addr) {
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEADDR) failed";
}
}
if (reuse_port) {
#ifdef SO_REUSEPORT
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEPORT) failed";
}
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
return "setsockopt(SO_REUSEPORT) failed";
}
#else
(void) reuse_port;
#endif
}
janet_net_socknoblock(sfd);
return NULL;
}
@@ -642,19 +807,22 @@ JANET_CORE_FN(cfun_net_shutdown,
}
JANET_CORE_FN(cfun_net_listen,
"(net/listen host port &opt type)",
"(net/listen host port &opt type no-reuse)",
"Creates a server. Returns a new stream that is neither readable nor "
"writeable. Use net/accept or net/accept-loop be to handle connections and start the server. "
"The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
":stream. The host and port arguments are the same as in net/address.") {
":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will "
"disable the use of `SO_REUSEADDR` and `SO_REUSEPORT` when creating a server on some operating systems.") {
janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
janet_arity(argc, 2, 3);
janet_arity(argc, 2, 4);
/* Get host, port, and handler*/
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix);
socklen_t addrlen = 0;
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix, &addrlen);
int reuse = !(argc >= 4 && janet_truthy(argv[3]));
JSock sfd = JSOCKDEFAULT;
#ifndef JANET_WINDOWS
@@ -664,8 +832,8 @@ JANET_CORE_FN(cfun_net_listen,
janet_free(ai);
janet_panicf("could not create socket: %V", janet_ev_lasterr());
}
const char *err = serverify_socket(sfd);
if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) {
const char *err = serverify_socket(sfd, reuse, 0);
if (NULL != err || bind(sfd, (struct sockaddr *)ai, addrlen)) {
JSOCKCLOSE(sfd);
janet_free(ai);
if (err) {
@@ -687,7 +855,7 @@ JANET_CORE_FN(cfun_net_listen,
sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
#endif
if (!JSOCKVALID(sfd)) continue;
const char *err = serverify_socket(sfd);
const char *err = serverify_socket(sfd, reuse, reuse);
if (NULL != err) {
JSOCKCLOSE(sfd);
continue;
@@ -962,6 +1130,8 @@ static const struct sockopt_type sockopt_type_list[] = {
#ifndef JANET_NO_IPV6
{ "ipv6-join-group", IPPROTO_IPV6, IPV6_JOIN_GROUP, JANET_POINTER },
{ "ipv6-leave-group", IPPROTO_IPV6, IPV6_LEAVE_GROUP, JANET_POINTER },
{ "ipv6-multicast-hops", IPPROTO_IPV6, IPV6_MULTICAST_HOPS, JANET_NUMBER },
{ "ipv6-unicast-hops", IPPROTO_IPV6, IPV6_UNICAST_HOPS, JANET_NUMBER },
#endif
{ NULL, 0, 0, JANET_POINTER }
};
@@ -978,7 +1148,10 @@ JANET_CORE_FN(cfun_net_setsockopt,
"- :ip-add-membership string\n"
"- :ip-drop-membership string\n"
"- :ipv6-join-group string\n"
"- :ipv6-leave-group string\n") {
"- :ipv6-leave-group string\n"
"- :ipv6-multicast-hops number\n"
"- :ipv6-unicast-hops number\n"
) {
janet_arity(argc, 3, 3);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_SOCKET);
@@ -997,6 +1170,7 @@ JANET_CORE_FN(cfun_net_setsockopt,
}
union {
unsigned char v_uchar;
int v_int;
struct ip_mreq v_mreq;
#ifndef JANET_NO_IPV6
@@ -1011,8 +1185,19 @@ JANET_CORE_FN(cfun_net_setsockopt,
val.v_int = janet_getboolean(argv, 2);
optlen = sizeof(val.v_int);
} else if (st->type == JANET_NUMBER) {
#if defined(JANET_BSD) || defined(JANET_ILLUMOS)
int v_int = janet_getinteger(argv, 2);
if (st->optname == IP_MULTICAST_TTL) {
val.v_uchar = v_int;
optlen = sizeof(val.v_uchar);
} else {
val.v_int = v_int;
optlen = sizeof(val.v_int);
}
#else
val.v_int = janet_getinteger(argv, 2);
optlen = sizeof(val.v_int);
#endif
} else if (st->optname == IP_ADD_MEMBERSHIP || st->optname == IP_DROP_MEMBERSHIP) {
const char *addr = janet_getcstring(argv, 2);
memset(&val.v_mreq, 0, sizeof val.v_mreq);
@@ -1067,6 +1252,7 @@ void janet_lib_net(JanetTable *env) {
JanetRegExt net_cfuns[] = {
JANET_CORE_REG("net/address", cfun_net_sockaddr),
JANET_CORE_REG("net/listen", cfun_net_listen),
JANET_CORE_REG("net/socket", cfun_net_socket),
JANET_CORE_REG("net/accept", cfun_stream_accept),
JANET_CORE_REG("net/accept-loop", cfun_stream_accept_loop),
JANET_CORE_REG("net/read", cfun_stream_read),
@@ -1090,6 +1276,8 @@ void janet_net_init(void) {
#ifdef JANET_WINDOWS
WSADATA wsaData;
janet_assert(!WSAStartup(MAKEWORD(2, 2), &wsaData), "could not start winsock");
janet_vm.connect_ex_loaded = 0;
janet_vm.connect_ex = NULL;
#endif
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose and contributors.
* Copyright (c) 2026 Calvin Rose and contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -40,6 +40,7 @@
#include <sys/stat.h>
#include <signal.h>
#include <locale.h>
#include <inttypes.h>
#ifdef JANET_BSD
#include <sys/sysctl.h>
@@ -55,8 +56,11 @@
#include <sys/utime.h>
#include <io.h>
#include <process.h>
#define JANET_SPAWN_CHDIR
#else
#ifndef JANET_PLAN9
#include <spawn.h>
#endif
#include <utime.h>
#include <unistd.h>
#include <dirent.h>
@@ -65,7 +69,9 @@
#ifdef JANET_APPLE
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
#else
#include <AvailabilityMacros.h>
int chroot(const char *dirname);
#elif !defined(JANET_PLAN9)
extern char **environ;
#endif
#ifdef JANET_THREADS
@@ -73,6 +79,26 @@ extern char **environ;
#endif
#endif
/* Detect availability of posix_spawn_file_actions_addchdir_np. Since
* this doesn't seem to follow any standard, just a common extension, we
* must enumerate supported systems for availability. Define JANET_SPAWN_NO_CHDIR
* to disable this. */
#ifndef JANET_SPAWN_NO_CHDIR
#ifdef __GLIBC__
#define JANET_SPAWN_CHDIR
#elif defined(JANET_APPLE)
/* The posix_spawn_file_actions_addchdir_np function
* has only been implemented since macOS 10.15 */
#if defined(MAC_OS_X_VERSION_10_15) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15)
#define JANET_SPAWN_CHDIR
#else
#define JANET_SPAWN_NO_CHDIR
#endif
#elif defined(__FreeBSD__) /* Not all BSDs work, for example openBSD doesn't seem to support this */
#define JANET_SPAWN_CHDIR
#endif
#endif
/* Not POSIX, but all Unixes but Solaris have this function. */
#if defined(JANET_POSIX) && !defined(__sun)
time_t timegm(struct tm *tm);
@@ -117,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"
@@ -131,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)
@@ -158,6 +187,8 @@ JANET_CORE_FN(os_which,
return janet_ckeywordv("dragonfly");
#elif defined(JANET_BSD)
return janet_ckeywordv("bsd");
#elif defined(JANET_ILLUMOS)
return janet_ckeywordv("illumos");
#else
return janet_ckeywordv("posix");
#endif
@@ -219,6 +250,7 @@ JANET_CORE_FN(os_compiler,
"* :gcc\n\n"
"* :clang\n\n"
"* :msvc\n\n"
"* :kencc\n\n"
"* :unknown\n\n") {
janet_fixarity(argc, 0);
(void) argv;
@@ -228,6 +260,8 @@ JANET_CORE_FN(os_compiler,
return janet_ckeywordv("clang");
#elif defined(__GNUC__)
return janet_ckeywordv("gcc");
#elif defined(JANET_PLAN9)
return janet_ckeywordv("kencc");
#else
return janet_ckeywordv("unknown");
#endif
@@ -266,39 +300,43 @@ JANET_CORE_FN(os_cpu_count,
"Get an approximate number of CPUs available on for this process to use. If "
"unable to get an approximation, will return a default value dflt.") {
janet_arity(argc, 0, 1);
Janet dflt = argc > 0 ? argv[0] : janet_wrap_nil();
(void) argv; /* Prevent unused argument warning */
#ifdef JANET_WINDOWS
(void) dflt;
SYSTEM_INFO info;
GetSystemInfo(&info);
return janet_wrap_integer(info.dwNumberOfProcessors);
#elif defined(JANET_LINUX)
(void) dflt;
cpu_set_t cs;
CPU_ZERO(&cs);
sched_getaffinity(0, sizeof(cs), &cs);
int count = CPU_COUNT(&cs);
return janet_wrap_integer(count);
#elif defined(JANET_BSD) && defined(HW_NCPUONLINE)
(void) dflt;
const int name[2] = {CTL_HW, HW_NCPUONLINE};
int result = 0;
size_t len = sizeof(int);
if (-1 == sysctl(name, 2, &result, &len, NULL, 0)) {
return dflt;
return argc > 0 ? argv[0] : janet_wrap_nil();
}
return janet_wrap_integer(result);
#elif defined(JANET_BSD) && defined(HW_NCPU)
(void) dflt;
const int name[2] = {CTL_HW, HW_NCPU};
int result = 0;
size_t len = sizeof(int);
if (-1 == sysctl(name, 2, &result, &len, NULL, 0)) {
return dflt;
return argc > 0 ? argv[0] : janet_wrap_nil();
}
return janet_wrap_integer(result);
#elif defined(JANET_ILLUMOS)
long result = sysconf(_SC_NPROCESSORS_CONF);
if (result < 0) {
return argc > 0 ? argv[0] : janet_wrap_nil();
}
return janet_wrap_integer(result);
#elif defined(JANET_PLAN9)
return janet_wrap_integer(atoi(getenv("NPROC")));
#else
return dflt;
return argc > 0 ? argv[0] : janet_wrap_nil();
#endif
}
@@ -328,6 +366,8 @@ static EnvBlock os_execute_env(int32_t argc, const Janet *argv) {
janet_buffer_push_bytes(temp, vals, janet_string_length(vals));
janet_buffer_push_u8(temp, '\0');
}
/* Windows environment blocks must be double-NULL terminated */
if (temp->count == 0) janet_buffer_push_u8(temp, '\0');
janet_buffer_push_u8(temp, '\0');
char *ret = janet_smalloc(temp->count);
memcpy(ret, temp->data, temp->count);
@@ -541,11 +581,12 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) {
proc->flags &= ~JANET_PROC_WAITING;
janet_gcunroot(janet_wrap_abstract(proc));
janet_gcunroot(janet_wrap_fiber(args.fiber));
if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) {
JanetString s = janet_formatc("command failed with non-zero exit code %d", status);
janet_cancel(args.fiber, janet_wrap_string(s));
} else {
if (janet_fiber_can_resume(args.fiber)) {
uint32_t sched_id = (uint32_t) args.argi;
if (janet_fiber_can_resume(args.fiber) && args.fiber->sched_id == sched_id) {
if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) {
JanetString s = janet_formatc("command failed with non-zero exit code %d", status);
janet_cancel(args.fiber, janet_wrap_string(s));
} else {
janet_schedule(args.fiber, janet_wrap_integer(status));
}
}
@@ -603,6 +644,7 @@ os_proc_wait_impl(JanetProc *proc) {
memset(&targs, 0, sizeof(targs));
targs.argp = proc;
targs.fiber = janet_root_fiber();
targs.argi = (uint32_t) targs.fiber->sched_id;
janet_gcroot(janet_wrap_abstract(proc));
janet_gcroot(janet_wrap_fiber(targs.fiber));
janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb);
@@ -629,16 +671,15 @@ os_proc_wait_impl(JanetProc *proc) {
JANET_CORE_FN(os_proc_wait,
"(os/proc-wait proc)",
"Suspend the current fiber until the subprocess completes. Returns the subprocess return code. "
"os/proc-wait cannot be called twice on the same process. If `ev/with-deadline` cancels `os/proc-wait` "
"with an error or os/proc-wait is cancelled with any error caused by anything else, os/proc-wait still "
"finishes in the background. Only after os/proc-wait finishes, a process is cleaned up by the operating "
"system. Thus, a process becomes a zombie process if os/proc-wait is not called.") {
"Suspend the current fiber until the subprocess `proc` completes. Once `proc` "
"completes, return the exit code of `proc`. If called more than once on the same "
"core/process value, will raise an error. When creating subprocesses using "
"`os/spawn`, this function should be called on the returned value to avoid zombie "
"processes.") {
janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV
os_proc_wait_impl(proc);
return janet_wrap_nil();
#else
return os_proc_wait_impl(proc);
#endif
@@ -743,12 +784,13 @@ static int get_signal_kw(const Janet *argv, int32_t n) {
JANET_CORE_FN(os_proc_kill,
"(os/proc-kill proc &opt wait signal)",
"Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process "
"handle on windows. If os/proc-wait already finished for proc, os/proc-kill raises an error. After "
"sending signal to proc, if `wait` is truthy, will wait for the process to finish and return the exit "
"code by calling os/proc-wait. Otherwise, returns `proc`. If signal is specified, send it instead. "
"Signal keywords are named after their C counterparts but in lowercase with the leading `SIG` stripped. "
"Signals are ignored on windows.") {
"Kill the subprocess `proc` by sending SIGKILL to it on POSIX systems, or by closing "
"the process handle on Windows. If `proc` has already completed, raise an error. If "
"`wait` is truthy, will wait for `proc` to complete and return the exit code (this "
"will raise an error if `proc` is being waited for). Otherwise, return `proc`. If "
"`signal` is provided, send it instead of SIGKILL. Signal keywords are named after "
"their C counterparts but in lowercase with the leading SIG stripped. `signal` is "
"ignored on Windows.") {
janet_arity(argc, 1, 3);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
if (proc->flags & JANET_PROC_WAITED) {
@@ -776,7 +818,6 @@ JANET_CORE_FN(os_proc_kill,
if (argc > 1 && janet_truthy(argv[1])) {
#ifdef JANET_EV
os_proc_wait_impl(proc);
return janet_wrap_nil();
#else
return os_proc_wait_impl(proc);
#endif
@@ -787,9 +828,9 @@ JANET_CORE_FN(os_proc_kill,
JANET_CORE_FN(os_proc_close,
"(os/proc-close proc)",
"Close pipes created by `os/spawn` if they have not been closed. Then, if os/proc-wait was not already "
"called on proc, os/proc-wait is called on it, and it returns the exit code returned by os/proc-wait. "
"Otherwise, returns nil.") {
"Close pipes created for subprocess `proc` by `os/spawn` if they have not been "
"closed. Then, if `proc` is not being waited for, wait. If this function waits, when "
"`proc` completes, return the exit code of `proc`. Otherwise, return nil.") {
janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV
@@ -807,12 +848,24 @@ JANET_CORE_FN(os_proc_close,
}
#ifdef JANET_EV
os_proc_wait_impl(proc);
return janet_wrap_nil();
#else
return os_proc_wait_impl(proc);
#endif
}
JANET_CORE_FN(os_proc_getpid,
"(os/getpid)",
"Get the process ID of the current process.") {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_fixarity(argc, 0);
(void) argv;
#ifdef JANET_WINDOWS
return janet_wrap_number((double) _getpid());
#else
return janet_wrap_number((double) getpid());
#endif
}
static void swap_handles(JanetHandle *handles) {
JanetHandle temp = handles[0];
handles[0] = handles[1];
@@ -1137,6 +1190,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL;
JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
int stderr_is_stdout = 0;
int pipe_errflag = 0; /* Track errors setting up pipes */
int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0;
@@ -1161,11 +1215,28 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) {
new_err = make_pipes(&pipe_err, 0, &pipe_errflag);
pipe_owner_flags |= JANET_PROC_OWNS_STDERR;
} else if (janet_keyeq(maybe_stderr, "out")) {
stderr_is_stdout = 1;
} else if (!janet_checktype(maybe_stderr, JANET_NIL)) {
new_err = janet_getjstream(&maybe_stderr, 0, &orig_err);
}
}
/* Optional working directory. Available for both os/execute and os/spawn. */
const char *chdir_path = NULL;
if (argc > 2) {
JanetDictView tab = janet_getdictionary(argv, 2);
Janet workdir = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("cd"));
if (janet_checktype(workdir, JANET_STRING)) {
chdir_path = (const char *) janet_unwrap_string(workdir);
#ifndef JANET_SPAWN_CHDIR
janet_panicf(":cd argument not supported on this system - %s", chdir_path);
#endif
} else if (!janet_checktype(workdir, JANET_NIL)) {
janet_panicf("expected string for :cd argumnet, got %v", workdir);
}
}
/* Clean up if any of the pipes have any issues */
if (pipe_errflag) {
if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in);
@@ -1180,6 +1251,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo;
LPCSTR lpCurrentDirectory = NULL;
memset(&saAttr, 0, sizeof(saAttr));
memset(&processInfo, 0, sizeof(processInfo));
memset(&startupInfo, 0, sizeof(startupInfo));
@@ -1196,6 +1268,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
}
const char *path = (const char *) janet_unwrap_string(exargs.items[0]);
if (chdir_path != NULL) {
lpCurrentDirectory = chdir_path;
}
/* Do IO redirection */
if (pipe_in != JANET_HANDLE_NONE) {
@@ -1203,7 +1279,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_in != JANET_HANDLE_NONE) {
startupInfo.hStdInput = new_in;
} else {
startupInfo.hStdInput = (HANDLE) _get_osfhandle(0);
startupInfo.hStdInput = (HANDLE) _get_osfhandle(_fileno(stdin));
}
if (pipe_out != JANET_HANDLE_NONE) {
@@ -1211,18 +1287,21 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_out != JANET_HANDLE_NONE) {
startupInfo.hStdOutput = new_out;
} else {
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(1);
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(_fileno(stdout));
}
if (pipe_err != JANET_HANDLE_NONE) {
startupInfo.hStdError = pipe_err;
} else if (new_err != NULL) {
startupInfo.hStdError = new_err;
} else if (stderr_is_stdout) {
startupInfo.hStdError = startupInfo.hStdOutput;
} else {
startupInfo.hStdError = (HANDLE) _get_osfhandle(2);
startupInfo.hStdError = (HANDLE) _get_osfhandle(_fileno(stderr));
}
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 */
@@ -1230,10 +1309,11 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
TRUE, /* handle inheritance */
0, /* flags */
use_environ ? NULL : envp, /* pass in environment */
NULL, /* use parents starting directory */
lpCurrentDirectory,
&startupInfo,
&processInfo)) {
cp_failed = 1;
cp_error_code = GetLastError();
}
if (pipe_in != JANET_HANDLE_NONE) CloseHandle(pipe_in);
@@ -1243,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;
@@ -1268,10 +1366,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* exec mode */
if (mode == JANET_EXECUTE_EXEC) {
#ifdef JANET_WINDOWS
janet_panic("not supported on windows");
#else
int status;
#ifdef JANET_PLAN9
status = exec(cargv[0], cargv);
#else
if (!use_environ) {
environ = envp;
}
@@ -1282,15 +1380,25 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
status = execv(cargv[0], cargv);
}
} while (status == -1 && errno == EINTR);
janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT));
#endif
janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT));
}
#ifndef JANET_NO_SPAWN
/* Use posix_spawn to spawn new process */
/* Posix spawn setup */
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
#ifdef JANET_SPAWN_CHDIR
if (chdir_path != NULL) {
#ifdef JANET_SPAWN_CHDIR_NO_NP
posix_spawn_file_actions_addchdir(&actions, chdir_path);
#else
posix_spawn_file_actions_addchdir_np(&actions, chdir_path);
#endif
}
#endif
if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
posix_spawn_file_actions_addclose(&actions, pipe_in);
@@ -1313,6 +1421,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_err != JANET_HANDLE_NONE && new_err != 2) {
posix_spawn_file_actions_adddup2(&actions, new_err, 2);
posix_spawn_file_actions_addclose(&actions, new_err);
} else if (stderr_is_stdout) {
posix_spawn_file_actions_adddup2(&actions, 1, 2);
}
pid_t pid;
@@ -1343,6 +1453,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
}
#endif
#endif
#ifndef JANET_NO_SPAWN
JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc));
proc->return_code = -1;
#ifdef JANET_WINDOWS
@@ -1380,65 +1492,82 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
return os_proc_wait_impl(proc);
#endif
}
#endif
}
JANET_CORE_FN(os_execute,
"(os/execute args &opt flags env)",
"Execute a program on the system and pass it string arguments. `flags` "
"is a keyword that modifies how the program will execute.\n"
"* :e - enables passing an environment to the program. Without :e, the "
"Execute a program on the system and return the exit code. `args` is an array/tuple "
"of strings. The first string is the name of the program and the remainder are "
"arguments passed to the program. `flags` is a keyword made from the following "
"characters that modifies how the program executes:\n"
"* :e - enables passing an environment to the program. Without 'e', the "
"current environment is inherited.\n"
"* :p - allows searching the current PATH for the binary to execute. "
"Without this flag, binaries must use absolute paths.\n"
"* :x - raise error if exit code is non-zero.\n"
"* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n"
"`env` is a table or struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. "
":in, :out, and :err should be core/file values or core/stream values. core/file values and core/stream "
"values passed to :in, :out, and :err should be closed manually because os/execute doesn't close them. "
"Returns the exit code of the program.") {
"* :p - allows searching the current PATH for the program to execute. "
"Without this flag, the first element of `args` must be an absolute path.\n"
"* :x - raises error if exit code is non-zero.\n"
"* :d - prevents the garbage collector terminating the program (if still running) "
"and calling the equivalent of `os/proc-wait` (allows zombie processes).\n"
"`env` is a table/struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the "
"subprocess. :in, :out, and :err should be core/file or core/stream values. "
"If core/stream values are used, the caller is responsible for ensuring pipes do not "
"cause the program to block and deadlock.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_EXECUTE);
}
JANET_CORE_FN(os_spawn,
"(os/spawn args &opt flags env)",
"Execute a program on the system and return a handle to the process. Otherwise, takes the "
"same arguments as `os/execute`. Does not wait for the process. For each of the :in, :out, and :err keys "
"of the `env` argument, one can also pass in the keyword `:pipe` to get streams for standard IO of the "
"subprocess that can be read from and written to. The returned value `proc` has the fields :in, :out, "
":err, and the additional field :pid on unix-like platforms. `(os/proc-wait proc)` must be called to "
"rejoin the subprocess. After `(os/proc-wait proc)` finishes, proc gains a new field, :return-code. "
"If :x flag is passed to os/spawn, non-zero exit code will cause os/proc-wait to raise an error. "
"If pipe streams created with :pipe keyword are not closed in time, janet can run out of file "
"descriptors. They can be closed individually, or `os/proc-close` can close all pipe streams on proc. "
"If pipe streams aren't read before `os/proc-wait` finishes, then pipe buffers become full, and the "
"process cannot finish because the process cannot print more on pipe buffers which are already full. "
"If the process cannot finish, os/proc-wait cannot finish, either.") {
"Execute a program on the system and return a core/process value representing the "
"spawned subprocess. Takes the same arguments as `os/execute` but does not wait for "
"the subprocess to complete. Unlike `os/execute`, the value `:pipe` can be used for "
":in, :out and :err keys in `env`. If used, the returned core/process will have a "
"writable stream in the :in field and readable streams in the :out and :err fields. "
"On non-Windows systems, the subprocess PID will be in the :pid field. The caller is "
"responsible for waiting on the process (e.g. by calling `os/proc-wait` on the "
"returned core/process value) to avoid creating zombie process. After the subprocess "
"completes, the exit value is in the :return-code field. If `flags` includes 'x', a "
"non-zero exit code will cause a waiting fiber to raise an error. The use of "
"`:pipe` may fail if there are too many active file descriptors. The caller is "
"responsible for closing pipes created by `:pipe` (either individually or using "
"`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring "
"pipes do not cause the program to block and deadlock. As a special case, the stream passed to `:err` "
"can be the keyword `:out` to redirect stderr to stdout in the subprocess.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN);
}
JANET_CORE_FN(os_posix_exec,
"(os/posix-exec args &opt flags env)",
"Use the execvpe or execve system calls to replace the current process with an interface similar to os/execute. "
"However, instead of creating a subprocess, the current process is replaced. Is not supported on windows, and "
"However, instead of creating a subprocess, the current process is replaced. Is not supported on Windows, and "
"does not allow redirection of stdio.") {
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("not supported on Windows");
#else
return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC);
#endif
}
JANET_CORE_FN(os_posix_fork,
"(os/posix-fork)",
"Make a `fork` system call and create a new process. Return nil if in the new process, otherwise a core/process object (as returned by os/spawn). "
"Not supported on all systems (POSIX only).") {
"Not supported on all systems (POSIX and Plan 9 only).") {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_fixarity(argc, 0);
(void) argv;
#ifdef JANET_WINDOWS
janet_panic("not supported");
janet_panic("not supported on Windows");
#else
pid_t result;
#ifdef JANET_PLAN9
result = fork();
#else
do {
result = fork();
} while (result == -1 && errno == EINTR);
#endif
if (result == -1) {
janet_panic(janet_strerror(errno));
}
@@ -1453,6 +1582,28 @@ JANET_CORE_FN(os_posix_fork,
#endif
}
JANET_CORE_FN(os_posix_chroot,
"(os/posix-chroot dirname)",
"Call `chroot` to change the root directory to `dirname`. "
"Not supported on all systems (POSIX only).") {
janet_sandbox_assert(JANET_SANDBOX_CHROOT);
janet_fixarity(argc, 1);
#if defined(JANET_WINDOWS) || defined(JANET_PLAN9)
(void) argv;
janet_panic("not supported on Windows or Plan 9");
#else
const char *root = janet_getcstring(argv, 0);
int result;
do {
result = chroot(root);
} while (result == -1 && errno == EINTR);
if (result == -1) {
janet_panic(janet_strerror(errno));
}
return janet_wrap_nil();
#endif
}
#ifdef JANET_EV
/* Runs in a separate thread */
static JanetEVGenericMessage os_shell_subr(JanetEVGenericMessage args) {
@@ -1488,6 +1639,7 @@ JANET_CORE_FN(os_shell,
#endif /* JANET_NO_PROCESSES */
#ifndef JANET_PLAN9
JANET_CORE_FN(os_environ,
"(os/environ)",
"Get a copy of the OS environment table.") {
@@ -1519,6 +1671,7 @@ JANET_CORE_FN(os_environ,
janet_unlock_environ();
return janet_wrap_table(t);
}
#endif
JANET_CORE_FN(os_getenv,
"(os/getenv variable &opt dflt)",
@@ -1543,6 +1696,9 @@ JANET_CORE_FN(os_setenv,
#ifdef JANET_WINDOWS
#define SETENV(K,V) _putenv_s(K, V)
#define UNSETENV(K) _putenv_s(K, "")
#elif defined(JANET_PLAN9)
#define SETENV(K,V) putenv(K, V)
#define UNSETENV(K) unsetenv(K)
#else
#define SETENV(K,V) setenv(K, V, 1)
#define UNSETENV(K) unsetenv(K)
@@ -1715,6 +1871,8 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct
_tzset();
localtime_s(t_infos, &t);
t_info = t_infos;
#elif defined(JANET_PLAN9)
t_info = localtime(&t);
#else
tzset();
t_info = localtime_r(&t, t_infos);
@@ -1724,6 +1882,8 @@ static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct
#ifdef JANET_WINDOWS
gmtime_s(t_infos, &t);
t_info = t_infos;
#elif defined(JANET_PLAN9)
t_info = gmtime(&t);
#else
t_info = gmtime_r(&t, t_infos);
#endif
@@ -1734,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"
@@ -1745,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;
@@ -1763,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" */
@@ -1778,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);
}
@@ -1787,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);
}
@@ -1795,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"));
@@ -1819,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));
@@ -1880,7 +2045,6 @@ JANET_CORE_FN(os_mktime,
/* utc time */
#ifdef JANET_NO_UTC_MKTIME
janet_panic("os/mktime UTC not supported on this platform");
return janet_wrap_nil();
#else
t = timegm(&t_info);
#endif
@@ -1899,6 +2063,7 @@ JANET_CORE_FN(os_mktime,
#define j_symlink symlink
#endif
#ifndef JANET_NO_LOCALES
JANET_CORE_FN(os_setlocale,
"(os/setlocale &opt locale category)",
"Set the system locale, which affects how dates and numbers are formatted. "
@@ -1935,20 +2100,20 @@ JANET_CORE_FN(os_setlocale,
if (old == NULL) return janet_wrap_nil();
return janet_cstringv(old);
}
#endif
JANET_CORE_FN(os_link,
"(os/link oldpath newpath &opt symlink)",
"Create a link at newpath that points to oldpath and returns nil. "
"Iff symlink is truthy, creates a symlink. "
"Iff symlink is falsey or not provided, "
"creates a hard link. Does not work on Windows.") {
"creates a hard link. Does not work on Windows or Plan 9.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_arity(argc, 2, 3);
#ifdef JANET_WINDOWS
#if defined(JANET_WINDOWS) || defined(JANET_PLAN9)
(void) argc;
(void) argv;
janet_panic("os/link not supported on Windows");
return janet_wrap_nil();
janet_panic("not supported on Windows or Plan 9");
#else
const char *oldpath = janet_getcstring(argv, 0);
const char *newpath = janet_getcstring(argv, 1);
@@ -1963,11 +2128,10 @@ JANET_CORE_FN(os_symlink,
"Create a symlink from oldpath to newpath, returning nil. Same as `(os/link oldpath newpath true)`.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 2);
#ifdef JANET_WINDOWS
#if defined(JANET_WINDOWS) || defined(JANET_PLAN9)
(void) argc;
(void) argv;
janet_panic("os/symlink not supported on Windows");
return janet_wrap_nil();
janet_panic("not supported on Windows or Plan 9");
#else
const char *oldpath = janet_getcstring(argv, 0);
const char *newpath = janet_getcstring(argv, 1);
@@ -2005,6 +2169,8 @@ JANET_CORE_FN(os_rmdir,
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
int res = _rmdir(path);
#elif defined(JANET_PLAN9)
int res = remove(path);
#else
int res = rmdir(path);
#endif
@@ -2069,8 +2235,7 @@ JANET_CORE_FN(os_readlink,
#ifdef JANET_WINDOWS
(void) argc;
(void) argv;
janet_panic("os/readlink not supported on Windows");
return janet_wrap_nil();
janet_panic("not supported on Windows");
#else
static char buffer[PATH_MAX];
const char *path = janet_getcstring(argv, 0);
@@ -2132,11 +2297,13 @@ static const uint8_t *janet_decode_mode(mode_t m) {
const char *str = "other";
if (S_ISREG(m)) str = "file";
else if (S_ISDIR(m)) str = "directory";
#ifndef JANET_PLAN9
else if (S_ISFIFO(m)) str = "fifo";
else if (S_ISBLK(m)) str = "block";
else if (S_ISSOCK(m)) str = "socket";
else if (S_ISLNK(m)) str = "link";
else if (S_ISCHR(m)) str = "character";
#endif
return janet_ckeyword(str);
}
@@ -2301,6 +2468,9 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
#ifdef JANET_WINDOWS
(void) do_lstat;
int res = _stat(path, &st);
#elif defined(JANET_PLAN9)
(void)do_lstat;
int res = stat(path, &st);
#else
int res;
if (do_lstat) {
@@ -2326,7 +2496,6 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
return sg->fn(&st);
}
janet_panicf("unexpected keyword %v", janet_wrap_keyword(key));
return janet_wrap_nil();
}
}
@@ -2362,9 +2531,13 @@ JANET_CORE_FN(os_chmod,
"Change file permissions, where `mode` is a permission string as returned by "
"`os/perm-string`, or an integer as returned by `os/perm-int`. "
"When `mode` is an integer, it is interpreted as a Unix permission value, best specified in octal, like "
"8r666 or 8r400. Windows will not differentiate between user, group, and other permissions, and thus will combine all of these permissions. Returns nil.") {
"8r666 or 8r400. Windows will not differentiate between user, group, and other permissions, and thus will combine all of these permissions. Returns nil."
"Unsupported on plan9.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 2);
#ifdef JANET_PLAN9
janet_panic("not supported on Plan 9");
#else
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
int res = _chmod(path, os_getmode(argv, 1));
@@ -2373,6 +2546,7 @@ JANET_CORE_FN(os_chmod,
#endif
if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path);
return janet_wrap_nil();
#endif
}
#ifndef JANET_NO_UMASK
@@ -2405,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 {
@@ -2471,7 +2645,15 @@ JANET_CORE_FN(os_realpath,
#endif
if (NULL == dest) janet_panicf("%s: %s", janet_strerror(errno), src);
Janet ret = janet_cstringv(dest);
#ifdef JANET_WINDOWS
DWORD attrib = GetFileAttributes(dest);
free(dest); /* if janet_malloc is redefined, still use free to correspond with _fullpath */
if (attrib == INVALID_FILE_ATTRIBUTES) {
janet_panicf("path does not exist: %v", ret);
}
#else
janet_free(dest);
#endif
return ret;
#endif
}
@@ -2515,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"
@@ -2528,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
@@ -2560,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);
@@ -2582,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
@@ -2623,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;
@@ -2667,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 */
@@ -2683,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,
@@ -2751,10 +2964,14 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/strftime", os_strftime),
JANET_CORE_REG("os/sleep", os_sleep),
JANET_CORE_REG("os/isatty", os_isatty),
#ifndef JANET_NO_LOCALES
JANET_CORE_REG("os/setlocale", os_setlocale),
#endif
/* env functions */
#ifndef JANET_PLAN9
JANET_CORE_REG("os/environ", os_environ),
#endif
JANET_CORE_REG("os/getenv", os_getenv),
JANET_CORE_REG("os/setenv", os_setenv),
@@ -2790,11 +3007,16 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/shell", os_shell),
JANET_CORE_REG("os/posix-fork", os_posix_fork),
JANET_CORE_REG("os/posix-exec", os_posix_exec),
JANET_CORE_REG("os/posix-chroot", os_posix_chroot),
/* no need to sandbox process management if you can't create processes
* (allows for limited functionality if use exposes C-functions to create specific processes) */
JANET_CORE_REG("os/proc-wait", os_proc_wait),
JANET_CORE_REG("os/proc-kill", os_proc_kill),
JANET_CORE_REG("os/proc-close", os_proc_close),
JANET_CORE_REG("os/getpid", os_proc_getpid),
#ifdef JANET_EV
JANET_CORE_REG("os/sigaction", os_sigaction),
#endif
#endif
/* high resolution timers */
@@ -2803,7 +3025,6 @@ void janet_lib_os(JanetTable *env) {
#ifdef JANET_EV
JANET_CORE_REG("os/open", os_open), /* fs read and write */
JANET_CORE_REG("os/pipe", os_pipe),
JANET_CORE_REG("os/sigaction", os_sigaction),
#endif
#endif
JANET_REG_END

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -105,15 +105,6 @@ static int to_hex(uint8_t c) {
}
}
typedef int (*Consumer)(JanetParser *p, JanetParseState *state, uint8_t c);
struct JanetParseState {
int32_t counter;
int32_t argn;
int flags;
size_t line;
size_t column;
Consumer consumer;
};
/* Define a stack on the main parser struct */
#define DEF_PARSER_STACK(NAME, T, STACK, STACKCOUNT, STACKCAP) \
@@ -363,8 +354,7 @@ static int stringend(JanetParser *p, JanetParseState *state) {
JanetParseState top = p->states[p->statecount - 1];
int32_t indent_col = (int32_t) top.column - 1;
uint8_t *r = bufstart, *end = r + buflen;
/* Check if there are any characters before the start column -
* if so, do not reindent. */
/* Unless there are only spaces before EOLs, disable reindenting */
int reindent = 1;
while (reindent && (r < end)) {
if (*r++ == '\n') {
@@ -374,34 +364,36 @@ static int stringend(JanetParser *p, JanetParseState *state) {
break;
}
}
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') reindent = 1;
}
}
/* Now reindent if able to, otherwise just drop leading newline. */
if (!reindent) {
if (buflen > 0 && bufstart[0] == '\n') {
buflen--;
bufstart++;
}
} else {
/* Now reindent if able */
if (reindent) {
uint8_t *w = bufstart;
r = bufstart;
while (r < end) {
if (*r == '\n') {
if (r == bufstart) {
/* Skip leading newline */
r++;
} else {
*w++ = *r++;
}
*w++ = *r++;
for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++);
if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') *w++ = *r++;
} else {
*w++ = *r++;
}
}
buflen = (int32_t)(w - bufstart);
}
/* Check for trailing newline character so we can remove it */
if (buflen > 0 && bufstart[buflen - 1] == '\n') {
/* Check for leading EOL so we can remove it */
if (buflen > 1 && bufstart[0] == '\r' && bufstart[1] == '\n') { /* Windows EOL */
buflen = buflen - 2;
bufstart = bufstart + 2;
} else if (buflen > 0 && bufstart[0] == '\n') { /* Unix EOL */
buflen--;
bufstart++;
}
/* Check for trailing EOL so we can remove it */
if (buflen > 1 && bufstart[buflen - 2] == '\r' && bufstart[buflen - 1] == '\n') { /* Windows EOL */
buflen = buflen - 2;
} else if (buflen > 0 && bufstart[buflen - 1] == '\n') { /* Unix EOL */
buflen--;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -194,6 +194,41 @@ tail:
return memcmp(text, rule + 2, len) ? NULL : text + len;
}
case RULE_DEBUG: {
char buffer[32] = {0};
size_t len = (size_t)(s->outer_text_end - text);
memcpy(buffer, text, (len > 31 ? 31 : len));
janet_eprintf("?? at [%s] (index %d)\n", buffer, (int32_t)(text - s->text_start));
int has_color = janet_truthy(janet_dyn("err-color"));
/* Accumulate buffer */
if (s->scratch->count) {
janet_eprintf("accumulate buffer: %v\n", janet_wrap_buffer(s->scratch));
}
/* Normal captures */
if (s->captures->count) {
janet_eprintf("stack [%d]:\n", s->captures->count);
for (int32_t i = 0; i < s->captures->count; i++) {
if (has_color) {
janet_eprintf(" [%d]: %M\n", i, s->captures->data[i]);
} else {
janet_eprintf(" [%d]: %m\n", i, s->captures->data[i]);
}
}
}
/* Tagged captures */
if (s->tagged_captures->count) {
janet_eprintf("tag stack [%d]:\n", s->tagged_captures->count);
for (int32_t i = 0; i < s->tagged_captures->count; i++) {
if (has_color) {
janet_eprintf(" [%d] tag=%d: %M\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]);
} else {
janet_eprintf(" [%d] tag=%d: %m\n", i, (int32_t) s->tags->data[i], s->tagged_captures->data[i]);
}
}
}
return text;
}
case RULE_NCHAR: {
uint32_t n = rule[1];
return (text + n > s->text_end) ? NULL : text + n;
@@ -342,7 +377,7 @@ tail:
while (captured < hi) {
CapState cs2 = cap_save(s);
next_text = peg_rule(s, rule_a, text);
if (!next_text || next_text == text) {
if (!next_text || ((next_text == text) && (hi == UINT32_MAX))) {
cap_load(s, cs2);
break;
}
@@ -544,45 +579,85 @@ tail:
return window_end;
}
case RULE_TIL: {
const uint32_t *rule_terminus = s->bytecode + rule[1];
const uint32_t *rule_subpattern = s->bytecode + rule[2];
const uint8_t *terminus_start = text;
const uint8_t *terminus_end = NULL;
down1(s);
while (terminus_start <= s->text_end) {
CapState cs2 = cap_save(s);
terminus_end = peg_rule(s, rule_terminus, terminus_start);
cap_load(s, cs2);
if (terminus_end) {
break;
}
terminus_start++;
}
up1(s);
if (!terminus_end) {
return NULL;
}
const uint8_t *saved_end = s->text_end;
s->text_end = terminus_start;
down1(s);
const uint8_t *matched = peg_rule(s, rule_subpattern, text);
up1(s);
s->text_end = saved_end;
if (!matched) {
return NULL;
}
return terminus_end;
}
case RULE_SPLIT: {
const uint8_t *saved_end = s->text_end;
const uint32_t *rule_separator = s->bytecode + rule[1];
const uint32_t *rule_subpattern = s->bytecode + rule[2];
const uint8_t *separator_end = NULL;
do {
const uint8_t *text_start = text;
const uint8_t *chunk_start = text;
const uint8_t *chunk_end = NULL;
while (text <= saved_end) {
/* Find next split (or end of text) */
CapState cs = cap_save(s);
down1(s);
while (text <= s->text_end) {
separator_end = peg_rule(s, rule_separator, text);
while (text <= saved_end) {
chunk_end = text;
const uint8_t *check = peg_rule(s, rule_separator, text);
cap_load(s, cs);
if (separator_end) {
if (check) {
text = check;
break;
}
text++;
}
up1(s);
if (separator_end) {
s->text_end = text;
text = separator_end;
}
/* Match between splits */
s->text_end = chunk_end;
down1(s);
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, text_start);
const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, chunk_start);
up1(s);
s->text_end = saved_end;
if (!subpattern_end) return NULL; /* Don't match anything */
if (!subpattern_end) {
return NULL;
}
} while (separator_end);
/* Ensure forward progress */
if (text == chunk_start) return NULL;
chunk_start = text;
}
s->text_end = saved_end;
return s->text_end;
}
case RULE_REPLACE:
case RULE_MATCHSPLICE:
case RULE_MATCHTIME: {
uint32_t tag = rule[3];
int oldmode = s->mode;
@@ -623,8 +698,17 @@ tail:
break;
}
cap_load_keept(s, cs);
if (rule[0] == RULE_MATCHTIME && !janet_truthy(cap)) return NULL;
pushcap(s, cap, tag);
if (rule[0] != RULE_REPLACE && !janet_truthy(cap)) return NULL; /* matchtime or matchtime flatten */
const Janet *elements = NULL;
int32_t len = 0;
if ((rule[0] == RULE_MATCHSPLICE) && janet_indexed_view(cap, &elements, &len)) {
/* unpack and flatten capture */
for (int32_t i = 0; i < len; i++) {
pushcap(s, elements[i], tag);
}
} else {
pushcap(s, cap, tag);
}
return result;
}
@@ -1196,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);
@@ -1205,7 +1297,7 @@ static void spec_replace(Builder *b, int32_t argc, const Janet *argv) {
emit_3(r, RULE_REPLACE, subrule, constant, tag);
}
static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) {
static void spec_matchtime_impl(Builder *b, int32_t argc, const Janet *argv, uint32_t op) {
peg_arity(b, argc, 2, 3);
Reserve r = reserve(b, 4);
uint32_t subrule = peg_compile1(b, argv[0]);
@@ -1216,7 +1308,15 @@ static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) {
}
uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0;
uint32_t cindex = emit_constant(b, fun);
emit_3(r, RULE_MATCHTIME, subrule, cindex, tag);
emit_3(r, op, subrule, cindex, tag);
}
static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) {
spec_matchtime_impl(b, argc, argv, RULE_MATCHTIME);
}
static void spec_matchtime_splice(Builder *b, int32_t argc, const Janet *argv) {
spec_matchtime_impl(b, argc, argv, RULE_MATCHSPLICE);
}
static void spec_sub(Builder *b, int32_t argc, const Janet *argv) {
@@ -1227,6 +1327,14 @@ static void spec_sub(Builder *b, int32_t argc, const Janet *argv) {
emit_2(r, RULE_SUB, subrule1, subrule2);
}
static void spec_til(Builder *b, int32_t argc, const Janet *argv) {
peg_fixarity(b, argc, 2);
Reserve r = reserve(b, 3);
uint32_t subrule1 = peg_compile1(b, argv[0]);
uint32_t subrule2 = peg_compile1(b, argv[1]);
emit_2(r, RULE_TIL, subrule1, subrule2);
}
static void spec_split(Builder *b, int32_t argc, const Janet *argv) {
peg_fixarity(b, argc, 2);
Reserve r = reserve(b, 3);
@@ -1284,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},
@@ -1294,9 +1403,11 @@ static const SpecialPair peg_specials[] = {
{"between", spec_between},
{"capture", spec_capture},
{"choice", spec_choice},
{"cms", spec_matchtime_splice},
{"cmt", spec_matchtime},
{"column", spec_column},
{"constant", spec_constant},
{"debug", spec_debug},
{"drop", spec_drop},
{"error", spec_error},
{"group", spec_group},
@@ -1323,6 +1434,7 @@ static const SpecialPair peg_specials[] = {
{"split", spec_split},
{"sub", spec_sub},
{"thru", spec_thru},
{"til", spec_til},
{"to", spec_to},
{"uint", spec_uint_le},
{"uint-be", spec_uint_be},
@@ -1416,6 +1528,11 @@ static uint32_t peg_compile1(Builder *b, Janet peg) {
emit_bytes(b, RULE_LITERAL, len, str);
break;
}
case JANET_BUFFER: {
const JanetBuffer *buf = janet_unwrap_buffer(peg);
emit_bytes(b, RULE_LITERAL, buf->count, buf->data);
break;
}
case JANET_TABLE: {
/* Build grammar table */
JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg));
@@ -1544,7 +1661,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
/* After here, no panics except for the bad: label. */
/* Keep track at each index if an instruction was
* reference (0x01) or is in a main bytecode position
* referenced (0x01) or is in a main bytecode position
* (0x02). This lets us do a linear scan and not
* need to a depth first traversal. It is stricter
* than a dfs by not allowing certain kinds of unused
@@ -1567,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:
@@ -1650,6 +1771,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
break;
case RULE_REPLACE:
case RULE_MATCHTIME:
case RULE_MATCHSPLICE:
/* [rule, constant, tag] */
if (rule[1] >= blen) goto bad;
if (rule[2] >= clen) goto bad;
@@ -1657,6 +1779,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
i += 4;
break;
case RULE_SUB:
case RULE_TIL:
case RULE_SPLIT:
/* [rule, rule] */
if (rule[1] >= blen) goto bad;
@@ -1780,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

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -72,7 +72,7 @@ static int count_dig10(int32_t x) {
}
}
static void integer_to_string_b(JanetBuffer *buffer, int32_t x) {
static int32_t integer_to_string_b(JanetBuffer *buffer, int32_t x) {
janet_buffer_extra(buffer, BUFSIZE);
uint8_t *buf = buffer->data + buffer->count;
int32_t neg = 0;
@@ -80,7 +80,7 @@ static void integer_to_string_b(JanetBuffer *buffer, int32_t x) {
if (x == 0) {
buf[0] = '0';
buffer->count++;
return;
return 1;
}
if (x > 0) {
x = -x;
@@ -96,6 +96,7 @@ static void integer_to_string_b(JanetBuffer *buffer, int32_t x) {
x /= 10;
}
buffer->count += len + neg;
return len + neg;
}
#define HEX(i) (((uint8_t *) janet_base64)[(i)])
@@ -134,43 +135,55 @@ static void string_description_b(JanetBuffer *buffer, const char *title, void *p
#undef POINTSIZE
}
static void janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, int32_t len) {
static int janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, int32_t len) {
janet_buffer_push_u8(buffer, '"');
int align = 1;
for (int32_t i = 0; i < len; ++i) {
uint8_t c = str[i];
switch (c) {
case '"':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\\"", 2);
align += 2;
break;
case '\n':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\n", 2);
align += 2;
break;
case '\r':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\r", 2);
align += 2;
break;
case '\0':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\0", 2);
align += 2;
break;
case '\f':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\f", 2);
align += 2;
break;
case '\v':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\v", 2);
align += 2;
break;
case '\a':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\a", 2);
align += 2;
break;
case '\b':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\b", 2);
align += 2;
break;
case 27:
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\e", 2);
align += 2;
break;
case '\\':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\\\", 2);
align += 2;
break;
case '\t':
janet_buffer_push_bytes(buffer, (const uint8_t *)"\\t", 2);
align += 2;
break;
default:
if (c < 32 || c > 126) {
@@ -180,13 +193,16 @@ static void janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, in
buf[2] = janet_base64[(c >> 4) & 0xF];
buf[3] = janet_base64[c & 0xF];
janet_buffer_push_bytes(buffer, buf, 4);
align += 4;
} else {
janet_buffer_push_u8(buffer, c);
align++;
}
break;
}
}
janet_buffer_push_u8(buffer, '"');
return align + 1;
}
static void janet_escape_string_b(JanetBuffer *buffer, const uint8_t *str) {
@@ -358,7 +374,7 @@ const uint8_t *janet_to_string(Janet x) {
struct pretty {
JanetBuffer *buffer;
int depth;
int indent;
int align;
int flags;
int32_t bufstartlen;
int32_t *keysort_buffer;
@@ -450,14 +466,15 @@ static int print_jdn_one(struct pretty *S, Janet x, int depth) {
return 0;
}
static void print_newline(struct pretty *S, int just_a_space) {
static void print_newline(struct pretty *S, int align) {
int i;
if (just_a_space || (S->flags & JANET_PRETTY_ONELINE)) {
S->align = align;
if (S->flags & JANET_PRETTY_ONELINE) {
janet_buffer_push_u8(S->buffer, ' ');
return;
}
janet_buffer_push_u8(S->buffer, '\n');
for (i = 0; i < S->indent; i++) {
for (i = 0; i < S->align; i++) {
janet_buffer_push_u8(S->buffer, ' ');
}
}
@@ -484,13 +501,12 @@ static const char *janet_pretty_colors[] = {
"\x1B[36m"
};
#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 */
static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
static void janet_pretty_one(struct pretty *S, Janet x) {
/* Add to seen */
switch (janet_type(x)) {
case JANET_NIL:
@@ -505,7 +521,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
janet_buffer_push_cstring(S->buffer, janet_cycle_color);
}
janet_buffer_push_cstring(S->buffer, "<cycle ");
integer_to_string_b(S->buffer, janet_unwrap_integer(seenid));
S->align += 8 + integer_to_string_b(S->buffer, janet_unwrap_integer(seenid));
janet_buffer_push_u8(S->buffer, '>');
if (S->flags & JANET_PRETTY_COLOR) {
janet_buffer_push_cstring(S->buffer, "\x1B[0m");
@@ -527,9 +543,11 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
if (janet_checktype(x, JANET_BUFFER) && janet_unwrap_buffer(x) == S->buffer) {
janet_buffer_ensure(S->buffer, S->buffer->count + S->bufstartlen * 4 + 3, 1);
janet_buffer_push_u8(S->buffer, '@');
janet_escape_string_impl(S->buffer, S->buffer->data, S->bufstartlen);
S->align += 1 + janet_escape_string_impl(S->buffer, S->buffer->data, S->bufstartlen);
} else {
S->align -= S->buffer->count;
janet_description_b(S->buffer, x);
S->align += S->buffer->count;
}
if (color && (S->flags & JANET_PRETTY_COLOR)) {
janet_buffer_push_cstring(S->buffer, "\x1B[0m");
@@ -546,35 +564,34 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
const char *startstr = isarray ? "@[" : hasbrackets ? "[" : "(";
const char endchar = isarray ? ']' : hasbrackets ? ']' : ')';
janet_buffer_push_cstring(S->buffer, startstr);
const int align = S->align += strlen(startstr);
S->depth--;
S->indent += 2;
if (S->depth == 0) {
janet_buffer_push_cstring(S->buffer, "...");
S->align += 3;
} else {
if (!isarray && !(S->flags & JANET_PRETTY_ONELINE) && len >= JANET_PRETTY_IND_ONELINE)
janet_buffer_push_u8(S->buffer, ' ');
if (is_dict_value && len >= JANET_PRETTY_IND_ONELINE) print_newline(S, 0);
if (len > JANET_PRETTY_ARRAY_LIMIT && !(S->flags & JANET_PRETTY_NOTRUNC)) {
for (i = 0; i < 3; i++) {
if (i) print_newline(S, 0);
janet_pretty_one(S, arr[i], 0);
if (i) print_newline(S, align);
janet_pretty_one(S, arr[i]);
}
print_newline(S, 0);
print_newline(S, align);
janet_buffer_push_cstring(S->buffer, "...");
for (i = 0; i < 3; i++) {
print_newline(S, 0);
janet_pretty_one(S, arr[len - 3 + i], 0);
S->align += 3;
for (i = len - 3; i < len; i++) {
print_newline(S, align);
janet_pretty_one(S, arr[i]);
}
} else {
for (i = 0; i < len; i++) {
if (i) print_newline(S, len < JANET_PRETTY_IND_ONELINE);
janet_pretty_one(S, arr[i], 0);
if (i) print_newline(S, align);
janet_pretty_one(S, arr[i]);
}
}
}
S->indent -= 2;
S->depth++;
janet_buffer_push_u8(S->buffer, endchar);
S->align++;
break;
}
case JANET_STRUCT:
@@ -585,6 +602,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
if (istable) {
JanetTable *t = janet_unwrap_table(x);
JanetTable *proto = t->proto;
S->align++;
janet_buffer_push_cstring(S->buffer, "@");
if (NULL != proto) {
Janet name = janet_table_get(proto, janet_ckeywordv("_name"));
@@ -595,6 +613,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
janet_buffer_push_cstring(S->buffer, janet_class_color);
}
janet_buffer_push_bytes(S->buffer, n, len);
S->align += len;
if (S->flags & JANET_PRETTY_COLOR) {
janet_buffer_push_cstring(S->buffer, "\x1B[0m");
}
@@ -612,73 +631,99 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
janet_buffer_push_cstring(S->buffer, janet_class_color);
}
janet_buffer_push_bytes(S->buffer, n, len);
S->align += len;
if (S->flags & JANET_PRETTY_COLOR) {
janet_buffer_push_cstring(S->buffer, "\x1B[0m");
}
}
}
}
janet_buffer_push_cstring(S->buffer, "{");
janet_buffer_push_u8(S->buffer, '{');
const int align = ++S->align;
S->depth--;
S->indent += 2;
if (S->depth == 0) {
janet_buffer_push_cstring(S->buffer, "...");
S->align += 3;
} 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, align);
janet_pretty_one(S, kvs[j].key);
janet_buffer_push_u8(S->buffer, ' ');
S->align++;
janet_pretty_one(S, kvs[j].value);
j++;
}
}
if (truncated) {
print_newline(S, align);
janet_buffer_push_cstring(S->buffer, "...");
S->align += 3;
}
} 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, align);
int32_t j = S->keysort_buffer[i + ks_start];
janet_pretty_one(S, kvs[j].key);
janet_buffer_push_u8(S->buffer, ' ');
S->align++;
janet_pretty_one(S, kvs[j].value);
}
if (truncated) {
print_newline(S, align);
janet_buffer_push_cstring(S->buffer, "...");
S->align += 3;
}
}
S->keysort_start = ks_start;
}
S->indent -= 2;
S->depth++;
janet_buffer_push_u8(S->buffer, '}');
S->align++;
break;
}
}
@@ -694,14 +739,14 @@ static JanetBuffer *janet_pretty_(JanetBuffer *buffer, int depth, int flags, Jan
}
S.buffer = buffer;
S.depth = depth;
S.indent = 0;
S.align = 0;
S.flags = flags;
S.bufstartlen = startlen;
S.keysort_capacity = 0;
S.keysort_buffer = NULL;
S.keysort_start = 0;
janet_table_init(&S.seen, 10);
janet_pretty_one(&S, x, 0);
janet_pretty_one(&S, x);
janet_table_deinit(&S.seen);
return S.buffer;
}
@@ -719,7 +764,7 @@ static JanetBuffer *janet_jdn_(JanetBuffer *buffer, int depth, Janet x, int32_t
}
S.buffer = buffer;
S.depth = depth;
S.indent = 0;
S.align = 0;
S.flags = 0;
S.bufstartlen = startlen;
S.keysort_capacity = 0;
@@ -897,7 +942,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) {
case 's':
case 'S': {
const char *str = va_arg(args, const char *);
int32_t len = c[-1] == 's'
int32_t len = (c[-1] == 's')
? (int32_t) strlen(str)
: janet_string_length((JanetString) str);
if (form[2] == '\0')
@@ -1023,10 +1068,20 @@ void janet_buffer_format(
char form[MAX_FORMAT], item[MAX_ITEM];
char width[3], precision[3];
int nb = 0; /* number of bytes in added item */
if (++arg >= argc)
janet_panic("not enough values for format");
#ifdef JANET_PLAN9
if (*strfrmt == 'r') {
rerrstr(item, MAX_ITEM);
nb = strlen(item);
} else
#endif
if (++arg >= argc)
janet_panic("not enough values for format");
strfrmt = scanformat(strfrmt, form, width, precision);
switch (*strfrmt++) {
#ifdef JANET_PLAN9
case 'r':
break;
#endif
case 'c': {
nb = snprintf(item, MAX_ITEM, form, (int)
janet_getinteger(argv, arg));
@@ -1060,19 +1115,11 @@ void janet_buffer_format(
break;
}
case 's': {
JanetByteView bytes = janet_getbytes(argv, arg);
const uint8_t *s = bytes.bytes;
int32_t l = bytes.len;
const char *s = janet_getcbytes(argv, arg);
if (form[2] == '\0')
janet_buffer_push_bytes(b, s, l);
janet_buffer_push_cstring(b, s);
else {
if (l != (int32_t) strlen((const char *) s))
janet_panic("string contains zeros");
if (!strchr(form, '.') && l >= 100) {
janet_panic("no precision and string is too long to be formatted");
} else {
nb = snprintf(item, MAX_ITEM, form, s);
}
nb = snprintf(item, MAX_ITEM, form, s);
}
break;
}

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -26,9 +26,10 @@
#include "state.h"
#endif
/* Run a string */
/* Run a string of code. The return value is a set of error flags, JANET_DO_ERROR_RUNTIME, JANET_DO_ERROR_COMPILE, and JANET_DOR_ERROR_PARSE if
* any errors were encountered in those phases. More information is printed to stderr. */
int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) {
JanetParser parser;
JanetParser *parser;
int errflags = 0, done = 0;
int32_t index = 0;
Janet ret = janet_wrap_nil();
@@ -37,14 +38,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
if (where) janet_gcroot(janet_wrap_string(where));
if (NULL == sourcePath) sourcePath = "<unknown>";
janet_parser_init(&parser);
parser = janet_abstract(&janet_parser_type, sizeof(JanetParser));
janet_parser_init(parser);
janet_gcroot(janet_wrap_abstract(parser));
/* While we haven't seen an error */
while (!done) {
/* Evaluate parsed values */
while (janet_parser_has_more(&parser)) {
Janet form = janet_parser_produce(&parser);
while (janet_parser_has_more(parser)) {
Janet form = janet_parser_produce(parser);
JanetCompileResult cres = janet_compile(form, env, where);
if (cres.status == JANET_COMPILE_OK) {
JanetFunction *f = janet_thunk(cres.funcdef);
@@ -53,27 +56,30 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
JanetSignal status = janet_continue(fiber, janet_wrap_nil(), &ret);
if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) {
janet_stacktrace_ext(fiber, ret, "");
errflags |= 0x01;
errflags |= JANET_DO_ERROR_RUNTIME;
done = 1;
}
} else {
ret = janet_wrap_string(cres.error);
int32_t line = (int32_t) parser.line;
int32_t col = (int32_t) parser.column;
int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser->column;
if ((cres.error_mapping.line > 0) &&
(cres.error_mapping.column > 0)) {
line = cres.error_mapping.line;
col = cres.error_mapping.column;
}
JanetString ctx = janet_formatc("%s:%d:%d: compile error",
sourcePath, line, col);
JanetString errstr = janet_formatc("%s: %s",
(const char *)ctx,
(const char *)cres.error);
ret = janet_wrap_string(errstr);
if (cres.macrofiber) {
janet_eprintf("%s:%d:%d: compile error", sourcePath,
line, col);
janet_eprintf("%s", (const char *)ctx);
janet_stacktrace_ext(cres.macrofiber, ret, "");
} else {
janet_eprintf("%s:%d:%d: compile error: %s\n", sourcePath,
line, col, (const char *)cres.error);
janet_eprintf("%s\n", (const char *)errstr);
}
errflags |= 0x02;
errflags |= JANET_DO_ERROR_COMPILE;
done = 1;
}
}
@@ -81,26 +87,28 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
if (done) break;
/* Dispatch based on parse state */
switch (janet_parser_status(&parser)) {
switch (janet_parser_status(parser)) {
case JANET_PARSE_DEAD:
done = 1;
break;
case JANET_PARSE_ERROR: {
const char *e = janet_parser_error(&parser);
errflags |= 0x04;
ret = janet_cstringv(e);
int32_t line = (int32_t) parser.line;
int32_t col = (int32_t) parser.column;
janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e);
errflags |= JANET_DO_ERROR_PARSE;
int32_t line = (int32_t) parser->line;
int32_t col = (int32_t) parser->column;
JanetString errstr = janet_formatc("%s:%d:%d: parse error: %s",
sourcePath, line, col,
janet_parser_error(parser));
ret = janet_wrap_string(errstr);
janet_eprintf("%s\n", (const char *)errstr);
done = 1;
break;
}
case JANET_PARSE_ROOT:
case JANET_PARSE_PENDING:
if (index >= len) {
janet_parser_eof(&parser);
janet_parser_eof(parser);
} else {
janet_parser_consume(&parser, bytes[index++]);
janet_parser_consume(parser, bytes[index++]);
}
break;
}
@@ -108,7 +116,7 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
}
/* Clean up and return errors */
janet_parser_deinit(&parser);
janet_gcunroot(janet_wrap_abstract(parser));
if (where) janet_gcunroot(janet_wrap_string(where));
#ifdef JANET_EV
/* Enter the event loop if we are not already in it */
@@ -119,7 +127,8 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
janet_loop();
if (fiber) {
janet_gcunroot(janet_wrap_fiber(fiber));
ret = fiber->last_value;
if (!errflags)
ret = fiber->last_value;
}
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -307,14 +307,14 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv)
/* Add attributes to a global def or var table */
static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) {
int32_t i;
JanetTable *tab = janet_table(2);
const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL
? ((const char *)janet_unwrap_symbol(argv[0]))
: "<multiple bindings>";
if (argn < 2) {
janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind));
return NULL;
}
JanetTable *tab = janet_table(2);
const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL
? ((const char *)janet_unwrap_symbol(argv[0]))
: "<multiple bindings>";
for (i = 1; i < argn - 1; i++) {
Janet attr = argv[i];
switch (janet_type(attr)) {
@@ -362,6 +362,23 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt
janet_indexed_view(lhs, &view_lhs.items, &view_lhs.len);
janet_indexed_view(rhs, &view_rhs.items, &view_rhs.len);
int found_amp = 0;
int found_splice = 0;
/* Check for (def [x y z] [(splice [1 2 3]) 4 5 6]), bail out of optimization */
for (int32_t i = 0; i < view_rhs.len; i++) {
if (!janet_checktype(view_rhs.items[i], JANET_TUPLE)) {
continue;
}
JanetTuple tup = janet_unwrap_tuple(view_rhs.items[i]);
if (janet_tuple_length(tup) == 0) {
continue;
}
if (janet_symeq(tup[0], "splice")) {
found_splice = 1;
/* Good error will be generated later. */
break;
}
}
/* Check for (def [x & more] [1 2 3]), bail out of optimization */
for (int32_t i = 0; i < view_lhs.len; i++) {
if (janet_symeq(view_lhs.items[i], "&")) {
found_amp = 1;
@@ -369,7 +386,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt
break;
}
}
if (!found_amp) {
if (!found_amp && !found_splice) {
for (int32_t i = 0; i < view_lhs.len; i++) {
Janet sub_rhs = view_rhs.len <= i ? janet_wrap_nil() : view_rhs.items[i];
into = dohead_destructure(c, into, subopts, view_lhs.items[i], sub_rhs);
@@ -387,7 +404,7 @@ SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopt
}
/* Def or var a symbol in a local scope */
static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret) {
static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, JanetSlot ret, int no_unused) {
int isUnnamedRegister = !(ret.flags & JANET_SLOT_NAMED) &&
ret.index > 0 &&
ret.envindex >= 0;
@@ -408,7 +425,11 @@ static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, Janet
ret = localslot;
}
ret.flags |= flags;
janetc_nameslot(c, head, ret);
if ((c->scope->flags & JANET_SCOPE_TOP) || no_unused) {
janetc_nameslot_no_unused(c, head, ret);
} else {
janetc_nameslot(c, head, ret);
}
return !isUnnamedRegister;
}
@@ -422,8 +443,7 @@ static int varleaf(
JanetSlot refslot;
JanetTable *entry = janet_table_clone(reftab);
Janet redef_kw = janet_ckeywordv("redef");
int is_redef = janet_truthy(janet_table_get(c->env, redef_kw));
int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef"));
JanetArray *ref;
JanetBinding old_binding;
@@ -443,7 +463,17 @@ static int varleaf(
janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0);
return 1;
} else {
return namelocal(c, sym, JANET_SLOT_MUTABLE, s);
int no_unused = reftab && reftab->count && janet_truthy(janet_table_get_keyword(reftab, "unused"));
return namelocal(c, sym, JANET_SLOT_MUTABLE, s, no_unused);
}
}
static void check_metadata_lint(JanetCompiler *c, JanetTable *attr_table) {
if (!(c->scope->flags & JANET_SCOPE_TOP) && attr_table && attr_table->count) {
/* A macro is a normal lint, other metadata is a strict lint */
if (janet_truthy(janet_table_get_keyword(attr_table, "macro"))) {
janetc_lintf(c, JANET_C_LINT_NORMAL, "macro tag is ignored in inner scopes");
}
}
}
@@ -453,6 +483,7 @@ static JanetSlot janetc_var(JanetFopts opts, int32_t argn, const Janet *argv) {
if (c->result.status == JANET_COMPILE_ERROR) {
return janetc_cslot(janet_wrap_nil());
}
check_metadata_lint(c, attr_table);
SlotHeadPair *into = NULL;
into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]);
if (c->result.status == JANET_COMPILE_ERROR) {
@@ -479,9 +510,8 @@ static int defleaf(
janet_table_put(entry, janet_ckeywordv("source-map"),
janet_wrap_tuple(janetc_make_sourcemap(c)));
Janet redef_kw = janet_ckeywordv("redef");
int is_redef = janet_truthy(janet_table_get(c->env, redef_kw));
if (is_redef) janet_table_put(entry, redef_kw, janet_wrap_true());
int is_redef = janet_truthy(janet_table_get_keyword(c->env, "redef"));
if (is_redef) janet_table_put(entry, janet_ckeywordv("redef"), janet_wrap_true());
if (is_redef) {
JanetBinding binding = janet_resolve_ext(c->env, sym);
@@ -504,7 +534,8 @@ static int defleaf(
/* Add env entry to env */
janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry));
}
return namelocal(c, sym, 0, s);
int no_unused = tab && tab->count && janet_truthy(janet_table_get_keyword(tab, "unused"));
return namelocal(c, sym, 0, s, no_unused);
}
static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) {
@@ -513,6 +544,7 @@ static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) {
if (c->result.status == JANET_COMPILE_ERROR) {
return janetc_cslot(janet_wrap_nil());
}
check_metadata_lint(c, attr_table);
opts.flags &= ~JANET_FOPTS_HINT;
SlotHeadPair *into = NULL;
into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]);
@@ -652,8 +684,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) {
/* Write jumps - only add jump lengths if jump actually emitted */
labeld = janet_v_count(c->buffer);
c->buffer[labeljr] |= (labelr - labeljr) << 16;
if (!tail) c->buffer[labeljd] |= (labeld - labeljd) << 8;
if (labeljr < labeld) {
c->buffer[labeljr] |= (labelr - labeljr) << 16;
if (!tail && labeljd < labeld) c->buffer[labeljd] |= (labeld - labeljd) << 8;
}
if (tail) target.flags |= JANET_SLOT_RETURNED;
return target;
@@ -875,7 +909,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv)
janetc_regalloc_freetemp(&c->scope->ra, tempself, JANETC_REGTEMP_0);
/* Compile function */
JanetFuncDef *def = janetc_pop_funcdef(c);
def->name = janet_cstring("_while");
def->name = janet_cstring("while");
janet_def_addflags(def);
int32_t defindex = janetc_addfuncdef(c, def);
/* And then load the closure and call it. */
@@ -1042,6 +1076,14 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
}
}
/* Compile named arguments */
if (namedargs) {
Janet param = janet_wrap_table(named_table);
destructure(c, param, named_slot, defleaf, NULL);
janetc_freeslot(c, named_slot);
janet_v_free(named_params);
}
/* Compile destructed params */
int32_t j = 0;
for (i = 0; i < paramcount; i++) {
@@ -1055,14 +1097,6 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
}
janet_v_free(destructed_params);
/* Compile named arguments */
if (namedargs) {
Janet param = janet_wrap_table(named_table);
destructure(c, param, named_slot, defleaf, NULL);
janetc_freeslot(c, named_slot);
janet_v_free(named_params);
}
max_arity = (vararg || allow_extra) ? INT32_MAX : arity;
if (!seenopt) min_arity = arity;
@@ -1084,7 +1118,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
JanetSlot slot = janetc_farslot(c);
slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION;
janetc_emit_s(c, JOP_LOAD_SELF, slot, 1);
janetc_nameslot(c, sym, slot);
janetc_nameslot_no_unused(c, sym, slot);
}
}
@@ -1105,8 +1139,12 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
def->arity = arity;
def->min_arity = min_arity;
def->max_arity = max_arity;
if (named_table != NULL) {
def->named_args_count = named_table->count;
}
if (vararg) def->flags |= JANET_FUNCDEF_FLAG_VARARG;
if (structarg) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG;
if (namedargs) def->flags |= JANET_FUNCDEF_FLAG_NAMEDARGS;
if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */
janet_def_addflags(def);

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -23,11 +23,16 @@
#ifndef JANET_STATE_H_defined
#define JANET_STATE_H_defined
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
#include <stdint.h>
#endif
#ifdef JANET_EV
#ifndef JANET_WINDOWS
#ifdef JANET_WINDOWS
#include <windows.h>
#else
#include <pthread.h>
#endif
#endif
@@ -53,13 +58,22 @@ typedef struct {
void *data;
} JanetQueue;
#ifdef JANET_EV
typedef struct {
JanetTimestamp when;
JanetFiber *fiber;
JanetFiber *curr_fiber;
uint32_t sched_id;
int is_error;
int has_worker;
#ifdef JANET_WINDOWS
HANDLE worker;
HANDLE worker_event;
#else
pthread_t worker;
#endif
} JanetTimeout;
#endif
/* Registry table for C functions - contains metadata that can
* be looked up by cfunction pointer. All strings here are pointing to
@@ -100,6 +114,7 @@ struct JanetVM {
* return point for panics. */
jmp_buf *signal_buf;
Janet *return_reg;
int coerce_error;
/* The global registry for c functions. Used to store meta-data
* along with otherwise bare c function pointers. */
@@ -167,6 +182,8 @@ struct JanetVM {
JanetTable signal_handlers;
#ifdef JANET_WINDOWS
void **iocp;
void *connect_ex; /* MSWsock extension if available */
int connect_ex_loaded;
#elif defined(JANET_EV_EPOLL)
pthread_attr_t new_thread_attr;
JanetHandle selfpipe[2];

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -301,6 +301,7 @@ int janet_scan_number_base(
if (base == 0) {
base = 10;
}
int exp_base = base;
/* Skip leading zeros */
while (str < end && (*str == '0' || *str == '.')) {
@@ -322,6 +323,12 @@ int janet_scan_number_base(
} else if (*str == '&') {
foundexp = 1;
break;
} else if (base == 16 && (*str == 'P' || *str == 'p')) { /* IEEE hex float */
foundexp = 1;
exp_base = 10;
base = 2;
ex *= 4; /* We need to correct the current exponent after we change the base */
break;
} else if (base == 10 && (*str == 'E' || *str == 'e')) {
foundexp = 1;
break;
@@ -360,9 +367,9 @@ int janet_scan_number_base(
}
while (str < end) {
int digit = digit_lookup[*str & 0x7F];
if (*str > 127 || digit >= base) goto error;
if (*str > 127 || digit >= exp_base) goto error;
if (ee < (INT32_MAX / 40)) {
ee = base * ee + digit;
ee = exp_base * ee + digit;
}
str++;
seenadigit = 1;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -294,6 +294,16 @@ JANET_CORE_FN(cfun_struct_to_table,
return janet_wrap_table(tab);
}
JANET_CORE_FN(cfun_struct_rawget,
"(struct/rawget st key)",
"Gets a value from a struct `st` without looking at the prototype struct. "
"If `st` does not contain the key directly, the function will return "
"nil without checking the prototype. Returns the value in the struct.") {
janet_fixarity(argc, 2);
JanetStruct st = janet_getstruct(argv, 0);
return janet_struct_rawget(st, argv[1]);
}
/* Load the struct module */
void janet_lib_struct(JanetTable *env) {
JanetRegExt struct_cfuns[] = {
@@ -301,6 +311,7 @@ void janet_lib_struct(JanetTable *env) {
JANET_CORE_REG("struct/getproto", cfun_struct_getproto),
JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten),
JANET_CORE_REG("struct/to-table", cfun_struct_to_table),
JANET_CORE_REG("struct/rawget", cfun_struct_rawget),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, struct_cfuns);

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -155,6 +155,17 @@ Janet janet_table_get(JanetTable *t, Janet key) {
return janet_wrap_nil();
}
/* Used internally for compiler stuff */
Janet janet_table_get_keyword(JanetTable *t, const char *keyword) {
int32_t keyword_len = (int32_t) strlen(keyword);
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
JanetKV *bucket = (JanetKV *) janet_dict_find_keyword(t->data, t->capacity, (const uint8_t *) keyword, keyword_len);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
return bucket->value;
}
return janet_wrap_nil();
}
/* Get a value out of the table, and record which prototype it was from. */
Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which) {
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
@@ -372,12 +383,14 @@ JANET_CORE_FN(cfun_table_setproto,
}
JANET_CORE_FN(cfun_table_tostruct,
"(table/to-struct tab)",
"Convert a table to a struct. Returns a new struct. This function "
"does not take into account prototype tables.") {
janet_fixarity(argc, 1);
"(table/to-struct tab &opt proto)",
"Convert a table to a struct. Returns a new struct.") {
janet_arity(argc, 1, 2);
JanetTable *t = janet_gettable(argv, 0);
return janet_wrap_struct(janet_table_to_struct(t));
JanetStruct proto = janet_optstruct(argv, argc, 1, NULL);
JanetStruct st = janet_table_to_struct(t);
janet_struct_proto(st) = proto;
return janet_wrap_struct(st);
}
JANET_CORE_FN(cfun_table_rawget,

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -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;
@@ -848,11 +896,15 @@ int janet_checksize(Janet x) {
return 0;
double dval = janet_unwrap_number(x);
if (dval != (double)((size_t) dval)) return 0;
#ifdef JANET_PLAN9
return dval <= SIZE_MAX;
#else
if (SIZE_MAX > JANET_INTMAX_INT64) {
return dval <= JANET_INTMAX_INT64;
} else {
return dval <= SIZE_MAX;
}
#endif
}
JanetTable *janet_get_core_table(const char *name) {
@@ -931,27 +983,24 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) {
#include <mach/clock.h>
#include <mach/mach.h>
int janet_gettime(struct timespec *spec, enum JanetTimeSource source) {
if (source == JANET_TIME_REALTIME) {
if (source == JANET_TIME_CPUTIME) {
clock_t tmp = clock();
spec->tv_sec = tmp / CLOCKS_PER_SEC;
spec->tv_nsec = ((tmp - (spec->tv_sec * CLOCKS_PER_SEC)) * 1000000000) / CLOCKS_PER_SEC;
} else {
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_id_t cid = CALENDAR_CLOCK;
if (source == JANET_TIME_REALTIME) {
cid = CALENDAR_CLOCK;
} else if (source == JANET_TIME_MONOTONIC) {
cid = SYSTEM_CLOCK;
}
host_get_clock_service(mach_host_self(), cid, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
spec->tv_sec = mts.tv_sec;
spec->tv_nsec = mts.tv_nsec;
} else if (source == JANET_TIME_MONOTONIC) {
clock_serv_t cclock;
int nsecs;
mach_msg_type_number_t count;
host_get_clock_service(mach_host_self(), clock, &cclock);
clock_get_attributes(cclock, CLOCK_GET_TIME_RES, (clock_attr_t)&nsecs, &count);
mach_port_deallocate(mach_task_self(), cclock);
clock_getres(CLOCK_MONOTONIC, spec);
}
if (source == JANET_TIME_CPUTIME) {
clock_t tmp = clock();
spec->tv_sec = tmp;
spec->tv_nsec = (tmp - spec->tv_sec) * 1.0e9;
}
return 0;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -66,42 +66,72 @@
/* Utils */
uint32_t janet_hash_mix(uint32_t input, uint32_t more);
#define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1))
int janet_valid_utf8(const uint8_t *str, int32_t len);
int janet_is_symbol_char(uint8_t c);
extern const char janet_base64[65];
int32_t janet_array_calchash(const Janet *array, int32_t len);
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len);
int32_t janet_string_calchash(const uint8_t *str, int32_t len);
int32_t janet_tablen(int32_t n);
void safe_memcpy(void *dest, const void *src, size_t len);
void janet_buffer_push_types(JanetBuffer *buffer, int types);
const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key);
void janet_memempty(JanetKV *mem, int32_t count);
void *janet_memalloc_empty(int32_t count);
JanetTable *janet_get_core_table(const char *name);
void janet_def_addflags(JanetFuncDef *def);
void janet_buffer_dtostr(JanetBuffer *buffer, double x);
const char *janet_strerror(int e);
const void *janet_strbinsearch(
const void *tab,
size_t tabcount,
size_t itemsize,
const uint8_t *key);
void janet_buffer_format(
JanetBuffer *b,
const char *strfrmt,
int32_t argstart,
int32_t argc,
Janet *argv);
Janet janet_next_impl(Janet ds, Janet key, int is_interpreter);
JanetBinding janet_binding_from_entry(Janet entry);
JanetByteView janet_text_substitution(
Janet *subst,
const uint8_t *bytes,
uint32_t len,
JanetArray *extra_args);
const JanetKV *janet_dict_find_keyword(
const JanetKV *buckets,
int32_t cap,
const uint8_t *cstr,
int32_t cstr_len);
Janet janet_table_get_keyword(JanetTable *t, const char *keyword);
/* Registry functions */
void janet_registry_put(
JanetCFunction key,
@@ -167,7 +197,26 @@ typedef void *Clib;
#endif
char *get_processed_name(const char *name);
#ifdef JANET_PLAN9
#define RETRY_EINTR(RC, CALL) (RC) = CALL;
#else
#define RETRY_EINTR(RC, CALL) do { (RC) = CALL; } while((RC) < 0 && errno == EINTR)
#endif
#ifdef JANET_EV
#ifdef JANET_WINDOWS
#include <winsock2.h>
#include <windows.h>
#include <io.h>
typedef struct {
union {
OVERLAPPED overlapped;
WSAOVERLAPPED wsaoverlapped;
} as;
uint32_t bytes_transfered;
} JanetOverlapped;
#endif
#endif
/* Initialize builtin libraries */
void janet_lib_io(JanetTable *env);
@@ -205,9 +254,9 @@ int janet_make_pipe(JanetHandle handles[2], int mode);
#ifdef JANET_FILEWATCH
void janet_lib_filewatch(JanetTable *env);
#endif
#endif
#ifdef JANET_FFI
void janet_lib_ffi(JanetTable *env);
#endif
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -322,7 +322,8 @@ int32_t janet_hash(Janet x) {
break;
case JANET_TUPLE:
hash = janet_tuple_hash(janet_unwrap_tuple(x));
hash += (janet_tuple_flag(janet_unwrap_tuple(x)) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : 0;
uint32_t inc = (janet_tuple_flag(janet_unwrap_tuple(x)) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : 0;
hash = (int32_t)((uint32_t)hash + inc); /* avoid overflow undefined behavior */
break;
case JANET_STRUCT:
hash = janet_struct_hash(janet_unwrap_struct(x));
@@ -334,10 +335,9 @@ int32_t janet_hash(Janet x) {
} as;
as.d = janet_unwrap_number(x);
as.d += 0.0; /* normalize negative 0 */
uint32_t lo = (uint32_t)(as.u & 0xFFFFFFFF);
as.u = murmur64(as.u);
uint32_t hi = (uint32_t)(as.u >> 32);
uint32_t hilo = (hi ^ lo) * 2654435769u;
hash = (int32_t)((hilo << 16) | (hilo >> 16));
hash = (int32_t)hi;
break;
}
case JANET_ABSTRACT: {
@@ -495,7 +495,7 @@ Janet janet_in(Janet ds, Janet key) {
if (!(type->get)(janet_unwrap_abstract(ds), key, &value))
janet_panicf("key %v not found in %v ", key, ds);
} else {
janet_panicf("no getter for %v ", ds);
janet_panicf("no getter for %v", ds);
}
break;
}
@@ -622,7 +622,7 @@ Janet janet_getindex(Janet ds, int32_t index) {
if (!(type->get)(janet_unwrap_abstract(ds), janet_wrap_integer(index), &value))
value = janet_wrap_nil();
} else {
janet_panicf("no getter for %v ", ds);
janet_panicf("no getter for %v", ds);
}
break;
}
@@ -724,6 +724,9 @@ void janet_putindex(Janet ds, int32_t index, Janet value) {
JanetArray *array = janet_unwrap_array(ds);
if (index >= array->count) {
janet_array_ensure(array, index + 1, 2);
for (int32_t i = array->count; i < index + 1; i++) {
array->data[i] = janet_wrap_nil();
}
array->count = index + 1;
}
array->data[index] = value;
@@ -735,6 +738,7 @@ void janet_putindex(Janet ds, int32_t index, Janet value) {
janet_panicf("can only put integers in buffers, got %v", value);
if (index >= buffer->count) {
janet_buffer_ensure(buffer, index + 1, 2);
memset(buffer->data + buffer->count, 0, index + 1 - buffer->count);
buffer->count = index + 1;
}
buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF);
@@ -767,7 +771,11 @@ void janet_put(Janet ds, Janet key, Janet value) {
JanetArray *array = janet_unwrap_array(ds);
int32_t index = getter_checkint(type, key, INT32_MAX - 1);
if (index >= array->count) {
janet_array_setcount(array, index + 1);
janet_array_ensure(array, index + 1, 2);
for (int32_t i = array->count; i < index + 1; i++) {
array->data[i] = janet_wrap_nil();
}
array->count = index + 1;
}
array->data[index] = value;
break;
@@ -778,7 +786,9 @@ void janet_put(Janet ds, Janet key, Janet value) {
if (!janet_checkint(value))
janet_panicf("can only put integers in buffers, got %v", value);
if (index >= buffer->count) {
janet_buffer_setcount(buffer, index + 1);
janet_buffer_ensure(buffer, index + 1, 2);
memset(buffer->data + buffer->count, 0, index + 1 - buffer->count);
buffer->count = index + 1;
}
buffer->data[index] = (uint8_t)(janet_unwrap_integer(value) & 0xFF);
break;

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -115,7 +115,7 @@
#define vm_maybe_auto_suspend(COND)
#else
#define vm_maybe_auto_suspend(COND) do { \
if ((COND) && janet_vm.auto_suspend) { \
if ((COND) && janet_atomic_load_relaxed(&janet_vm.auto_suspend)) { \
fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \
vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \
} \
@@ -129,7 +129,9 @@
if (!janet_checktype(op1, JANET_NUMBER)) {\
vm_commit();\
Janet _argv[2] = { op1, janet_wrap_number(CS) };\
stack[A] = janet_mcall(#op, 2, _argv);\
Janet a = janet_mcall(#op, 2, _argv);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
} else {\
double x1 = janet_unwrap_number(op1);\
@@ -143,7 +145,9 @@
if (!janet_checktype(op1, JANET_NUMBER)) {\
vm_commit();\
Janet _argv[2] = { op1, janet_wrap_number(CS) };\
stack[A] = janet_mcall(#op, 2, _argv);\
Janet a = janet_mcall(#op, 2, _argv);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
} else {\
double y1 = janet_unwrap_number(op1);\
@@ -166,7 +170,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\
Janet a = janet_binop_call(#op, "r" #op, op1, op2);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -186,7 +192,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_binop_call(#op, "r" #op, op1, op2);\
Janet a = janet_binop_call(#op, "r" #op, op1, op2);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -203,7 +211,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_wrap_boolean(janet_compare(op1, op2) op 0);\
Janet a = janet_wrap_boolean(janet_compare(op1, op2) op 0);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -217,7 +227,9 @@
vm_pcnext();\
} else {\
vm_commit();\
stack[A] = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\
Janet a = janet_wrap_boolean(janet_compare(op1, janet_wrap_integer(CS)) op 0);\
stack = fiber->data + fiber->frame;\
stack[A] = a;\
vm_checkgc_pcnext();\
}\
}
@@ -710,7 +722,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_binop_call("div", "rdiv", op1, op2);
Janet a = janet_binop_call("div", "rdiv", op1, op2);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -730,7 +744,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_binop_call("mod", "rmod", op1, op2);
Janet a = janet_binop_call("mod", "rmod", op1, op2);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -745,7 +761,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_binop_call("%", "r%", op1, op2);
Janet a = janet_binop_call("%", "r%", op1, op2);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -766,7 +784,9 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
} else {
vm_commit();
stack[A] = janet_unary_call("~", op);
Janet a = janet_unary_call("~", op);
stack = fiber->data + fiber->frame;
stack[A] = a;
vm_checkgc_pcnext();
}
}
@@ -798,14 +818,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_pcnext();
VM_OP(JOP_JUMP)
pc += DS;
vm_maybe_auto_suspend(DS <= 0);
pc += DS;
vm_next();
VM_OP(JOP_JUMP_IF)
if (janet_truthy(stack[A])) {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} else {
pc++;
}
@@ -815,15 +835,15 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (janet_truthy(stack[A])) {
pc++;
} else {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
}
vm_next();
VM_OP(JOP_JUMP_IF_NIL)
if (janet_checktype(stack[A], JANET_NIL)) {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
} else {
pc++;
}
@@ -833,8 +853,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
if (janet_checktype(stack[A], JANET_NIL)) {
pc++;
} else {
pc += ES;
vm_maybe_auto_suspend(ES <= 0);
pc += ES;
}
vm_next();
@@ -872,8 +892,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
stack[A] = janet_wrap_boolean(!janet_checktype(stack[B], JANET_NUMBER) || (janet_unwrap_number(stack[B]) != (double) CS));
vm_pcnext();
VM_OP(JOP_COMPARE)
stack[A] = janet_wrap_integer(janet_compare(stack[B], stack[C]));
VM_OP(JOP_COMPARE) {
Janet a = janet_wrap_integer(janet_compare(stack[B], stack[C]));
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_NEXT)
@@ -1104,11 +1127,11 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
}
fiber->child = child;
JanetSignal sig = janet_continue_no_check(child, stack[C], &retreg);
stack = fiber->data + fiber->frame;
if (sig != JANET_SIGNAL_OK && !(child->flags & (1 << sig))) {
vm_return(sig, retreg);
}
fiber->child = NULL;
stack = fiber->data + fiber->frame;
stack[A] = retreg;
vm_checkgc_pcnext();
}
@@ -1157,6 +1180,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_commit();
fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL;
janet_put(stack[A], stack[B], stack[C]);
stack = fiber->data + fiber->frame;
fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL;
vm_checkgc_pcnext();
@@ -1164,27 +1188,44 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_commit();
fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL;
janet_putindex(stack[A], C, stack[B]);
stack = fiber->data + fiber->frame;
fiber->flags &= ~JANET_FIBER_RESUME_NO_USEVAL;
vm_checkgc_pcnext();
VM_OP(JOP_IN)
vm_commit();
stack[A] = janet_in(stack[B], stack[C]);
{
Janet a = janet_in(stack[B], stack[C]);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_GET)
vm_commit();
stack[A] = janet_get(stack[B], stack[C]);
{
Janet a = janet_get(stack[B], stack[C]);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_GET_INDEX)
vm_commit();
stack[A] = janet_getindex(stack[B], C);
{
Janet a = janet_getindex(stack[B], C);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_LENGTH)
vm_commit();
stack[A] = janet_lengthv(stack[E]);
{
Janet a = janet_lengthv(stack[E]);
stack = fiber->data + fiber->frame;
stack[A] = a;
}
vm_pcnext();
VM_OP(JOP_MAKE_ARRAY) {
@@ -1373,7 +1414,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
/* Run vm */
janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP;
int old_coerce_error = janet_vm.coerce_error;
janet_vm.coerce_error = 1;
JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil());
janet_vm.coerce_error = old_coerce_error;
/* Teardown */
janet_vm.stackn = oldn;
@@ -1384,6 +1428,15 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
}
if (signal != JANET_SIGNAL_OK) {
/* Should match logic in janet_signalv */
#ifdef JANET_EV
if (janet_vm.root_fiber != NULL && signal == JANET_SIGNAL_EVENT) {
janet_vm.root_fiber->sched_id++;
}
#endif
if (signal != JANET_SIGNAL_ERROR) {
*janet_vm.return_reg = janet_wrap_string(janet_formatc("%v coerced from %s to error", *janet_vm.return_reg, janet_signal_names[signal]));
}
janet_panicv(*janet_vm.return_reg);
}
@@ -1430,8 +1483,10 @@ void janet_try_init(JanetTryState *state) {
state->vm_fiber = janet_vm.fiber;
state->vm_jmp_buf = janet_vm.signal_buf;
state->vm_return_reg = janet_vm.return_reg;
state->coerce_error = janet_vm.coerce_error;
janet_vm.return_reg = &(state->payload);
janet_vm.signal_buf = &(state->buf);
janet_vm.coerce_error = 0;
}
void janet_restore(JanetTryState *state) {
@@ -1440,6 +1495,7 @@ void janet_restore(JanetTryState *state) {
janet_vm.fiber = state->vm_fiber;
janet_vm.signal_buf = state->vm_jmp_buf;
janet_vm.return_reg = state->vm_return_reg;
janet_vm.coerce_error = state->coerce_error;
}
static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) {
@@ -1503,6 +1559,15 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
}
}
/* If this is a nested continue (root_fiber already set), root the fiber
* so it survives GC. janet_collect only marks root_fiber, so without
* this a nested fiber (e.g., from janet_pcall in a C function) would be
* invisible to GC and could be collected while actively running. */
int fiber_rooted = (janet_vm.root_fiber != NULL);
if (fiber_rooted) {
janet_gcroot(janet_wrap_fiber(fiber));
}
/* Save global state */
JanetTryState tstate;
JanetSignal sig = janet_try(&tstate);
@@ -1518,6 +1583,9 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
if (janet_vm.root_fiber == fiber) janet_vm.root_fiber = NULL;
janet_fiber_set_status(fiber, sig);
janet_restore(&tstate);
if (fiber_rooted) {
janet_gcunroot(janet_wrap_fiber(fiber));
}
fiber->last_value = tstate.payload;
*out = tstate.payload;

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -67,11 +67,21 @@ extern "C" {
#define JANET_LINUX 1
#endif
/* Check for Android */
#ifdef __ANDROID__
#define JANET_ANDROID 1
#endif
/* Check for Cygwin */
#if defined(__CYGWIN__)
#define JANET_CYGWIN 1
#endif
/* Check for Illumos */
#if defined(__illumos__)
#define JANET_ILLUMOS 1
#endif
/* Check Unix */
#if defined(_AIX) \
|| defined(__APPLE__) /* Darwin */ \
@@ -111,6 +121,7 @@ extern "C" {
|| (defined(__sparc__) && defined(__arch64__) || defined (__sparcv9)) /* BE */ \
|| defined(__s390x__) /* S390 64-bit (BE) */ \
|| (defined(__ppc64__) || defined(__PPC64__)) \
|| defined(PLAN9_arm64) || defined(PLAN9_amd64) \
|| defined(__aarch64__) /* ARM 64-bit */ \
|| (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ \
|| defined(__loongarch64) /* LoongArch64 64-bit */
@@ -137,6 +148,7 @@ extern "C" {
|| defined(__s390x__) /* S390 64-bit */ \
|| defined(__s390__) /* S390 32-bit */ \
|| defined(__ARMEB__) /* ARM big endian */ \
|| defined(__AARCH64EB__) /* ARM64 big endian */ \
|| ((defined(__CC_ARM) || defined(__ARMCC__)) /* ARM RealView compiler */ \
&& defined(__BIG_ENDIAN))
#define JANET_BIG_ENDIAN 1
@@ -157,7 +169,7 @@ extern "C" {
#endif
/* Check sun */
#ifdef __sun
#if defined(__sun) && !defined(JANET_ILLUMOS)
#define JANET_NO_UTC_MKTIME
#endif
@@ -165,14 +177,12 @@ extern "C" {
/* Also enable the thread library only if not single-threaded */
#ifdef JANET_SINGLE_THREADED
#define JANET_THREAD_LOCAL
#undef JANET_THREADS
#elif defined(__GNUC__)
#elif !(defined(JANET_THREAD_LOCAL)) && defined(__GNUC__)
#define JANET_THREAD_LOCAL __thread
#elif defined(_MSC_BUILD)
#elif !(defined(JANET_THREAD_LOCAL)) && defined(_MSC_BUILD)
#define JANET_THREAD_LOCAL __declspec(thread)
#else
#elif !(defined(JANET_THREAD_LOCAL))
#define JANET_THREAD_LOCAL
#undef JANET_THREADS
#endif
/* Enable or disable dynamic module loading. Enabled by default. */
@@ -591,6 +601,7 @@ typedef void *JanetAbstract;
#define JANET_STREAM_WRITABLE 0x400
#define JANET_STREAM_ACCEPTABLE 0x800
#define JANET_STREAM_UDPSERVER 0x1000
#define JANET_STREAM_NOT_CLOSEABLE 0x2000
#define JANET_STREAM_TOCLOSE 0x10000
typedef enum {
@@ -657,12 +668,15 @@ JANET_API void janet_stream_level_triggered(JanetStream *stream);
* signals. Define them here */
#ifdef JANET_WINDOWS
typedef long JanetAtomicInt;
#elif defined(JANET_PLAN9)
typedef long JanetAtomicInt;
#else
typedef int32_t JanetAtomicInt;
#endif
JANET_API JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x);
/* We provide three possible implementations of Janets. The preferred
* nanboxing approach, for 32 or 64 bits, and the standard C version. Code in the rest of the
@@ -1060,6 +1074,7 @@ struct JanetAbstractHead {
#define JANET_FUNCDEF_FLAG_HASSOURCEMAP 0x800000
#define JANET_FUNCDEF_FLAG_STRUCTARG 0x1000000
#define JANET_FUNCDEF_FLAG_HASCLOBITSET 0x2000000
#define JANET_FUNCDEF_FLAG_NAMEDARGS 0x4000000
#define JANET_FUNCDEF_FLAG_TAG 0xFFFF
/* Source mapping structure for a bytecode instruction */
@@ -1101,6 +1116,7 @@ struct JanetFuncDef {
int32_t environments_length;
int32_t defs_length;
int32_t symbolmap_length;
int32_t named_args_count;
};
/* A function environment */
@@ -1124,9 +1140,21 @@ struct JanetFunction {
JanetFuncEnv *envs[];
};
/* Use to read Janet data structures into memory from source code */
typedef struct JanetParseState JanetParseState;
typedef struct JanetParser JanetParser;
typedef int (*Consumer)(JanetParser *p, JanetParseState *state, uint8_t c);
struct JanetParseState {
int32_t counter;
int32_t argn;
int flags;
size_t line;
size_t column;
Consumer consumer;
};
enum JanetParserStatus {
JANET_PARSE_ROOT,
JANET_PARSE_ERROR,
@@ -1162,7 +1190,10 @@ typedef struct {
const JanetAbstractType *at;
} JanetMarshalContext;
/* Defines an abstract type */
/* Defines an abstract type. Use a const pointer to one of these structures
* when creating abstract types. The memory for this pointer should not be free
* until after janet_deinit is called. Usually, this means declaring JanetAbstractType's
* as const data at file scope, and creating instances with janet_abstract(&MyType, sizeof(MyTypeStruct)); */
struct JanetAbstractType {
const char *name;
int (*gc)(void *data, size_t len);
@@ -1178,6 +1209,7 @@ struct JanetAbstractType {
Janet(*call)(void *p, int32_t argc, Janet *argv);
size_t (*length)(void *p, size_t len);
JanetByteView(*bytes)(void *p, size_t len);
int (*gcperthread)(void *data, size_t len);
};
/* Some macros to let us add extra types to JanetAbstract types without
@@ -1197,7 +1229,8 @@ struct JanetAbstractType {
#define JANET_ATEND_NEXT NULL,JANET_ATEND_CALL
#define JANET_ATEND_CALL NULL,JANET_ATEND_LENGTH
#define JANET_ATEND_LENGTH NULL,JANET_ATEND_BYTES
#define JANET_ATEND_BYTES
#define JANET_ATEND_BYTES NULL,JANET_ATEND_GCPERTHREAD
#define JANET_ATEND_GCPERTHREAD
struct JanetReg {
const char *name;
@@ -1248,6 +1281,7 @@ typedef struct JanetFile JanetFile;
struct JanetFile {
FILE *file;
int32_t flags;
size_t vbufsize;
};
/* For janet_try and janet_restore */
@@ -1261,6 +1295,7 @@ typedef struct {
/* new state */
jmp_buf buf;
Janet payload;
int coerce_error;
} JanetTryState;
/***** END SECTION TYPES *****/
@@ -1411,6 +1446,7 @@ JANET_API void janet_loop(void);
* } else {
* janet_schedule(interrupted_fiber, janet_wrap_nil());
* }
* janet_interpreter_interrupt_handled(NULL);
* }
* }
*
@@ -1450,18 +1486,27 @@ JANET_API void janet_ev_dec_refcount(void);
JANET_API void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size);
JANET_API void *janet_abstract_end_threaded(void *x);
JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size);
/* Allow reference counting on threaded abstract types. This is useful when external code , either
* in the current OS thread or in a different OS thread, takes a pointer to this abstract type. The programmer
* should tncrement the reference count when taking the pointer, and then decrement and possibly cleanup and free
* if the reference count is 0. */
JANET_API int32_t janet_abstract_incref(void *abst);
JANET_API int32_t janet_abstract_decref(void *abst);
/* If this returns 0, *abst will be deinitialized and freed. Useful shorthand if there is no other cleanup for
* this abstract type before calling `janet_free` on it's backing memory. */
JANET_API int32_t janet_abstract_decref_maybe_free(void *abst);
/* Expose channel utilities */
JanetChannel *janet_channel_make(uint32_t limit);
JanetChannel *janet_channel_make_threaded(uint32_t limit);
JanetChannel *janet_getchannel(const Janet *argv, int32_t n);
JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, JanetChannel *dflt);
JANET_API JanetChannel *janet_channel_make(uint32_t limit);
JANET_API JanetChannel *janet_channel_make_threaded(uint32_t limit);
JANET_API JanetChannel *janet_getchannel(const Janet *argv, int32_t n);
JANET_API JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, JanetChannel *dflt);
JANET_API int janet_channel_give(JanetChannel *channel, Janet x);
JANET_API int janet_channel_take(JanetChannel *channel, Janet *out);
/* Expose some OS sync primitives */
/* Expose some OS sync primitives - mutexes and reader-writer locks */
JANET_API size_t janet_os_mutex_size(void);
JANET_API size_t janet_os_rwlock_size(void);
JANET_API void janet_os_mutex_init(JanetOSMutex *mutex);
@@ -1529,7 +1574,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
@@ -1538,7 +1584,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
@@ -1550,17 +1597,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 */
@@ -1604,7 +1697,13 @@ JANET_API JanetCompileResult janet_compile_lint(
JANET_API JanetTable *janet_core_env(JanetTable *replacements);
JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements);
/* Execute strings */
/* Execute strings.
*
* These functions wrap parsing, compilation, and evalutation into convenient functions.
* */
#define JANET_DO_ERROR_RUNTIME 0x01
#define JANET_DO_ERROR_COMPILE 0x02
#define JANET_DO_ERROR_PARSE 0x04
JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out);
JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out);
@@ -1795,21 +1894,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 */
@@ -1858,7 +1977,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);
@@ -1883,9 +2002,14 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr
#define JANET_SANDBOX_FFI_USE 2048
#define JANET_SANDBOX_FFI_JIT 4096
#define JANET_SANDBOX_SIGNAL 8192
#define JANET_SANDBOX_CHROOT 16384
#define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT)
#define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP)
#define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN)
#define JANET_SANDBOX_COMPILE 32768
#define JANET_SANDBOX_ASM 65536
#define JANET_SANDBOX_THREADS 131072
#define JANET_SANDBOX_UNMARSHAL 262144
#define JANET_SANDBOX_ALL (UINT32_MAX)
JANET_API void janet_sandbox(uint32_t flags);
JANET_API void janet_sandbox_assert(uint32_t forbidden_flags);
@@ -1930,7 +2054,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)
@@ -2085,6 +2216,8 @@ JANET_API int32_t janet_optinteger(const Janet *argv, int32_t argc, int32_t n, i
JANET_API int64_t janet_optinteger64(const Janet *argv, int32_t argc, int32_t n, int64_t dflt);
JANET_API size_t janet_optsize(const Janet *argv, int32_t argc, int32_t n, size_t dflt);
JANET_API JanetAbstract janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetAbstractType *at, JanetAbstract dflt);
JANET_API uint32_t janet_optuinteger(const Janet *argv, int32_t argc, int32_t n, uint32_t dflt);
JANET_API uint64_t janet_optuinteger64(const Janet *argv, int32_t argc, int32_t n, uint64_t dflt);
/* Mutable optional types specify a size default, and construct a new value if none is provided */
JANET_API JanetBuffer *janet_optbuffer(const Janet *argv, int32_t argc, int32_t n, int32_t dflt_len);
@@ -2181,10 +2314,13 @@ typedef enum {
RULE_UNREF, /* [rule, tag] */
RULE_CAPTURE_NUM, /* [rule, tag] */
RULE_SUB, /* [rule, rule] */
RULE_TIL, /* [rule, rule] */
RULE_SPLIT, /* [rule, rule] */
RULE_NTH, /* [nth, rule, tag] */
RULE_ONLY_TAGS, /* [rule] */
} JanetPegOpcod;
RULE_MATCHSPLICE, /* [rule, constant, tag] */
RULE_DEBUG, /* [] */
} JanetPegOpcode;
typedef struct {
uint32_t *bytecode;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Calvin Rose
* Copyright (c) 2026 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -26,6 +26,7 @@
#include <janet.h>
#include <errno.h>
#include <assert.h>
#ifdef _WIN32
#include <windows.h>
@@ -79,9 +80,11 @@ static void simpleline(JanetBuffer *buffer) {
int c;
for (;;) {
c = fgetc(in);
#ifndef JANET_PLAN9
if (c < 0 && !feof(in) && errno == EINTR) {
continue;
}
#endif
if (feof(in) || c < 0) {
break;
}
@@ -110,6 +113,8 @@ static JANET_THREAD_LOCAL int gbl_historyi = 0;
static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
static JANET_THREAD_LOCAL int gbl_match_count = 0;
static JANET_THREAD_LOCAL int gbl_lines_below = 0;
static JANET_THREAD_LOCAL int gbl_history_loaded = 0;
static JANET_THREAD_LOCAL char *gbl_history_file = NULL;
#endif
/* Fallback */
@@ -307,7 +312,9 @@ static int curpos(void) {
char buf[32];
int cols, rows;
unsigned int i = 0;
#ifndef JANET_PLAN9
if (write_console("\x1b[6n", 4) != 4) return -1;
#endif
while (i < sizeof(buf) - 1) {
if (read_console(buf + i, 1) != 1) break;
if (buf[i] == 'R') break;
@@ -356,33 +363,52 @@ static void clear(void) {
}
}
static int getplen(void) {
int _plen = gbl_plen;
/* Ensure at least 16 characters of data entry; */
while (_plen && (_plen + 16 > gbl_cols)) {
_plen--;
}
return _plen;
}
static void refresh(void) {
char seq[64];
JanetBuffer b;
/* If prompt is too long, truncate */
int _plen = getplen();
/* Keep cursor position on screen */
char *_buf = gbl_buf;
int _len = gbl_len;
int _pos = gbl_pos;
while ((gbl_plen + _pos) >= gbl_cols) {
while ((_plen + _pos) >= gbl_cols) {
_buf++;
_len--;
_pos--;
}
while ((gbl_plen + _len) > gbl_cols) {
while ((_plen + _len) > gbl_cols) {
_len--;
}
janet_buffer_init(&b, 0);
/* Cursor to left edge, gbl_prompt and buffer */
janet_buffer_push_u8(&b, '\r');
janet_buffer_push_cstring(&b, gbl_prompt);
janet_buffer_push_bytes(&b, (uint8_t *) _buf, _len);
janet_buffer_push_bytes(&b, (const uint8_t *) gbl_prompt, _plen);
if (_len > 0) {
janet_buffer_push_bytes(&b, (uint8_t *) _buf, _len);
}
/* Erase to right */
janet_buffer_push_cstring(&b, "\x1b[0K");
janet_buffer_push_cstring(&b, "\x1b[0K\r");
/* Move cursor to original position. */
snprintf(seq, 64, "\r\x1b[%dC", (int)(_pos + gbl_plen));
janet_buffer_push_cstring(&b, seq);
if (_pos + _plen) {
snprintf(seq, 64, "\x1b[%dC", (int)(_pos + _plen));
janet_buffer_push_cstring(&b, seq);
}
if (write_console((char *) b.data, b.count) == -1) {
exit(1);
}
@@ -406,7 +432,8 @@ static int insert(char c, int draw) {
gbl_buf[gbl_pos++] = c;
gbl_buf[++gbl_len] = '\0';
if (draw) {
if (gbl_plen + gbl_len < gbl_cols) {
int _plen = getplen();
if (_plen + gbl_len < gbl_cols) {
/* Avoid a full update of the line in the
* trivial case. */
if (write_console(&c, 1) == -1) return -1;
@@ -424,6 +451,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]);
@@ -860,11 +944,12 @@ static int line() {
gbl_len = 0;
gbl_pos = 0;
while (gbl_prompt[gbl_plen]) gbl_plen++;
int _plen = getplen();
gbl_buf[0] = '\0';
addhistory();
if (write_console((char *) gbl_prompt, gbl_plen) == -1) return -1;
if (write_console((char *) gbl_prompt, _plen) == -1) return -1;
for (;;) {
char c;
char seq[5];
@@ -890,6 +975,7 @@ static int line() {
case 3: /* ctrl-c */
clearlines();
norawmode();
savehistory();
#ifdef _WIN32
ExitProcess(1);
#else
@@ -1083,17 +1169,21 @@ void janet_line_init() {
}
void janet_line_deinit() {
int i;
norawmode();
for (i = 0; i < gbl_history_count; i++)
for (int i = 0; i < gbl_history_count; i++)
janet_free(gbl_history[i]);
gbl_historyi = 0;
if (gbl_history_file) {
janet_free(gbl_history_file);
gbl_history_file = NULL;
}
}
void janet_line_get(const char *p, JanetBuffer *buffer) {
gbl_prompt = p;
buffer->count = 0;
gbl_historyi = 0;
loadhistory();
if (check_simpleline(buffer)) return;
FILE *out = janet_dynfile("err", stderr);
if (line()) {
@@ -1129,6 +1219,10 @@ int main(int argc, char **argv) {
JanetArray *args;
JanetTable *env;
#ifdef JANET_PLAN9
setfcr(0);
#endif
#ifdef _WIN32
setup_console_output();
#endif
@@ -1184,6 +1278,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();

View File

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

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

@@ -1,5 +1,8 @@
# Helper code for running tests
# Turn on strict linting by default in test suite.
(put root-env *lint-warn* :strict)
(var num-tests-passed 0)
(var num-tests-run 0)
(var suite-name 0)
@@ -7,7 +10,7 @@
(var skip-count 0)
(var skip-n 0)
(def is-verbose (os/getenv "VERBOSE"))
(var is-verbose (os/getenv "VERBOSE"))
(defn- assert-no-tail
"Override's the default assert with some nice error handling."
@@ -19,7 +22,6 @@
(break x))
(default e "assert error")
(when x (++ num-tests-passed))
(def str (string e))
(def stack (debug/stack (fiber/current)))
(def frame (last stack))
(def line-info (string/format "%s:%d"
@@ -39,7 +41,7 @@
(defmacro assert
[x &opt e]
(def xx (gensym))
(default e ~',x)
(default e (string/format "%j" x))
~(do
(def ,xx ,x)
(,assert-no-tail ,xx ,e)
@@ -50,6 +52,11 @@
(def errsym (keyword (gensym)))
~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg))
(defmacro assert-error-value
[msg errval & forms]
(def e (gensym))
~(assert (= ,errval (try (do ,;forms) ([,e] ,e))) ,msg))
(defn check-compile-error
[form]
(def result (compile form))
@@ -60,8 +67,8 @@
(def e (gensym))
(def f (gensym))
(if is-verbose
~(try (do ,;forms (,assert true ,msg)) ([,e ,f] (,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m ")))
~(try (do ,;forms (,assert true ,msg)) ([_] (,assert false ,msg)))))
~(try (do ,;forms (as-macro ,assert true ,msg)) ([,e ,f] (as-macro ,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m ")))
~(try (do ,;forms (as-macro ,assert true ,msg)) ([_] (as-macro ,assert false ,msg)))))
(defn start-suite [&opt x]
(default x (dyn :current-file))

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -86,5 +86,10 @@
(assert-error "array/join error 4" (array/join @[] "abc123"))
(assert-error "array/join error 5" (array/join @[] "abc123"))
# Regression 1714
(repeat 10
(assert (deep= (put @[] 100 10) (put (seq [_ :range [0 101]] nil) 100 10)) "regression 1714")
(assert (deep= (put @[] 200 10) (put (seq [_ :range [0 201]] nil) 200 10)) "regression 1714"))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -21,6 +21,8 @@
(import ./helper :prefix "" :exit true)
(start-suite)
(setdyn *lint-warn* :none)
# Assembly test
# Fibonacci sequence, implemented with naive recursion.
# a679f60

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -21,6 +21,8 @@
(import ./helper :prefix "" :exit true)
(start-suite)
(setdyn *lint-warn* :none)
# Let
# 807f981
(assert (= (let [a 1 b 2] (+ a b)) 3) "simple let")
@@ -865,6 +867,13 @@
(assert (deep= ~(,import* "a" :as "b" :fresh maybe)
(macex '(import a :as b :fresh maybe))) "import macro 2")
# 2af3f21d
(assert-error "import macro 2" (macex '(import a :fresh)))
(assert-error "import macro 3" (macex '(import a :as b :fresh)))
(assert-error "import macro 4" (macex '(import b "notakeyword" value)))
(assert (deep= ~(,import* "a" :fresh nil)
(macex '(import a :fresh nil))) "import macro 5")
# #477 walk preserving bracket type
# 0a1d902f4
(assert (= :brackets (tuple/type (postwalk identity '[])))
@@ -896,11 +905,18 @@
(struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2"))
(table/setproto table-to-freeze @{:a @[1 2 3]})
(assert (deep= {:a [1 2 3] :b [1 2 3 4] :c 22 :d "test" :e "test2"}
(freeze table-to-freeze)))
(assert (deep= struct-to-thaw (freeze table-to-freeze)))
(assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze)))
(assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw)))
# Check that freezing mutable keys is deterministic
# for issue #1535
(def hashes @{})
(repeat 200
(def x (freeze {@"" 1 @"" 2 @"" 3 @"" 4 @"" 5}))
(put hashes (hash x) true))
(assert (= 1 (length hashes)) "freeze mutable keys is deterministic")
# Make sure Carriage Returns don't end up in doc strings
# e528b86
(assert (not (string/find "\r"
@@ -995,4 +1011,25 @@
(assert-error "assertf error 3" (assertf false "%s message" "mystery"))
(assert-error "assertf error 4" (assertf nil "%s %s" "alice" "bob"))
# issue #1535
(loop [i :range [1 1000]]
(assert (deep-not= @{:key1 "value1" @"key" "value2"}
@{:key1 "value1" @"key" "value2"}) "deep= mutable keys"))
(assert (deep-not= {"abc" 123} {@"abc" 123}) "deep= mutable keys vs immutable key")
(assert (deep-not= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys")
(assert (deep-not= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2")
(assert (deep-not= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3")
(assert (deep-not= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4")
(assert (deep-not= @{:key1 "value1" @"key2" @"value2"}
@{:key1 "value1" @"key2" "value2"}) "deep= mutable keys")
(assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"}
@{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys")
# different try overloads
(assert (= (try (error :error) ([] :caught)) :caught))
(assert (= (try (error :error) ([e] e)) :error))
(assert (= (try (error :error) ([e fib] [e (fiber? fib)])) [:error true]))
# regression test for #1659
(assert (= (try (error :error) ([_ _] :caught)) :caught))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -179,5 +179,10 @@
(assert (= (string buf) "xxxxxx") "buffer/format-at negative index")
(assert-error "expected index at to be in range [0, 0), got 1" (buffer/format-at @"" 1 "abc"))
# Regression 1714
(repeat 10
(assert (deep= (put @"" 100 10) (put (buffer (string/repeat "\0" 101)) 100 10)) "regression 1714")
(assert (deep= (put @"" 200 10) (put (buffer (string/repeat "\0" 201)) 200 10)) "regression 1714"))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -117,8 +117,17 @@
(assert (= 0 (length (bundle/list))) "bundles are listed correctly 7")
(assert (= 0 (length (bundle/topolist))) "bundles are listed correctly 8")
# Try installing a bundle that is missing bundle script
(assert-error-value "bundle missing bundle script"
"bundle must contain bundle.janet or bundle/init.janet"
(bundle/install "./examples/sample-bad-bundle1"))
(assert (= 0 (length (bundle/list))) "check failure 0")
(assert (= 0 (length (bundle/topolist))) "check failure 1")
# Try installing a bundle that fails check
(assert-error "bad test" (bundle/install "./examples/sample-bad-bundle" :check true))
(assert-error-value "bundle check hook fails"
"Check failed!"
(bundle/install "./examples/sample-bad-bundle2" :check true))
(assert (= 0 (length (bundle/list))) "check failure 0")
(assert (= 0 (length (bundle/topolist))) "check failure 1")

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -30,7 +30,7 @@
(defn myfun [x]
(var a 10)
(set a (do
(def y x)
(def _y x)
(if x 8 9))))
(assert (= (myfun true) 8) "check do form regression")
@@ -46,8 +46,10 @@
# Edge case should cause old compilers to fail due to
# if statement optimization
# 17283241
(setdyn *lint-warn* :relaxed)
(var var-a 1)
(var var-b (if false 2 (string "hello")))
(setdyn *lint-warn* nil)
(assert (= var-b "hello") "regression 1")
@@ -73,5 +75,80 @@
(foo 0)
10)
# Issue #1699 - fuzz case with bad def
(def result
(compile '(defn sum3
"Solve the 3SUM problem in O(n^2) time."
[s]
(def)tab @{})))
(assert (get result :error) "bad sum3 fuzz issue valgrind")
# Issue #1700
(def result
(compile
'(defn fuzz-case-1
[start end &]
(if end
(if e start (lazy-range (+ 1 start) end)))
1)))
(assert (get result :error) "fuzz case issue #1700")
# Issue #1702 - fuzz case with upvalues
(def result
(compile
'(each item [1 2 3]
# Generate a lot of upvalues (more than 224)
(def ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;out-buf @"")
(with-dyns [:out out-buf] 1))))
(assert result "bad upvalues fuzz case")
# Named argument linting
# Enhancement for #1654
(defn fnamed [&named x y z] [x y z])
(defn fkeys [&keys ks] ks)
(defn fnamed2 [_a _b _c &named x y z] [x y z])
(defn fkeys2 [_a _b _c &keys ks] ks)
(defn fnamed3 [{:x x} &named a b c] [x a b c])
(defn fnamed4 [_y &opt _z &named a b c] [a b c])
(defn fnamed5 [&opt _z &named a b c] [a b c])
(defn g [x &opt y &named z] [x y z])
(defn check-good-compile
[code msg]
(def lints @[])
(def result (compile code (curenv) "suite-compile.janet" lints))
(assert (and (function? result) (empty? lints)) msg))
(defn check-lint-compile
[code msg]
(def lints @[])
(def result (compile code (curenv) "suite-compile.janet" lints))
(assert (and (function? result) (next lints)) msg))
(check-good-compile '(fnamed) "named no args")
(check-good-compile '(fnamed :x 1 :y 2 :z 3) "named full args")
(check-lint-compile '(fnamed :x) "named odd args")
(check-lint-compile '(fnamed :w 0) "named wrong key args")
(check-good-compile '(fkeys :a 1) "keys even args")
(check-lint-compile '(fkeys :a 1 :b) "keys odd args")
(check-good-compile '(fnamed2 nil nil nil) "named 2 no args")
(check-good-compile '(fnamed2 nil nil nil :x 1 :y 2 :z 3) "named 2 full args")
(check-lint-compile '(fnamed2 nil nil nil :x) "named 2 odd args")
(check-lint-compile '(fnamed2 nil nil nil :w 0) "named 2 wrong key args")
(check-good-compile '(fkeys2 nil nil nil :a 1) "keys 2 even args")
(check-lint-compile '(fkeys2 nil nil nil :a 1 :b) "keys 2 odd args")
(check-good-compile '(fnamed3 {:x 1} :a 1 :b 2 :c 3) "named 3 good")
(check-lint-compile '(fnamed3 {:x 1} :a 1 :b 2 :d 3) "named 3 lint")
(check-good-compile '(fnamed4 10 20 :a 1 :b 2 :c 3) "named 4 good")
(check-lint-compile '(fnamed4 10 20 :a 1 :b 2 :d 3) "named 4 lint")
(check-good-compile '(fnamed5 10 :a 1 :b 2 :c 3) "named 5 good")
(check-lint-compile '(fnamed5 10 :a 1 :b 2 :d 3) "named 5 lint")
(check-good-compile '(g 1) "g good 1")
(check-good-compile '(g 1 2) "g good 2")
(check-good-compile '(g 1 2 :z 10) "g good 3")
(check-lint-compile '(g 1 2 :z) "g lint 1")
(check-lint-compile '(g 1 2 :z 4 5) "g lint 2")
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2023 Calvin Rose
# Copyright (c) 2026 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@@ -174,6 +174,7 @@
(assert (deep= (range 0 17 4) @[0 4 8 12 16]) "(range 0 17 4)")
(assert (deep= (range 16 0 -4) @[16 12 8 4]) "(range 16 0 -4)")
(assert (deep= (range 17 0 -4) @[17 13 9 5 1]) "(range 17 0 -4)")
(assert-error "large range" (range 0xFFFFFFFFFF))
(assert (= (length (range 10)) 10) "(range 10)")
(assert (= (length (range -10)) 0) "(range -10)")
@@ -184,5 +185,26 @@
(assert-no-error "iterate over coro 2" (keys (generate [x :range [0 10]] x)))
(assert-no-error "iterate over coro 3" (pairs (generate [x :range [0 10]] x)))
(end-suite)
# thaw
(def ds1 [1 2 3 {:a 2} {:b 3} 4 5 6])
(def ds2 [1 2 3 {:a 2 {:c :d} {:e :f}} {:b 3} 4 5 6])
(assert (deep= (thaw ds1) (thaw-keep-keys ds1)) "thaw vs. thaw-keep-keys 1")
(assert (deep-not= (thaw ds2) (thaw-keep-keys ds2)) "thaw vs. thaw-keep-keys 2")
# match
(setdyn *lint-warn* :none)
(assert (= :yes (match [1 2 3] [x y z w] :no1 [x y $] :no2 [x y z] :yes)) "match dollar suffix 1")
(assert (= :yes (match [1 2 3] [x y z w] :no1 [x y z $] :yes [x y z] :no2)) "match dollar suffix 2")
(setdyn *lint-warn* nil)
# Issue #1687
(assert-no-error "def destructure splice works 1" (do (def [a] [;[1]]) a))
(assert-no-error "def destructure splice works 2" (do (def (n) [(splice [])]) n))
(assert-no-error "var destructure splice works" (do (var [a] [;[1]]) a))
# Issue #1709
(assert (= (macex1 '|(set (my-table [2 1]) 'foo))
'(fn :short-fn [] (set (my-table [2 1]) (quote foo))))
"Macro expand inside set preserves tuple type correctly")
(end-suite)

View File

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

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