1
0
mirror of https://github.com/janet-lang/janet synced 2025-10-28 06:07:43 +00:00

Compare commits

...

218 Commits

Author SHA1 Message Date
Calvin Rose
358f5a03bf Version bump to 1.28.0 2023-05-13 09:59:55 -05:00
Calvin Rose
fba1fdabe4 Update short-fn to fix #1123
Symbols are renamed on expansion to avoid the issue.
2023-05-13 09:44:30 -05:00
Calvin Rose
d42afd21e5 Merge branch 'master' of github.com:janet-lang/janet 2023-05-12 19:08:35 -05:00
Calvin Rose
20ada86761 Fix NAN typo. 2023-05-12 19:08:26 -05:00
Calvin Rose
3b353f1855 Merge pull request #1133 from zevv/cross
Updated Makefile for better cross-compilation support.
2023-05-12 08:41:54 -05:00
Calvin Rose
1467ab4f93 Copy paste error. 2023-05-11 20:56:12 -05:00
Calvin Rose
7e65c2bdad Fix #1130 - mod flipped for signed integers. 2023-05-11 18:15:37 -05:00
Calvin Rose
84a4e3e98a Update CHANGELOG.
and format.
2023-05-11 18:03:38 -05:00
Calvin Rose
bcbeedb001 Merge pull request #1128 from zevv/master
Added os.strftime()
2023-05-11 18:01:39 -05:00
Calvin Rose
e04b103b5d Merge pull request #1134 from CosmicToast/const_sourceline
Make JANET_FN_S* sourceline const
2023-05-11 17:59:27 -05:00
Chloe Kudryavtsev
ac75b94679 Make JANET_FN_S* sourceline const
Otherwise attempts to use it on some platforms cause the following error
`error: initializer element is not a compile-time constant`
when attempting to use the corresponding `JANET_REG`.
2023-05-11 16:07:34 -04:00
Ico Doornekamp
d3bb06cfd6 Updated Makefile for better cross-compilation support.
Building janet requires janet_boot to be run on the host at build time;

- $(UNAME) can now be overridden from the make cmdline
- Added $(RUN) variable to allow a emulator to be specified
- Added ".exe" extension to binaries when using MINGW

Examples:

Cross compiling for win32 and running under wine:

```
make test \
       CC=i686-w64-mingw32-gcc \
       LD=i686-w64-mingw32-gcc \
       UNAME=MINGW \
       RUN=wine

Janet 1.27.0-ad7c3bed mingw/x86/gcc - '(doc)' for help
```

Cross compiling for aarch64 and running under qemu:

```
make repl \
        CC=aarch64-none-linux-gnu-gcc \
        LD=aarch64-none-linux-gnu-gcc \
        RUN="qemu-aarch64 -L /tmp/aarch64/"

Janet 1.27.0-ad7c3bed linux/aarch64/gcc - '(doc)' for help
```
2023-05-11 07:46:36 +02:00
Ico Doornekamp
5cd729c4c1 Added os.strftime() 2023-05-11 06:08:19 +02:00
Calvin Rose
c9fd2bdf39 Merge branch 'master' of github.com:janet-lang/janet 2023-05-10 18:46:42 -05:00
Calvin Rose
e4be5992b3 Address issue with #1131 2023-05-10 18:43:33 -05:00
Calvin Rose
2ac4988f1b Merge pull request #1124 from sogaiu/tweak-ev-select-docstring
Tweak ev/select docstring
2023-05-08 04:17:25 -05:00
Calvin Rose
19f14adb9e Update changelog. 2023-05-07 21:07:22 -05:00
Calvin Rose
86de039492 Merge branch 'master' of github.com:janet-lang/janet 2023-05-07 11:54:20 -05:00
Calvin Rose
2360164e4f Address #1125 - fix ev/select to only take and release locks once.
By take and releasing locks twice per channel in the case where nothing
is reading, there was an opportunity for ev/select to hang in the
multithreaded case. Also silence valgrind/helgrind errors.
2023-05-07 11:54:06 -05:00
Calvin Rose
c93ddceadb Merge pull request #1122 from CosmicToast/getcbytes
Add get/opt cbytes
2023-05-07 10:33:09 -05:00
sogaiu
cd19dec44a Tweak ev/select docstring 2023-05-07 22:22:33 +09:00
Chloe Kudryavtsev
53ba9c800a Add get/opt cbytes
Like getcstring, but operates on a byteview.
When writing bindings (i.e what capi.c is primarily used for), it's
common to want to accept a buffer *or* a string rather than just
a string.
For this, a byteview is perfect (and why not accept keywords while
you're at it?).
However, there's no built-in function for getting a cstring out of
a byteview, this adds one.
This also reformulates getcstring to be an edge-case of getcbytes
(simply adding an explicit check for stringness).
2023-05-06 22:13:53 -04:00
Calvin Rose
cabbaded68 Add support for the NO_COLOR environment variable. 2023-05-06 16:33:45 -05:00
Calvin Rose
9bb589f827 update readme 2023-05-06 15:56:27 -05:00
Calvin Rose
c3a06686c2 Merge branch 'shell-win-feature-check' 2023-05-06 15:53:02 -05:00
Calvin Rose
7d57f87007 Address #1121 - disallow extra splices.
This turns splices that are ignored into compiler errors. Other
alternatives here should also be considered, for example making this
a compiler warning rather than an error. For example, the latest
spork as of a3ee63c137ee3234987dbbca71b566994ff8ae8c has an error of this
kind, but the resulting program does work correctly.

Also disallow splice propagation - code of the
form (+ 1 (do ;[2 3 4]) 5).
2023-05-06 13:12:31 -05:00
Calvin Rose
4cc4a9d38b (and ... <truthy-value> true) will return true as per docs. 2023-05-06 10:16:05 -05:00
Calvin Rose
02c7cd0194 Merge pull request #1116 from sogaiu/tweak-long-string-peg
Tweak long-string|bytes peg in test suite files
2023-04-30 16:21:12 -05:00
Calvin Rose
696efcb9e2 Add header file. 2023-04-30 12:19:55 -05:00
Calvin Rose
6e9cde8ac1 Add feature check for windows version shell.c
Tried to get console working on windows 7 and below
2023-04-30 10:36:42 -05:00
sogaiu
a9fae49671 Tweak long-string|bytes peg in test suite files 2023-04-30 21:55:43 +09:00
Calvin Rose
440af9fd64 Remove extra allocation in drop. 2023-04-24 09:41:33 -05:00
Calvin Rose
347721ae40 Fix macos behavior - Closes #1097, Fixes #1015 2023-04-24 09:37:49 -05:00
Calvin Rose
daea91044c Give different names to the user9 and user8 fiber statuses.
These now have semantic menaings that are pretty difficult to
work around. Code that tries to maniuplate user8 and user9 signals
right now may be affected
2023-04-24 09:19:15 -05:00
Calvin Rose
4ed3f2c662 Merge pull request #1114 from ianthehenry/drop-from-end
drop with a negative count now drops from the end
2023-04-24 09:16:10 -05:00
Calvin Rose
3641c8f60a Merge pull request #1108 from wackbyte/read
README grammar and formatting changes
2023-04-24 09:15:07 -05:00
Calvin Rose
e4b68cd940 Merge pull request #1109 from wackbyte/remove-double-space
Remove double space in help message
2023-04-24 09:11:45 -05:00
Calvin Rose
b8c936e2fe Merge pull request #1113 from ianthehenry/catseq
add catseq
2023-04-24 09:07:38 -05:00
Calvin Rose
83cd519702 Merge pull request #1112 from ianthehenry/dynamic-replace
string and peg replacement functions can now take functions
2023-04-24 09:07:03 -05:00
Ian Henry
54b54f85f3 drop with a negative count now drops from the end 2023-04-23 21:39:14 -07:00
Ian Henry
ccd874fe4e add catseq 2023-04-23 21:20:01 -07:00
Ian Henry
9dc7e8ed3a peg replacement functions have access to captures
When peg/replace or peg/replace-all are given a function to serve as the text
replacement, any captures produced by the PEG are passed as additional
arguments to that function.
2023-04-23 09:15:46 -07:00
Ian Henry
485099fd6e string and peg replacement functions can now take functions
Functions will be invoked with the matched text, and their result will be
coerced to a string and used as the new replacement text.

This also allows passing non-function, non-byteviewable values, which will be
converted into strings during replacement (only once, and only if at least
one match is found).
2023-04-23 08:36:17 -07:00
wackbyte
d359c6b43e Remove double space in help message 2023-04-22 21:46:24 -04:00
Calvin Rose
d9ed7a77f8 Merge pull request #1106 from CosmicToast/1105
handle null-byte case in scanformat (fixes #1105)
2023-04-22 15:25:48 -05:00
wackbyte
4238a4ca6a README grammar and formatting changes 2023-04-20 21:06:33 -04:00
Chloe Kudryavtsev
0902a5a981 improve null format handling
there was a request to improve the error message, but the whole function
has non-informative errors. (both functions, actually, since the code is
duplicated)
as such, instead of catching it directly, address the assumption that
led to the SIGSEGV and let it be caught by the functions themselves,
thus reusing existing error messages (which can then be improved
separately).
2023-04-20 11:51:11 -04:00
Chloe Kudryavtsev
f3192303ab check for NULL in get_fmt_mapping (fixes #1105)
When there is no format to be found after a %, get_fmt_mapping returns
NULL. It then gets called against strlen, which is a typical SEGV.
Check for NULL aginst mapping, which signals a null format being
specified.
2023-04-19 12:55:25 -04:00
Calvin Rose
bef5bd72c2 Merge pull request #1095 from ml-2/ml
Add `keep-syntax` and `keep-syntax!` functions
2023-04-08 10:46:47 -05:00
ML
b6175e4296 Add keep-syntax and keep-syntax! functions
These functions are designed to make it easier to properly preserve the
sourcemap and tuple type in macros. This commit also modifies the threading
macros to make use of these functions.
2023-04-07 21:09:52 -04:00
Calvin Rose
3858b2e177 Add ev/all-tasks to get running and pending root fibers. 2023-04-01 18:57:13 -05:00
Calvin Rose
9a76e77981 Update for undefined behavior sanitizer. 2023-03-24 18:49:21 -05:00
Calvin Rose
8182d640cd Merge branch 'master' of github.com:janet-lang/janet 2023-03-12 10:33:06 -05:00
Calvin Rose
1c6fda1a5c Address #1076 - unexpected shadowing behavior
While the old behavior was reasonable, it is not spelled out anywhere
in the documentation and was incidental rather than intentional.
Parameters of the same name of the function should probably take
precedence on name collision, following the principle of least surprise.
2023-03-12 10:30:59 -05:00
Calvin Rose
c51db1cf2f Merge pull request #1081 from ianthehenry/log-gamma-docstring
remove quotes in math/log-gamma docstring
2023-03-12 10:17:51 -05:00
Ian Henry
4e7930fc4c remove quotes around log-gamma 2023-03-11 12:26:45 -08:00
Calvin Rose
3563f8ccdb Merge pull request #1079 from dressupgeekout/charlotte_dragonfly
Some basic fixes for DragonFly
2023-03-08 15:05:51 -06:00
Charlotte Koch
575af763f6 os/which can return :dragonfly
While here, document that :bsd is another possible return value.
2023-03-07 06:45:31 -08:00
Charlotte Koch
8b16b9b246 Need to enable __BSD_VISIBLE on DragonFly in conjunction with -std=c99 2023-03-07 06:40:58 -08:00
Calvin Rose
01aab66667 Prepare for 1.27.0 release. 2023-03-05 09:48:25 -06:00
Calvin Rose
aa5c987a94 Change semantics of bracketed tuple equality.
Comparison between different bracket and normal tuples
will now take into account the delimiter type. This solves strange
non-locality issues in the compiler due to this false equality, and is
more consistent with Janet's otherwise strong equality philosophy.
2023-03-03 18:24:02 -06:00
Calvin Rose
75229332c8 Merge pull request #1075 from sogaiu/math-rng-int-docs
Tweak math/rng-int docs
2023-03-03 18:09:11 -06:00
sogaiu
9d5b1ba838 Tweak math/rng-int docs 2023-02-24 19:55:47 +09:00
Calvin Rose
f27b225b34 Merge pull request #1072 from sogaiu/ftell
Add file/tell
2023-02-21 07:13:00 -06:00
sogaiu
3c523d66e9 Add file/tell 2023-02-21 20:19:17 +09:00
Calvin Rose
1144c27c54 Merge pull request #1071 from ianthehenry/number-pegfault
fix a segfault in the (number) special
2023-02-20 21:01:33 -06:00
Ian Henry
b442b21d3f fix a segfault in the (number) special
This was an error about the base argument that referenced the tag argument,
which might not exist.
2023-02-20 17:07:42 -08:00
Calvin Rose
746ff5307d Update for more minimal builds. 2023-02-12 12:14:11 -06:00
Calvin Rose
ef85b24d8f Add optional offset to ffi/write. 2023-02-12 12:08:28 -06:00
Calvin Rose
c55d93512b Add buffer/push-at for easier manipulation of buffers
buffer/blit is difficult to use, and while buffer/push is the easiet
buffer manipulation function to use it only appends to the buffer.
buffer/push-at lets users manipulate buffers at any index - useful
for buffers used as an in-memory databases, for example.
2023-02-12 11:26:00 -06:00
Calvin Rose
2e38f9ba61 Allow passing pointer-buffers to other threads. 2023-02-12 11:07:45 -06:00
Calvin Rose
1cadff8e58 Add ffi/pointer-buffer for easier memory manipulation in FFI.
Added underlying buffer support for buffer instances that cannot
reallocated underlying memory - useful for (small) memory mapped
files and other FFI utilties.
2023-02-12 09:20:05 -06:00
Calvin Rose
d1eba60ba8 Add separate sandbox flag for file/temp
Doesn't really impart (much) file systtem information when used, and
can be used for a lot of things where file functions are used to process
in a stream.
2023-02-09 08:57:53 -06:00
Calvin Rose
057dccad8f Turn on nanboxing by default for risc-v 64. 2023-02-09 03:09:53 -06:00
Calvin Rose
4285200b4b Add a sandbox option to disable native modules.
Also sort the sandbox docstring list.
2023-02-09 00:19:56 -06:00
Calvin Rose
73c2fbbc2a Check sandbox capabilities instead of ignoring unknown ones. 2023-02-06 17:38:00 -06:00
Calvin Rose
37b7e170fa Update changelist. 2023-02-06 12:31:49 -06:00
Calvin Rose
b032d94877 Add sandboxing API.
The sandboxing API is meant to make janet a bit more attractive
for certain application embedding use cases. The sandboxing API
puts limits on what system resources the interpreter can access.
2023-02-06 09:05:57 -06:00
Calvin Rose
9476016741 Update asm.c 2023-02-05 23:49:18 -06:00
Calvin Rose
7a1c9c7798 Add support for debugging upvalues.
Upvalues are stored in the symbol slots structure as well, but
since they are always live, we repurpose the death_pc field to
refer to the environment index that we want to look at at runtime.
2023-02-05 15:30:01 -06:00
Calvin Rose
c7fb7b4451 Merge branch 'master' into localbindings 2023-02-05 11:36:57 -06:00
Calvin Rose
67c474fc7a More fixes to ev/gather (tested on httpf server). 2023-02-05 10:21:37 -06:00
Calvin Rose
4e8154cf8a Fix ev/gather to cancel children on cancellation.
Otherwise, we would be leaving zombie fibers around.
2023-02-05 09:43:16 -06:00
Calvin Rose
9582d3c623 Allow infinite wait to work without issues. 2023-02-05 09:29:39 -06:00
Calvin Rose
0079500713 Merge branch 'master' into localbindings 2023-02-04 13:39:24 -06:00
Calvin Rose
55af6ce834 Fix write after free with printing to files. 2023-02-04 13:36:30 -06:00
Calvin Rose
3e82fdc125 Update symbolmapping code with marshal/unmarshal. 2023-02-03 17:33:11 -06:00
Calvin Rose
7344a6cfc0 Fix null check. 2023-02-03 16:24:50 -06:00
Calvin Rose
0aded71343 Fix issue with environments in asm.c 2023-02-03 16:24:50 -06:00
Calvin Rose
7663b1e703 Fix null check. 2023-02-02 22:03:18 -06:00
Calvin Rose
282546c03f Fix issue with environments in asm.c 2023-02-02 21:12:17 -06:00
Calvin Rose
f4bc89d1c0 Progress. 2023-02-02 21:08:48 -06:00
Jona Ekenberg
fa277c3797 added future test for upvalues and symbolslots 2023-02-01 21:26:29 +01:00
Jona Ekenberg
c0c8ab25e6 added symbolslots to asm 2023-02-01 21:12:42 +01:00
Jona Ekenberg
b685bf3026 updated symbolslots-test 2023-02-01 11:46:36 +01:00
Jona Ekenberg
ce31db09e4 symbolslots should be gc'd, local_symbols always pushed in case *debug* is set in middle of function 2023-02-01 11:45:13 +01:00
Jona Ekenberg
624a6cf619 symbolslots nil when there are no symbols, changed debugger to not have special case 2023-02-01 11:25:52 +01:00
Jona Ekenberg
587aa87d28 symbolslots now use janet_v vectors, flat structure 2023-02-01 11:06:33 +01:00
Jona Ekenberg
88813c4f87 initial slotsyms implementation 2023-02-01 09:39:24 +01:00
Calvin Rose
dacbe29771 Allow round-tripping more functions with disasm and asm.
Nested functions that captured with environments didn't
work well with asm.
2023-01-30 09:04:42 -06:00
Calvin Rose
244833cfa1 Merge pull request #1040 from lgtm-migrator/codeql
Add CodeQL workflow for GitHub code scanning
2023-01-29 09:04:18 -06:00
Calvin Rose
05e7f974e3 Add os/compiler to the core.
Allows querying what compiler was used to compile Janet.
2023-01-28 14:00:02 -06:00
Calvin Rose
0dbef65a73 Merge pull request #1065 from sogaiu/comment-tweaks
Misc comment tweaks
2023-01-27 11:26:05 -06:00
Calvin Rose
9106228787 Add :riscv32 and :riscv32 values for os/arch. 2023-01-27 11:23:57 -06:00
Calvin Rose
6ae3bdb25c Add RISC-V 64 bit detection in janet.h 2023-01-26 22:40:05 -06:00
sogaiu
310bcec260 Misc comment tweaks 2023-01-25 18:45:19 +09:00
Calvin Rose
8c4cc4e671 Merge pull request #1064 from sogaiu/realpath-doc-tweak
Update os/realpath docstring
2023-01-24 09:50:44 -06:00
Calvin Rose
c6eaaa83ed Buffer initialized with janet_buffer_init will not be gced.
Set an internal flag that disables garbage collection on such
buffers. For all currently correct usage, this should have no effect,
and will fix use cases where buffers are initialized this way and then
passed to the interpreter.
2023-01-23 20:57:30 -06:00
sogaiu
8f598d6f96 Update os/realpath docstring 2023-01-23 12:55:04 +09:00
Calvin Rose
20bc323d17 Use gcc explicitly in mingw build. 2023-01-22 11:21:28 -06:00
Calvin Rose
8b0bcf4db9 Add the mingw toolchain. 2023-01-22 11:10:04 -06:00
Calvin Rose
8955e6f536 Merge branch 'master' of github.com:janet-lang/janet 2023-01-22 11:05:39 -06:00
Calvin Rose
f8ddea6452 Add msys2 testing with github actions. 2023-01-22 11:05:27 -06:00
Calvin Rose
987e04086d Merge pull request #1063 from AlecTroemel/1062-io-flag-types
use int32_t type for file flags, just like header
2023-01-22 10:56:19 -06:00
Calvin Rose
85f2acbf52 Fix tools/format.sh file permissions. 2023-01-22 10:47:33 -06:00
alectroemel
1acf4c3ab7 add int32_t type for file flags, just like header
Signed-off-by: alectroemel <alectroemel@hotmail.com>
Signed-off-by: alectroemel <alec@mirusresearch.com>
2023-01-22 10:24:46 -06:00
Calvin Rose
07a3158fba Merge pull request #1060 from ianthehenry/doc-typos
Fix some docstring typos
2023-01-21 16:21:57 -06:00
bakpakin
2f8bed9d82 Build and install an import library on mingw 2023-01-21 14:31:48 -06:00
bakpakin
a490937cd9 Add :mingw value when getting the OS setting when compiled with mingw. 2023-01-21 11:50:03 -06:00
bakpakin
8ee5942481 Fix windows build with JANET_NO_NET - #1055 2023-01-21 10:56:20 -06:00
bakpakin
93b469885a Initial Mingw support with Makefile.
Also add a macro JANET_MSVC to distinguish between
a windows build (JANET_WINDOWS) and a build with msvc.
2023-01-21 10:37:34 -06:00
Calvin Rose
d8d1de2dcb Don't compile library loading code on windows if it is disabled. 2023-01-21 09:36:03 -06:00
Ian Henry
ab224514f0 Fix some docstring typos. 2023-01-18 19:41:56 -08:00
Calvin Rose
75179de8da Merge pull request #1056 from fd00/use-dev-urandom-on-cygwin
Use `/dev/urandom` for `janet_cryptorand` on cygwin
2023-01-08 09:29:52 -06:00
Calvin Rose
c28df14e6b Prepare for 1.26.0 release 2023-01-07 15:08:40 -06:00
Calvin Rose
b73855b193 Merge branch 'master' of github.com:janet-lang/janet 2023-01-07 15:05:16 -06:00
Calvin Rose
2093ab2baa Update copyrights. 2023-01-07 15:04:56 -06:00
Calvin Rose
a0f40042cb Update copyright year. 2023-01-07 15:03:35 -06:00
Daisuke Fujimura (fd0)
3254c2c477 Use /dev/urandom for janet_cryptorand on cygwin 2023-01-07 08:58:35 +09:00
Calvin Rose
0a8eb9e3ba Merge pull request #1057 from sogaiu/update-tests-for-meson
Add test files to meson suite 11 - 14
2023-01-04 04:13:18 -06:00
sogaiu
70e0c6f9ef Add test files to meson suite 11 - 14 2023-01-04 12:59:26 +09:00
Calvin Rose
a8a78d4525 Merge pull request #1052 from dressupgeekout/cpu_count_reduced_os
(os/cpu-count) should not be defined at all with JANET_REDUCED_OS
2022-12-16 11:11:30 -06:00
Calvin Rose
57e6ee963d Merge pull request #1046 from dressupgeekout/charlotte_sort_doc
Explicitly mention that `sort-by` sorts a list in place.
2022-12-16 11:11:07 -06:00
Calvin Rose
ce6bfb8420 Merge pull request #1049 from harryvederci/patch-1
Improve documentation for the `all` function.
2022-12-16 09:51:59 -06:00
Calvin Rose
f0672bdc59 Merge pull request #1051 from sogaiu/math-abs-vs-fabs-issue
Tweak math/abs to improve doc result
2022-12-16 09:51:43 -06:00
Charlotte Koch
23de953fbd (os/cpu-count) should not be defined at all with JANET_REDUCED_OS 2022-12-15 20:16:43 -08:00
Calvin Rose
03c496bdd8 Update changelog 2022-12-15 21:38:36 -06:00
Calvin Rose
d5ee6cf521 Add malloc and free to ffi.
Very "unsafe", but a good tool of last resort. In most cases
a buffer is preferable, but the lifetime can be a bit unclear.
This allows very granular control over memory.
2022-12-15 21:35:44 -06:00
sogaiu
fb7981e053 Tweak math/abs to improve doc result 2022-12-11 00:09:50 +09:00
harryvederci
846123ecab Improve documentation for the all function. 2022-12-09 11:16:42 +00:00
Calvin Rose
373cb444fe Merge branch 'master' of github.com:janet-lang/janet 2022-12-04 11:08:28 -06:00
Calvin Rose
90f212df92 Add length method ffi/jitfn abstract values. 2022-12-04 11:08:17 -06:00
Calvin Rose
12286e4246 Add length method ffi/jitfn abstract values. 2022-12-04 10:27:28 -06:00
Calvin Rose
aa60c1f36a Add ffi jit example. 2022-12-03 17:52:23 -06:00
Calvin Rose
c731f01067 Address windows compilation warning. 2022-12-03 12:10:22 -06:00
Calvin Rose
6c9c1cdb30 MAP_ANON(YMOUS) not strictly needed. 2022-12-03 11:45:33 -06:00
Calvin Rose
9ba2b40e87 Add MAP_ANON instead of MAP_ANONYMOUS for mac 2022-12-03 11:31:04 -06:00
Calvin Rose
7a3d055012 Add ffi/jitfn for JIT compilation.
Convert a byte sequence of machine code to an
an executable pointer that can be used with ffi/call.
2022-12-03 11:26:23 -06:00
Calvin Rose
0824f45e29 Format compile.c 2022-11-27 10:15:01 -06:00
Charlotte Koch
4debe3446c Explicitly mention that sort-by sorts a list in place. 2022-11-20 20:51:49 -08:00
Calvin Rose
07fe9bcdf6 Update state header to include pthread 2022-11-11 11:48:50 -06:00
Calvin Rose
6a557a73f5 Simplify eval.
Also add more conventional handling of nil to the `compile` function.
2022-11-11 11:25:06 -06:00
Calvin Rose
8d1cfe0c56 Simplify eval-string implementation. 2022-11-11 11:15:53 -06:00
Calvin Rose
a3a42eebea Create pthread threads with detached attribute.
Rather than calling pthread_detach on a default thread.
2022-11-11 11:01:59 -06:00
Calvin Rose
76be8006a4 Add channel marshalling. 2022-11-10 16:32:54 -06:00
Calvin Rose
bfcfd58259 Update for TCC to include stdatomic.h 2022-11-09 07:55:21 -06:00
Calvin Rose
914a4360e7 Indicate version bump since header file changed. 2022-11-05 16:38:52 -05:00
Calvin Rose
8c31874eeb Remove unused assert.h 2022-11-05 11:44:14 -05:00
Calvin Rose
ef7afeb2ea Add 64 bit integer support to printf and other formatting functions. 2022-11-05 11:33:19 -05:00
LGTM Migrator
4067f883a2 Add CodeQL workflow for GitHub code scanning 2022-11-05 10:22:49 +00:00
Calvin Rose
c8974fffbe Fix docstring. 2022-11-04 11:23:08 -05:00
Calvin Rose
b75fb8dc9e Add :@all: to module/expand-path
Allow more easily importing modules from custom directories
without jumping through too many hoops. Technically, this was
possible before but required circumventing the built-in module/paths
and was just a hassle.

Also add entries to module/path (and module/add-path) to allow code
like the following.

(setdyn :my-libs "/home/me/janet-stuff/")

(import @my-libs/toolbox)

Intended for things like test harnesses where code might not
be installed to the usual directories.
2022-11-04 11:15:48 -05:00
Calvin Rose
57356781a9 Fix typo. 2022-10-30 13:36:13 -05:00
Calvin Rose
e43eab5fd6 Fix panicf call. 2022-10-30 09:57:40 -05:00
Calvin Rose
894cd0e022 Prepare for 1.25.1 release. 2022-10-29 11:58:29 -05:00
Calvin Rose
db2c63fffc Update CHANGELOG.md 2022-10-24 20:32:02 -05:00
Calvin Rose
60e0f32f1a Fix os/open with :rw permissions on posix. 2022-10-24 19:39:58 -05:00
Calvin Rose
e731996a68 Allow overriding JANETCONF_HEADER in Makefile.
This allows a configuration workflow that is a bit simpler than before
and doesn't requiring applying patches. Instead, add a config.mk to
source dir with JANETCONF_HEADER=myconfig.h and compile as usual.

The patching workflow will of course still work exactly as before.
2022-10-24 09:49:51 -05:00
Calvin Rose
2f69cd4209 Add easier option for adding config.mk in root directory. 2022-10-23 13:11:07 -05:00
Calvin Rose
fd59de25c5 Add memcmp to the core. Useful in binary protocol implementations. 2022-10-18 11:54:07 -05:00
Calvin Rose
af12c3d41a Typo fixes. 2022-10-10 18:38:24 -05:00
Calvin Rose
54b52bbeb5 Prepare for 1.25.0 release. 2022-10-10 18:24:48 -05:00
Calvin Rose
1174c68d9a Update CHANGELOG.md 2022-10-10 18:23:15 -05:00
Calvin Rose
448ea7167f Add CLOEXEC when calling accept on Linux.
Prevents leakage of file descriptors to subprocesses.
The symptom of the above issue is sockets that don't seem to close
until a subprocess completes.
2022-10-10 18:06:31 -05:00
Calvin Rose
6b27008c99 Fix os/date with nil argument. 2022-10-10 15:24:28 -05:00
Calvin Rose
725c785882 Formatting. 2022-10-10 14:24:03 -05:00
Calvin Rose
ab068cff67 Remove WNOWAIT code on linux.
Would cause os/proc-wait to block in some circumstances.
2022-10-10 14:23:17 -05:00
bakpakin
9dc03adfda Fix pass by reference in windows FFI to accomodate stack shift. 2022-09-22 10:58:16 -05:00
bakpakin
49f9e4eddf Fix ifdef in capi.c for janet_getuinteger64 and janet_getinteger64 2022-09-20 15:42:20 -05:00
bakpakin
43c47ac44c Address #1037 - move stack hack after arg writing logic to avoid
clobber.
2022-09-20 15:37:20 -05:00
Calvin Rose
1cebe64664 Add some soft test cases for #1037. 2022-09-20 10:01:12 -05:00
Calvin Rose
f33c381043 Improve sysv64 classify algorithm. 2022-09-20 09:45:17 -05:00
Calvin Rose
3479841c77 Address #1034 - add handling for 8-16 byte structs in FFI. 2022-09-20 09:28:46 -05:00
Calvin Rose
6a899968a9 Allow passing user signals to (signal) as keywords. 2022-09-17 21:18:07 -05:00
Calvin Rose
bb8405a36e Merge pull request #1029 from locriacyber/patch-0
Fix documentation for ev/go, ev/spawn
2022-09-16 07:32:25 -05:00
bakpakin
c7bc711f63 Add windows FFI example test case for void functions with double
argument.
2022-09-15 13:58:54 -05:00
bakpakin
e326071c35 Fix void returns in windows FFI - address #1025 2022-09-15 13:51:11 -05:00
Locria Cyber
ad6a669381 Add doc for ev/go
Document that you can pass a function instead of a fiber to ev/go
2022-09-14 00:17:53 +00:00
Locria Cyber
e4c9dafc9a Fix typo in ev/spawn doc 2022-09-13 23:49:42 +00:00
Calvin Rose
dfc0aefd87 Merge pull request #1028 from autumnull/master
Made peg 'not' and 'if-not' drop their captures on success
2022-09-13 15:20:10 -05:00
Calvin Rose
356b39c6f5 Add test case for #1027 2022-09-12 19:00:59 -05:00
Calvin Rose
8da7bb6b68 Fix peg/replace-all and family - Fix #1027 2022-09-12 18:58:48 -05:00
Autumn!
9341081a4d Made peg 'not' and 'if-not' drop their captures on success 2022-09-12 23:07:56 +01:00
Calvin Rose
324a086eb4 Merge pull request #1023 from ScriptDevil/set-manpath
Set JANET_MANPATH environment variable while installing.
2022-09-10 09:55:01 -05:00
Ashok Gautham
ed595f52c2 Set JANET_MANPATH environment variable while installing.
JPM on windows currently installs its manpage to C:\ directly because this isn't set when installing Janet through the MSI installer
2022-09-09 13:24:36 +05:30
Calvin Rose
64ad0023bb Merge pull request #1022 from autumnull/master
Removed unnecessary backslashes from documentation
2022-09-08 08:52:55 -05:00
Autumn!
fe5f661d15 Removed unnecessary backslashes from documentation 2022-09-08 13:21:17 +01:00
Calvin Rose
ff26e3a8ba Remove end of string check that is now redudant.
The addition of some code to avoid valgrind warnings made this code
redundant.
2022-09-05 20:13:15 -05:00
Calvin Rose
14657a762c Fix peg RULE_SET op code when at tail of string in some cases. 2022-09-05 14:11:03 -05:00
Calvin Rose
4754fa3902 Fix issue #1021 - bad format specifiers in run.c 2022-09-03 14:03:51 -05:00
Calvin Rose
f302f87337 Merge pull request #1019 from Techcable/fix/inttypes-overflow
Signed integer overflow is undefined behavior in C, avoid it in inttypes.c
2022-08-30 23:23:11 -05:00
Calvin Rose
94dbcde292 Merge pull request #1020 from pepe/comment-typo
Fix typo in define comment
2022-08-30 22:57:52 -05:00
Josef Pospíšil
4336a174b1 Fix typo in define comment 2022-08-30 09:21:20 +02:00
Techcable
0adb13ed71 inttypes.c: Avoid signed integer overflow (U.B.)
In C, signed arithmetic overflow is undefined behvior
but unsigned arithmetic overflow is twos complement

Unconditionally switch to unsigned arithmetic internally for +, -, *
This will not affect the result thanks to twos complement awesomeness.

I don't think this will be an issue in these functions,
but it has a history of causing bugs.....
2022-08-29 18:38:51 -07:00
Calvin Rose
03ba1f7021 Update CHANGELOG and version numbers. 2022-08-26 13:15:30 -05:00
Calvin Rose
1f7f20788c Add line loop example for awk or sed like processing. 2022-08-26 12:29:23 -05:00
Calvin Rose
c59dd29190 Add stress test for marshalling to examples. 2022-08-26 12:27:53 -05:00
Calvin Rose
99f63a41a3 Improve pointer hashing to avoid hash collisions. 2022-08-26 12:18:10 -05:00
Calvin Rose
a575f5df36 Add option to marshal values without cycle detection. 2022-08-26 11:20:02 -05:00
Calvin Rose
0817e627ee Prepare for 1.24.1 release. 2022-08-24 13:23:53 -05:00
Calvin Rose
14d90239a7 Merge branch 'master' of github.com:janet-lang/janet 2022-08-24 11:35:37 -05:00
Calvin Rose
f5d11dc656 Address #1014 improve parse errors when bad delimiters are found.
Reuse some existing logic for eof errors.
2022-08-24 11:34:59 -05:00
Calvin Rose
6dcf5bf077 Merge pull request #1012 from Techcable/doc/clarify-flag-E
Clarify the documentation of janet -E flag
2022-08-21 13:45:17 -05:00
Calvin Rose
ac2082e9b3 Allow adding name to short-fns.
When short-fn is used in a macro, it can be useful to
give the function a nicer name then a raw pointer.
2022-08-18 14:33:59 -05:00
Techcable
dbac495bee Clarify the documentation of janet -E flag
This confused me, despite having a fair deal of janet experience.
2022-08-18 12:16:14 -07:00
Calvin Rose
fe5ccb163e Merge branch 'master' of github.com:janet-lang/janet 2022-08-16 12:38:59 -05:00
Calvin Rose
1aea5ee007 Remove stack inversion code for sysv64 FFI. 2022-08-16 12:38:44 -05:00
Calvin Rose
13cd9f8067 Remove stack inversion code for sysv64 FFI. 2022-08-16 12:20:38 -05:00
98 changed files with 2698 additions and 728 deletions

41
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "2 7 * * 4"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ cpp ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"

View File

@@ -35,3 +35,25 @@ jobs:
- name: Test the project
shell: cmd
run: build_win test
test-mingw:
name: Build on Windows with Mingw (no test yet)
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup Mingw
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: >-
base-devel
git
gcc
- name: Build the project
shell: cmd
run: make -j CC=gcc

5
.gitignore vendored
View File

@@ -68,10 +68,13 @@ tags
vgcore.*
*.out.*
# Wix artifacts
# WiX artifacts
*.msi
*.wixpdb
# Makefile config
/config.mk
# Created by https://www.gitignore.io/api/c
### C ###

View File

@@ -1,15 +1,79 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.28.0 - 2023-05-13
- Various bug fixes
- Make nested short-fn's behave a bit more predictably (it is still not recommended to nest short-fns).
- Add `os/strftime` for date formatting.
- Fix `ev/select` on threaded channels sometimes live-locking.
- Support the `NO_COLOR` environment variable to turn off VT100 color codes in repl (and in scripts).
See http://no-color.org/
- Disallow using `(splice x)` in contexts where it doesn't make sense rather than silently coercing to `x`.
Instead, raise a compiler error.
- Change the names of `:user8` and `:user9` sigals to `:interrupt` and `:await`
- Change the names of `:user8` and `:user9` fiber statuses to `:interrupted` and `:suspended`.
- Add `ev/all-tasks` to see all currently suspended fibers.
- Add `keep-syntax` and `keep-syntax!` functions to make writing macros easier.
## 1.27.0 - 2023-03-05
- Change semantics around bracket tuples to no longer be equal to regular tuples.
- Add `index` argument to `ffi/write` for symmetry with `ffi/read`.
- Add `buffer/push-at`
- Add `ffi/pointer-buffer` to convert pointers to buffers the cannot be reallocated. This
allows easier manipulation of FFI memory, memory mapped files, and buffer memory shared between threads.
- Calling `ev/cancel` on a fiber waiting on `ev/gather` will correctly
cancel the child fibers.
- Add `(sandbox ...)` function to core for permission based security. Also add `janet_sandbox` to C API.
The sandbox allows limiting access to the file system, network, ffi, and OS resources at runtime.
- Add `(.locals)` function to debugger to see currently bound local symbols.
- Track symbol -> slot mapping so debugger can get symbolic information. This exposes local bindings
in `debug/stack` and `disasm`.
- Add `os/compiler` to detect what host compiler was used to compile the interpreter
- Add support for mingw and cygwin builds (mingw support also added in jpm).
## 1.26.0 - 2023-01-07
- Add `ffi/malloc` and `ffi/free`. Useful as tools of last resort.
- Add `ffi/jitfn` to allow calling function pointers generated at runtime from machine code.
Bring your own assembler, though.
- Channels can now be marshalled. Pending state is not saved, only items in the channel.
- Use the new `.length` function pointer on abstract types for lengths. Adding
a `length` method will still work as well.
- Support byte views on abstract types with the `.bytes` function pointer.
- Add the `u` format specifier to printf family functions.
- Allow printing 64 integer types in `printf` and `string/format` family functions.
- Allow importing modules from custom directories more easily with the `@` prefix
to module paths. For example, if there is a dynamic binding :custom-modules that
is a file system path to a directory of modules, import from that directory with
`(import @custom-modules/mymod)`.
- Fix error message bug in FFI library.
## 1.25.1 - 2022-10-29
- Add `memcmp` function to core library.
- Fix bug in `os/open` with `:rw` permissions not correct on Linux.
- Support config.mk for more easily configuring the Makefile.
## 1.25.0 - 2022-10-10
- Windows FFI fixes.
- Fix PEG `if-not` combinator with captures in the condition
- Fix bug with `os/date` with nil first argument
- Fix bug with `net/accept` on Linux that could leak file descriptors to subprocesses
- Reduce number of hash collisions from pointer hashing
- Add optional parameter to `marshal` to skip cycle checking code
## 1.24.1 - 2022-08-24
- Fix FFI bug on Linux/Posix
- Improve parse error messages for bad delimiters.
- Add optional `name` parameter to the `short-fn` macro.
## 1.24.0 - 2022-08-14
- Add FFI support to 64-bit windows compiled with MSVC
- Don't process shared object names passed to dlopen.
- Add better support for windows console in the default shell.c for autocompletion and
- Add better support for windows console in the default shell.c for auto-completion and
other shell-like input features.
- Improve default error message from `assert`.
- Add the `tabseq` macro for simpler table comprehensions.
- Allow setting `(dyn :task-id)` in fibers to improve context in supervisor messages. Prior to
this change, supverisor messages over threaded channels would be from ambiguous threads/fibers.
this change, supervisor messages over threaded channels would be from ambiguous threads/fibers.
## 1.23.0 - 2022-06-20
- Add experimental `ffi/` module for interfacing with dynamic libraries and raw function pointers. Only available

View File

@@ -1,4 +1,4 @@
Copyright (c) 2021 Calvin Rose and contributors
Copyright (c) 2023 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) 2022 Calvin Rose
# Copyright (c) 2023 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,15 +21,18 @@
################################
##### Set global variables #####
################################
sinclude config.mk
PREFIX?=/usr/local
JANETCONF_HEADER?=src/conf/janetconf.h
INCLUDEDIR?=$(PREFIX)/include
BINDIR?=$(PREFIX)/bin
LIBDIR?=$(PREFIX)/lib
JANET_BUILD?="\"$(shell git log --pretty=format:'%h' -n 1 2> /dev/null || echo local)\""
CLIBS=-lm -lpthread
JANET_TARGET=build/janet
JANET_BOOT=build/janet_boot
JANET_IMPORT_LIB=build/janet.lib
JANET_LIBRARY=build/libjanet.so
JANET_STATIC_LIBRARY=build/libjanet.a
JANET_PATH?=$(LIBDIR)/janet
@@ -45,6 +48,7 @@ HOSTCC?=$(CC)
HOSTAR?=$(AR)
CFLAGS?=-O2
LDFLAGS?=-rdynamic
RUN:=$(RUN)
COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC
BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 -g $(COMMON_CFLAGS)
@@ -54,7 +58,7 @@ BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS)
LDCONFIG:=ldconfig "$(LIBDIR)"
# Check OS
UNAME:=$(shell uname -s)
UNAME?=$(shell uname -s)
ifeq ($(UNAME), Darwin)
CLIBS:=$(CLIBS) -ldl
SONAME_SETTER:=-Wl,-install_name,
@@ -76,6 +80,14 @@ ifeq ($(shell uname -o), Android)
endif
endif
# Mingw
ifeq ($(findstring MINGW,$(UNAME)), MINGW)
CLIBS:=-lws2_32 -lpsapi -lwsock32
LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB)
JANET_TARGET:=$(JANET_TARGET).exe
JANET_BOOT:=$(JANET_BOOT).exe
endif
$(shell mkdir -p build/core build/c build/boot)
all: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h
@@ -83,7 +95,7 @@ all: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h
##### Name Files #####
######################
JANET_HEADERS=src/include/janet.h src/conf/janetconf.h
JANET_HEADERS=src/include/janet.h $(JANETCONF_HEADER)
JANET_LOCAL_HEADERS=src/core/features.h \
src/core/util.h \
@@ -155,12 +167,12 @@ $(JANET_BOOT_OBJECTS): $(JANET_BOOT_HEADERS)
build/%.boot.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile
$(CC) $(BOOT_CFLAGS) -o $@ -c $<
build/janet_boot: $(JANET_BOOT_OBJECTS)
$(JANET_BOOT): $(JANET_BOOT_OBJECTS)
$(CC) $(BOOT_CFLAGS) -o $@ $(JANET_BOOT_OBJECTS) $(CLIBS)
# Now the reason we bootstrap in the first place
build/c/janet.c: build/janet_boot src/boot/boot.janet
build/janet_boot . JANET_PATH '$(JANET_PATH)' > $@
build/c/janet.c: $(JANET_BOOT) src/boot/boot.janet
$(RUN) $(JANET_BOOT) . JANET_PATH '$(JANET_PATH)' > $@
cksum $@
########################
@@ -168,24 +180,24 @@ build/c/janet.c: build/janet_boot src/boot/boot.janet
########################
ifeq ($(UNAME), Darwin)
SONAME=libjanet.1.24.dylib
SONAME=libjanet.1.28.dylib
else
SONAME=libjanet.so.1.24
SONAME=libjanet.so.1.28
endif
build/c/shell.c: src/mainclient/shell.c
cp $< $@
build/janet.h: $(JANET_TARGET) src/include/janet.h src/conf/janetconf.h
./$(JANET_TARGET) tools/patch-header.janet src/include/janet.h src/conf/janetconf.h $@
build/janet.h: $(JANET_TARGET) src/include/janet.h $(JANETCONF_HEADER)
$(RUN) ./$(JANET_TARGET) tools/patch-header.janet src/include/janet.h $(JANETCONF_HEADER) $@
build/janetconf.h: src/conf/janetconf.h
build/janetconf.h: $(JANETCONF_HEADER)
cp $< $@
build/janet.o: build/c/janet.c src/conf/janetconf.h src/include/janet.h
build/janet.o: build/c/janet.c $(JANETCONF_HEADER) src/include/janet.h
$(HOSTCC) $(BUILD_CFLAGS) -c $< -o $@
build/shell.o: build/c/shell.c src/conf/janetconf.h src/include/janet.h
build/shell.o: build/c/shell.c $(JANETCONF_HEADER) src/include/janet.h
$(HOSTCC) $(BUILD_CFLAGS) -c $< -o $@
$(JANET_TARGET): build/janet.o build/shell.o
@@ -206,7 +218,7 @@ $(JANET_STATIC_LIBRARY): build/janet.o build/shell.o
TEST_SCRIPTS=$(wildcard test/suite*.janet)
repl: $(JANET_TARGET)
./$(JANET_TARGET)
$(RUN) ./$(JANET_TARGET)
debug: $(JANET_TARGET)
$(DEBUGGER) ./$(JANET_TARGET)
@@ -217,8 +229,8 @@ valgrind: $(JANET_TARGET)
$(VALGRIND_COMMAND) ./$(JANET_TARGET)
test: $(JANET_TARGET) $(TEST_PROGRAMS)
for f in test/suite*.janet; do ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done
for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done
valtest: $(JANET_TARGET) $(TEST_PROGRAMS)
for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
@@ -257,7 +269,7 @@ build/janet-%.tar.gz: $(JANET_TARGET) \
docs: build/doc.html
build/doc.html: $(JANET_TARGET) tools/gendoc.janet
$(JANET_TARGET) tools/gendoc.janet > build/doc.html
$(RUN) $(JANET_TARGET) tools/gendoc.janet > build/doc.html
########################
##### Installation #####
@@ -273,7 +285,7 @@ build/janet.pc: $(JANET_TARGET)
echo "Name: janet" >> $@
echo "Url: https://janet-lang.org" >> $@
echo "Description: Library for the Janet programming language." >> $@
$(JANET_TARGET) -e '(print "Version: " janet/version)' >> $@
$(RUN) $(JANET_TARGET) -e '(print "Version: " janet/version)' >> $@
echo 'Cflags: -I$${includedir}' >> $@
echo 'Libs: -L$${libdir} -ljanet' >> $@
echo 'Libs.private: $(CLIBS)' >> $@
@@ -300,6 +312,7 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc
cp janet.1 '$(DESTDIR)$(JANET_MANPATH)'
mkdir -p '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)'
cp build/janet.pc '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc'
cp '$(JANET_IMPORT_LIB)' '$(DESTDIR)$(LIBDIR)' || echo 'no import lib to install (mingw only)'
[ -z '$(DESTDIR)' ] && $(LDCONFIG) || echo "You can ignore this error for non-Linux systems or local installs"
install-jpm-git: $(JANET_TARGET)
@@ -312,7 +325,7 @@ install-jpm-git: $(JANET_TARGET)
JANET_HEADERPATH='$(INCLUDEDIR)/janet' \
JANET_BINPATH='$(BINDIR)' \
JANET_LIBPATH='$(LIBDIR)' \
../../$(JANET_TARGET) ./bootstrap.janet
$(RUN) ../../$(JANET_TARGET) ./bootstrap.janet
uninstall:
-rm '$(DESTDIR)$(BINDIR)/janet'
@@ -332,7 +345,7 @@ format:
grammar: build/janet.tmLanguage
build/janet.tmLanguage: tools/tm_lang_gen.janet $(JANET_TARGET)
$(JANET_TARGET) $< > $@
$(RUN) $(JANET_TARGET) $< > $@
compile-commands:
# Requires pip install copmiledb

View File

@@ -7,14 +7,14 @@
<img src="https://raw.githubusercontent.com/janet-lang/janet/master/assets/janet-w200.png" alt="Janet logo" width=200 align="left">
**Janet** is a functional and imperative programming language and bytecode interpreter. It is a
lisp-like language, but lists are replaced
Lisp-like language, but lists are replaced
by other data structures (arrays, tables (hash table), struct (immutable hash table), tuples).
The language also supports bridging to native code written in C, meta-programming with macros, and bytecode assembly.
There is a REPL for trying out the language, as well as the ability
to run script files. This client program is separate from the core runtime, so
Janet can be embedded in other programs. Try Janet in your browser at
[https://janet-lang.org](https://janet-lang.org).
<https://janet-lang.org>.
If you'd like to financially support the ongoing development of Janet, consider
[sponsoring its primary author](https://github.com/sponsors/bakpakin) through GitHub.
@@ -41,8 +41,8 @@ Lua, but smaller than GNU Guile or Python.
* Macros
* Multithreading
* Per-thread event loop for efficient evented IO
* Byte code interpreter with an assembly interface, as well as bytecode verification
* Tail call Optimization
* Bytecode interpreter with an assembly interface, as well as bytecode verification
* Tail-call optimization
* Direct interop with C via abstract types and C functions
* Dynamically load C libraries
* Functional and imperative standard library
@@ -57,7 +57,7 @@ Lua, but smaller than GNU Guile or Python.
## Documentation
* For a quick tutorial, see [the introduction](https://janet-lang.org/docs/index.html) for more details.
* For the full API for all functions in the core library, see [the core API doc](https://janet-lang.org/api/index.html)
* For the full API for all functions in the core library, see [the core API doc](https://janet-lang.org/api/index.html).
Documentation is also available locally in the REPL.
Use the `(doc symbol-name)` macro to get API
@@ -65,7 +65,7 @@ documentation for symbols in the core library. For example,
```
(doc apply)
```
Shows documentation for the `apply` function.
shows documentation for the `apply` function.
To get a list of all bindings in the default
environment, use the `(all-bindings)` function. You
@@ -84,7 +84,7 @@ the SourceHut mirror is actively maintained.
The Makefile is non-portable and requires GNU-flavored make.
```
```sh
cd somewhere/my/projects/janet
make
make test
@@ -100,7 +100,7 @@ Find out more about the available make targets by running `make help`.
32-bit Haiku build instructions are the same as the UNIX-like build instructions,
but you need to specify an alternative compiler, such as `gcc-x86`.
```
```sh
cd somewhere/my/projects/janet
make CC=gcc-x86
make test
@@ -112,10 +112,9 @@ make install-jpm-git
### FreeBSD
FreeBSD build instructions are the same as the UNIX-like build instructions,
but you need `gmake` to compile. Alternatively, install directly from
packages, using `pkg install lang/janet`.
but you need `gmake` to compile. Alternatively, install the package directly with `pkg install lang/janet`.
```
```sh
cd somewhere/my/projects/janet
gmake
gmake test
@@ -127,19 +126,19 @@ gmake install-jpm-git
### NetBSD
NetBSD build instructions are the same as the FreeBSD build instructions.
Alternatively, install directly from packages, using `pkgin install janet`.
Alternatively, install the package directly with `pkgin install janet`.
### 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#)
2. Run a Visual Studio Command Prompt (cl.exe and link.exe need to be on the PATH) and cd to the directory with janet.
3. Run `build_win` to compile janet.
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#).
2. Run a Visual Studio Command Prompt (`cl.exe` and `link.exe` need to be on your PATH) and `cd` to the directory with Janet.
3. Run `build_win` to compile Janet.
4. Run `build_win test` to make sure everything is working.
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)
6. run `build_win dist`
5. Install, or otherwise add to your PATH the [WiX 3.11 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.
@@ -175,9 +174,9 @@ ninja -C build install
Janet can be hacked on with pretty much any environment you like, but for IDE
lovers, [Gnome Builder](https://wiki.gnome.org/Apps/Builder) is probably the
best option, as it has excellent meson integration. It also offers code completion
best option, as it has excellent Meson integration. It also offers code completion
for Janet's C API right out of the box, which is very useful for exploring. VSCode, Vim,
Emacs, and Atom will have syntax packages for the Janet language, though.
Emacs, and Atom each have syntax packages for the Janet language, though.
## Installation
@@ -186,8 +185,8 @@ to try out the language, you don't need to install anything. You can also move t
## Usage
A REPL is launched when the binary is invoked with no arguments. Pass the -h flag
to display the usage information. Individual scripts can be run with `./janet myscript.janet`
A REPL is launched when the binary is invoked with no arguments. Pass the `-h` flag
to display the usage information. Individual scripts can be run with `./janet myscript.janet`.
If you are looking to explore, you can print a list of all available macros, functions, and constants
by entering the command `(all-bindings)` into the REPL.
@@ -202,20 +201,26 @@ Hello, World!
nil
janet:3:> (os/exit)
$ janet -h
usage: build/janet [options] script args...
usage: janet [options] script args...
Options are:
-h : Show this help
-v : Print the version string
-s : Use raw stdin instead of getline like functionality
-e code : Execute a string of janet
-E code arguments... : Evaluate an expression as a short-fn with arguments
-d : Set the debug flag in the REPL
-r : Enter the REPL after running all scripts
-R : Disables loading profile.janet when JANET_PROFILE is present
-p : Keep on executing if there is a top-level error (persistent)
-q : Hide prompt, logo, and REPL output (quiet)
-q : Hide logo (quiet)
-k : Compile scripts but do not execute (flycheck)
-m syspath : Set system path for loading global modules
-c source output : Compile janet source code into an image
-i : Load the script argument as an image file instead of source code
-n : Disable ANSI color output in the REPL
-l path : Execute code in a file before running the main script
-l lib : Use a module before processing more arguments
-w level : Set the lint warning level - default is "normal"
-x level : Set the lint error level - default is "none"
-- : Stop handling options
```
@@ -226,8 +231,8 @@ If installed, you can also run `man janet` to get usage information.
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
`src/include/janet.h` and `src/conf/janetconf.h` can be dragged into any C
project and compiled into the project. Janet should be compiled with `-std=c99`
`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
the dynamic linker, `-ldl`, if one wants to be able to load dynamic modules. If
there is no need for dynamic modules, add the define
@@ -237,24 +242,24 @@ See the [Embedding Section](https://janet-lang.org/capi/embedding.html) on the w
## Examples
See the examples directory for some example janet code.
See the examples directory for some example Janet code.
## Discussion
Feel free to ask questions and join the discussion on the [Janet Gitter Channel](https://gitter.im/janet-language/community).
Gitter provides Matrix and irc bridges as well.
Feel free to ask questions and join the discussion on the [Janet Gitter channel](https://gitter.im/janet-language/community).
Gitter provides Matrix and IRC bridges as well.
## FAQ
### Where is (favorite feature from other language)?
It may exist, it may not. If you want to propose major language features, go ahead and open an issue, but
they will likely by closed as "will not implement". Often, such features make one usecase simpler at the expense
It may exist, it may not. If you want to propose a major language feature, go ahead and open an issue, but
it will likely be closed as "will not implement". Often, such features make one usecase simpler at the expense
of 5 others by making the language more complicated.
### Is there a language spec?
There is not currently a spec besides the documentation at https://janet-lang.org.
There is not currently a spec besides the documentation at <https://janet-lang.org>.
### Is this Scheme/Common Lisp? Where are the cons cells?
@@ -270,13 +275,13 @@ Internally, Janet is not at all like Clojure.
No. They are immutable arrays and hash tables. Don't try and use them like Clojure's vectors
and maps, instead they work well as table keys or other identifiers.
### Can I do Object Oriented programming with Janet?
### Can I do object-oriented programming with Janet?
To some extent, yes. However, it is not the recommended method of abstraction, and performance may suffer.
That said, tables can be used to make mutable objects with inheritance and polymorphism, where object
methods are implemeted with keywords.
methods are implemented with keywords.
```
```clj
(def Car @{:honk (fn [self msg] (print "car " self " goes " msg)) })
(def my-car (table/setproto @{} Car))
(:honk my-car "Beep!")
@@ -287,9 +292,9 @@ methods are implemeted with keywords.
Usually, one of a few reasons:
- Often, it already exists in a different form and the Clojure port would be redundant.
- Clojure programs often generate a lot of garbage and rely on the JVM to clean it up.
Janet does not run on the JVM, and has a more primitive garbage collector.
- We want to keep the Janet core small. With Lisps, usually a feature can be added as a library
without feeling "bolted on", especially when compared to ALGOL like languages. Adding features
Janet does not run on the JVM and has a more primitive garbage collector.
- We want to keep the Janet core small. With Lisps, a feature can usually be added as a library
without feeling "bolted on", especially when compared to ALGOL-like languages. Adding features
to the core also makes it a bit more difficult to keep Janet maximally portable.
### Why is my terminal spitting out junk when I run the REPL?
@@ -297,7 +302,7 @@ Usually, one of a few reasons:
Make sure your terminal supports ANSI escape codes. Most modern terminals will
support these, but some older terminals, Windows consoles, or embedded terminals
will not. If your terminal does not support ANSI escape codes, run the REPL with
the `-n` flag, which disables color output. You can also try the `-s` if further issues
the `-n` flag, which disables color output. You can also try the `-s` flag if further issues
ensue.
## Why is it called "Janet"?

View File

@@ -3,11 +3,46 @@
#include <string.h>
#ifdef _WIN32
#define EXPORTER __declspec(dllexport)
#define EXPORTER __declspec(dllexport)
#else
#define EXPORTER
#endif
/* Structs */
typedef struct {
int a, b;
float c, d;
} Split;
typedef struct {
float c, d;
int a, b;
} SplitFlip;
typedef struct {
int u, v, w, x, y, z;
} SixInts;
typedef struct {
int a;
int b;
} intint;
typedef struct {
int a;
int b;
int c;
} intintint;
typedef struct {
int64_t a;
int64_t b;
int64_t c;
} big;
/* Functions */
EXPORTER
int int_fn(int a, int b) {
return (a << 2) + b;
@@ -57,15 +92,15 @@ double double_lots_2(
double i,
double j) {
return a +
10.0 * b +
100.0 * c +
1000.0 * d +
10000.0 * e +
100000.0 * f +
1000000.0 * g +
10000000.0 * h +
100000000.0 * i +
1000000000.0 * j;
10.0 * b +
100.0 * c +
1000.0 * d +
10000.0 * e +
100000.0 * f +
1000000.0 * g +
10000000.0 * h +
100000000.0 * i +
1000000000.0 * j;
}
EXPORTER
@@ -73,17 +108,6 @@ double float_fn(float x, float y, float z) {
return (x + y) * z;
}
typedef struct {
int a;
int b;
} intint;
typedef struct {
int a;
int b;
int c;
} intintint;
EXPORTER
int intint_fn(double x, intint ii) {
printf("double: %g\n", x);
@@ -104,12 +128,6 @@ intint return_struct(int i) {
return ret;
}
typedef struct {
int64_t a;
int64_t b;
int64_t c;
} big;
EXPORTER
big struct_big(int i, double d) {
big ret;
@@ -124,7 +142,67 @@ void void_fn(void) {
printf("void fn ran\n");
}
EXPORTER
void void_fn_2(double y) {
printf("y = %f\n", y);
}
EXPORTER
void void_ret_fn(int x) {
printf("void fn ran: %d\n", x);
}
EXPORTER
int intintint_fn_2(intintint iii, int i) {
fprintf(stderr, "iii.a = %d, iii.b = %d, iii.c = %d, i = %d\n", iii.a, iii.b, iii.c, i);
return i * (iii.a + iii.b + iii.c);
}
EXPORTER
float split_fn(Split s) {
return s.a * s.c + s.b * s.d;
}
EXPORTER
float split_flip_fn(SplitFlip s) {
return s.a * s.c + s.b * s.d;
}
EXPORTER
Split split_ret_fn(int x, float y) {
Split ret;
ret.a = x;
ret.b = x;
ret.c = y;
ret.d = y;
return ret;
}
EXPORTER
SplitFlip split_flip_ret_fn(int x, float y) {
SplitFlip ret;
ret.a = x;
ret.b = x;
ret.c = y;
ret.d = y;
return ret;
}
EXPORTER
SixInts sixints_fn(void) {
return (SixInts) {
6666, 1111, 2222, 3333, 4444, 5555
};
}
EXPORTER
int sixints_fn_2(int x, SixInts s) {
return x + s.u + s.v + s.w + s.x + s.y + s.z;
}
EXPORTER
int sixints_fn_3(SixInts s, int x) {
return x + s.u + s.v + s.w + s.x + s.y + s.z;
}

View File

@@ -14,6 +14,9 @@
(def intintint (ffi/struct :int :int :int))
(def big (ffi/struct :s64 :s64 :s64))
(def split (ffi/struct :int :int :float :float))
(def split-flip (ffi/struct :float :float :int :int))
(def six-ints (ffi/struct :int :int :int :int :int :int))
(ffi/defbind int-fn :int [a :int b :int])
(ffi/defbind double-fn :double [a :double b :double c :double])
@@ -43,6 +46,15 @@
h :double
i :double
j :double])
(ffi/defbind void-fn-2 :void [y :double])
(ffi/defbind intintint-fn-2 :int [iii intintint i :int])
(ffi/defbind split-fn :float [s split])
(ffi/defbind split-flip-fn :float [s split-flip])
(ffi/defbind split-ret-fn split [x :int y :float])
(ffi/defbind split-flip-ret-fn split-flip [x :int y :float])
(ffi/defbind sixints-fn six-ints [])
(ffi/defbind sixints-fn-2 :int [x :int s six-ints])
(ffi/defbind sixints-fn-3 :int [s six-ints x :int])
#
# Struct reading and writing
@@ -84,6 +96,15 @@
# Call functions
#
(tracev (sixints-fn))
(tracev (sixints-fn-2 100 [1 2 3 4 5 6]))
(tracev (sixints-fn-3 [1 2 3 4 5 6] 200))
(tracev (split-ret-fn 10 12))
(tracev (split-flip-ret-fn 10 12))
(tracev (split-flip-ret-fn 12 10))
(tracev (intintint-fn-2 [10 20 30] 3))
(tracev (split-fn [5 6 1.2 3.4]))
(tracev (void-fn-2 10.3))
(tracev (double-many 1 2 3 4 5 6))
(tracev (string/format "%.17g" (double-many 1 2 3 4 5 6)))
(tracev (type (double-many 1 2 3 4 5 6)))
@@ -99,6 +120,10 @@
(tracev (double-lots 1 2 3 4 5 6 700 800 9 10))
(tracev (struct-big 11 99.5))
(assert (= [10 10 12 12] (split-ret-fn 10 12)))
(assert (= [12 12 10 10] (split-flip-ret-fn 10 12)))
(assert (= 183 (intintint-fn-2 [10 20 31] 3)))
(assert (= 264 (math/round (* 10 (split-fn [5 6 1.2 3.4])))))
(assert (= 9876543210 (double-lots-2 0 1 2 3 4 5 6 7 8 9)))
(assert (= 60 (int-fn 10 20)))
(assert (= 42 (double-fn 1.5 2.5 3.5)))

BIN
examples/jitfn/hello.bin Normal file

Binary file not shown.

17
examples/jitfn/hello.nasm Normal file
View File

@@ -0,0 +1,17 @@
BITS 64
;;;
;;; Code
;;;
mov rax, 1 ; write(
mov rdi, 1 ; STDOUT_FILENO,
lea rsi, [rel msg] ; msg,
mov rdx, msglen ; sizeof(msg)
syscall ; );
ret ; return;
;;;
;;; Constants
;;;
msg: db "Hello, world!", 10
msglen: equ $ - msg

View File

@@ -0,0 +1,13 @@
###
### Relies on NASM being installed to assemble code.
### Only works on x86-64 Linux.
###
### Before running, compile hello.nasm to hello.bin with
### $ nasm hello.nasm -o hello.bin
(def bin (slurp "hello.bin"))
(def f (ffi/jitfn bin))
(def signature (ffi/signature :default :void))
(ffi/call f signature)
(print "called a jitted function with FFI!")
(print "machine code: " (describe (string/slice f)))

2
examples/lineloop.janet Normal file
View File

@@ -0,0 +1,2 @@
(while (not (empty? (def line (getline))))
(prin "line: " line))

View File

@@ -0,0 +1,30 @@
(defn init-db [c]
(def res @{:clients @{}})
(var i 0)
(repeat c
(def n (string "client" i))
(put-in res [:clients n] @{:name n :projects @{}})
(++ i)
(repeat c
(def pn (string "project" i))
(put-in res [:clients n :projects pn] @{:name pn})
(++ i)
(repeat c
(def tn (string "task" i))
(put-in res [:clients n :projects pn :tasks tn] @{:name pn})
(++ i))))
res)
(loop [c :range [30 80 1]]
(var s (os/clock))
(print "Marshal DB with " c " clients, "
(* c c) " projects and "
(* c c c) " tasks. "
"Total " (+ (* c c c) (* c c) c) " tables")
(def buf (marshal (init-db c) @{} @""))
(print "Buffer is " (length buf) " bytes")
(print "Duration " (- (os/clock) s))
(set s (os/clock))
(gccollect)
(print "Collected garbage in " (- (os/clock) s)))

17
janet.1
View File

@@ -164,10 +164,15 @@ Execute a string of Janet source. Source code is executed in the order it is enc
arguments are executed before later ones.
.TP
.BR \-E\ code arguments
.BR \-E\ code\ arguments...
Execute a single Janet expression as a Janet short-fn, passing the remaining command line arguments to the expression. This allows
more concise one-liners with command line arguments.
Example: janet -E '(print $0)' 12 is equivalent to '((short-fn (print $0)) 12)', which is in turn equivalent to
`((fn [k] (print k)) 12)`
See docs for the `short-fn` function for more details.
.TP
.BR \-d
Enable debug mode. On all terminating signals as well the debug signal, this will
@@ -178,6 +183,10 @@ default repl.
.BR \-n
Disable ANSI colors in the repl. Has no effect if no repl is run.
.TP
.BR \-N
Enable ANSI colors in the repl. Has no effect if no repl is run.
.TP
.BR \-r
Open a REPL (Read Eval Print Loop) after executing all sources. By default, if Janet is called with no
@@ -263,5 +272,11 @@ 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 NO_COLOR
.RS
Turn off color by default in the repl and in the error handler of scripts. This can be changed at runtime
via dynamic bindings *err-color* and *pretty-format*, or via the command line parameters -n and -N.
.RE
.SH AUTHOR
Written by Calvin Rose <calsrose@gmail.com>

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose and contributors
# Copyright (c) 2023 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.24.0')
version : '1.28.0')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
@@ -77,6 +77,7 @@ conf.set('JANET_EV_NO_EPOLL', not get_option('epoll'))
conf.set('JANET_EV_NO_KQUEUE', not get_option('kqueue'))
conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt'))
conf.set('JANET_NO_FFI', not get_option('ffi'))
conf.set('JANET_NO_FFI_JIT', not get_option('ffi_jit'))
if get_option('os_name') != ''
conf.set('JANET_OS_NAME', get_option('os_name'))
endif
@@ -236,7 +237,11 @@ test_files = [
'test/suite0007.janet',
'test/suite0008.janet',
'test/suite0009.janet',
'test/suite0010.janet'
'test/suite0010.janet',
'test/suite0011.janet',
'test/suite0012.janet',
'test/suite0013.janet',
'test/suite0014.janet'
]
foreach t : test_files
test(t, janet_nativeclient, args : files([t]), workdir : meson.current_source_dir())

View File

@@ -20,6 +20,7 @@ option('epoll', type : 'boolean', value : false)
option('kqueue', type : 'boolean', value : false)
option('interpreter_interrupt', type : 'boolean', value : false)
option('ffi', type : 'boolean', value : true)
option('ffi_jit', type : 'boolean', value : true)
option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024)
option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200)

View File

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

View File

@@ -1,5 +1,5 @@
# The core janet library
# Copyright 2022 © Calvin Rose
# Copyright 2023 © Calvin Rose
###
###
@@ -129,20 +129,18 @@
# For macros, we define an imcomplete odd? function that will be overriden.
(defn odd? [x] (= 1 (mod x 2)))
(def idempotent?
```
(idempotent? x)
(def- non-atomic-types
{:array true
:tuple true
:table true
:buffer true
:symbol true
:struct true})
Check if x is a value that evaluates to itself when compiled.
```
(do
(def non-atomic-types
{:array true
:tuple true
:table true
:buffer true
:struct true})
(fn idempotent? [x] (not (in non-atomic-types (type x))))))
(defn idempotent?
"Check if x is a value that evaluates to itself when compiled."
[x]
(not (in non-atomic-types (type x))))
# C style macros and functions for imperative sugar. No bitwise though.
(defn inc "Returns x + 1." [x] (+ x 1))
@@ -282,7 +280,7 @@
(while (> i 0)
(-- i)
(def v (in forms i))
(set ret (if (= ret true)
(set ret (if (= i (- len 1))
v
(if (idempotent? v)
['if v ret v]
@@ -613,6 +611,13 @@
(def $accum (gensym))
~(do (def ,$accum @[]) (loop ,head (,array/push ,$accum (do ,;body))) ,$accum))
(defmacro catseq
``Similar to `loop`, but concatenates each element from the loop body into an array and returns that.
See `loop` for details.``
[head & body]
(def $accum (gensym))
~(do (def ,$accum @[]) (loop ,head (,array/concat ,$accum (do ,;body))) ,$accum))
(defmacro tabseq
``Similar to `loop`, but accumulates key value pairs into a table.
See `loop` for details.``
@@ -839,15 +844,15 @@
a)
(defn sort
``Sort `ind` in-place, and return it. Uses quick-sort and is not a stable sort.
``Sorts `ind` in-place, and returns it. Uses quick-sort and is not a stable sort.
If a `before?` comparator function is provided, sorts elements using that,
otherwise uses `<`.``
[ind &opt before?]
(sort-help ind 0 (- (length ind) 1) (or before? <)))
(defn sort-by
``Returns `ind` sorted by calling
a function `f` on each element and comparing the result with `<`.``
``Sorts `ind` in-place by calling a function `f` on each element and
comparing the result with `<`.``
[f ind]
(sort ind (fn [x y] (< (f x) (f y)))))
@@ -1135,16 +1140,16 @@
(take-until (complement pred) ind))
(defn drop
``Drop the first n elements in an indexed or bytes type. Returns a new tuple or string
instance, respectively.``
``Drop the first `n elements in an indexed or bytes type. Returns a new tuple or string
instance, respectively. If `n` is negative, drops the last `n` elements instead.``
[n ind]
(def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice))
(def len (length ind))
# make sure start is in [0, len]
(def m (if (> n 0) n 0))
(def start (if (> m len) len m))
(f ind start -1))
(def negn (>= n 0))
(def start (if negn (min n len) 0))
(def end (if negn len (max 0 (+ len n))))
(f ind start end))
(defn drop-until
"Same as `(drop-while (complement pred) ind)`."
@@ -1234,6 +1239,29 @@
(,eprintf (,dyn :pretty-format "%q") ,s)
,s))
(defn keep-syntax
``Creates a tuple with the tuple type and sourcemap of `before` but the
elements of `after`. If either one of its argements is not a tuple, returns
`after` unmodified. Useful to preserve syntactic information when transforming
an ast in macros.``
[before after]
(if (and (= :tuple (type before))
(= :tuple (type after)))
(do
(def res (if (= :parens (tuple/type before))
(tuple/slice after)
(tuple/brackets ;after)))
(tuple/setmap res ;(tuple/sourcemap before)))
after))
(defn keep-syntax!
``Like `keep-syntax`, but if `after` is an array, it is coerced into a tuple.
Useful to preserve syntactic information when transforming an ast in macros.``
[before after]
(keep-syntax before (if (= :array (type after))
(tuple/slice after)
after)))
(defmacro ->
``Threading macro. Inserts x as the second value in the first form
in `forms`, and inserts the modified first form into the second form
@@ -1244,7 +1272,7 @@
(tuple (in n 0) (array/slice n 1))
(tuple n @[])))
(def parts (array/concat @[h last] t))
(tuple/slice parts 0))
(keep-syntax! n parts))
(reduce fop x forms))
(defmacro ->>
@@ -1257,7 +1285,7 @@
(tuple (in n 0) (array/slice n 1))
(tuple n @[])))
(def parts (array/concat @[h] t @[last]))
(tuple/slice parts 0))
(keep-syntax! n parts))
(reduce fop x forms))
(defmacro -?>
@@ -1273,7 +1301,7 @@
(tuple n @[])))
(def sym (gensym))
(def parts (array/concat @[h sym] t))
~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
~(let [,sym ,last] (if ,sym ,(keep-syntax! n parts))))
(reduce fop x forms))
(defmacro -?>>
@@ -1289,7 +1317,7 @@
(tuple n @[])))
(def sym (gensym))
(def parts (array/concat @[h] t @[sym]))
~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
~(let [,sym ,last] (if ,sym ,(keep-syntax! n parts))))
(reduce fop x forms))
(defn- walk-ind [f form]
@@ -1313,10 +1341,7 @@
:table (walk-dict f form)
:struct (table/to-struct (walk-dict f form))
:array (walk-ind f form)
:tuple (let [x (walk-ind f form)]
(if (= :parens (tuple/type form))
(tuple/slice x)
(tuple/brackets ;x)))
:tuple (keep-syntax! form (walk-ind f form))
form))
(defn postwalk
@@ -1749,7 +1774,7 @@
* tuple -- a tuple pattern will match if its first element matches, and the
following elements are treated as predicates and are true.
* `\_` symbol -- the last special case is the `\_` symbol, which is a wildcard
* `_` symbol -- the last special case is the `_` symbol, which is a wildcard
that will match any value without creating a binding.
While a symbol pattern will ordinarily match any value, the pattern `(@ <sym>)`,
@@ -2065,8 +2090,9 @@
ret)
(defn all
``Returns true if all `xs` are truthy, otherwise the result of first
falsey predicate value, `(pred x)`.``
``Returns true if `(pred item)` returns a truthy value for every item in `xs`.
Otherwise, returns the first falsey `(pred item)` result encountered.
Returns true if `xs` is empty.``
[pred xs]
(var ret true)
(loop [x :in xs :while ret] (set ret (pred x)))
@@ -2177,12 +2203,13 @@
|(+ $ $) # use pipe reader macro for terse function literals.
|(+ $&) # variadic functions
```
[arg]
[arg &opt name]
(var max-param-seen -1)
(var vararg false)
(defn saw-special-arg
[num]
(set max-param-seen (max max-param-seen num)))
(def prefix (gensym))
(defn on-binding
[x]
(if (string/has-prefix? '$ x)
@@ -2190,21 +2217,24 @@
(= '$ x)
(do
(saw-special-arg 0)
'$0)
(symbol prefix '$0))
(= '$& x)
(do
(set vararg true)
x)
(symbol prefix x))
:else
(do
(def num (scan-number (string/slice x 1)))
(if (nat? num)
(saw-special-arg num))
x))
(do
(saw-special-arg num)
(symbol prefix x))
x)))
x))
(def expanded (macex arg on-binding))
(def fn-args (seq [i :range [0 (+ 1 max-param-seen)]] (symbol '$ i)))
~(fn [,;fn-args ,;(if vararg ['& '$&] [])] ,expanded))
(def name-splice (if name [name] []))
(def fn-args (seq [i :range [0 (+ 1 max-param-seen)]] (symbol prefix '$ i)))
~(fn ,;name-splice [,;fn-args ,;(if vararg ['& (symbol prefix '$&)] [])] ,expanded))
###
###
@@ -2533,7 +2563,7 @@
(in env :exit-value env))
(defn quit
``Tries to exit from the current repl or context. Does not always exit the application.
``Tries to exit from the current repl or run-context. Does not always exit the application.
Works by setting the :exit dynamic binding to true. Passing a non-nil `value` here will cause the outer
run-context to return that value.``
[&opt value]
@@ -2541,36 +2571,11 @@
(setdyn :exit-value value)
nil)
(defn eval-string
``Evaluates a string in the current environment. If more control over the
environment is needed, use `run-context`.``
[str]
(var state (string str))
(defn chunks [buf _]
(def ret state)
(set state nil)
(when ret
(buffer/push-string buf str)
(buffer/push-string buf "\n")))
(var returnval nil)
(run-context {:chunks chunks
:on-compile-error (fn compile-error [msg errf &]
(error (string "compile error: " msg)))
:on-parse-error (fn parse-error [p x]
(error (string "parse error: " (:error p))))
:fiber-flags :i
:on-status (fn on-status [f val]
(if-not (= (fiber/status f) :dead)
(error val))
(set returnval val))
:source :eval-string})
returnval)
(defn eval
``Evaluates a form in the current environment. If more control over the
environment is needed, use `run-context`.``
[form]
(def res (compile form (fiber/getenv (fiber/current)) :eval))
(def res (compile form nil :eval))
(if (= (type res) :function)
(res)
(error (get res :error))))
@@ -2581,6 +2586,8 @@
[str]
(let [p (parser/new)]
(parser/consume p str)
(if (= :error (parser/status p))
(error (parser/error p)))
(parser/eof p)
(if (parser/has-more p)
(parser/produce p)
@@ -2595,6 +2602,8 @@
(let [p (parser/new)
ret @[]]
(parser/consume p str)
(if (= :error (parser/status p))
(error (parser/error p)))
(parser/eof p)
(while (parser/has-more p)
(array/push ret (parser/produce p)))
@@ -2602,6 +2611,14 @@
(error (parser/error p))
ret)))
(defn eval-string
``Evaluates a string in the current environment. If more control over the
environment is needed, use `run-context`.``
[str]
(var ret nil)
(each x (parse-all str) (set ret (eval x)))
ret)
(def load-image-dict
``A table used in combination with `unmarshal` to unmarshal byte sequences created
by `make-image`, such that `(load-image bytes)` is the same as `(unmarshal bytes load-image-dict)`.``
@@ -2641,9 +2658,10 @@
[image]
(unmarshal image load-image-dict))
(defn- check-dyn-relative [x] (if (string/has-prefix? "@" x) x))
(defn- check-relative [x] (if (string/has-prefix? "." x) x))
(defn- check-not-relative [x] (if-not (string/has-prefix? "." x) x))
(defn- check-is-dep [x] (unless (or (string/has-prefix? "/" x) (string/has-prefix? "." x)) x))
(defn- check-is-dep [x] (unless (or (string/has-prefix? "/" x) (string/has-prefix? "@" x) (string/has-prefix? "." x)) x))
(defn- check-project-relative [x] (if (string/has-prefix? "/" x) x))
(def module/cache
@@ -2677,6 +2695,8 @@
(defn- find-prefix
[pre]
(or (find-index |(and (string? ($ 0)) (string/has-prefix? pre ($ 0))) module/paths) 0))
(def dyn-index (find-prefix ":@all:"))
(array/insert module/paths dyn-index [(string ":@all:" ext) loader check-dyn-relative])
(def all-index (find-prefix ".:all:"))
(array/insert module/paths all-index [(string ".:all:" ext) loader check-project-relative])
(def sys-index (find-prefix ":sys:"))
@@ -2774,6 +2794,7 @@
(put nextenv :fiber fiber)
(put nextenv :debug-level level)
(put nextenv :signal (fiber/last-value fiber))
(merge-into nextenv debugger-env)
(defn debugger-chunks [buf p]
(def status (:state p :delimiters))
@@ -3382,11 +3403,16 @@
(print))
(defn .frame
"Show a stack frame."
"Show a stack frame"
[&opt n]
(def stack (debug/stack (.fiber)))
(in stack (or n 0)))
(defn .locals
"Show local bindings"
[&opt n]
(get (.frame n) :locals))
(defn .fn
"Get the current function."
[&opt n]
@@ -3567,7 +3593,7 @@
(ev/go (fn _call [&] (f ;args))))
(defmacro ev/spawn
"Run some code in a new fiber. This is shorthand for `(ev/call (fn [] ;body))`."
"Run some code in a new fiber. This is shorthand for `(ev/go (fn [] ;body))`."
[& body]
~(,ev/go (fn _spawn [&] ,;body)))
@@ -3591,13 +3617,18 @@
(,ev/deadline ,deadline nil ,f)
(,resume ,f))))
(defn- cancel-all [fibers reason] (each f fibers (ev/cancel f reason) (put fibers f nil)))
(defn- wait-for-fibers
[chan fibers]
(repeat (length fibers)
(def [sig fiber] (ev/take chan))
(unless (= sig :ok)
(each f fibers (ev/cancel f "sibling canceled"))
(propagate (fiber/last-value fiber) fiber))))
(defer (cancel-all fibers "parent canceled")
(repeat (length fibers)
(def [sig fiber] (ev/take chan))
(if (= sig :ok)
(put fibers fiber nil)
(do
(cancel-all fibers "sibling canceled")
(propagate (fiber/last-value fiber) fiber))))))
(defmacro ev/gather
``
@@ -3605,13 +3636,16 @@
Returns the gathered results in an array.
``
[& bodies]
(with-syms [chan res]
(with-syms [chan res fset ftemp]
~(do
(def ,fset @{})
(def ,chan (,ev/chan))
(def ,res @[])
(,wait-for-fibers ,chan
,(seq [[i body] :pairs bodies]
~(,ev/go (fn [] (put ,res ,i ,body)) nil ,chan)))
,;(seq [[i body] :pairs bodies]
~(do
(def ,ftemp (,ev/go (fn [] (put ,res ,i ,body)) nil ,chan))
(,put ,fset ,ftemp ,ftemp)))
(,wait-for-fibers ,chan ,fset)
,res))))
(compwhen (dyn 'net/listen)
@@ -3824,6 +3858,7 @@
(if-let [jp (getenv-alias "JANET_PATH")] (setdyn *syspath* jp))
(if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn *profilepath* jprofile))
(set colorize (not (getenv-alias "NO_COLOR")))
(defn- get-lint-level
[i]
@@ -3841,7 +3876,7 @@
-v : Print the version string
-s : Use raw stdin instead of getline like functionality
-e code : Execute a string of janet
-E code arguments... : Evaluate an expression as a short-fn with arguments
-E code arguments... : Evaluate an expression as a short-fn with arguments
-d : Set the debug flag in the REPL
-r : Enter the REPL after running all scripts
-R : Disables loading profile.janet when JANET_PROFILE is present
@@ -3852,6 +3887,7 @@
-c source output : Compile janet source code into an image
-i : Load the script argument as an image file instead of source code
-n : Disable ANSI color output in the REPL
-N : Enable ANSI color output in the REPL
-l lib : Use a module before processing more arguments
-w level : Set the lint warning level - default is "normal"
-x level : Set the lint error level - default is "none"
@@ -3867,6 +3903,7 @@
"i" (fn [&] (set expect-image true) 1)
"k" (fn [&] (set compile-only true) (set exit-on-error false) 1)
"n" (fn [&] (set colorize false) 1)
"N" (fn [&] (set colorize true) 1)
"m" (fn [i &] (setdyn *syspath* (in args (+ i 1))) 2)
"c" (fn c-switch [i &]
(def path (in args (+ i 1)))
@@ -3886,7 +3923,7 @@
"E" (fn E-switch [i &]
(set no-file false)
(def subargs (array/slice args (+ i 2)))
(def src ~|,(parse (in args (+ i 1))))
(def src ~(short-fn ,(parse (in args (+ i 1))) E-expression))
(def thunk (compile src))
(if (function? thunk)
((thunk) ;subargs)
@@ -3941,7 +3978,7 @@
compile-only (flycheck stdin :source :stdin :exit exit-on-error)
(do
(if-not quiet
(print "Janet " janet/version "-" janet/build " " (os/which) "/" (os/arch) " - '(doc)' for help"))
(print "Janet " janet/version "-" janet/build " " (os/which) "/" (os/arch) "/" (os/compiler) " - '(doc)' for help"))
(flush)
(defn getprompt [p]
(def [line] (parser/where p))

View File

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

View File

@@ -4,10 +4,10 @@
#define JANETCONF_H
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 24
#define JANET_VERSION_MINOR 28
#define JANET_VERSION_PATCH 0
#define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.24.0"
#define JANET_VERSION "1.28.0"
/* #define JANET_BUILD "local" */
@@ -33,6 +33,8 @@
/* #define JANET_NO_SYMLINKS */
/* #define JANET_NO_UMASK */
/* #define JANET_NO_THREADS */
/* #define JANET_NO_FFI */
/* #define JANET_NO_FFI_JIT */
/* Other settings */
/* #define JANET_DEBUG */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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,8 @@
#ifdef JANET_EV
#ifdef JANET_WINDOWS
#include <windows.h>
#else
#include <stdatomic.h>
#endif
#endif
@@ -96,11 +98,11 @@ size_t janet_os_rwlock_size(void) {
}
static int32_t janet_incref(JanetAbstractHead *ab) {
return InterlockedIncrement(&ab->gc.data.refcount);
return InterlockedIncrement((LONG volatile *) &ab->gc.data.refcount);
}
static int32_t janet_decref(JanetAbstractHead *ab) {
return InterlockedDecrement(&ab->gc.data.refcount);
return InterlockedDecrement((LONG volatile *) &ab->gc.data.refcount);
}
void janet_os_mutex_init(JanetOSMutex *mutex) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -187,7 +187,11 @@ static void janet_asm_longjmp(JanetAssembler *a) {
/* Throw some kind of assembly error */
static void janet_asm_error(JanetAssembler *a, const char *message) {
a->errmessage = janet_formatc("%s, instruction %d", message, a->errindex);
if (a->errindex < 0) {
a->errmessage = janet_formatc("%s", message);
} else {
a->errmessage = janet_formatc("%s, instruction %d", message, a->errindex);
}
janet_asm_longjmp(a);
}
#define janet_asm_assert(a, c, m) do { if (!(c)) janet_asm_error((a), (m)); } while (0)
@@ -516,6 +520,7 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
#endif
if (NULL != a.parent) {
janet_asm_deinit(&a);
a.parent->errmessage = a.errmessage;
janet_asm_longjmp(a.parent);
}
result.funcdef = NULL;
@@ -601,6 +606,9 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
/* Parse sub funcdefs */
x = janet_get1(s, janet_ckeywordv("closures"));
if (janet_checktype(x, JANET_NIL)) {
x = janet_get1(s, janet_ckeywordv("defs"));
}
if (janet_indexed_view(x, &arr, &count)) {
int32_t i;
for (i = 0; i < count; i++) {
@@ -713,10 +721,63 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
}
}
/* Set symbolmap */
def->symbolmap = NULL;
def->symbolmap_length = 0;
x = janet_get1(s, janet_ckeywordv("symbolmap"));
if (janet_indexed_view(x, &arr, &count)) {
def->symbolmap_length = count;
def->symbolmap = janet_malloc(sizeof(JanetSymbolMap) * (size_t)count);
if (NULL == def->symbolmap) {
JANET_OUT_OF_MEMORY;
}
for (i = 0; i < count; i++) {
const Janet *tup;
Janet entry = arr[i];
JanetSymbolMap ss;
if (!janet_checktype(entry, JANET_TUPLE)) {
janet_asm_error(&a, "expected tuple");
}
tup = janet_unwrap_tuple(entry);
if (janet_keyeq(tup[0], "upvalue")) {
ss.birth_pc = UINT32_MAX;
} else if (!janet_checkint(tup[0])) {
janet_asm_error(&a, "expected integer");
} else {
ss.birth_pc = janet_unwrap_integer(tup[0]);
}
if (!janet_checkint(tup[1])) {
janet_asm_error(&a, "expected integer");
}
if (!janet_checkint(tup[2])) {
janet_asm_error(&a, "expected integer");
}
if (!janet_checktype(tup[3], JANET_SYMBOL)) {
janet_asm_error(&a, "expected symbol");
}
ss.death_pc = janet_unwrap_integer(tup[1]);
ss.slot_index = janet_unwrap_integer(tup[2]);
ss.symbol = janet_unwrap_symbol(tup[3]);
def->symbolmap[i] = ss;
}
}
if (def->symbolmap_length) def->flags |= JANET_FUNCDEF_FLAG_HASSYMBOLMAP;
/* Set environments */
def->environments =
janet_realloc(def->environments, def->environments_length * sizeof(int32_t));
if (NULL == def->environments) {
x = janet_get1(s, janet_ckeywordv("environments"));
if (janet_indexed_view(x, &arr, &count)) {
def->environments_length = count;
if (def->environments_length) {
def->environments = janet_realloc(def->environments, def->environments_length * sizeof(int32_t));
}
for (int32_t i = 0; i < count; i++) {
if (!janet_checkint(arr[i])) {
janet_asm_error(&a, "expected integer");
}
def->environments[i] = janet_unwrap_integer(arr[i]);
}
}
if (def->environments_length && NULL == def->environments) {
JANET_OUT_OF_MEMORY;
}
@@ -865,6 +926,30 @@ static Janet janet_disasm_slotcount(JanetFuncDef *def) {
return janet_wrap_integer(def->slotcount);
}
static Janet janet_disasm_symbolslots(JanetFuncDef *def) {
if (def->symbolmap == NULL) {
return janet_wrap_nil();
}
JanetArray *symbolslots = janet_array(def->symbolmap_length);
Janet upvaluekw = janet_ckeywordv("upvalue");
for (int32_t i = 0; i < def->symbolmap_length; i++) {
JanetSymbolMap ss = def->symbolmap[i];
Janet *t = janet_tuple_begin(4);
if (ss.birth_pc == UINT32_MAX) {
t[0] = upvaluekw;
} else {
t[0] = janet_wrap_integer(ss.birth_pc);
}
t[1] = janet_wrap_integer(ss.death_pc);
t[2] = janet_wrap_integer(ss.slot_index);
t[3] = janet_wrap_symbol(ss.symbol);
symbolslots->data[i] = janet_wrap_tuple(janet_tuple_end(t));
}
symbolslots->count = def->symbolmap_length;
return janet_wrap_array(symbolslots);
}
static Janet janet_disasm_bytecode(JanetFuncDef *def) {
JanetArray *bcode = janet_array(def->bytecode_length);
for (int32_t i = 0; i < def->bytecode_length; i++) {
@@ -944,6 +1029,7 @@ Janet janet_disasm(JanetFuncDef *def) {
janet_table_put(ret, janet_ckeywordv("structarg"), janet_disasm_structarg(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));
janet_table_put(ret, janet_ckeywordv("constants"), janet_disasm_constants(def));
janet_table_put(ret, janet_ckeywordv("sourcemap"), janet_disasm_sourcemap(def));
janet_table_put(ret, janet_ckeywordv("environments"), janet_disasm_environments(def));
@@ -961,7 +1047,7 @@ JANET_CORE_FN(cfun_asm,
JanetAssembleResult res;
res = janet_asm(argv[0], 0);
if (res.status != JANET_ASSEMBLE_OK) {
janet_panics(res.error);
janet_panics(res.error ? res.error : janet_cstring("invalid assembly"));
}
return janet_wrap_function(janet_thunk(res.funcdef));
}
@@ -980,6 +1066,7 @@ JANET_CORE_FN(cfun_disasm,
"* :source - name of source file that this function was compiled from.\n"
"* :name - name of function.\n"
"* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n"
"* :symbolmap - all symbols and their slots.\n"
"* :constants - an array of constants referenced by this function.\n"
"* :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"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -28,8 +28,15 @@
#include "state.h"
#endif
/* Allow for managed buffers that cannot realloc/free their backing memory */
static void janet_buffer_can_realloc(JanetBuffer *buffer) {
if (buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC) {
janet_panic("buffer cannot reallocate foreign memory");
}
}
/* Initialize a buffer */
JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) {
static JanetBuffer *janet_buffer_init_impl(JanetBuffer *buffer, int32_t capacity) {
uint8_t *data = NULL;
if (capacity < 4) capacity = 4;
janet_gcpressure(capacity);
@@ -43,15 +50,37 @@ JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) {
return buffer;
}
/* Initialize a buffer */
JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) {
janet_buffer_init_impl(buffer, capacity);
buffer->gc.data.next = NULL;
buffer->gc.flags = JANET_MEM_DISABLED;
return buffer;
}
/* Initialize an unmanaged buffer */
JanetBuffer *janet_pointer_buffer_unsafe(void *memory, int32_t capacity, int32_t count) {
if (count < 0) janet_panic("count < 0");
if (capacity < count) janet_panic("capacity < count");
JanetBuffer *buffer = janet_gcalloc(JANET_MEMORY_BUFFER, sizeof(JanetBuffer));
buffer->gc.flags |= JANET_BUFFER_FLAG_NO_REALLOC;
buffer->capacity = capacity;
buffer->count = count;
buffer->data = (uint8_t *) memory;
return buffer;
}
/* Deinitialize a buffer (free data memory) */
void janet_buffer_deinit(JanetBuffer *buffer) {
janet_free(buffer->data);
if (!(buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) {
janet_free(buffer->data);
}
}
/* Initialize a buffer */
JanetBuffer *janet_buffer(int32_t capacity) {
JanetBuffer *buffer = janet_gcalloc(JANET_MEMORY_BUFFER, sizeof(JanetBuffer));
return janet_buffer_init(buffer, capacity);
return janet_buffer_init_impl(buffer, capacity);
}
/* Ensure that the buffer has enough internal capacity */
@@ -59,6 +88,7 @@ void janet_buffer_ensure(JanetBuffer *buffer, int32_t capacity, int32_t growth)
uint8_t *new_data;
uint8_t *old = buffer->data;
if (capacity <= buffer->capacity) return;
janet_buffer_can_realloc(buffer);
int64_t big_capacity = ((int64_t) capacity) * growth;
capacity = big_capacity > INT32_MAX ? INT32_MAX : (int32_t) big_capacity;
janet_gcpressure(capacity - buffer->capacity);
@@ -91,6 +121,7 @@ void janet_buffer_extra(JanetBuffer *buffer, int32_t n) {
}
int32_t new_size = buffer->count + n;
if (new_size > buffer->capacity) {
janet_buffer_can_realloc(buffer);
int32_t new_capacity = (new_size > (INT32_MAX / 2)) ? INT32_MAX : (new_size * 2);
uint8_t *new_data = janet_realloc(buffer->data, new_capacity * sizeof(uint8_t));
janet_gcpressure(new_capacity - buffer->capacity);
@@ -212,6 +243,7 @@ JANET_CORE_FN(cfun_buffer_trim,
"modified buffer.") {
janet_fixarity(argc, 1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
janet_buffer_can_realloc(buffer);
if (buffer->count < buffer->capacity) {
int32_t newcap = buffer->count > 4 ? buffer->count : 4;
uint8_t *newData = janet_realloc(buffer->data, newcap);
@@ -275,17 +307,8 @@ JANET_CORE_FN(cfun_buffer_chars,
return argv[0];
}
JANET_CORE_FN(cfun_buffer_push,
"(buffer/push buffer & xs)",
"Push both individual bytes and byte sequences to a buffer. For each x in xs, "
"push the byte if x is an integer, otherwise push the bytesequence to the buffer. "
"Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. "
"Returns the modified buffer. "
"Will throw an error if the buffer overflows.") {
int32_t i;
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
for (i = 1; i < argc; i++) {
static void buffer_push_impl(JanetBuffer *buffer, Janet *argv, int32_t argc_offset, int32_t argc) {
for (int32_t i = argc_offset; i < argc; i++) {
if (janet_checktype(argv[i], JANET_NUMBER)) {
janet_buffer_push_u8(buffer, (uint8_t)(janet_getinteger(argv, i) & 0xFF));
} else {
@@ -297,6 +320,36 @@ JANET_CORE_FN(cfun_buffer_push,
janet_buffer_push_bytes(buffer, view.bytes, view.len);
}
}
}
JANET_CORE_FN(cfun_buffer_push_at,
"(buffer/push-at buffer index & xs)",
"Same as buffer/push, but inserts new data at index `index`.") {
janet_arity(argc, 2, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
int32_t index = janet_getinteger(argv, 1);
int32_t old_count = buffer->count;
if (index < 0 || index > old_count) {
janet_panicf("index out of range [0, %d)", old_count);
}
buffer->count = index;
buffer_push_impl(buffer, argv, 2, argc);
if (buffer->count < old_count) {
buffer->count = old_count;
}
return argv[0];
}
JANET_CORE_FN(cfun_buffer_push,
"(buffer/push buffer & xs)",
"Push both individual bytes and byte sequences to a buffer. For each x in xs, "
"push the byte if x is an integer, otherwise push the bytesequence to the buffer. "
"Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. "
"Returns the modified buffer. "
"Will throw an error if the buffer overflows.") {
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
buffer_push_impl(buffer, argv, 1, argc);
return argv[0];
}
@@ -442,7 +495,7 @@ JANET_CORE_FN(cfun_buffer_blit,
JANET_CORE_FN(cfun_buffer_format,
"(buffer/format buffer format & args)",
"Snprintf like functionality for printing values into a buffer. Returns "
" the modified buffer.") {
"the modified buffer.") {
janet_arity(argc, 2, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
const char *strfrmt = (const char *) janet_getstring(argv, 1);
@@ -460,6 +513,7 @@ void janet_lib_buffer(JanetTable *env) {
JANET_CORE_REG("buffer/push-word", cfun_buffer_word),
JANET_CORE_REG("buffer/push-string", cfun_buffer_chars),
JANET_CORE_REG("buffer/push", cfun_buffer_push),
JANET_CORE_REG("buffer/push-at", cfun_buffer_push_at),
JANET_CORE_REG("buffer/popn", cfun_buffer_popn),
JANET_CORE_REG("buffer/clear", cfun_buffer_clear),
JANET_CORE_REG("buffer/slice", cfun_buffer_slice),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -218,6 +218,7 @@ JanetFuncDef *janet_funcdef_alloc(void) {
def->closure_bitset = NULL;
def->flags = 0;
def->slotcount = 0;
def->symbolmap = NULL;
def->arity = 0;
def->min_arity = 0;
def->max_arity = INT32_MAX;
@@ -229,6 +230,7 @@ JanetFuncDef *janet_funcdef_alloc(void) {
def->constants_length = 0;
def->bytecode_length = 0;
def->environments_length = 0;
def->symbolmap_length = 0;
return def;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -209,14 +209,28 @@ const char *janet_optcstring(const Janet *argv, int32_t argc, int32_t n, const c
#undef DEFINE_OPTLEN
const char *janet_getcstring(const Janet *argv, int32_t n) {
const uint8_t *jstr = janet_getstring(argv, n);
const char *cstr = (const char *)jstr;
if (strlen(cstr) != (size_t) janet_string_length(jstr)) {
janet_panic("string contains embedded 0s");
if (!janet_checktype(argv[n], JANET_STRING)) {
janet_panic_type(argv[n], n, JANET_TFLAG_STRING);
}
return janet_getcbytes(argv, n);
}
const char *janet_getcbytes(const Janet *argv, int32_t n) {
JanetByteView view = janet_getbytes(argv, n);
const char *cstr = (const char *)view.bytes;
if (strlen(cstr) != (size_t) view.len) {
janet_panic("bytes contain embedded 0s");
}
return cstr;
}
const char *janet_optcbytes(const Janet *argv, int32_t argc, int32_t n, const char *dflt) {
if (n >= argc || janet_checktype(argv[n], JANET_NIL)) {
return dflt;
}
return janet_getcbytes(argv, n);
}
int32_t janet_getnat(const Janet *argv, int32_t n) {
Janet x = argv[n];
if (!janet_checkint(x)) goto bad;
@@ -260,7 +274,7 @@ int32_t janet_getinteger(const Janet *argv, int32_t n) {
}
int64_t janet_getinteger64(const Janet *argv, int32_t n) {
#ifdef JANET_INTTYPES
#ifdef JANET_INT_TYPES
return janet_unwrap_s64(argv[n]);
#else
Janet x = argv[n];
@@ -272,7 +286,7 @@ int64_t janet_getinteger64(const Janet *argv, int32_t n) {
}
uint64_t janet_getuinteger64(const Janet *argv, int32_t n) {
#ifdef JANET_INTTYPES
#ifdef JANET_INT_TYPES
return janet_unwrap_u64(argv[n]);
#else
Janet x = argv[n];

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -93,10 +93,14 @@ void janetc_freeslot(JanetCompiler *c, JanetSlot s) {
/* Add a slot to a scope with a symbol associated with it (def or var). */
void janetc_nameslot(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.slot.flags |= JANET_SLOT_NAMED;
sp.birth_pc = cnt ? cnt - 1 : 0;
sp.death_pc = UINT32_MAX;
janet_v_push(c->scope->syms, sp);
}
@@ -159,21 +163,27 @@ void janetc_popscope(JanetCompiler *c) {
if (oldscope->flags & JANET_SCOPE_CLOSURE) {
newscope->flags |= JANET_SCOPE_CLOSURE;
}
if (newscope->ra.max < oldscope->ra.max)
if (newscope->ra.max < oldscope->ra.max) {
newscope->ra.max = oldscope->ra.max;
/* Keep upvalue slots */
for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) {
SymPair pair = oldscope->syms[i];
if (pair.keep) {
/* The variable should not be lexically accessible */
pair.sym = NULL;
janet_v_push(newscope->syms, pair);
janetc_regalloc_touch(&newscope->ra, pair.slot.index);
}
}
/* Keep upvalue slots and symbols for debugging. */
for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) {
SymPair pair = oldscope->syms[i];
/* The variable should not be lexically accessible */
pair.sym = NULL;
if (pair.death_pc == UINT32_MAX) {
pair.death_pc = (uint32_t) janet_v_count(c->buffer);
}
if (pair.keep) {
/* The variable should also not be included in the locals */
pair.sym2 = NULL;
janetc_regalloc_touch(&newscope->ra, pair.slot.index);
}
janet_v_push(newscope->syms, pair);
}
}
/* Free the old scope */
janet_v_free(oldscope->consts);
janet_v_free(oldscope->syms);
@@ -334,6 +344,7 @@ found:
}
/* non-local scope needs to expose its environment */
JanetScope *original_scope = scope;
pair->keep = 1;
while (scope && !(scope->flags & JANET_SCOPE_FUNCTION))
scope = scope->parent;
@@ -355,7 +366,7 @@ found:
/* Check if scope already has env. If so, break */
len = janet_v_count(scope->envs);
for (j = 0; j < len; j++) {
if (scope->envs[j] == envindex) {
if (scope->envs[j].envindex == envindex) {
scopefound = 1;
envindex = j;
break;
@@ -364,7 +375,10 @@ found:
/* Add the environment if it is not already referenced */
if (!scopefound) {
len = janet_v_count(scope->envs);
janet_v_push(scope->envs, envindex);
JanetEnvRef ref;
ref.envindex = envindex;
ref.scope = original_scope;
janet_v_push(scope->envs, ref);
envindex = len;
}
}
@@ -408,6 +422,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len) {
int32_t i;
JanetSlot *ret = NULL;
JanetFopts subopts = janetc_fopts_default(c);
subopts.flags |= JANET_FOPTS_ACCEPT_SPLICE;
for (i = 0; i < len; i++) {
janet_v_push(ret, janetc_value(subopts, vals[i]));
}
@@ -418,6 +433,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len) {
JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) {
JanetSlot *ret = NULL;
JanetFopts subopts = janetc_fopts_default(c);
subopts.flags |= JANET_FOPTS_ACCEPT_SPLICE;
const JanetKV *kvs = NULL;
int32_t cap = 0, len = 0;
janet_dictionary_view(ds, &kvs, &len, &cap);
@@ -868,7 +884,10 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
/* Copy envs */
def->environments_length = janet_v_count(scope->envs);
def->environments = janet_v_flatten(scope->envs);
def->environments = janet_malloc(sizeof(int32_t) * def->environments_length);
for (int32_t i = 0; i < def->environments_length; i++) {
def->environments[i] = scope->envs[i].envindex;
}
def->constants_length = janet_v_count(scope->consts);
def->constants = janet_v_flatten(scope->consts);
@@ -923,6 +942,50 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
def->closure_bitset = chunks;
}
/* Capture symbol to local mapping */
JanetSymbolMap *locals = NULL;
/* Symbol -> upvalue mapping */
JanetScope *top = c->scope;
while (top->parent) top = top->parent;
for (JanetScope *s = top; s != NULL; s = s->child) {
for (int32_t j = 0; j < janet_v_count(scope->envs); j++) {
JanetEnvRef ref = scope->envs[j];
JanetScope *upscope = ref.scope;
if (upscope != s) continue;
for (int32_t i = 0; i < janet_v_count(upscope->syms); i++) {
SymPair pair = upscope->syms[i];
if (pair.sym2) {
JanetSymbolMap jsm;
jsm.birth_pc = UINT32_MAX;
jsm.death_pc = j;
jsm.slot_index = pair.slot.index;
jsm.symbol = pair.sym2;
janet_v_push(locals, jsm);
}
}
}
}
/* Symbol -> slot mapping */
for (int32_t i = 0; i < janet_v_count(scope->syms); i++) {
SymPair pair = scope->syms[i];
if (pair.sym2) {
if (pair.death_pc == UINT32_MAX) {
pair.death_pc = def->bytecode_length;
}
JanetSymbolMap jsm;
jsm.birth_pc = pair.birth_pc;
jsm.death_pc = pair.death_pc;
jsm.slot_index = pair.slot.index;
jsm.symbol = pair.sym2;
janet_v_push(locals, jsm);
}
}
def->symbolmap_length = janet_v_count(locals);
def->symbolmap = janet_v_flatten(locals);
if (def->symbolmap_length) def->flags |= JANET_FUNCDEF_FLAG_HASSYMBOLMAP;
/* Pop the scope */
janetc_popscope(c);
@@ -996,7 +1059,7 @@ JanetCompileResult janet_compile(Janet source, JanetTable *env, const uint8_t *w
}
/* C Function for compiling */
JANET_CORE_FN(cfun,
JANET_CORE_FN(cfun_compile,
"(compile ast &opt env source lints)",
"Compiles an Abstract Syntax Tree (ast) into a function. "
"Pair the compile function with parsing functionality to implement "
@@ -1005,7 +1068,8 @@ JANET_CORE_FN(cfun,
"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_arity(argc, 1, 4);
JanetTable *env = argc > 1 ? janet_gettable(argv, 1) : janet_vm.fiber->env;
JanetTable *env = (argc > 1 && !janet_checktype(argv[1], JANET_NIL))
? janet_gettable(argv, 1) : janet_vm.fiber->env;
if (NULL == env) {
env = janet_table(0);
janet_vm.fiber->env = env;
@@ -1017,11 +1081,12 @@ JANET_CORE_FN(cfun,
source = janet_unwrap_string(x);
} else if (janet_checktype(x, JANET_KEYWORD)) {
source = janet_unwrap_keyword(x);
} else {
} else if (!janet_checktype(x, JANET_NIL)) {
janet_panic_type(x, 2, JANET_TFLAG_STRING | JANET_TFLAG_KEYWORD);
}
}
JanetArray *lints = (argc >= 4) ? janet_getarray(argv, 3) : NULL;
JanetArray *lints = (argc >= 4 && !janet_checktype(argv[3], JANET_NIL))
? janet_getarray(argv, 3) : NULL;
JanetCompileResult res = janet_compile_lint(argv[0], env, source, lints);
if (res.status == JANET_COMPILE_OK) {
return janet_wrap_function(janet_thunk(res.funcdef));
@@ -1043,7 +1108,7 @@ JANET_CORE_FN(cfun,
void janet_lib_compile(JanetTable *env) {
JanetRegExt cfuns[] = {
JANET_CORE_REG("compile", cfun),
JANET_CORE_REG("compile", cfun_compile),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, cfuns);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -111,13 +111,21 @@ struct JanetSlot {
typedef struct SymPair {
JanetSlot slot;
const uint8_t *sym;
const uint8_t *sym2;
int keep;
uint32_t birth_pc;
uint32_t death_pc;
} SymPair;
typedef struct JanetEnvRef {
int32_t envindex;
JanetScope *scope;
} JanetEnvRef;
/* A lexical scope during compilation */
struct JanetScope {
/* For debugging */
/* For debugging the compiler */
const char *name;
/* Scopes are doubly linked list */
@@ -133,7 +141,7 @@ struct JanetScope {
/* FuncDefs */
JanetFuncDef **defs;
/* Regsiter allocator */
/* Register allocator */
JanetcRegisterAllocator ra;
/* Upvalue allocator */
@@ -142,7 +150,7 @@ struct JanetScope {
/* Referenced closure environments. The values at each index correspond
* to which index to get the environment from in the parent. The environment
* that corresponds to the direct parent's stack will always have value 0. */
int32_t *envs;
JanetEnvRef *envs;
int32_t bytecode_start;
int flags;
@@ -179,6 +187,7 @@ struct JanetCompiler {
#define JANET_FOPTS_TAIL 0x10000
#define JANET_FOPTS_HINT 0x20000
#define JANET_FOPTS_DROP 0x40000
#define JANET_FOPTS_ACCEPT_SPLICE 0x80000
/* Options for compiling a single form */
struct JanetFopts {
@@ -227,7 +236,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len);
/* Get a bunch of slots for function arguments */
JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds);
/* Push slots load via janetc_toslots. */
/* Push slots loaded via janetc_toslots. */
int32_t janetc_pushslots(JanetCompiler *c, JanetSlot *slots);
/* Free slots loaded via janetc_toslots */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -43,6 +43,7 @@ extern size_t janet_core_image_size;
#endif
JanetModule janet_native(const char *name, const uint8_t **error) {
janet_sandbox_assert(JANET_SANDBOX_DYNAMIC_MODULES);
char *processed_name = get_processed_name(name);
Clib lib = load_clib(processed_name);
JanetModule init;
@@ -111,7 +112,10 @@ JANET_CORE_FN(janet_core_expand_path,
"This takes in a path (the argument to require) and a template string, "
"to expand the path to a path that can be "
"used for importing files. The replacements are as follows:\n\n"
"* :all: -- the value of path verbatim\n\n"
"* :all: -- the value of path verbatim.\n\n"
"* :@all: -- Same as :all:, but if `path` starts with the @ character,\n"
" the first path segment is replaced with a dynamic binding\n"
" `(dyn <first path segment as keyword>)`.\n\n"
"* :cur: -- the current file, or (dyn :current-file)\n\n"
"* :dir: -- the directory containing the current file\n\n"
"* :name: -- the name component of path, with extension if given\n\n"
@@ -157,6 +161,21 @@ JANET_CORE_FN(janet_core_expand_path,
if (strncmp(template + i, ":all:", 5) == 0) {
janet_buffer_push_cstring(out, input);
i += 4;
} else if (strncmp(template + i, ":@all:", 6) == 0) {
if (input[0] == '@') {
const char *p = input;
while (*p && !is_path_sep(*p)) p++;
size_t len = p - input - 1;
char *str = janet_smalloc(len + 1);
memcpy(str, input + 1, len);
str[len] = '\0';
janet_formatb(out, "%V", janet_dyn(str));
janet_sfree(str);
janet_buffer_push_cstring(out, p);
} else {
janet_buffer_push_cstring(out, input);
}
i += 5;
} else if (strncmp(template + i, ":cur:", 5) == 0) {
janet_buffer_push_bytes(out, (const uint8_t *)curdir, curlen);
i += 4;
@@ -614,27 +633,97 @@ JANET_CORE_FN(janet_core_signal,
"(signal what x)",
"Raise a signal with payload x. ") {
janet_arity(argc, 1, 2);
int sig;
Janet payload = argc == 2 ? argv[1] : janet_wrap_nil();
if (janet_checkint(argv[0])) {
int32_t s = janet_unwrap_integer(argv[0]);
if (s < 0 || s > 9) {
janet_panicf("expected user signal between 0 and 9, got %d", s);
}
sig = JANET_SIGNAL_USER0 + s;
janet_signalv(JANET_SIGNAL_USER0 + s, payload);
} else {
JanetKeyword kw = janet_getkeyword(argv, 0);
if (!janet_cstrcmp(kw, "yield")) {
sig = JANET_SIGNAL_YIELD;
} else if (!janet_cstrcmp(kw, "error")) {
sig = JANET_SIGNAL_ERROR;
} else if (!janet_cstrcmp(kw, "debug")) {
sig = JANET_SIGNAL_DEBUG;
} else {
janet_panicf("unknown signal, expected :yield, :error, or :debug, got %v", argv[0]);
for (unsigned i = 0; i < sizeof(janet_signal_names) / sizeof(char *); i++) {
if (!janet_cstrcmp(kw, janet_signal_names[i])) {
janet_signalv((JanetSignal) i, payload);
}
}
}
Janet payload = argc == 2 ? argv[1] : janet_wrap_nil();
janet_signalv(sig, payload);
janet_panicf("unknown signal %v", argv[0]);
}
JANET_CORE_FN(janet_core_memcmp,
"(memcmp a b &opt len offset-a offset-b)",
"Compare memory. Takes two byte sequences `a` and `b`, and "
"return 0 if they have identical contents, a negative integer if a is less than b, "
"and a positive integer if a is greater than b. Optionally take a length and offsets "
"to compare slices of the bytes sequences.") {
janet_arity(argc, 2, 5);
JanetByteView a = janet_getbytes(argv, 0);
JanetByteView b = janet_getbytes(argv, 1);
int32_t len = janet_optnat(argv, argc, 2, a.len < b.len ? a.len : b.len);
int32_t offset_a = janet_optnat(argv, argc, 3, 0);
int32_t offset_b = janet_optnat(argv, argc, 4, 0);
if (offset_a + len > a.len) janet_panicf("invalid offset-a: %d", offset_a);
if (offset_b + len > b.len) janet_panicf("invalid offset-b: %d", offset_b);
return janet_wrap_integer(memcmp(a.bytes + offset_a, b.bytes + offset_b, (size_t) len));
}
typedef struct SandboxOption {
const char *name;
uint32_t flag;
} SandboxOption;
static const SandboxOption sandbox_options[] = {
{"all", JANET_SANDBOX_ALL},
{"env", JANET_SANDBOX_ENV},
{"ffi", JANET_SANDBOX_FFI},
{"fs", JANET_SANDBOX_FS},
{"fs-read", JANET_SANDBOX_FS_READ},
{"fs-temp", JANET_SANDBOX_FS_TEMP},
{"fs-write", JANET_SANDBOX_FS_WRITE},
{"hrtime", JANET_SANDBOX_HRTIME},
{"modules", JANET_SANDBOX_DYNAMIC_MODULES},
{"net", JANET_SANDBOX_NET},
{"net-connect", JANET_SANDBOX_NET_CONNECT},
{"net-listen", JANET_SANDBOX_NET_LISTEN},
{"sandbox", JANET_SANDBOX_SANDBOX},
{"subprocess", JANET_SANDBOX_SUBPROCESS},
{NULL, 0}
};
JANET_CORE_FN(janet_core_sandbox,
"(sandbox & forbidden-capabilities)",
"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"
"* :env - disallow reading and write env variables\n"
"* :ffi - disallow FFI (recommended if disabling anything else)\n"
"* :fs - disallow access to the file system\n"
"* :fs-read - disallow read access to the file system\n"
"* :fs-temp - disallow creating temporary files\n"
"* :fs-write - disallow write access to the file system\n"
"* :hrtime - disallow high-resolution timers\n"
"* :modules - disallow load dynamic modules (natives)\n"
"* :net - disallow network access\n"
"* :net-connect - disallow making outbound network connections\n"
"* :net-listen - disallow accepting inbound network connections\n"
"* :sandbox - disallow calling this function\n"
"* :subprocess - disallow running subprocesses") {
uint32_t flags = 0;
for (int32_t i = 0; i < argc; i++) {
JanetKeyword kw = janet_getkeyword(argv, i);
const SandboxOption *opt = sandbox_options;
while (opt->name != NULL) {
if (janet_cstrcmp(kw, opt->name) == 0) {
flags |= opt->flag;
break;
}
opt++;
}
if (opt->name == NULL) janet_panicf("unknown capability %v", argv[i]);
}
janet_sandbox(flags);
return janet_wrap_nil();
}
#ifdef JANET_BOOTSTRAP
@@ -938,7 +1027,9 @@ static void janet_load_libs(JanetTable *env) {
JANET_CORE_REG("nat?", janet_core_check_nat),
JANET_CORE_REG("slice", janet_core_slice),
JANET_CORE_REG("signal", janet_core_signal),
JANET_CORE_REG("memcmp", janet_core_memcmp),
JANET_CORE_REG("getproto", janet_core_getproto),
JANET_CORE_REG("sandbox", janet_core_sandbox),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, corelib_cfuns);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -131,9 +131,9 @@ void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) {
if (!wrote_error) {
JanetFiberStatus status = janet_fiber_status(fiber);
janet_eprintf("%s%s: %s\n",
prefix,
prefix ? prefix : "",
janet_status_names[status],
errstr);
errstr ? errstr : janet_status_names[status]);
wrote_error = 1;
}
@@ -329,6 +329,27 @@ static Janet doframe(JanetStackFrame *frame) {
safe_memcpy(slots->data, stack, sizeof(Janet) * def->slotcount);
slots->count = def->slotcount;
janet_table_put(t, janet_ckeywordv("slots"), janet_wrap_array(slots));
/* Add local bindings */
if (def->symbolmap) {
JanetTable *local_bindings = janet_table(0);
for (int32_t i = def->symbolmap_length - 1; i >= 0; i--) {
JanetSymbolMap jsm = def->symbolmap[i];
Janet value = janet_wrap_nil();
uint32_t pc = (uint32_t)(frame->pc - def->bytecode);
if (jsm.birth_pc == UINT32_MAX) {
JanetFuncEnv *env = frame->func->envs[jsm.death_pc];
if (env->offset > 0) {
value = env->as.fiber->data[env->offset + jsm.slot_index];
} else {
value = env->as.values[jsm.slot_index];
}
} else if (pc >= jsm.birth_pc && pc < jsm.death_pc) {
value = stack[jsm.slot_index];
}
janet_table_put(local_bindings, janet_wrap_symbol(jsm.symbol), value);
}
janet_table_put(t, janet_ckeywordv("locals"), janet_wrap_table(local_bindings));
}
}
return janet_wrap_table(t);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -172,6 +172,9 @@ static JanetTimestamp ts_now(void);
/* Get current timestamp + an interval (millisecond precision) */
static JanetTimestamp ts_delta(JanetTimestamp ts, double delta) {
if (isinf(delta)) {
return delta < 0 ? ts : INT64_MAX;
}
ts += (int64_t)round(delta * 1000);
return ts;
}
@@ -469,8 +472,12 @@ const JanetAbstractType janet_stream_type = {
/* Register a fiber to resume with value */
void janet_schedule_signal(JanetFiber *fiber, Janet value, JanetSignal sig) {
if (fiber->gc.flags & JANET_FIBER_EV_FLAG_CANCELED) return;
fiber->gc.flags |= JANET_FIBER_FLAG_ROOT;
if (!(fiber->gc.flags & JANET_FIBER_FLAG_ROOT)) {
Janet task_element = janet_wrap_fiber(fiber);
janet_table_put(&janet_vm.active_tasks, task_element, janet_wrap_true());
}
JanetTask t = { fiber, value, sig, ++fiber->sched_id };
fiber->gc.flags |= JANET_FIBER_FLAG_ROOT;
if (sig == JANET_SIGNAL_ERROR) fiber->gc.flags |= JANET_FIBER_EV_FLAG_CANCELED;
janet_q_push(&janet_vm.spawn, &t, sizeof(t));
}
@@ -556,7 +563,12 @@ void janet_ev_init_common(void) {
janet_vm.tq_count = 0;
janet_vm.tq_capacity = 0;
janet_table_init_raw(&janet_vm.threaded_abstracts, 0);
janet_table_init_raw(&janet_vm.active_tasks, 0);
janet_rng_seed(&janet_vm.ev_rng, 0);
#ifndef JANET_WINDOWS
pthread_attr_init(&janet_vm.new_thread_attr);
pthread_attr_setdetachstate(&janet_vm.new_thread_attr, PTHREAD_CREATE_DETACHED);
#endif
}
/* Common deinit code */
@@ -566,10 +578,15 @@ void janet_ev_deinit_common(void) {
janet_free(janet_vm.listeners);
janet_vm.listeners = NULL;
janet_table_deinit(&janet_vm.threaded_abstracts);
janet_table_deinit(&janet_vm.active_tasks);
#ifndef JANET_WINDOWS
pthread_attr_destroy(&janet_vm.new_thread_attr);
#endif
}
/* Short hand to yield to event loop */
/* Shorthand to yield to event loop */
void janet_await(void) {
/* Store the fiber in a gobal table */
janet_signalv(JANET_SIGNAL_EVENT, janet_wrap_nil());
}
@@ -655,19 +672,6 @@ static void janet_chan_init(JanetChannel *chan, int32_t limit, int threaded) {
janet_os_mutex_init((JanetOSMutex *) &chan->lock);
}
static void janet_chan_deinit(JanetChannel *chan) {
janet_q_deinit(&chan->read_pending);
janet_q_deinit(&chan->write_pending);
if (janet_chan_is_threaded(chan)) {
Janet item;
while (!janet_q_pop(&chan->items, &item, sizeof(item))) {
janet_chan_unpack(chan, &item, 1);
}
}
janet_q_deinit(&chan->items);
janet_os_mutex_deinit((JanetOSMutex *) &chan->lock);
}
static void janet_chan_lock(JanetChannel *chan) {
if (!janet_chan_is_threaded(chan)) return;
janet_os_mutex_lock((JanetOSMutex *) &chan->lock);
@@ -678,6 +682,25 @@ static void janet_chan_unlock(JanetChannel *chan) {
janet_os_mutex_unlock((JanetOSMutex *) &chan->lock);
}
static void janet_chan_deinit(JanetChannel *chan) {
if (janet_chan_is_threaded(chan)) {
Janet item;
janet_chan_lock(chan);
janet_q_deinit(&chan->read_pending);
janet_q_deinit(&chan->write_pending);
while (!janet_q_pop(&chan->items, &item, sizeof(item))) {
janet_chan_unpack(chan, &item, 1);
}
janet_q_deinit(&chan->items);
janet_chan_unlock(chan);
} else {
janet_q_deinit(&chan->read_pending);
janet_q_deinit(&chan->write_pending);
janet_q_deinit(&chan->items);
}
janet_os_mutex_deinit((JanetOSMutex *) &chan->lock);
}
/*
* Janet Channel abstract type
*/
@@ -754,6 +777,7 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
int mode = msg.tag;
JanetChannel *channel = (JanetChannel *) msg.argp;
Janet x = msg.argj;
janet_chan_lock(channel);
if (fiber->sched_id == sched_id) {
if (mode == JANET_CP_MODE_CHOICE_READ) {
janet_assert(!janet_chan_unpack(channel, &x, 0), "packing error");
@@ -774,7 +798,6 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
int is_read = (mode == JANET_CP_MODE_CHOICE_READ) || (mode == JANET_CP_MODE_READ);
if (is_read) {
JanetChannelPending reader;
janet_chan_lock(channel);
if (!janet_q_pop(&channel->read_pending, &reader, sizeof(reader))) {
JanetVM *vm = reader.thread;
JanetEVGenericMessage msg;
@@ -785,10 +808,8 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
janet_chan_unlock(channel);
} else {
JanetChannelPending writer;
janet_chan_lock(channel);
if (!janet_q_pop(&channel->write_pending, &writer, sizeof(writer))) {
JanetVM *vm = writer.thread;
JanetEVGenericMessage msg;
@@ -799,21 +820,21 @@ static void janet_thread_chan_cb(JanetEVGenericMessage msg) {
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
}
janet_chan_unlock(channel);
}
}
janet_chan_unlock(channel);
}
/* Push a value to a channel, and return 1 if channel should block, zero otherwise.
* If the push would block, will add to the write_pending queue in the channel.
* Handles both threaded and unthreaded channels. */
static int janet_channel_push(JanetChannel *channel, Janet x, int mode) {
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);
}
janet_chan_lock(channel);
if (channel->closed) {
janet_chan_unlock(channel);
janet_panic("cannot write to closed channel");
@@ -874,12 +895,16 @@ static int janet_channel_push(JanetChannel *channel, Janet x, int mode) {
return 0;
}
static int janet_channel_push(JanetChannel *channel, Janet x, int mode) {
janet_chan_lock(channel);
return janet_channel_push_with_lock(channel, x, mode);
}
/* Pop from a channel - returns 1 if item was obtained, 0 otherwise. The item
* is returned by reference. If the pop would block, will add to the read_pending
* queue in the channel. */
static int janet_channel_pop(JanetChannel *channel, Janet *item, int is_choice) {
static int janet_channel_pop_with_lock(JanetChannel *channel, Janet *item, int is_choice) {
JanetChannelPending writer;
janet_chan_lock(channel);
if (channel->closed) {
janet_chan_unlock(channel);
*item = janet_wrap_nil();
@@ -924,6 +949,11 @@ static int janet_channel_pop(JanetChannel *channel, Janet *item, int is_choice)
return 1;
}
static int janet_channel_pop(JanetChannel *channel, Janet *item, int is_choice) {
janet_chan_lock(channel);
return janet_channel_pop_with_lock(channel, item, is_choice);
}
JanetChannel *janet_channel_unwrap(void *abstract) {
return abstract;
}
@@ -966,13 +996,32 @@ JANET_CORE_FN(cfun_channel_pop,
janet_await();
}
static void chan_unlock_args(const Janet *argv, int32_t n) {
for (int32_t i = 0; i < n; i++) {
int32_t len;
const Janet *data;
JanetChannel *chan;
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
chan = janet_getchannel(data, 0);
} else {
chan = janet_getchannel(argv, i);
}
janet_chan_unlock(chan);
}
}
JANET_CORE_FN(cfun_channel_choice,
"(ev/select & clauses)",
"Block until the first of several channel operations occur. Returns a tuple of the form [:give chan], [:take chan x], or [:close chan], where "
"a :give tuple is the result of a write and :take tuple is the result of a read. Each clause must be either a channel (for "
"a channel take operation) or a tuple [channel x] for a channel give operation. Operations are tried in order, such that the first "
"clauses will take precedence over later clauses. Both and give and take operations can return a [:close chan] tuple, which indicates that "
"the specified channel was closed while waiting, or that the channel was already closed.") {
"Block until the first of several channel operations occur. Returns a "
"tuple of the form [:give chan], [:take chan x], or [:close chan], "
"where a :give tuple is the result of a write and a :take tuple is the "
"result of a read. Each clause must be either a channel (for a channel "
"take operation) or a tuple [channel x] (for a channel give operation). "
"Operations are tried in order such that earlier clauses take "
"precedence over later clauses. Both give and take operations can "
"return a [:close chan] tuple, which indicates that the specified "
"channel was closed while waiting, or that the channel was already "
"closed.") {
janet_arity(argc, 1, -1);
int32_t len;
const Janet *data;
@@ -985,29 +1034,29 @@ 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_chan_unlock(chan);
janet_channel_push(chan, data[1], 1);
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_chan_unlock(chan);
janet_channel_pop(chan, &item, 1);
janet_channel_pop_with_lock(chan, &item, 1);
chan_unlock_args(argv, i);
return make_read_result(chan, item);
}
janet_chan_unlock(chan);
}
}
@@ -1016,12 +1065,12 @@ JANET_CORE_FN(cfun_channel_choice,
if (janet_indexed_view(argv[i], &data, &len) && len == 2) {
/* Write */
JanetChannel *chan = janet_getchannel(data, 0);
janet_channel_push(chan, data[1], 1);
janet_channel_push_with_lock(chan, data[1], 1);
} else {
/* Read */
Janet item;
JanetChannel *chan = janet_getchannel(argv, i);
janet_channel_pop(chan, &item, 1);
janet_channel_pop_with_lock(chan, &item, 1);
}
}
@@ -1175,14 +1224,48 @@ static Janet janet_chanat_next(void *p, Janet key) {
return janet_nextmethod(ev_chanat_methods, key);
}
static void janet_chanat_marshal(void *p, JanetMarshalContext *ctx) {
JanetChannel *channel = (JanetChannel *)p;
janet_marshal_byte(ctx, channel->closed);
janet_marshal_int(ctx, channel->limit);
int32_t count = janet_q_count(&channel->items);
janet_marshal_int(ctx, count);
JanetQueue *items = &channel->items;
Janet *data = channel->items.data;
if (items->head <= items->tail) {
for (int32_t i = items->head; i < items->tail; i++)
janet_marshal_janet(ctx, data[i]);
} else {
for (int32_t i = items->head; i < items->capacity; i++)
janet_marshal_janet(ctx, data[i]);
for (int32_t i = 0; i < items->tail; i++)
janet_marshal_janet(ctx, data[i]);
}
}
static void *janet_chanat_unmarshal(JanetMarshalContext *ctx) {
JanetChannel *abst = janet_unmarshal_abstract(ctx, sizeof(JanetChannel));
uint8_t is_closed = janet_unmarshal_byte(ctx);
int32_t limit = janet_unmarshal_int(ctx);
int32_t count = janet_unmarshal_int(ctx);
if (count < 0) janet_panic("invalid negative 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));
}
return abst;
}
const JanetAbstractType janet_channel_type = {
"core/channel",
janet_chanat_gc,
janet_chanat_mark,
janet_chanat_get,
NULL, /* put */
NULL, /* marshal */
NULL, /* unmarshal */
janet_chanat_marshal,
janet_chanat_unmarshal,
NULL, /* tostring */
NULL, /* compare */
NULL, /* hash */
@@ -1208,16 +1291,7 @@ JanetFiber *janet_loop1(void) {
while (peek_timeout(&to) && to.when <= now) {
pop_timeout(0);
if (to.curr_fiber != NULL) {
/* This is a deadline (for a fiber, not a function call) */
JanetFiberStatus s = janet_fiber_status(to.curr_fiber);
int isFinished = (s == JANET_STATUS_DEAD ||
s == JANET_STATUS_ERROR ||
s == JANET_STATUS_USER0 ||
s == JANET_STATUS_USER1 ||
s == JANET_STATUS_USER2 ||
s == JANET_STATUS_USER3 ||
s == JANET_STATUS_USER4);
if (!isFinished) {
if (janet_fiber_can_resume(to.curr_fiber)) {
janet_cancel(to.fiber, janet_cstringv("deadline expired"));
}
} else {
@@ -1241,6 +1315,9 @@ JanetFiber *janet_loop1(void) {
if (task.expected_sched_id != task.fiber->sched_id) continue;
Janet res;
JanetSignal sig = janet_continue_signal(task.fiber, task.value, &res, task.sig);
if (!janet_fiber_can_resume(task.fiber)) {
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(task.fiber));
}
void *sv = task.fiber->supervisor_channel;
int is_suspended = sig == JANET_SIGNAL_EVENT || sig == JANET_SIGNAL_YIELD || sig == JANET_SIGNAL_INTERRUPT;
if (is_suspended) {
@@ -1272,15 +1349,8 @@ JanetFiber *janet_loop1(void) {
/* Drop timeouts that are no longer needed */
while ((has_timeout = peek_timeout(&to))) {
if (to.curr_fiber != NULL) {
JanetFiberStatus s = janet_fiber_status(to.curr_fiber);
int is_finished = (s == JANET_STATUS_DEAD ||
s == JANET_STATUS_ERROR ||
s == JANET_STATUS_USER0 ||
s == JANET_STATUS_USER1 ||
s == JANET_STATUS_USER2 ||
s == JANET_STATUS_USER3 ||
s == JANET_STATUS_USER4);
if (is_finished) {
if (!janet_fiber_can_resume(to.curr_fiber)) {
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
pop_timeout(0);
continue;
}
@@ -2038,12 +2108,11 @@ void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage ar
#else
init->write_pipe = janet_vm.selfpipe[1];
pthread_t waiter_thread;
int err = pthread_create(&waiter_thread, NULL, janet_thread_body, init);
int err = pthread_create(&waiter_thread, &janet_vm.new_thread_attr, janet_thread_body, init);
if (err) {
janet_free(init);
janet_panicf("%s", strerror(err));
}
pthread_detach(waiter_thread);
#endif
/* Increment ev refcount so we don't quit while waiting for a subprocess */
@@ -2172,9 +2241,9 @@ typedef struct {
JanetReadMode mode;
#ifdef JANET_WINDOWS
OVERLAPPED overlapped;
DWORD flags;
#ifdef JANET_NET
WSABUF wbuf;
DWORD flags;
struct sockaddr from;
int fromlen;
#endif
@@ -2233,7 +2302,7 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
#ifdef JANET_NET
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
state->wbuf.len = (ULONG) chunk_size;
state->wbuf.buf = state->chunk_buf;
state->wbuf.buf = (char *) state->chunk_buf;
status = WSARecvFrom((SOCKET) s->stream->handle, &state->wbuf, 1,
NULL, &state->flags, &state->from, &state->fromlen, &state->overlapped, NULL);
if (status && (WSA_IO_PENDING != WSAGetLastError())) {
@@ -2248,8 +2317,8 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
state->overlapped.Offset = (DWORD) state->bytes_read;
status = ReadFile(s->stream->handle, state->chunk_buf, chunk_size, NULL, &state->overlapped);
if (!status && (ERROR_IO_PENDING != WSAGetLastError())) {
if (WSAGetLastError() == ERROR_BROKEN_PIPE) {
if (!status && (ERROR_IO_PENDING != GetLastError())) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
if (state->bytes_read) {
janet_schedule(s->fiber, janet_wrap_buffer(state->buf));
} else {
@@ -2401,9 +2470,9 @@ typedef struct {
void *dest_abst;
#ifdef JANET_WINDOWS
OVERLAPPED overlapped;
DWORD flags;
#ifdef JANET_NET
WSABUF wbuf;
DWORD flags;
#endif
#else
int flags;
@@ -2488,7 +2557,7 @@ JanetAsyncStatus ev_machine_write(JanetListenerState *s, JanetAsyncEvent event)
state->overlapped.Offset = (DWORD) 0xFFFFFFFF;
state->overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF;
status = WriteFile(s->stream->handle, bytes, len, NULL, &state->overlapped);
if (!status && (ERROR_IO_PENDING != WSAGetLastError())) {
if (!status && (ERROR_IO_PENDING != GetLastError())) {
janet_cancel(s->fiber, janet_ev_lasterr());
return JANET_ASYNC_STATUS_DONE;
}
@@ -2624,15 +2693,15 @@ int janet_make_pipe(JanetHandle handles[2], int mode) {
* so we lift from the windows source code and modify for our own version.
*/
JanetHandle shandle, chandle;
UCHAR PipeNameBuffer[MAX_PATH];
CHAR PipeNameBuffer[MAX_PATH];
SECURITY_ATTRIBUTES saAttr;
memset(&saAttr, 0, sizeof(saAttr));
saAttr.nLength = sizeof(saAttr);
saAttr.bInheritHandle = TRUE;
sprintf(PipeNameBuffer,
"\\\\.\\Pipe\\JanetPipeFile.%08x.%08x",
GetCurrentProcessId(),
InterlockedIncrement(&PipeSerialNumber));
(unsigned int) GetCurrentProcessId(),
(unsigned int) InterlockedIncrement(&PipeSerialNumber));
/* server handle goes to subprocess */
shandle = CreateNamedPipeA(
@@ -2687,9 +2756,10 @@ error:
/* C functions */
JANET_CORE_FN(cfun_ev_go,
"(ev/go fiber &opt value supervisor)",
"Put a fiber on the event loop to be resumed later. Optionally pass "
"a value to resume with, otherwise resumes with nil. Returns the fiber. "
"(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.") {
@@ -2734,6 +2804,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
uint32_t flags = args.tag;
args.tag = 0;
janet_init();
janet_vm.sandbox_flags = (uint32_t) args.argi;
JanetTryState tstate;
JanetSignal signal = janet_try(&tstate);
if (!signal) {
@@ -2886,19 +2957,19 @@ JANET_CORE_FN(cfun_ev_thread,
JanetEVGenericMessage arguments;
memset(&arguments, 0, sizeof(arguments));
arguments.tag = (uint32_t) flags;
arguments.argi = argc;
arguments.argi = (uint32_t) janet_vm.sandbox_flags;
arguments.argp = buffer;
arguments.fiber = NULL;
janet_ev_threaded_call(janet_go_thread_subr, arguments, janet_ev_default_threaded_callback);
return janet_wrap_nil();
} else {
janet_ev_threaded_await(janet_go_thread_subr, (uint32_t) flags, argc, buffer);
janet_ev_threaded_await(janet_go_thread_subr, (uint32_t) flags, (uint32_t) janet_vm.sandbox_flags, buffer);
}
}
JANET_CORE_FN(cfun_ev_give_supervisor,
"(ev/give-supervisor tag & payload)",
"Send a message to the current supervior channel if there is one. The message will be a "
"Send a message to the current supervisor channel if there is one. The message will be a "
"tuple of all of the arguments combined into a single message, where the first element is tag. "
"By convention, tag should be a keyword indicating the type of message. Returns nil.") {
janet_arity(argc, 1, -1);
@@ -3129,6 +3200,20 @@ JANET_CORE_FN(janet_cfun_rwlock_write_release,
return argv[0];
}
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.") {
janet_fixarity(argc, 0);
(void) argv;
JanetArray *array = janet_array(janet_vm.active_tasks.count);
for (int32_t i = 0; i < janet_vm.active_tasks.capacity; i++) {
if (!janet_checktype(janet_vm.active_tasks.data[i].key, JANET_NIL)) {
janet_array_push(array, janet_vm.active_tasks.data[i].key);
}
}
return janet_wrap_array(array);
}
void janet_lib_ev(JanetTable *env) {
JanetRegExt ev_cfuns_ext[] = {
JANET_CORE_REG("ev/give", cfun_channel_push),
@@ -3159,6 +3244,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/all-tasks", janet_cfun_ev_all_tasks),
JANET_REG_END
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -61,4 +61,9 @@
#define _NETBSD_SOURCE
#endif
/* Needed for several things when building with -std=c99. */
#if !__BSD_VISIBLE && defined(__DragonFly__)
#define __BSD_VISIBLE 1
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -24,6 +24,7 @@
#include "features.h"
#include <janet.h>
#include "util.h"
#include "gc.h"
#endif
#ifdef JANET_FFI
@@ -37,6 +38,13 @@
#define alloca __builtin_alloca
#endif
/* FFI jit includes */
#ifdef JANET_FFI_JIT
#ifndef JANET_WINDOWS
#include <sys/mman.h>
#endif
#endif
#define JANET_FFI_MAX_RECUR 64
/* Compiler, OS, and arch detection. Used
@@ -123,9 +131,10 @@ typedef enum {
JANET_SYSV64_INTEGER,
JANET_SYSV64_SSE,
JANET_SYSV64_SSEUP,
JANET_SYSV64_X87,
JANET_SYSV64_X87UP,
JANET_SYSV64_COMPLEX_X87,
JANET_SYSV64_PAIR_INTINT,
JANET_SYSV64_PAIR_INTSSE,
JANET_SYSV64_PAIR_SSEINT,
JANET_SYSV64_PAIR_SSESSE,
JANET_SYSV64_NO_CLASS,
JANET_SYSV64_MEMORY,
JANET_WIN64_REGISTER,
@@ -201,6 +210,11 @@ int struct_mark(void *p, size_t s) {
return 0;
}
typedef struct {
void *function_pointer;
size_t size;
} JanetFFIJittedFn;
static const JanetAbstractType janet_struct_type = {
"core/ffi-struct",
NULL,
@@ -208,6 +222,42 @@ static const JanetAbstractType janet_struct_type = {
JANET_ATEND_GCMARK
};
static int janet_ffijit_gc(void *p, size_t s) {
(void) s;
JanetFFIJittedFn *fn = p;
if (fn->function_pointer == NULL) return 0;
#ifdef JANET_FFI_JIT
#ifdef JANET_WINDOWS
VirtualFree(fn->function_pointer, fn->size, MEM_RELEASE);
#else
munmap(fn->function_pointer, fn->size);
#endif
#endif
return 0;
}
static JanetByteView janet_ffijit_getbytes(void *p, size_t s) {
(void) s;
JanetFFIJittedFn *fn = p;
JanetByteView bytes;
bytes.bytes = fn->function_pointer;
bytes.len = (int32_t) fn->size;
return bytes;
}
static size_t janet_ffijit_length(void *p, size_t s) {
(void) s;
JanetFFIJittedFn *fn = p;
return fn->size;
}
const JanetAbstractType janet_type_ffijit = {
.name = "ffi/jitfn",
.gc = janet_ffijit_gc,
.bytes = janet_ffijit_getbytes,
.length = janet_ffijit_length
};
typedef struct {
Clib clib;
int closed;
@@ -260,6 +310,7 @@ static JanetFFIPrimType decode_ffi_prim(const uint8_t *name) {
if (!janet_cstrcmp(name, "void")) return JANET_FFI_TYPE_VOID;
if (!janet_cstrcmp(name, "bool")) return JANET_FFI_TYPE_BOOL;
if (!janet_cstrcmp(name, "ptr")) return JANET_FFI_TYPE_PTR;
if (!janet_cstrcmp(name, "pointer")) return JANET_FFI_TYPE_PTR;
if (!janet_cstrcmp(name, "string")) return JANET_FFI_TYPE_STRING;
if (!janet_cstrcmp(name, "float")) return JANET_FFI_TYPE_FLOAT;
if (!janet_cstrcmp(name, "double")) return JANET_FFI_TYPE_DOUBLE;
@@ -424,13 +475,15 @@ JANET_CORE_FN(cfun_ffi_align,
static void *janet_ffi_getpointer(const Janet *argv, int32_t n) {
switch (janet_type(argv[n])) {
default:
janet_panicf("bad slot #%d, expected ffi pointer convertable type, got %v", argv[n]);
janet_panicf("bad slot #%d, expected ffi pointer convertable type, got %v", n, argv[n]);
case JANET_POINTER:
case JANET_STRING:
case JANET_KEYWORD:
case JANET_SYMBOL:
case JANET_ABSTRACT:
case JANET_CFUNCTION:
return janet_unwrap_pointer(argv[n]);
case JANET_ABSTRACT:
return (void *) janet_getbytes(argv, n).bytes;
case JANET_BUFFER:
return janet_unwrap_buffer(argv[n])->data;
case JANET_FUNCTION:
@@ -443,6 +496,19 @@ static void *janet_ffi_getpointer(const Janet *argv, int32_t n) {
}
}
static void *janet_ffi_get_callable_pointer(const Janet *argv, int32_t n) {
switch (janet_type(argv[n])) {
default:
break;
case JANET_POINTER:
return janet_unwrap_pointer(argv[n]);
case JANET_ABSTRACT:
if (!janet_checkabstract(argv[n], &janet_type_ffijit)) break;
return ((JanetFFIJittedFn *)janet_unwrap_abstract(argv[n]))->function_pointer;
}
janet_panicf("bad slot #%d, expected ffi callable pointer type, got %v", n, argv[n]);
}
/* Write a value given by some Janet values and an FFI type as it would appear in memory.
* The alignment and space available is assumed to already be sufficient */
static void janet_ffi_write_one(void *to, const Janet *argv, int32_t n, JanetFFIType type, int recur) {
@@ -601,7 +667,7 @@ static JanetFFIMapping void_mapping(void) {
#ifdef JANET_FFI_SYSV64_ENABLED
/* AMD64 ABI Draft 0.99.7 November 17, 2014 15:08
* See section 3.2.3 Parameter Passing */
static JanetFFIWordSpec sysv64_classify(JanetFFIType type) {
static JanetFFIWordSpec sysv64_classify_ext(JanetFFIType type, size_t shift) {
switch (type.prim) {
case JANET_FFI_TYPE_PTR:
case JANET_FFI_TYPE_STRING:
@@ -623,20 +689,63 @@ static JanetFFIWordSpec sysv64_classify(JanetFFIType type) {
if (st->size > 16) return JANET_SYSV64_MEMORY;
if (!st->is_aligned) return JANET_SYSV64_MEMORY;
JanetFFIWordSpec clazz = JANET_SYSV64_NO_CLASS;
for (uint32_t i = 0; i < st->field_count; i++) {
JanetFFIWordSpec next_class = sysv64_classify(st->fields[i].type);
if (next_class != clazz) {
if (clazz == JANET_SYSV64_NO_CLASS) {
clazz = next_class;
} else if (clazz == JANET_SYSV64_MEMORY || next_class == JANET_SYSV64_MEMORY) {
clazz = JANET_SYSV64_MEMORY;
} else if (clazz == JANET_SYSV64_INTEGER || next_class == JANET_SYSV64_INTEGER) {
clazz = JANET_SYSV64_INTEGER;
} else if (next_class == JANET_SYSV64_X87 || next_class == JANET_SYSV64_X87UP
|| next_class == JANET_SYSV64_COMPLEX_X87) {
clazz = JANET_SYSV64_MEMORY;
} else {
clazz = JANET_SYSV64_SSE;
if (st->size > 8 && st->size <= 16) {
/* map to pair classification */
int has_int_lo = 0;
int has_int_hi = 0;
for (uint32_t i = 0; i < st->field_count; i++) {
JanetFFIWordSpec next_class = sysv64_classify_ext(st->fields[i].type, shift + st->fields[i].offset);
switch (next_class) {
default:
break;
case JANET_SYSV64_INTEGER:
if (shift + st->fields[i].offset + type_size(st->fields[i].type) <= 8) {
has_int_lo = 1;
} else {
has_int_hi = 2;
}
break;
case JANET_SYSV64_PAIR_INTINT:
has_int_lo = 1;
has_int_hi = 2;
break;
case JANET_SYSV64_PAIR_INTSSE:
has_int_lo = 1;
break;
case JANET_SYSV64_PAIR_SSEINT:
has_int_hi = 2;
break;
break;
}
}
switch (has_int_hi + has_int_lo) {
case 0:
clazz = JANET_SYSV64_PAIR_SSESSE;
break;
case 1:
clazz = JANET_SYSV64_PAIR_INTSSE;
break;
case 2:
clazz = JANET_SYSV64_PAIR_SSEINT;
break;
case 3:
clazz = JANET_SYSV64_PAIR_INTINT;
break;
}
} else {
/* Normal struct classification */
for (uint32_t i = 0; i < st->field_count; i++) {
JanetFFIWordSpec next_class = sysv64_classify_ext(st->fields[i].type, shift + st->fields[i].offset);
if (next_class != clazz) {
if (clazz == JANET_SYSV64_NO_CLASS) {
clazz = next_class;
} else if (clazz == JANET_SYSV64_MEMORY || next_class == JANET_SYSV64_MEMORY) {
clazz = JANET_SYSV64_MEMORY;
} else if (clazz == JANET_SYSV64_INTEGER || next_class == JANET_SYSV64_INTEGER) {
clazz = JANET_SYSV64_INTEGER;
} else {
clazz = JANET_SYSV64_SSE;
}
}
}
}
@@ -649,6 +758,9 @@ static JanetFFIWordSpec sysv64_classify(JanetFFIType type) {
return JANET_SYSV64_NO_CLASS;
}
}
static JanetFFIWordSpec sysv64_classify(JanetFFIType type) {
return sysv64_classify_ext(type, 0);
}
#endif
JANET_CORE_FN(cfun_ffi_signature,
@@ -687,7 +799,7 @@ JANET_CORE_FN(cfun_ffi_signature,
uint32_t ref_stack_count = 0;
ret.spec = JANET_WIN64_REGISTER;
uint32_t next_register = 0;
if (ret_size != 1 && ret_size != 2 && ret_size != 4 && ret_size != 8) {
if (ret_size != 0 && ret_size != 1 && ret_size != 2 && ret_size != 4 && ret_size != 8) {
ret.spec = JANET_WIN64_REGISTER_REF;
next_register++;
} else if (ret.type.prim == JANET_FFI_TYPE_FLOAT ||
@@ -728,7 +840,6 @@ JANET_CORE_FN(cfun_ffi_signature,
}
/* Add reference items */
size_t old_stack_count = stack_count;
stack_count += 2 * ref_stack_count;
if (stack_count & 0x1) {
stack_count++;
@@ -753,6 +864,8 @@ JANET_CORE_FN(cfun_ffi_signature,
JanetFFIWordSpec ret_spec = sysv64_classify(ret.type);
ret.spec = ret_spec;
if (ret_spec == JANET_SYSV64_SSE) variant = 1;
if (ret_spec == JANET_SYSV64_PAIR_INTSSE) variant = 2;
if (ret_spec == JANET_SYSV64_PAIR_SSEINT) variant = 3;
/* Spill register overflow to memory */
uint32_t next_register = 0;
uint32_t next_fp_register = 0;
@@ -781,8 +894,8 @@ JANET_CORE_FN(cfun_ffi_signature,
mappings[i].offset = stack_count;
stack_count += el_size;
}
break;
}
break;
case JANET_SYSV64_SSE: {
if (next_fp_register < max_fp_regs) {
mappings[i].offset = next_fp_register++;
@@ -791,21 +904,57 @@ JANET_CORE_FN(cfun_ffi_signature,
mappings[i].offset = stack_count;
stack_count += el_size;
}
break;
}
break;
case JANET_SYSV64_MEMORY: {
mappings[i].offset = stack_count;
stack_count += el_size;
}
}
}
/* Invert stack */
for (uint32_t i = 0; i < arg_count; i++) {
if (mappings[i].spec == JANET_SYSV64_MEMORY) {
uint32_t old_offset = mappings[i].offset;
size_t el_size = type_size(mappings[i].type);
mappings[i].offset = stack_count - ((el_size + 7) / 8) - old_offset;
break;
case JANET_SYSV64_PAIR_INTINT: {
if (next_register + 1 < max_regs) {
mappings[i].offset = next_register++;
mappings[i].offset2 = next_register++;
} else {
mappings[i].spec = JANET_SYSV64_MEMORY;
mappings[i].offset = stack_count;
stack_count += el_size;
}
}
break;
case JANET_SYSV64_PAIR_INTSSE: {
if (next_register < max_regs && next_fp_register < max_fp_regs) {
mappings[i].offset = next_register++;
mappings[i].offset2 = next_fp_register++;
} else {
mappings[i].spec = JANET_SYSV64_MEMORY;
mappings[i].offset = stack_count;
stack_count += el_size;
}
}
break;
case JANET_SYSV64_PAIR_SSEINT: {
if (next_register < max_regs && next_fp_register < max_fp_regs) {
mappings[i].offset = next_fp_register++;
mappings[i].offset2 = next_register++;
} else {
mappings[i].spec = JANET_SYSV64_MEMORY;
mappings[i].offset = stack_count;
stack_count += el_size;
}
}
break;
case JANET_SYSV64_PAIR_SSESSE: {
if (next_fp_register < max_fp_regs) {
mappings[i].offset = next_fp_register++;
mappings[i].offset2 = next_fp_register++;
} else {
mappings[i].spec = JANET_SYSV64_MEMORY;
mappings[i].offset = stack_count;
stack_count += el_size;
}
}
break;
}
}
}
@@ -841,23 +990,38 @@ typedef struct {
double x;
double y;
} sysv64_sse_return;
typedef struct {
uint64_t x;
double y;
} sysv64_intsse_return;
typedef struct {
double y;
uint64_t x;
} sysv64_sseint_return;
typedef sysv64_int_return janet_sysv64_variant_1(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f,
double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8);
typedef sysv64_sse_return janet_sysv64_variant_2(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f,
double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8);
typedef sysv64_intsse_return janet_sysv64_variant_3(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f,
double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8);
typedef sysv64_sseint_return janet_sysv64_variant_4(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f,
double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8);
static Janet janet_ffi_sysv64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) {
sysv64_int_return int_return;
sysv64_sse_return sse_return;
union {
sysv64_int_return int_return;
sysv64_sse_return sse_return;
sysv64_sseint_return sseint_return;
sysv64_intsse_return intsse_return;
} retu;
uint64_t pair[2];
uint64_t regs[6];
double fp_regs[8];
JanetFFIWordSpec ret_spec = signature->ret.spec;
void *ret_mem = &int_return;
void *ret_mem = &retu.int_return;
if (ret_spec == JANET_SYSV64_MEMORY) {
ret_mem = alloca(type_size(signature->ret.type));
regs[0] = (uint64_t) ret_mem;
} else if (ret_spec == JANET_SYSV64_SSE) {
ret_mem = &sse_return;
}
uint64_t *stack = alloca(sizeof(uint64_t) * signature->stack_count);
for (uint32_t i = 0; i < signature->arg_count; i++) {
@@ -876,21 +1040,55 @@ static Janet janet_ffi_sysv64(JanetFFISignature *signature, void *function_point
case JANET_SYSV64_MEMORY:
to = stack + arg.offset;
break;
case JANET_SYSV64_PAIR_INTINT:
janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR);
regs[arg.offset] = pair[0];
regs[arg.offset2] = pair[1];
continue;
case JANET_SYSV64_PAIR_INTSSE:
janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR);
regs[arg.offset] = pair[0];
((uint64_t *) fp_regs)[arg.offset2] = pair[1];
continue;
case JANET_SYSV64_PAIR_SSEINT:
janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR);
((uint64_t *) fp_regs)[arg.offset] = pair[0];
regs[arg.offset2] = pair[1];
continue;
case JANET_SYSV64_PAIR_SSESSE:
janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR);
((uint64_t *) fp_regs)[arg.offset] = pair[0];
((uint64_t *) fp_regs)[arg.offset2] = pair[1];
continue;
}
janet_ffi_write_one(to, argv, n, arg.type, JANET_FFI_MAX_RECUR);
}
if (signature->variant) {
sse_return = ((janet_sysv64_variant_2 *)(function_pointer))(
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5],
fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3],
fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]);
} else {
int_return = ((janet_sysv64_variant_1 *)(function_pointer))(
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5],
fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3],
fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]);
switch (signature->variant) {
case 0:
retu.int_return = ((janet_sysv64_variant_1 *)(function_pointer))(
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5],
fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3],
fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]);
break;
case 1:
retu.sse_return = ((janet_sysv64_variant_2 *)(function_pointer))(
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5],
fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3],
fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]);
break;
case 2:
retu.intsse_return = ((janet_sysv64_variant_3 *)(function_pointer))(
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5],
fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3],
fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]);
break;
case 3:
retu.sseint_return = ((janet_sysv64_variant_4 *)(function_pointer))(
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5],
fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3],
fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]);
break;
}
return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR);
@@ -959,8 +1157,9 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
ret_mem = alloca(type_size(signature->ret.type));
regs[0].integer = (uint64_t) ret_mem;
}
uint64_t *stack = alloca(signature->stack_count * 8);
stack -= 2; /* hack to get proper stack placement */
size_t stack_size = signature->stack_count * 8;
size_t stack_shift = 2;
uint64_t *stack = alloca(stack_size);
for (uint32_t i = 0; i < signature->arg_count; i++) {
int32_t n = i + 2;
JanetFFIMapping arg = signature->args[i];
@@ -969,16 +1168,26 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
} else if (arg.spec == JANET_WIN64_STACK_REF) {
uint8_t *ptr = (uint8_t *)(stack + arg.offset2);
janet_ffi_write_one(ptr, argv, n, arg.type, JANET_FFI_MAX_RECUR);
stack[arg.offset] = (uint64_t) ptr;
stack[arg.offset] = (uint64_t)(ptr - stack_shift * sizeof(uint64_t));
} else if (arg.spec == JANET_WIN64_REGISTER_REF) {
uint8_t *ptr = (uint8_t *)(stack + arg.offset2);
janet_ffi_write_one(ptr, argv, n, arg.type, JANET_FFI_MAX_RECUR);
regs[arg.offset].integer = (uint64_t) ptr;
regs[arg.offset].integer = (uint64_t)(ptr - stack_shift * sizeof(uint64_t));
} else {
janet_ffi_write_one((uint8_t *) &regs[arg.offset].integer, argv, n, arg.type, JANET_FFI_MAX_RECUR);
}
}
/* hack to get proper stack placement and avoid clobbering from logic above - shift stack down, otherwise we have issues.
* Technically, this writes into 16 bytes of unallocated stack memory */
#ifdef JANET_MINGW
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#endif
if (stack_size) memmove(stack - stack_shift, stack, stack_size);
#ifdef JANET_MINGW
#pragma GCC diagnostic pop
#endif
switch (signature->variant) {
default:
janet_panicf("unknown variant %d", signature->variant);
@@ -1085,12 +1294,71 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
#endif
/* Allocate executable memory chunks in sizes of a page. Ideally we would keep
* an allocator around so that multiple JIT allocations would point to the same
* region but it isn't really worth it. */
#define FFI_PAGE_MASK 0xFFF
JANET_CORE_FN(cfun_ffi_jitfn,
"(ffi/jitfn bytes)",
"Create an abstract type that can be used as the pointer argument to `ffi/call`. The content "
"of `bytes` is architecture specific machine code that will be copied into executable memory.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_fixarity(argc, 1);
JanetByteView bytes = janet_getbytes(argv, 0);
/* Quick hack to align to page boundary, we should query OS. FIXME */
size_t alloc_size = ((size_t) bytes.len + FFI_PAGE_MASK) & ~FFI_PAGE_MASK;
#ifdef JANET_FFI_JIT
#ifdef JANET_EV
JanetFFIJittedFn *fn = janet_abstract_threaded(&janet_type_ffijit, sizeof(JanetFFIJittedFn));
#else
JanetFFIJittedFn *fn = janet_abstract(&janet_type_ffijit, sizeof(JanetFFIJittedFn));
#endif
fn->function_pointer = NULL;
fn->size = 0;
#ifdef JANET_WINDOWS
void *ptr = VirtualAlloc(NULL, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif defined(MAP_ANONYMOUS)
void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#elif defined(MAP_ANON)
/* macos doesn't have MAP_ANONYMOUS */
void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
#else
/* -std=c99 gets in the way */
/* #define MAP_ANONYMOUS 0x20 should work, though. */
void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0);
#endif
if (!ptr) {
janet_panic("failed to memory map writable memory");
}
memcpy(ptr, bytes.bytes, bytes.len);
#ifdef JANET_WINDOWS
DWORD old = 0;
if (!VirtualProtect(ptr, alloc_size, PAGE_EXECUTE_READ, &old)) {
janet_panic("failed to make mapped memory executable");
}
#else
if (mprotect(ptr, alloc_size, PROT_READ | PROT_EXEC) == -1) {
janet_panic("failed to make mapped memory executable");
}
#endif
fn->size = alloc_size;
fn->function_pointer = ptr;
return janet_wrap_abstract(fn);
#else
janet_panic("ffi/jitfn not available on this platform");
#endif
}
JANET_CORE_FN(cfun_ffi_call,
"(ffi/call pointer signature & args)",
"Call a raw pointer as a function pointer. The function signature specifies "
"how Janet values in `args` are converted to native machine types.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_arity(argc, 2, -1);
void *function_pointer = janet_getpointer(argv, 0);
void *function_pointer = janet_ffi_get_callable_pointer(argv, 0);
JanetFFISignature *signature = janet_getabstract(argv, 1, &janet_signature_type);
janet_fixarity(argc - 2, signature->arg_count);
switch (signature->cc) {
@@ -1109,18 +1377,25 @@ JANET_CORE_FN(cfun_ffi_call,
}
JANET_CORE_FN(cfun_ffi_buffer_write,
"(ffi/write ffi-type data &opt buffer)",
"Append a native tyep to a buffer such as it would appear in memory. This can be used "
"(ffi/write ffi-type data &opt buffer index)",
"Append a native type to a buffer such as it would appear in memory. This can be used "
"to pass pointers to structs in the ffi, or send C/C++/native structs over the network "
"or to files. Returns a modifed buffer or a new buffer if one is not supplied.") {
janet_arity(argc, 2, 3);
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_arity(argc, 2, 4);
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 old_count = buffer->count;
if (index > old_count) janet_panic("index out of bounds");
buffer->count = index;
janet_buffer_extra(buffer, el_size);
memset(buffer->data, 0, el_size);
janet_ffi_write_one(buffer->data, argv, 1, type, JANET_FFI_MAX_RECUR);
buffer->count += el_size;
buffer->count = old_count;
memset(buffer->data + index, 0, el_size);
janet_ffi_write_one(buffer->data + index, argv, 1, type, JANET_FFI_MAX_RECUR);
index += el_size;
if (buffer->count < index) buffer->count = index;
return janet_wrap_buffer(buffer);
}
@@ -1129,6 +1404,7 @@ JANET_CORE_FN(cfun_ffi_buffer_read,
"Parse a native struct out of a buffer and convert it to normal Janet data structures. "
"This function is the inverse of `ffi/write`. `bytes` can also be a raw pointer, although "
"this is unsafe.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_arity(argc, 2, 3);
JanetFFIType type = decode_ffi_type(argv[0]);
size_t offset = (size_t) janet_optnat(argv, argc, 2, 0);
@@ -1175,6 +1451,7 @@ JANET_CORE_FN(janet_core_raw_native,
" or run any code from it. This is different than `native`, which will "
"run initialization code to get a module table. If `path` is nil, opens the current running binary. "
"Returns a `core/native`.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_arity(argc, 0, 1);
const char *path = janet_optcstring(argv, argc, 0, NULL);
Clib lib = load_clib(path);
@@ -1190,6 +1467,7 @@ JANET_CORE_FN(janet_core_native_lookup,
"(ffi/lookup native symbol-name)",
"Lookup a symbol from a native object. All symbol lookups will return a raw pointer "
"if the symbol is found, else nil.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_fixarity(argc, 2);
JanetAbstractNative *anative = janet_getabstract(argv, 0, &janet_native_type);
const char *sym = janet_getcstring(argv, 1);
@@ -1203,6 +1481,7 @@ JANET_CORE_FN(janet_core_native_close,
"(ffi/close native)",
"Free a native object. Dereferencing pointers to symbols in the object will have undefined "
"behavior after freeing.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_fixarity(argc, 1);
JanetAbstractNative *anative = janet_getabstract(argv, 0, &janet_native_type);
if (anative->closed) janet_panic("native object already closed");
@@ -1212,6 +1491,44 @@ JANET_CORE_FN(janet_core_native_close,
return janet_wrap_nil();
}
JANET_CORE_FN(cfun_ffi_malloc,
"(ffi/malloc size)",
"Allocates memory directly using the janet memory allocator. Memory allocated in this way must be freed manually! Returns a raw pointer, or nil if size = 0.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_fixarity(argc, 1);
size_t size = janet_getsize(argv, 0);
if (size == 0) return janet_wrap_nil();
return janet_wrap_pointer(janet_malloc(size));
}
JANET_CORE_FN(cfun_ffi_free,
"(ffi/free pointer)",
"Free memory allocated with `ffi/malloc`. Returns nil.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_fixarity(argc, 1);
if (janet_checktype(argv[0], JANET_NIL)) return janet_wrap_nil();
void *pointer = janet_getpointer(argv, 0);
janet_free(pointer);
return janet_wrap_nil();
}
JANET_CORE_FN(cfun_ffi_pointer_buffer,
"(ffi/pointer-buffer pointer capacity &opt count offset)",
"Create a buffer from a pointer. The underlying memory of the buffer will not be "
"reallocated or freed by the garbage collector, allowing unmanaged, mutable memory "
"to be manipulated with buffer functions. Attempts to resize or extend the buffer "
"beyond its initial capacity will raise an error. As with many FFI functions, this is memory "
"unsafe and can potentially allow out of bounds memory access. Returns a new buffer.") {
janet_sandbox_assert(JANET_SANDBOX_FFI);
janet_arity(argc, 2, 4);
void *pointer = janet_getpointer(argv, 0);
int32_t capacity = janet_getnat(argv, 1);
int32_t count = janet_optnat(argv, argc, 2, 0);
int64_t offset = janet_optinteger64(argv, argc, 3, 0);
uint8_t *offset_pointer = ((uint8_t *) pointer) + offset;
return janet_wrap_buffer(janet_pointer_buffer_unsafe(offset_pointer, capacity, count));
}
void janet_lib_ffi(JanetTable *env) {
JanetRegExt ffi_cfuns[] = {
JANET_CORE_REG("ffi/native", janet_core_raw_native),
@@ -1225,6 +1542,10 @@ void janet_lib_ffi(JanetTable *env) {
JANET_CORE_REG("ffi/size", cfun_ffi_size),
JANET_CORE_REG("ffi/align", cfun_ffi_align),
JANET_CORE_REG("ffi/trampoline", cfun_ffi_get_callback_trampoline),
JANET_CORE_REG("ffi/jitfn", cfun_ffi_jitfn),
JANET_CORE_REG("ffi/malloc", cfun_ffi_malloc),
JANET_CORE_REG("ffi/free", cfun_ffi_free),
JANET_CORE_REG("ffi/pointer-buffer", cfun_ffi_pointer_buffer),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, ffi_cfuns);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -495,6 +495,8 @@ JANET_CORE_FN(cfun_fiber_new,
"* :t - block termination signals: error + user[0-4]\n"
"* :u - block user signals\n"
"* :y - block yield signals\n"
"* :w - block await signals (user9)\n"
"* :r - block interrupt signals (user8)\n"
"* :0-9 - block a specific user signal\n\n"
"The sigmask argument also can take environment flags. If any mutually "
"exclusive flags are present, the last flag takes precedence.\n\n"
@@ -518,7 +520,7 @@ JANET_CORE_FN(cfun_fiber_new,
} else {
switch (view.bytes[i]) {
default:
janet_panicf("invalid flag %c, expected a, t, d, e, u, y, i, or p", view.bytes[i]);
janet_panicf("invalid flag %c, expected a, t, d, e, u, y, w, r, i, or p", view.bytes[i]);
break;
case 'a':
fiber->flags |=
@@ -548,6 +550,12 @@ JANET_CORE_FN(cfun_fiber_new,
case 'y':
fiber->flags |= JANET_FIBER_MASK_YIELD;
break;
case 'w':
fiber->flags |= JANET_FIBER_MASK_USER9;
break;
case 'r':
fiber->flags |= JANET_FIBER_MASK_USER8;
break;
case 'i':
if (!janet_vm.fiber->env) {
janet_vm.fiber->env = janet_table(0);
@@ -575,7 +583,9 @@ JANET_CORE_FN(cfun_fiber_status,
"* :error - the fiber has errored out\n"
"* :debug - the fiber is suspended in debug mode\n"
"* :pending - the fiber has been yielded\n"
"* :user(0-9) - the fiber is suspended by a user signal\n"
"* :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") {
janet_fixarity(argc, 1);
@@ -625,11 +635,7 @@ JANET_CORE_FN(cfun_fiber_setmaxstack,
return argv[0];
}
JANET_CORE_FN(cfun_fiber_can_resume,
"(fiber/can-resume? fiber)",
"Check if a fiber is finished and cannot be resumed.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
int janet_fiber_can_resume(JanetFiber *fiber) {
JanetFiberStatus s = janet_fiber_status(fiber);
int isFinished = s == JANET_STATUS_DEAD ||
s == JANET_STATUS_ERROR ||
@@ -638,7 +644,15 @@ JANET_CORE_FN(cfun_fiber_can_resume,
s == JANET_STATUS_USER2 ||
s == JANET_STATUS_USER3 ||
s == JANET_STATUS_USER4;
return janet_wrap_boolean(!isFinished);
return !isFinished;
}
JANET_CORE_FN(cfun_fiber_can_resume,
"(fiber/can-resume? fiber)",
"Check if a fiber is finished and cannot be resumed.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
return janet_wrap_boolean(janet_fiber_can_resume(fiber));
}
JANET_CORE_FN(cfun_fiber_last_value,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -209,6 +209,12 @@ static void janet_mark_funcdef(JanetFuncDef *def) {
janet_mark_string(def->source);
if (def->name)
janet_mark_string(def->name);
if (def->symbolmap) {
for (int i = 0; i < def->symbolmap_length; i++) {
janet_mark_string(def->symbolmap[i].symbol);
}
}
}
static void janet_mark_function(JanetFunction *func) {
@@ -314,6 +320,7 @@ static void janet_deinit_block(JanetGCObject *mem) {
janet_free(def->bytecode);
janet_free(def->sourcemap);
janet_free(def->closure_bitset);
janet_free(def->symbolmap);
}
break;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose & contributors
* Copyright (c) 2023 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
@@ -407,13 +407,26 @@ static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
/*
* In C, signed arithmetic overflow is undefined behvior
* but unsigned arithmetic overflow is twos complement
*
* Reference:
* https://en.cppreference.com/w/cpp/language/ub
* http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
*
* This means OPMETHOD & OPMETHODINVERT must always use
* unsigned arithmetic internally, regardless of the true type.
* This will not affect the end result (property of twos complement).
*/
#define OPMETHOD(T, type, name, oper) \
static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
janet_arity(argc, 2, -1); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[0]); \
for (int32_t i = 1; i < argc; i++) \
*box oper##= janet_unwrap_##type(argv[i]); \
/* This avoids undefined behavior. See above for why. */ \
*box = (T) ((uint64_t) (*box)) oper ((uint64_t) janet_unwrap_##type(argv[i])); \
return janet_wrap_abstract(box); \
} \
@@ -422,7 +435,8 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \
janet_fixarity(argc, 2); \
T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \
*box = janet_unwrap_##type(argv[1]); \
*box oper##= janet_unwrap_##type(argv[0]); \
/* This avoids undefined behavior. See above for why. */ \
*box = (T) ((uint64_t) *box) oper ((uint64_t) janet_unwrap_##type(argv[0])); \
return janet_wrap_abstract(box); \
} \
@@ -488,6 +502,18 @@ static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) {
return janet_wrap_abstract(box);
}
static Janet cfun_it_s64_modi(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t));
int64_t op2 = janet_unwrap_s64(argv[0]);
int64_t op1 = janet_unwrap_s64(argv[1]);
int64_t x = op1 % op2;
*box = (op1 > 0)
? ((op2 > 0) ? x : (0 == x ? x : x + op2))
: ((op2 > 0) ? (0 == x ? x : x + op2) : x);
return janet_wrap_abstract(box);
}
OPMETHOD(int64_t, s64, add, +)
OPMETHOD(int64_t, s64, sub, -)
OPMETHODINVERT(int64_t, s64, subi, -)
@@ -495,6 +521,7 @@ OPMETHOD(int64_t, s64, mul, *)
DIVMETHOD_SIGNED(int64_t, s64, div, /)
DIVMETHOD_SIGNED(int64_t, s64, rem, %)
DIVMETHODINVERT_SIGNED(int64_t, s64, divi, /)
DIVMETHODINVERT_SIGNED(int64_t, s64, remi, %)
OPMETHOD(int64_t, s64, and, &)
OPMETHOD(int64_t, s64, or, |)
OPMETHOD(int64_t, s64, xor, ^)
@@ -507,6 +534,7 @@ OPMETHOD(uint64_t, u64, mul, *)
DIVMETHOD(uint64_t, u64, div, /)
DIVMETHOD(uint64_t, u64, mod, %)
DIVMETHODINVERT(uint64_t, u64, divi, /)
DIVMETHODINVERT(uint64_t, u64, modi, %)
OPMETHOD(uint64_t, u64, and, &)
OPMETHOD(uint64_t, u64, or, |)
OPMETHOD(uint64_t, u64, xor, ^)
@@ -518,7 +546,6 @@ OPMETHOD(uint64_t, u64, rshift, >>)
#undef DIVMETHOD_SIGNED
#undef COMPMETHOD
static JanetMethod it_s64_methods[] = {
{"+", cfun_it_s64_add},
{"r+", cfun_it_s64_add},
@@ -529,9 +556,9 @@ static JanetMethod it_s64_methods[] = {
{"/", cfun_it_s64_div},
{"r/", cfun_it_s64_divi},
{"mod", cfun_it_s64_mod},
{"rmod", cfun_it_s64_mod},
{"rmod", cfun_it_s64_modi},
{"%", cfun_it_s64_rem},
{"r%", cfun_it_s64_rem},
{"r%", cfun_it_s64_remi},
{"&", cfun_it_s64_and},
{"r&", cfun_it_s64_and},
{"|", cfun_it_s64_or},
@@ -541,7 +568,6 @@ static JanetMethod it_s64_methods[] = {
{"<<", cfun_it_s64_lshift},
{">>", cfun_it_s64_rshift},
{"compare", cfun_it_s64_compare},
{NULL, NULL}
};
@@ -555,9 +581,9 @@ static JanetMethod it_u64_methods[] = {
{"/", cfun_it_u64_div},
{"r/", cfun_it_u64_divi},
{"mod", cfun_it_u64_mod},
{"rmod", cfun_it_u64_mod},
{"rmod", cfun_it_u64_modi},
{"%", cfun_it_u64_mod},
{"r%", cfun_it_u64_mod},
{"r%", cfun_it_u64_modi},
{"&", cfun_it_u64_and},
{"r&", cfun_it_u64_and},
{"|", cfun_it_u64_or},
@@ -567,7 +593,6 @@ static JanetMethod it_u64_methods[] = {
{"<<", cfun_it_u64_lshift},
{">>", cfun_it_u64_rshift},
{"compare", cfun_it_u64_compare},
{NULL, NULL}
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -69,12 +69,15 @@ static int32_t checkflags(const uint8_t *str) {
break;
case 'w':
flags |= JANET_FILE_WRITE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'a':
flags |= JANET_FILE_APPEND;
janet_sandbox_assert(JANET_SANDBOX_FS);
break;
case 'r':
flags |= JANET_FILE_READ;
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
break;
}
for (i = 1; i < len; i++) {
@@ -84,6 +87,7 @@ static int32_t checkflags(const uint8_t *str) {
break;
case '+':
if (flags & JANET_FILE_UPDATE) return -1;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
flags |= JANET_FILE_UPDATE;
break;
case 'b':
@@ -116,6 +120,7 @@ JANET_CORE_FN(cfun_io_temp,
"(file/temp)",
"Open an anonymous temporary file that is removed on close. "
"Raises an error on failure.") {
janet_sandbox_assert(JANET_SANDBOX_FS_TEMP);
(void)argv;
janet_fixarity(argc, 0);
// XXX use mkostemp when we can to avoid CLOEXEC race.
@@ -148,6 +153,7 @@ JANET_CORE_FN(cfun_io_fopen,
flags = checkflags(fmode);
} else {
fmode = (const uint8_t *)"r";
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
flags = JANET_FILE_READ;
}
FILE *f = fopen((const char *)fname, (const char *)fmode);
@@ -243,6 +249,13 @@ JANET_CORE_FN(cfun_io_fwrite,
return argv[0];
}
static void io_assert_writeable(JanetFile *iof) {
if (iof->flags & JANET_FILE_CLOSED)
janet_panic("file is closed");
if (!(iof->flags & (JANET_FILE_WRITE | JANET_FILE_APPEND | JANET_FILE_UPDATE)))
janet_panic("file is not writeable");
}
/* Flush the bytes in the file */
JANET_CORE_FN(cfun_io_fflush,
"(file/flush f)",
@@ -250,10 +263,7 @@ JANET_CORE_FN(cfun_io_fflush,
"buffered for efficiency reasons. Returns the file handle.") {
janet_fixarity(argc, 1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
janet_panic("file is closed");
if (!(iof->flags & (JANET_FILE_WRITE | JANET_FILE_APPEND | JANET_FILE_UPDATE)))
janet_panic("file is not writeable");
io_assert_writeable(iof);
if (fflush(iof->file))
janet_panic("could not flush file");
return argv[0];
@@ -269,6 +279,7 @@ int janet_file_close(JanetFile *file) {
if (!(file->flags & (JANET_FILE_NOT_CLOSEABLE | JANET_FILE_CLOSED))) {
ret = fclose(file->file);
file->flags |= JANET_FILE_CLOSED;
file->file = NULL; /* NULL derefence is easier to debug then other problems */
return ret;
}
return 0;
@@ -337,11 +348,24 @@ JANET_CORE_FN(cfun_io_fseek,
return argv[0];
}
JANET_CORE_FN(cfun_io_ftell,
"(file/tell f)",
"Get the current value of the file position for file `f`.") {
janet_fixarity(argc, 1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
janet_panic("file is closed");
long pos = ftell(iof->file);
if (pos == -1) janet_panic("error getting position in file");
return janet_wrap_number((double)pos);
}
static JanetMethod io_file_methods[] = {
{"close", cfun_io_fclose},
{"flush", cfun_io_fflush},
{"read", cfun_io_fread},
{"seek", cfun_io_fseek},
{"tell", cfun_io_ftell},
{"write", cfun_io_fwrite},
{NULL, NULL}
};
@@ -449,6 +473,7 @@ static Janet cfun_io_print_impl_x(int32_t argc, Janet *argv, int newline,
if (janet_abstract_type(abstract) != &janet_file_type)
return janet_wrap_nil();
JanetFile *iofile = abstract;
io_assert_writeable(iofile);
f = iofile->file;
break;
}
@@ -564,6 +589,10 @@ static Janet cfun_io_printf_impl_x(int32_t argc, Janet *argv, int newline,
if (janet_abstract_type(abstract) != &janet_file_type)
return janet_wrap_nil();
JanetFile *iofile = abstract;
if (iofile->flags & JANET_FILE_CLOSED) {
janet_panic("cannot print to closed file");
}
io_assert_writeable(iofile);
f = iofile->file;
break;
}
@@ -688,6 +717,7 @@ void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...)
if (janet_abstract_type(abstract) != &janet_file_type)
break;
JanetFile *iofile = abstract;
io_assert_writeable(iofile);
f = iofile->file;
}
fwrite(buffer.data, buffer.count, 1, f);
@@ -718,17 +748,17 @@ JanetFile *janet_getjfile(const Janet *argv, int32_t n) {
return janet_getabstract(argv, n, &janet_file_type);
}
FILE *janet_getfile(const Janet *argv, int32_t n, int *flags) {
FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) {
JanetFile *iof = janet_getabstract(argv, n, &janet_file_type);
if (NULL != flags) *flags = iof->flags;
return iof->file;
}
JanetFile *janet_makejfile(FILE *f, int flags) {
JanetFile *janet_makejfile(FILE *f, int32_t flags) {
return makef(f, flags);
}
Janet janet_makefile(FILE *f, int flags) {
Janet janet_makefile(FILE *f, int32_t flags) {
return janet_wrap_abstract(makef(f, flags));
}
@@ -736,7 +766,7 @@ JanetAbstract janet_checkfile(Janet j) {
return janet_checkabstract(j, &janet_file_type);
}
FILE *janet_unwrapfile(Janet j, int *flags) {
FILE *janet_unwrapfile(Janet j, int32_t *flags) {
JanetFile *iof = janet_unwrap_abstract(j);
if (NULL != flags) *flags = iof->flags;
return iof->file;
@@ -766,6 +796,7 @@ void janet_lib_io(JanetTable *env) {
JANET_CORE_REG("file/write", cfun_io_fwrite),
JANET_CORE_REG("file/flush", cfun_io_fflush),
JANET_CORE_REG("file/seek", cfun_io_fseek),
JANET_CORE_REG("file/tell", cfun_io_ftell),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, io_cfuns);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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,6 +37,7 @@ typedef struct {
JanetFuncEnv **seen_envs;
JanetFuncDef **seen_defs;
int32_t nextid;
int maybe_cycles;
} MarshalState;
/* Lead bytes in marshaling protocol */
@@ -66,7 +67,8 @@ enum {
LB_UNSAFE_POINTER, /* 222 */
LB_STRUCT_PROTO, /* 223 */
#ifdef JANET_EV
LB_THREADED_ABSTRACT/* 224 */
LB_THREADED_ABSTRACT, /* 224 */
LB_POINTER_BUFFER, /* 224 */
#endif
} LeadBytes;
@@ -152,6 +154,10 @@ static void pushbytes(MarshalState *st, const uint8_t *bytes, int32_t len) {
janet_buffer_push_bytes(st->buf, bytes, len);
}
static void pushpointer(MarshalState *st, void *ptr) {
janet_buffer_push_bytes(st->buf, (const uint8_t *) &ptr, sizeof(ptr));
}
/* Marshal a size_t onto the buffer */
static void push64(MarshalState *st, uint64_t x) {
if (x <= 0xF0) {
@@ -251,6 +257,8 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
pushint(st, def->environments_length);
if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS)
pushint(st, def->defs_length);
if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP)
pushint(st, def->symbolmap_length);
if (def->flags & JANET_FUNCDEF_FLAG_HASNAME)
marshal_one(st, janet_wrap_string(def->name), flags);
if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCE)
@@ -260,6 +268,14 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
for (int32_t i = 0; i < def->constants_length; i++)
marshal_one(st, def->constants[i], flags);
/* Marshal symbol map, if needed */
for (int32_t i = 0; i < def->symbolmap_length; i++) {
pushint(st, (int32_t) def->symbolmap[i].birth_pc);
pushint(st, (int32_t) def->symbolmap[i].death_pc);
pushint(st, (int32_t) def->symbolmap[i].slot_index);
marshal_one(st, janet_wrap_symbol(def->symbolmap[i].symbol), flags);
}
/* marshal the bytecode */
janet_marshal_u32s(st, def->bytecode, def->bytecode_length);
@@ -269,7 +285,7 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
/* marshal the sub funcdefs if needed */
for (int32_t i = 0; i < def->defs_length; i++)
marshal_one_def(st, def->defs[i], flags);
marshal_one_def(st, def->defs[i], flags + 1);
/* marshal source maps if needed */
if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCEMAP) {
@@ -364,13 +380,15 @@ void janet_marshal_janet(JanetMarshalContext *ctx, Janet x) {
void janet_marshal_abstract(JanetMarshalContext *ctx, void *abstract) {
MarshalState *st = (MarshalState *)(ctx->m_state);
janet_table_put(&st->seen,
janet_wrap_abstract(abstract),
janet_wrap_integer(st->nextid++));
if (st->maybe_cycles) {
janet_table_put(&st->seen,
janet_wrap_abstract(abstract),
janet_wrap_integer(st->nextid++));
}
}
#define MARK_SEEN() \
janet_table_put(&st->seen, x, janet_wrap_integer(st->nextid++))
do { if (st->maybe_cycles) janet_table_put(&st->seen, x, janet_wrap_integer(st->nextid++)); } while (0)
static void marshal_one_abstract(MarshalState *st, Janet x, int flags) {
void *abstract = janet_unwrap_abstract(x);
@@ -428,11 +446,14 @@ static void marshal_one(MarshalState *st, Janet x, int flags) {
/* Check reference and registry value */
{
Janet check = janet_table_get(&st->seen, x);
if (janet_checkint(check)) {
pushbyte(st, LB_REFERENCE);
pushint(st, janet_unwrap_integer(check));
return;
Janet check;
if (st->maybe_cycles) {
check = janet_table_get(&st->seen, x);
if (janet_checkint(check)) {
pushbyte(st, LB_REFERENCE);
pushint(st, janet_unwrap_integer(check));
return;
}
}
if (st->rreg) {
check = janet_table_get(st->rreg, x);
@@ -495,6 +516,16 @@ static void marshal_one(MarshalState *st, Janet x, int flags) {
JanetBuffer *buffer = janet_unwrap_buffer(x);
/* Record reference */
MARK_SEEN();
#ifdef JANET_EV
if ((flags & JANET_MARSHAL_UNSAFE) &&
(buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) {
pushbyte(st, LB_POINTER_BUFFER);
pushint(st, buffer->count);
pushint(st, buffer->capacity);
pushpointer(st, buffer->data);
return;
}
#endif
pushbyte(st, LB_BUFFER);
pushint(st, buffer->count);
pushbytes(st, buffer->data, buffer->count);
@@ -590,8 +621,7 @@ static void marshal_one(MarshalState *st, Janet x, int flags) {
if (!(flags & JANET_MARSHAL_UNSAFE)) goto no_registry;
MARK_SEEN();
pushbyte(st, LB_UNSAFE_POINTER);
void *ptr = janet_unwrap_pointer(x);
pushbytes(st, (uint8_t *) &ptr, sizeof(void *));
pushpointer(st, janet_unwrap_pointer(x));
return;
}
no_registry:
@@ -613,6 +643,7 @@ void janet_marshal(
st.seen_defs = NULL;
st.seen_envs = NULL;
st.rreg = rreg;
st.maybe_cycles = !(flags & JANET_MARSHAL_NO_CYCLES);
janet_table_init(&st.seen, 0);
marshal_one(&st, x, flags);
janet_table_deinit(&st.seen);
@@ -817,6 +848,8 @@ static const uint8_t *unmarshal_one_def(
def->constants = NULL;
def->bytecode = NULL;
def->sourcemap = NULL;
def->symbolmap = NULL;
def->symbolmap_length = 0;
janet_v_push(st->lookup_defs, def);
/* Set default lengths to zero */
@@ -824,6 +857,7 @@ static const uint8_t *unmarshal_one_def(
int32_t constants_length = 0;
int32_t environments_length = 0;
int32_t defs_length = 0;
int32_t symbolmap_length = 0;
/* Read flags and other fixed values */
def->flags = readint(st, &data);
@@ -839,6 +873,8 @@ static const uint8_t *unmarshal_one_def(
environments_length = readnat(st, &data);
if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS)
defs_length = readnat(st, &data);
if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP)
symbolmap_length = readnat(st, &data);
/* Check name and source (optional) */
if (def->flags & JANET_FUNCDEF_FLAG_HASNAME) {
@@ -867,6 +903,26 @@ static const uint8_t *unmarshal_one_def(
}
def->constants_length = constants_length;
/* Unmarshal symbol map, if needed */
if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP) {
size_t size = sizeof(JanetSymbolMap) * symbolmap_length;
def->symbolmap = janet_malloc(size);
if (def->symbolmap == NULL) {
JANET_OUT_OF_MEMORY;
}
for (int32_t i = 0; i < symbolmap_length; i++) {
def->symbolmap[i].birth_pc = (uint32_t) readint(st, &data);
def->symbolmap[i].death_pc = (uint32_t) readint(st, &data);
def->symbolmap[i].slot_index = (uint32_t) readint(st, &data);
Janet value;
data = unmarshal_one(st, data, &value, flags + 1);
if (!janet_checktype(value, JANET_SYMBOL))
janet_panic("expected symbol in symbol map");
def->symbolmap[i].symbol = janet_unwrap_symbol(value);
}
def->symbolmap_length = (uint32_t) symbolmap_length;
}
/* Unmarshal bytecode */
def->bytecode = janet_malloc(sizeof(uint32_t) * bytecode_length);
if (!def->bytecode) {
@@ -1373,6 +1429,29 @@ static const uint8_t *unmarshal_one(
janet_v_push(st->lookup, *out);
return data;
}
#ifdef JANET_EV
case LB_POINTER_BUFFER: {
data++;
int32_t count = readnat(st, &data);
int32_t capacity = readnat(st, &data);
MARSH_EOS(st, data + sizeof(void *));
union {
void *ptr;
uint8_t bytes[sizeof(void *)];
} u;
if (!(flags & JANET_MARSHAL_UNSAFE)) {
janet_panicf("unsafe flag not given, "
"will not unmarshal raw pointer at index %d",
(int)(data - st->start));
}
memcpy(u.bytes, data, sizeof(void *));
data += sizeof(void *);
JanetBuffer *buffer = janet_pointer_buffer_unsafe(u.ptr, capacity, count);
*out = janet_wrap_buffer(buffer);
janet_v_push(st->lookup, *out);
return data;
}
#endif
case LB_UNSAFE_CFUNCTION: {
MARSH_EOS(st, data + sizeof(JanetCFunction));
data++;
@@ -1471,16 +1550,17 @@ JANET_CORE_FN(cfun_env_lookup,
}
JANET_CORE_FN(cfun_marshal,
"(marshal x &opt reverse-lookup buffer)",
"(marshal x &opt reverse-lookup buffer no-cycles)",
"Marshal a value into a buffer and return the buffer. The buffer "
"can then later be unmarshalled to reconstruct the initial value. "
"Optionally, one can pass in a reverse lookup table to not marshal "
"aliased values that are found in the table. Then a forward "
"lookup table can be used to recover the original value when "
"unmarshalling.") {
janet_arity(argc, 1, 3);
janet_arity(argc, 1, 4);
JanetBuffer *buffer;
JanetTable *rreg = NULL;
uint32_t flags = 0;
if (argc > 1) {
rreg = janet_gettable(argv, 1);
}
@@ -1489,7 +1569,10 @@ JANET_CORE_FN(cfun_marshal,
} else {
buffer = janet_buffer(10);
}
janet_marshal(buffer, argv[0], rreg, 0);
if (argc > 3 && janet_truthy(argv[3])) {
flags |= JANET_MARSHAL_NO_CYCLES;
}
janet_marshal(buffer, argv[0], rreg, flags);
return janet_wrap_buffer(buffer);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -150,8 +150,8 @@ JANET_CORE_FN(cfun_rng_uniform,
JANET_CORE_FN(cfun_rng_int,
"(math/rng-int rng &opt max)",
"Extract a random random integer in the range [0, max] from the RNG. If "
"no max is given, the default is 2^31 - 1."
"Extract a random integer in the range [0, max) for max > 0 from the RNG. "
"If max is 0, return 0. If no max is given, the default is 2^31 - 1."
) {
janet_arity(argc, 1, 2);
JanetRNG *rng = janet_getabstract(argv, 0, &janet_rng_type);
@@ -254,43 +254,45 @@ JANET_CORE_FN(janet_srand,
return janet_wrap_nil();
}
#define JANET_DEFINE_MATHOP(name, fop, doc)\
JANET_CORE_FN(janet_##name, "(math/" #name " x)", doc) {\
#define JANET_DEFINE_NAMED_MATHOP(janet_name, fop, doc)\
JANET_CORE_FN(janet_##fop, "(math/" janet_name " x)", doc) {\
janet_fixarity(argc, 1); \
double x = janet_getnumber(argv, 0); \
return janet_wrap_number(fop(x)); \
}
JANET_DEFINE_MATHOP(acos, acos, "Returns the arccosine of x.")
JANET_DEFINE_MATHOP(asin, asin, "Returns the arcsin of x.")
JANET_DEFINE_MATHOP(atan, atan, "Returns the arctangent of x.")
JANET_DEFINE_MATHOP(cos, cos, "Returns the cosine of x.")
JANET_DEFINE_MATHOP(cosh, cosh, "Returns the hyperbolic cosine of x.")
JANET_DEFINE_MATHOP(acosh, acosh, "Returns the hyperbolic arccosine of x.")
JANET_DEFINE_MATHOP(sin, sin, "Returns the sine of x.")
JANET_DEFINE_MATHOP(sinh, sinh, "Returns the hyperbolic sine of x.")
JANET_DEFINE_MATHOP(asinh, asinh, "Returns the hypberbolic arcsine of x.")
JANET_DEFINE_MATHOP(tan, tan, "Returns the tangent of x.")
JANET_DEFINE_MATHOP(tanh, tanh, "Returns the hyperbolic tangent of x.")
JANET_DEFINE_MATHOP(atanh, atanh, "Returns the hyperbolic arctangent of x.")
JANET_DEFINE_MATHOP(exp, exp, "Returns e to the power of x.")
JANET_DEFINE_MATHOP(exp2, exp2, "Returns 2 to the power of x.")
JANET_DEFINE_MATHOP(expm1, expm1, "Returns e to the power of x minus 1.")
JANET_DEFINE_MATHOP(log, log, "Returns the natural logarithm of x.")
JANET_DEFINE_MATHOP(log10, log10, "Returns the log base 10 of x.")
JANET_DEFINE_MATHOP(log2, log2, "Returns the log base 2 of x.")
JANET_DEFINE_MATHOP(sqrt, sqrt, "Returns the square root of x.")
JANET_DEFINE_MATHOP(cbrt, cbrt, "Returns the cube root of x.")
JANET_DEFINE_MATHOP(ceil, ceil, "Returns the smallest integer value number that is not less than x.")
JANET_DEFINE_MATHOP(fabs, fabs, "Return the absolute value of x.")
JANET_DEFINE_MATHOP(floor, floor, "Returns the largest integer value number that is not greater than x.")
JANET_DEFINE_MATHOP(trunc, trunc, "Returns the integer between x and 0 nearest to x.")
JANET_DEFINE_MATHOP(round, round, "Returns the integer nearest to x.")
JANET_DEFINE_MATHOP(gamma, tgamma, "Returns gamma(x).")
JANET_DEFINE_MATHOP(lgamma, lgamma, "Returns log-gamma(x).")
JANET_DEFINE_MATHOP(log1p, log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
JANET_DEFINE_MATHOP(erf, erf, "Returns the error function of x.")
JANET_DEFINE_MATHOP(erfc, erfc, "Returns the complementary error function of x.")
#define JANET_DEFINE_MATHOP(fop, doc) JANET_DEFINE_NAMED_MATHOP(#fop, fop, doc)
JANET_DEFINE_MATHOP(acos, "Returns the arccosine of x.")
JANET_DEFINE_MATHOP(asin, "Returns the arcsin of x.")
JANET_DEFINE_MATHOP(atan, "Returns the arctangent of x.")
JANET_DEFINE_MATHOP(cos, "Returns the cosine of x.")
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(expm1, "Returns e to the power of x minus 1.")
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) {\
@@ -303,7 +305,7 @@ 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.")
JANET_DEFINE_MATH2OP(nextafter, nextafter, "(math/next x y)", "Returns the next representable floating point vaue after x in the direction of y.")
JANET_DEFINE_MATH2OP(nextafter, nextafter, "(math/next x y)", "Returns the next representable floating point value after x in the direction of y.")
JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") {
janet_fixarity(argc, 1);
@@ -315,7 +317,7 @@ static double janet_gcd(double x, double y) {
#ifdef NAN
return NAN;
#else
return 0.0 \ 0.0;
return 0.0 / 0.0;
#endif
}
if (isinf(x) || isinf(y)) return INFINITY;
@@ -383,7 +385,7 @@ 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_gamma),
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),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose and contributors.
* Copyright (c) 2023 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
@@ -34,9 +34,11 @@
#include <windows.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#ifdef JANET_MSVC
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "Advapi32.lib")
#endif
#else
#include <arpa/inet.h>
#include <unistd.h>
@@ -173,7 +175,6 @@ JanetAsyncStatus net_machine_accept(JanetListenerState *s, JanetAsyncEvent event
JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) {
Janet err;
SOCKET lsock = (SOCKET) stream->handle;
JanetListenerState *s = janet_listen(stream, net_machine_accept, JANET_ASYNC_LISTEN_READ, sizeof(NetStateAccept), NULL);
NetStateAccept *state = (NetStateAccept *)s;
memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED));
@@ -224,7 +225,12 @@ JanetAsyncStatus net_machine_accept(JanetListenerState *s, JanetAsyncEvent event
janet_schedule(s->fiber, janet_wrap_nil());
return JANET_ASYNC_STATUS_DONE;
case JANET_ASYNC_EVENT_READ: {
#if defined(JANET_LINUX)
JSock connfd = accept4(s->stream->handle, NULL, NULL, SOCK_CLOEXEC);
#else
/* On BSDs, CLOEXEC should be inherited from server socket */
JSock connfd = accept(s->stream->handle, NULL, NULL);
#endif
if (JSOCKVALID(connfd)) {
janet_net_socknoblock(connfd);
JanetStream *stream = make_stream(connfd, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE);
@@ -328,6 +334,7 @@ JANET_CORE_FN(cfun_net_sockaddr,
"given in the port argument. On Linux, abstract "
"unix domain sockets are specified with a leading '@' character in port. If `multi` is truthy, will "
"return all address that match in an array instead of just the first.") {
janet_sandbox_assert(JANET_SANDBOX_NET_CONNECT); /* connect OR listen */
janet_arity(argc, 2, 4);
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
@@ -373,6 +380,7 @@ JANET_CORE_FN(cfun_net_connect,
"to specify a connection type, either :stream or :datagram. The default is :stream. "
"Bindhost is an optional string to select from what address to make the outgoing "
"connection, with the default being the same as using the OS's preferred address. ") {
janet_sandbox_assert(JANET_SANDBOX_NET_CONNECT);
janet_arity(argc, 2, 5);
/* Check arguments */
@@ -567,6 +575,7 @@ JANET_CORE_FN(cfun_net_listen,
"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.") {
janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
janet_arity(argc, 2, 3);
/* Get host, port, and handler*/
@@ -701,7 +710,7 @@ JANET_CORE_FN(cfun_net_getsockname,
if (getsockname((JSock)js->handle, (struct sockaddr *) &ss, &slen)) {
janet_panicf("Failed to get localname on %v: %V", argv[0], janet_ev_lasterr());
}
janet_assert(slen <= sizeof(ss), "socket address truncated");
janet_assert(slen <= (socklen_t) sizeof(ss), "socket address truncated");
return janet_so_getname(&ss);
}
@@ -717,13 +726,13 @@ JANET_CORE_FN(cfun_net_getpeername,
if (getpeername((JSock)js->handle, (struct sockaddr *)&ss, &slen)) {
janet_panicf("Failed to get peername on %v: %V", argv[0], janet_ev_lasterr());
}
janet_assert(slen <= sizeof(ss), "socket address truncated");
janet_assert(slen <= (socklen_t) sizeof(ss), "socket address truncated");
return janet_so_getname(&ss);
}
JANET_CORE_FN(cfun_net_address_unpack,
"(net/address-unpack address)",
"Given an address returned by net/adress, return a host, port pair. Unix domain sockets "
"Given an address returned by net/address, return a host, port pair. Unix domain sockets "
"will have only the path in the returned tuple.") {
janet_fixarity(argc, 1);
struct sockaddr *sa = janet_getabstract(argv, 0, &janet_address_type);
@@ -793,7 +802,7 @@ JANET_CORE_FN(cfun_stream_chunk,
}
JANET_CORE_FN(cfun_stream_recv_from,
"(net/recv-from stream nbytes buf &opt timoeut)",
"(net/recv-from stream nbytes buf &opt timeout)",
"Receives data from a server stream and puts it into a buffer. Returns the socket-address the "
"packet came from. Takes an optional timeout in seconds, after which will return nil.") {
janet_arity(argc, 3, 4);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose and contributors.
* Copyright (c) 2023 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
@@ -118,18 +118,26 @@ JANET_CORE_FN(os_which,
"(os/which)",
"Check the current operating system. Returns one of:\n\n"
"* :windows\n\n"
"* :mingw\n\n"
"* :cygwin\n\n"
"* :macos\n\n"
"* :web - Web assembly (emscripten)\n\n"
"* :linux\n\n"
"* :freebsd\n\n"
"* :openbsd\n\n"
"* :netbsd\n\n"
"* :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;
#if defined(JANET_OS_NAME)
return janet_ckeywordv(janet_stringify(JANET_OS_NAME));
#elif defined(JANET_MINGW)
return janet_ckeywordv("mingw");
#elif defined(JANET_CYGWIN)
return janet_ckeywordv("cygwin");
#elif defined(JANET_WINDOWS)
return janet_ckeywordv("windows");
#elif defined(JANET_APPLE)
@@ -144,6 +152,8 @@ JANET_CORE_FN(os_which,
return janet_ckeywordv("netbsd");
#elif defined(__OpenBSD__)
return janet_ckeywordv("openbsd");
#elif defined(__DragonFly__)
return janet_ckeywordv("dragonfly");
#elif defined(JANET_BSD)
return janet_ckeywordv("bsd");
#else
@@ -159,6 +169,8 @@ JANET_CORE_FN(os_arch,
"* :x64\n\n"
"* :arm\n\n"
"* :aarch64\n\n"
"* :riscv32\n\n"
"* :riscv64\n\n"
"* :sparc\n\n"
"* :wasm\n\n"
"* :unknown\n") {
@@ -177,6 +189,10 @@ JANET_CORE_FN(os_arch,
return janet_ckeywordv("aarch64");
#elif defined(_M_ARM) || defined(__arm__)
return janet_ckeywordv("arm");
#elif (defined(__riscv) && (__riscv_xlen == 64))
return janet_ckeywordv("riscv64");
#elif (defined(__riscv) && (__riscv_xlen == 32))
return janet_ckeywordv("riscv32");
#elif (defined(__sparc__))
return janet_ckeywordv("sparc");
#elif (defined(__ppc__))
@@ -188,6 +204,27 @@ JANET_CORE_FN(os_arch,
#endif
}
/* Detect the compiler used to build the interpreter */
JANET_CORE_FN(os_compiler,
"(os/compiler)",
"Get the compiler used to compile the interpreter. Returns one of:\n\n"
"* :gcc\n\n"
"* :clang\n\n"
"* :msvc\n\n"
"* :unknown\n\n") {
janet_fixarity(argc, 0);
(void) argv;
#if defined(_MSC_VER)
return janet_ckeywordv("msvc");
#elif defined(__clang__)
return janet_ckeywordv("clang");
#elif defined(__GNUC__)
return janet_ckeywordv("gcc");
#else
return janet_ckeywordv("unknown");
#endif
}
#undef janet_stringify1
#undef janet_stringify
@@ -209,6 +246,8 @@ JANET_CORE_FN(os_exit,
return janet_wrap_nil();
}
#ifndef JANET_REDUCED_OS
JANET_CORE_FN(os_cpu_count,
"(os/cpu-count &opt dflt)",
"Get an approximate number of CPUs available on for this process to use. If "
@@ -250,7 +289,6 @@ JANET_CORE_FN(os_cpu_count,
#endif
}
#ifndef JANET_REDUCED_OS
#ifndef JANET_NO_PROCESSES
@@ -444,7 +482,9 @@ typedef struct {
static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
JanetProc *proc = (JanetProc *) args.argp;
WaitForSingleObject(proc->pHandle, INFINITE);
GetExitCodeProcess(proc->pHandle, &args.tag);
DWORD exitcode = 0;
GetExitCodeProcess(proc->pHandle, &exitcode);
args.tag = (int32_t) exitcode;
return args;
}
@@ -470,15 +510,7 @@ static int proc_get_status(JanetProc *proc) {
/* Function that is called in separate thread to wait on a pid */
static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
JanetProc *proc = (JanetProc *) args.argp;
#ifdef WNOWAIT
pid_t result;
int status = 0;
do {
result = waitpid(proc->pid, &status, WNOWAIT);
} while (result == -1 && errno == EINTR);
#else
args.tag = proc_get_status(proc);
#endif
return args;
}
@@ -489,11 +521,7 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) {
janet_ev_dec_refcount();
JanetProc *proc = (JanetProc *) args.argp;
if (NULL != proc) {
#ifdef WNOWAIT
int status = proc_get_status(proc);
#else
int status = args.tag;
#endif
proc->return_code = (int32_t) status;
proc->flags |= JANET_PROC_WAITED;
proc->flags &= ~JANET_PROC_WAITING;
@@ -854,6 +882,7 @@ static JanetFile *get_stdio_for_handle(JanetHandle handle, void *orig, int iswri
#endif
static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_arity(argc, 1, 3);
/* Get flags */
@@ -913,9 +942,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
janet_panic("failed to create pipes");
}
/* Result */
int status = 0;
#ifdef JANET_WINDOWS
HANDLE pHandle, tHandle;
@@ -994,6 +1020,9 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
#else
/* Result */
int status = 0;
const char **child_argv = janet_smalloc(sizeof(char *) * ((size_t) exargs.len + 1));
for (int32_t i = 0; i < exargs.len; i++)
child_argv[i] = janet_getcstring(exargs.items, i);
@@ -1014,21 +1043,21 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
posix_spawn_file_actions_addclose(&actions, pipe_in);
} else if (new_in != JANET_HANDLE_NONE) {
} else if (new_in != JANET_HANDLE_NONE && new_in != 0) {
posix_spawn_file_actions_adddup2(&actions, new_in, 0);
posix_spawn_file_actions_addclose(&actions, new_in);
}
if (pipe_out != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_out, 1);
posix_spawn_file_actions_addclose(&actions, pipe_out);
} else if (new_out != JANET_HANDLE_NONE) {
} else if (new_out != JANET_HANDLE_NONE && new_out != 1) {
posix_spawn_file_actions_adddup2(&actions, new_out, 1);
posix_spawn_file_actions_addclose(&actions, new_out);
}
if (pipe_err != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_err, 2);
posix_spawn_file_actions_addclose(&actions, pipe_err);
} else if (new_err != JANET_HANDLE_NONE) {
} 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);
}
@@ -1056,7 +1085,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
os_execute_cleanup(envp, child_argv);
if (status) {
janet_panicf("%p: %s", argv[0], strerror(errno));
/* correct for macos bug where errno is not set */
janet_panicf("%p: %s", argv[0], strerror(errno ? errno : ENOENT));
}
#endif
@@ -1147,6 +1177,7 @@ static JanetEVGenericMessage os_shell_subr(JanetEVGenericMessage args) {
JANET_CORE_FN(os_shell,
"(os/shell str)",
"Pass a command string str directly to the system shell.") {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_arity(argc, 0, 1);
const char *cmd = argc
? janet_getcstring(argv, 0)
@@ -1166,6 +1197,7 @@ JANET_CORE_FN(os_shell,
JANET_CORE_FN(os_environ,
"(os/environ)",
"Get a copy of the OS environment table.") {
janet_sandbox_assert(JANET_SANDBOX_ENV);
(void) argv;
janet_fixarity(argc, 0);
int32_t nenv = 0;
@@ -1197,6 +1229,7 @@ JANET_CORE_FN(os_environ,
JANET_CORE_FN(os_getenv,
"(os/getenv variable &opt dflt)",
"Get the string value of an environment variable.") {
janet_sandbox_assert(JANET_SANDBOX_ENV);
janet_arity(argc, 1, 2);
const char *cstr = janet_getcstring(argv, 0);
const char *res = getenv(cstr);
@@ -1220,6 +1253,7 @@ JANET_CORE_FN(os_setenv,
#define SETENV(K,V) setenv(K, V, 1)
#define UNSETENV(K) unsetenv(K)
#endif
janet_sandbox_assert(JANET_SANDBOX_ENV);
janet_arity(argc, 1, 2);
const char *ks = janet_getcstring(argv, 0);
const char *vs = janet_optcstring(argv, argc, 1, NULL);
@@ -1247,6 +1281,7 @@ JANET_CORE_FN(os_clock,
"(os/clock)",
"Return the number of whole + fractional seconds since some fixed point in time. The clock "
"is guaranteed to be non-decreasing in real time.") {
janet_sandbox_assert(JANET_SANDBOX_HRTIME);
janet_fixarity(argc, 0);
(void) argv;
struct timespec tv;
@@ -1316,6 +1351,40 @@ JANET_CORE_FN(os_cryptorand,
return janet_wrap_buffer(buffer);
}
/* Helper function to get given or current time as local or UTC struct tm.
* - arg n+0: optional time_t to be converted, uses current time if not given
* - arg n+1: optional truthy to indicate the convnersion uses local time */
static struct tm *time_to_tm(const Janet *argv, int32_t argc, int32_t n, struct tm *t_infos) {
time_t t;
if (argc > n && !janet_checktype(argv[n], JANET_NIL)) {
int64_t integer = janet_getinteger64(argv, n);
t = (time_t) integer;
} else {
time(&t);
}
struct tm *t_info = NULL;
if (argc > n + 1 && janet_truthy(argv[n + 1])) {
/* local time */
#ifdef JANET_WINDOWS
_tzset();
localtime_s(t_infos, &t);
t_info = t_infos;
#else
tzset();
t_info = localtime_r(&t, t_infos);
#endif
} else {
/* utc time */
#ifdef JANET_WINDOWS
gmtime_s(t_infos, &t);
t_info = t_infos;
#else
t_info = gmtime_r(&t, t_infos);
#endif
}
return t_info;
}
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. "
@@ -1333,34 +1402,8 @@ JANET_CORE_FN(os_date,
"* :dst - if Day Light Savings is in effect") {
janet_arity(argc, 0, 2);
(void) argv;
time_t t;
struct tm t_infos;
struct tm *t_info = NULL;
if (argc) {
int64_t integer = janet_getinteger64(argv, 0);
t = (time_t) integer;
} else {
time(&t);
}
if (argc >= 2 && janet_truthy(argv[1])) {
/* local time */
#ifdef JANET_WINDOWS
_tzset();
localtime_s(&t_infos, &t);
t_info = &t_infos;
#else
tzset();
t_info = localtime_r(&t, &t_infos);
#endif
} else {
/* utc time */
#ifdef JANET_WINDOWS
gmtime_s(&t_infos, &t);
t_info = &t_infos;
#else
t_info = gmtime_r(&t, &t_infos);
#endif
}
struct tm *t_info = time_to_tm(argv, argc, 0, &t_infos);
JanetKV *st = janet_struct_begin(9);
janet_struct_put(st, janet_ckeywordv("seconds"), janet_wrap_number(t_info->tm_sec));
janet_struct_put(st, janet_ckeywordv("minutes"), janet_wrap_number(t_info->tm_min));
@@ -1374,6 +1417,34 @@ JANET_CORE_FN(os_date,
return janet_wrap_struct(janet_struct_end(st));
}
#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.") {
janet_arity(argc, 1, 3);
const char *fmt = janet_getcstring(argv, 0);
/* ANSI X3.159-1989, section 4.12.3.5 "The strftime function" */
static const char *valid = "aAbBcdHIjmMpSUwWxXyYZ%";
const char *p = fmt;
while (*p) {
if (*p++ == '%') {
if (!strchr(valid, *p)) {
janet_panicf("invalid conversion specifier '%%%c'", *p);
}
p++;
}
}
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);
return janet_cstringv(buf);
}
static int entry_getdst(Janet env_entry) {
Janet v;
if (janet_checktype(env_entry, JANET_TABLE)) {
@@ -1488,6 +1559,7 @@ JANET_CORE_FN(os_link,
"Iff symlink is truthy, creates a symlink. "
"Iff symlink is falsey or not provided, "
"creates a hard link. Does not work on Windows.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_arity(argc, 2, 3);
#ifdef JANET_WINDOWS
(void) argc;
@@ -1506,6 +1578,7 @@ JANET_CORE_FN(os_link,
JANET_CORE_FN(os_symlink,
"(os/symlink oldpath newpath)",
"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
(void) argc;
@@ -1528,6 +1601,7 @@ JANET_CORE_FN(os_mkdir,
"Create a new directory. The path will be relative to the current directory if relative, otherwise "
"it will be an absolute path. Returns true if the directory was created, false if the directory already exists, and "
"errors otherwise.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@@ -1543,6 +1617,7 @@ JANET_CORE_FN(os_mkdir,
JANET_CORE_FN(os_rmdir,
"(os/rmdir path)",
"Delete a directory. The directory must be empty to succeed.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@@ -1557,6 +1632,7 @@ JANET_CORE_FN(os_rmdir,
JANET_CORE_FN(os_cd,
"(os/cd path)",
"Change current directory to path. Returns nil on success, errors on failure.") {
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@@ -1572,6 +1648,7 @@ JANET_CORE_FN(os_touch,
"(os/touch path &opt actime modtime)",
"Update the access time and modification times for a file. By default, sets "
"times to the current time.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_arity(argc, 1, 3);
const char *path = janet_getcstring(argv, 0);
struct utimbuf timebuf, *bufp;
@@ -1780,9 +1857,11 @@ static Janet os_stat_changed(jstat_t *st) {
}
#ifdef JANET_WINDOWS
static Janet os_stat_blocks(jstat_t *st) {
(void) st;
return janet_wrap_number(0);
}
static Janet os_stat_blocksize(jstat_t *st) {
(void) st;
return janet_wrap_number(0);
}
#else
@@ -1819,14 +1898,13 @@ static const struct OsStatGetter os_stat_getters[] = {
};
static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
janet_arity(argc, 1, 2);
const char *path = janet_getcstring(argv, 0);
JanetTable *tab = NULL;
int getall = 1;
const uint8_t *key;
const uint8_t *key = NULL;
if (argc == 2) {
if (janet_checktype(argv[1], JANET_KEYWORD)) {
getall = 0;
key = janet_getkeyword(argv, 1);
} else {
tab = janet_gettable(argv, 1);
@@ -1852,7 +1930,7 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
if (getall) {
if (NULL == key) {
/* Put results in table */
for (const struct OsStatGetter *sg = os_stat_getters; sg->name != NULL; sg++) {
janet_table_put(tab, janet_ckeywordv(sg->name), sg->fn(&st));
@@ -1872,7 +1950,7 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
JANET_CORE_FN(os_stat,
"(os/stat path &opt tab|key)",
"Gets information about a file or directory. Returns a table if the second argument is a keyword, returns "
" only that information from stat. If the file or directory does not exist, returns nil. The keys are:\n\n"
"only that information from stat. If the file or directory does not exist, returns nil. The keys are:\n\n"
"* :dev - the device that the file is on\n\n"
"* :mode - the type of file, one of :file, :directory, :block, :character, :fifo, :socket, :link, or :other\n\n"
"* :int-permissions - A Unix permission integer like 8r744\n\n"
@@ -1902,6 +1980,7 @@ JANET_CORE_FN(os_chmod,
"`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.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 2);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@@ -1917,6 +1996,7 @@ JANET_CORE_FN(os_chmod,
JANET_CORE_FN(os_umask,
"(os/umask mask)",
"Set a new umask, returns the old umask.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 1);
int mask = (int) os_getmode(argv, 0);
#ifdef JANET_WINDOWS
@@ -1932,6 +2012,7 @@ JANET_CORE_FN(os_dir,
"(os/dir dir &opt array)",
"Iterate over files and subdirectories in a directory. Returns an array of paths parts, "
"with only the file name or directory name and no prefix.") {
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
janet_arity(argc, 1, 2);
const char *dir = janet_getcstring(argv, 0);
JanetArray *paths = (argc == 2) ? janet_getarray(argv, 1) : janet_array(0);
@@ -1969,6 +2050,7 @@ JANET_CORE_FN(os_dir,
JANET_CORE_FN(os_rename,
"(os/rename oldname newname)",
"Rename a file on disk to a new path. Returns nil.") {
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
janet_fixarity(argc, 2);
const char *src = janet_getcstring(argv, 0);
const char *dest = janet_getcstring(argv, 1);
@@ -1982,7 +2064,8 @@ JANET_CORE_FN(os_rename,
JANET_CORE_FN(os_realpath,
"(os/realpath path)",
"Get the absolute path for a given path, following ../, ./, and symlinks. "
"Returns an absolute path as a string. Will raise an error on Windows.") {
"Returns an absolute path as a string.") {
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
janet_fixarity(argc, 1);
const char *src = janet_getcstring(argv, 0);
#ifdef JANET_NO_REALPATH
@@ -2060,6 +2143,7 @@ JANET_CORE_FN(os_open,
uint32_t stream_flags = 0;
JanetHandle fd;
#ifdef JANET_WINDOWS
(void) mode;
DWORD desiredAccess = 0;
DWORD shareMode = 0;
DWORD creationDisp = 0;
@@ -2076,19 +2160,23 @@ JANET_CORE_FN(os_open,
case 'r':
desiredAccess |= GENERIC_READ;
stream_flags |= JANET_STREAM_READABLE;
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
break;
case 'w':
desiredAccess |= GENERIC_WRITE;
stream_flags |= JANET_STREAM_WRITABLE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'c':
creatUnix |= OCREAT;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'e':
creatUnix |= OEXCL;
break;
case 't':
creatUnix |= OTRUNC;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
/* Windows only flags */
case 'D':
@@ -2149,30 +2237,32 @@ JANET_CORE_FN(os_open,
#ifdef JANET_LINUX
open_flags |= O_CLOEXEC;
#endif
int read_flag = 0;
int write_flag = 0;
for (const uint8_t *c = opt_flags; *c; c++) {
switch (*c) {
default:
break;
case 'r':
open_flags = (open_flags & O_WRONLY)
? ((open_flags & ~O_WRONLY) | O_RDWR)
: (open_flags | O_RDONLY);
read_flag = 1;
stream_flags |= JANET_STREAM_READABLE;
janet_sandbox_assert(JANET_SANDBOX_FS_READ);
break;
case 'w':
open_flags = (open_flags & O_RDONLY)
? ((open_flags & ~O_RDONLY) | O_RDWR)
: (open_flags | O_WRONLY);
write_flag = 1;
stream_flags |= JANET_STREAM_WRITABLE;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'c':
open_flags |= O_CREAT;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
case 'e':
open_flags |= O_EXCL;
break;
case 't':
open_flags |= O_TRUNC;
janet_sandbox_assert(JANET_SANDBOX_FS_WRITE);
break;
/* posix only */
case 'x':
@@ -2186,6 +2276,15 @@ JANET_CORE_FN(os_open,
break;
}
}
/* If both read and write, fix up to O_RDWR */
if (read_flag && !write_flag) {
open_flags |= O_RDONLY;
} else if (write_flag && !read_flag) {
open_flags |= O_WRONLY;
} else {
open_flags = O_RDWR;
}
do {
fd = open(path, open_flags, mode);
} while (fd == -1 && errno == EINTR);
@@ -2236,49 +2335,68 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/exit", os_exit),
JANET_CORE_REG("os/which", os_which),
JANET_CORE_REG("os/arch", os_arch),
JANET_CORE_REG("os/compiler", os_compiler),
#ifndef JANET_REDUCED_OS
/* misc (un-sandboxed) */
JANET_CORE_REG("os/cpu-count", os_cpu_count),
JANET_CORE_REG("os/cwd", os_cwd),
JANET_CORE_REG("os/cryptorand", os_cryptorand),
JANET_CORE_REG("os/perm-string", os_permission_string),
JANET_CORE_REG("os/perm-int", os_permission_int),
JANET_CORE_REG("os/mktime", os_mktime),
JANET_CORE_REG("os/time", os_time), /* not high resolution */
JANET_CORE_REG("os/date", os_date), /* not high resolution */
JANET_CORE_REG("os/strftime", os_strftime),
JANET_CORE_REG("os/sleep", os_sleep),
/* env functions */
JANET_CORE_REG("os/environ", os_environ),
JANET_CORE_REG("os/getenv", os_getenv),
JANET_CORE_REG("os/setenv", os_setenv),
/* fs read */
JANET_CORE_REG("os/dir", os_dir),
JANET_CORE_REG("os/stat", os_stat),
JANET_CORE_REG("os/lstat", os_lstat),
JANET_CORE_REG("os/chmod", os_chmod),
JANET_CORE_REG("os/touch", os_touch),
JANET_CORE_REG("os/realpath", os_realpath),
JANET_CORE_REG("os/cd", os_cd),
JANET_CORE_REG("os/cpu-count", os_cpu_count),
#ifndef JANET_NO_UMASK
JANET_CORE_REG("os/umask", os_umask),
#endif
#ifndef JANET_NO_SYMLINKS
JANET_CORE_REG("os/readlink", os_readlink),
#endif
/* fs write */
JANET_CORE_REG("os/mkdir", os_mkdir),
JANET_CORE_REG("os/rmdir", os_rmdir),
JANET_CORE_REG("os/rm", os_remove),
JANET_CORE_REG("os/link", os_link),
JANET_CORE_REG("os/rename", os_rename),
#ifndef JANET_NO_SYMLINKS
JANET_CORE_REG("os/symlink", os_symlink),
JANET_CORE_REG("os/readlink", os_readlink),
#endif
/* processes */
#ifndef JANET_NO_PROCESSES
JANET_CORE_REG("os/execute", os_execute),
JANET_CORE_REG("os/spawn", os_spawn),
JANET_CORE_REG("os/shell", os_shell),
/* 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),
#endif
JANET_CORE_REG("os/setenv", os_setenv),
JANET_CORE_REG("os/time", os_time),
JANET_CORE_REG("os/mktime", os_mktime),
/* high resolution timers */
JANET_CORE_REG("os/clock", os_clock),
JANET_CORE_REG("os/sleep", os_sleep),
JANET_CORE_REG("os/cwd", os_cwd),
JANET_CORE_REG("os/cryptorand", os_cryptorand),
JANET_CORE_REG("os/date", os_date),
JANET_CORE_REG("os/rename", os_rename),
JANET_CORE_REG("os/realpath", os_realpath),
JANET_CORE_REG("os/perm-string", os_permission_string),
JANET_CORE_REG("os/perm-int", os_permission_int),
#ifdef JANET_EV
JANET_CORE_REG("os/open", os_open),
JANET_CORE_REG("os/open", os_open), /* fs read and write */
JANET_CORE_REG("os/pipe", os_pipe),
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -206,6 +206,37 @@ static void popstate(JanetParser *p, Janet val) {
}
}
static void delim_error(JanetParser *parser, size_t stack_index, char c, const char *msg) {
JanetParseState *s = parser->states + stack_index;
JanetBuffer *buffer = janet_buffer(40);
if (msg) {
janet_buffer_push_cstring(buffer, msg);
}
if (c) {
janet_buffer_push_u8(buffer, c);
}
if (stack_index > 0) {
janet_buffer_push_cstring(buffer, ", ");
if (s->flags & PFLAG_PARENS) {
janet_buffer_push_u8(buffer, '(');
} else if (s->flags & PFLAG_SQRBRACKETS) {
janet_buffer_push_u8(buffer, '[');
} else if (s->flags & PFLAG_CURLYBRACKETS) {
janet_buffer_push_u8(buffer, '{');
} else if (s->flags & PFLAG_STRING) {
janet_buffer_push_u8(buffer, '"');
} else if (s->flags & PFLAG_LONGSTRING) {
int32_t i;
for (i = 0; i < s->argn; i++) {
janet_buffer_push_u8(buffer, '`');
}
}
janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column);
}
parser->error = (const char *) janet_string(buffer->data, buffer->count);
parser->flag |= JANET_PARSER_GENERATED_ERROR;
}
static int checkescape(uint8_t c) {
switch (c) {
default:
@@ -612,7 +643,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) {
case '}': {
Janet ds;
if (p->statecount == 1) {
p->error = "unexpected delimiter";
delim_error(p, 0, c, "unexpected closing delimiter ");
return 1;
}
if ((c == ')' && (state->flags & PFLAG_PARENS)) ||
@@ -633,7 +664,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) {
ds = close_struct(p, state);
}
} else {
p->error = "mismatched delimiter";
delim_error(p, p->statecount - 1, c, "mismatched delimiter ");
return 1;
}
popstate(p, ds);
@@ -684,26 +715,7 @@ void janet_parser_eof(JanetParser *parser) {
size_t oldline = parser->line;
janet_parser_consume(parser, '\n');
if (parser->statecount > 1) {
JanetParseState *s = parser->states + (parser->statecount - 1);
JanetBuffer *buffer = janet_buffer(40);
janet_buffer_push_cstring(buffer, "unexpected end of source, ");
if (s->flags & PFLAG_PARENS) {
janet_buffer_push_u8(buffer, '(');
} else if (s->flags & PFLAG_SQRBRACKETS) {
janet_buffer_push_u8(buffer, '[');
} else if (s->flags & PFLAG_CURLYBRACKETS) {
janet_buffer_push_u8(buffer, '{');
} else if (s->flags & PFLAG_STRING) {
janet_buffer_push_u8(buffer, '"');
} else if (s->flags & PFLAG_LONGSTRING) {
int32_t i;
for (i = 0; i < s->argn; i++) {
janet_buffer_push_u8(buffer, '`');
}
}
janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column);
parser->error = (const char *) janet_string(buffer->data, buffer->count);
parser->flag |= JANET_PARSER_GENERATED_ERROR;
delim_error(parser, parser->statecount - 1, 0, "unexpected end of source");
}
parser->line = oldline;
parser->column = oldcolumn;
@@ -1182,7 +1194,8 @@ static Janet parser_state_delimiters(const JanetParser *_p) {
}
}
}
str = janet_string(p->buf + oldcount, (int32_t)(p->bufcount - oldcount));
/* avoid ptr arithmetic on NULL */
str = janet_string(oldcount ? p->buf + oldcount : p->buf, (int32_t)(p->bufcount - oldcount));
p->bufcount = oldcount;
return janet_wrap_string(str);
}
@@ -1193,10 +1206,11 @@ static Janet parser_state_frames(const JanetParser *p) {
states->count = count;
uint8_t *buf = p->buf;
/* Iterate arg stack backwards */
Janet *args = p->args + p->argcount;
Janet *args = p->argcount ? p->args + p->argcount : p->args; /* avoid ptr arithmetic on NULL */
for (int32_t i = count - 1; i >= 0; --i) {
JanetParseState *s = p->states + i;
if (s->flags & PFLAG_CONTAINER) {
/* avoid ptr arithmetic on args if NULL */
if ((s->flags & PFLAG_CONTAINER) && s->argn) {
args -= s->argn;
}
states->data[i] = janet_wrap_parse_state(s, args, buf, (uint32_t) p->bufcount);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -211,9 +211,10 @@ tail:
}
case RULE_SET: {
if (text >= s->text_end) return NULL;
uint32_t word = rule[1 + (text[0] >> 5)];
uint32_t mask = (uint32_t)1 << (text[0] & 0x1F);
return (text < s->text_end && (word & mask))
return (word & mask)
? text + 1
: NULL;
}
@@ -260,30 +261,52 @@ tail:
goto tail;
}
case RULE_IF:
case RULE_IFNOT: {
case RULE_IF: {
const uint32_t *rule_a = s->bytecode + rule[1];
const uint32_t *rule_b = s->bytecode + rule[2];
down1(s);
const uint8_t *result = peg_rule(s, rule_a, text);
up1(s);
if (rule[0] == RULE_IF ? !result : !!result) return NULL;
if (!result) return NULL;
rule = rule_b;
goto tail;
}
case RULE_IFNOT: {
const uint32_t *rule_a = s->bytecode + rule[1];
const uint32_t *rule_b = s->bytecode + rule[2];
down1(s);
CapState cs = cap_save(s);
const uint8_t *result = peg_rule(s, rule_a, text);
if (!!result) {
up1(s);
return NULL;
} else {
cap_load(s, cs);
up1(s);
rule = rule_b;
goto tail;
}
}
case RULE_NOT: {
const uint32_t *rule_a = s->bytecode + rule[1];
down1(s);
CapState cs = cap_save(s);
const uint8_t *result = peg_rule(s, rule_a, text);
up1(s);
return (result) ? NULL : text;
if (result) {
up1(s);
return NULL;
} else {
cap_load(s, cs);
up1(s);
return text;
}
}
case RULE_THRU:
case RULE_TO: {
const uint32_t *rule_a = s->bytecode + rule[1];
const uint8_t *next_text;
const uint8_t *next_text = NULL;
CapState cs = cap_save(s);
down1(s);
while (text <= s->text_end) {
@@ -1011,7 +1034,7 @@ static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) {
emit_3(r, RULE_CAPTURE_NUM, rule, base, tag);
return;
error:
peg_panicf(b, "expected integer between 2 and 36, got %v", argv[2]);
peg_panicf(b, "expected integer between 2 and 36, got %v", argv[1]);
}
static void spec_reference(Builder *b, int32_t argc, const Janet *argv) {
@@ -1614,7 +1637,7 @@ typedef struct {
JanetPeg *peg;
PegState s;
JanetByteView bytes;
JanetByteView repl;
Janet subst;
int32_t start;
} PegCall;
@@ -1630,7 +1653,7 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) {
ret.peg = compile_peg(argv[0]);
}
if (get_replace) {
ret.repl = janet_getbytes(argv, 1);
ret.subst = argv[1];
ret.bytes = janet_getbytes(argv, 2);
} else {
ret.bytes = janet_getbytes(argv, 1);
@@ -1661,7 +1684,9 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) {
}
static void peg_call_reset(PegCall *c) {
c->s.depth = JANET_RECURSION_GUARD;
c->s.captures->count = 0;
c->s.tagged_captures->count = 0;
c->s.scratch->count = 0;
c->s.tags->count = 0;
}
@@ -1713,7 +1738,8 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) {
trail = i;
}
int32_t nexti = (int32_t)(result - c.bytes.bytes);
janet_buffer_push_bytes(ret, c.repl.bytes, c.repl.len);
JanetByteView subst = janet_text_substitution(&c.subst, c.bytes.bytes + i, nexti - i, c.s.captures);
janet_buffer_push_bytes(ret, subst.bytes, subst.len);
trail = nexti;
if (nexti == i) nexti++;
i = nexti;
@@ -1729,14 +1755,20 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) {
}
JANET_CORE_FN(cfun_peg_replace_all,
"(peg/replace-all peg repl text &opt start & args)",
"Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.") {
"(peg/replace-all peg subst text &opt start & args)",
"Replace all matches of `peg` in `text` with `subst`, returning a new buffer. "
"The peg does not need to make captures to do replacement. "
"If `subst` is a function, it will be called with the "
"matching text followed by any captures.") {
return cfun_peg_replace_generic(argc, argv, 0);
}
JANET_CORE_FN(cfun_peg_replace,
"(peg/replace peg repl text &opt start & args)",
"Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. "
"Replace first match of `peg` in `text` with `subst`, returning a new buffer. "
"The peg does not need to make captures to do replacement. "
"If `subst` is a function, it will be called with the "
"matching text followed by any captures. "
"If no matches are found, returns the input string in a new buffer.") {
return cfun_peg_replace_generic(argc, argv, 1);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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,6 +30,7 @@
#include <string.h>
#include <ctype.h>
#include <inttypes.h>
/* Implements a pretty printer for Janet. The pretty printer
* is simple and not that flexible, but fast. */
@@ -108,7 +109,7 @@ static void string_description_b(JanetBuffer *buffer, const char *title, void *p
pbuf.p = pointer;
*c++ = '<';
/* Maximum of 32 bytes for abstract type name */
for (i = 0; title[i] && i < 32; ++i)
for (i = 0; i < 32 && title[i]; ++i)
*c++ = ((uint8_t *)title) [i];
*c++ = ' ';
*c++ = '0';
@@ -636,7 +637,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
}
}
janet_sorted_keys(kvs, cap, S->keysort_buffer + ks_start);
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;
@@ -750,14 +751,41 @@ static void pushtypes(JanetBuffer *buffer, int types) {
#define MAX_ITEM 256
#define FMT_FLAGS "-+ #0"
#define FMT_REPLACE_INTTYPES "diouxX"
#define MAX_FORMAT 32
struct FmtMapping {
char c;
const char *mapping;
};
/* Janet uses fixed width integer types for most things, so map
* format specifiers to these fixed sizes */
static const struct FmtMapping format_mappings[] = {
{'d', PRId64},
{'i', PRIi64},
{'o', PRIo64},
{'u', PRIu64},
{'x', PRIx64},
{'X', PRIX64},
};
static const char *get_fmt_mapping(char c) {
for (size_t i = 0; i < (sizeof(format_mappings) / sizeof(struct FmtMapping)); i++) {
if (format_mappings[i].c == c)
return format_mappings[i].mapping;
}
return NULL;
}
static const char *scanformat(
const char *strfrmt,
char *form,
char width[3],
char precision[3]) {
const char *p = strfrmt;
/* Parse strfrmt */
memset(width, '\0', 3);
memset(precision, '\0', 3);
while (*p != '\0' && strchr(FMT_FLAGS, *p) != NULL)
@@ -776,10 +804,23 @@ static const char *scanformat(
}
if (isdigit((int)(*p)))
janet_panic("invalid format (width or precision too long)");
/* Write to form - replace characters with fixed size stuff */
*(form++) = '%';
memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char));
form += (p - strfrmt) + 1;
const char *p2 = strfrmt;
while (p2 <= p) {
char *loc = strchr(FMT_REPLACE_INTTYPES, *p2);
if (loc != NULL && *loc != '\0') {
const char *mapping = get_fmt_mapping(*p2++);
size_t len = strlen(mapping);
strcpy(form, mapping);
form += len;
} else {
*(form++) = *(p2++);
}
}
*form = '\0';
return p;
}
@@ -804,11 +845,16 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) {
break;
}
case 'd':
case 'i':
case 'o':
case 'i': {
int64_t n = va_arg(args, long);
nb = snprintf(item, MAX_ITEM, form, n);
break;
}
case 'x':
case 'X': {
int32_t n = va_arg(args, long);
case 'X':
case 'o':
case 'u': {
uint64_t n = va_arg(args, unsigned long);
nb = snprintf(item, MAX_ITEM, form, n);
break;
}
@@ -962,11 +1008,16 @@ void janet_buffer_format(
break;
}
case 'd':
case 'i':
case 'o':
case 'i': {
int64_t n = janet_getinteger64(argv, arg);
nb = snprintf(item, MAX_ITEM, form, n);
break;
}
case 'x':
case 'X': {
int32_t n = janet_getinteger(argv, arg);
case 'X':
case 'o':
case 'u': {
uint64_t n = janet_getuinteger64(argv, arg);
nb = snprintf(item, MAX_ITEM, form, n);
break;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -80,9 +80,9 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
const char *e = janet_parser_error(&parser);
errflags |= 0x04;
ret = janet_cstringv(e);
size_t line = parser.line;
size_t col = parser.column;
janet_eprintf("%s:%lu:%lu: parse error: %s\n", sourcePath, line, col, 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);
done = 1;
break;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -39,6 +39,10 @@ static JanetSlot janetc_quote(JanetFopts opts, int32_t argn, const Janet *argv)
static JanetSlot janetc_splice(JanetFopts opts, int32_t argn, const Janet *argv) {
JanetSlot ret;
if (!(opts.flags & JANET_FOPTS_ACCEPT_SPLICE)) {
janetc_cerror(opts.compiler, "splice can only be used in function parameters and data constructors, it has no effect here");
return janetc_cslot(janet_wrap_nil());
}
if (argn != 1) {
janetc_cerror(opts.compiler, "expected 1 argument to splice");
return janetc_cslot(janet_wrap_nil());
@@ -75,7 +79,9 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) {
const uint8_t *head = janet_unwrap_symbol(tup[0]);
if (!janet_cstrcmp(head, "unquote")) {
if (level == 0) {
return janetc_value(janetc_fopts_default(opts.compiler), tup[1]);
JanetFopts subopts = janetc_fopts_default(opts.compiler);
subopts.flags |= JANET_FOPTS_ACCEPT_SPLICE;
return janetc_value(subopts, tup[1]);
} else {
level--;
}
@@ -203,8 +209,9 @@ static int destructure(JanetCompiler *c,
janetc_emit(c, JOP_JUMP);
int32_t label_loop_exit = janet_v_count(c->buffer);
c->buffer[label_loop_cond_jump] |= (label_loop_exit - label_loop_cond_jump) << 16;
c->buffer[label_loop_loop] |= (label_loop_start - label_loop_loop) << 8;
/* avoid shifting negative numbers */
c->buffer[label_loop_cond_jump] |= (uint32_t)(label_loop_exit - label_loop_cond_jump) << 16;
c->buffer[label_loop_loop] |= (uint32_t)(label_loop_start - label_loop_loop) << 8;
janetc_freeslot(c, argi);
janetc_freeslot(c, arg);
@@ -487,6 +494,7 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) {
/* Get options */
condopts = janetc_fopts_default(c);
bodyopts = opts;
bodyopts.flags &= ~JANET_FOPTS_ACCEPT_SPLICE;
/* Set target for compilation */
target = (drop || tail)
@@ -563,6 +571,7 @@ static JanetSlot janetc_do(JanetFopts opts, int32_t argn, const Janet *argv) {
subopts.flags = JANET_FOPTS_DROP;
} else {
subopts = opts;
subopts.flags &= ~JANET_FOPTS_ACCEPT_SPLICE;
}
ret = janetc_value(subopts, argv[i]);
if (i != argn - 1) {
@@ -586,6 +595,7 @@ static JanetSlot janetc_upscope(JanetFopts opts, int32_t argn, const Janet *argv
subopts.flags = JANET_FOPTS_DROP;
} else {
subopts = opts;
subopts.flags &= ~JANET_FOPTS_ACCEPT_SPLICE;
}
ret = janetc_value(subopts, argv[i]);
if (i != argn - 1) {
@@ -813,7 +823,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
JanetSlot ret;
Janet head;
JanetScope fnscope;
int32_t paramcount, argi, parami, arity, min_arity, max_arity, defindex, i;
int32_t paramcount, argi, parami, arity, min_arity = 0, max_arity, defindex, i;
JanetFopts subopts = janetc_fopts_default(c);
const Janet *params;
const char *errmsg = NULL;
@@ -958,12 +968,26 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) {
max_arity = (vararg || allow_extra) ? INT32_MAX : arity;
if (!seenopt) min_arity = arity;
/* Check for self ref */
/* Check for self ref (also avoid if arguments shadow own name) */
if (selfref) {
JanetSlot slot = janetc_farslot(c);
slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION;
janetc_emit_s(c, JOP_LOAD_SELF, slot, 1);
janetc_nameslot(c, janet_unwrap_symbol(head), slot);
/* Check if the parameters shadow the function name. If so, don't
* emit JOP_LOAD_SELF and add a binding since that most users
* seem to expect that function parameters take precedence over the
* function name */
const uint8_t *sym = janet_unwrap_symbol(head);
int32_t len = janet_v_count(c->scope->syms);
int found = 0;
for (int32_t i = 0; i < len; i++) {
if (c->scope->syms[i].sym == sym) {
found = 1;
}
}
if (!found) {
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);
}
}
/* Compile function body */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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,8 +23,15 @@
#ifndef JANET_STATE_H_defined
#define JANET_STATE_H_defined
#include <janet.h>
#include <stdint.h>
#ifdef JANET_EV
#ifndef JANET_WINDOWS
#include <pthread.h>
#endif
#endif
typedef int64_t JanetTimestamp;
typedef struct JanetScratch {
@@ -54,7 +61,7 @@ typedef struct {
int is_error;
} JanetTimeout;
/* Registry table for C functions - containts metadata that can
/* Registry table for C functions - contains metadata that can
* be looked up by cfunction pointer. All strings here are pointing to
* static memory not managed by Janet. */
typedef struct {
@@ -85,7 +92,7 @@ struct JanetVM {
int auto_suspend;
/* The current running fiber on the current thread.
* Set and unset by janet_run. */
* Set and unset by functions in vm.c */
JanetFiber *fiber;
JanetFiber *root_fiber;
@@ -101,7 +108,7 @@ struct JanetVM {
size_t registry_count;
int registry_dirty;
/* Registry for abstract abstract types that can be marshalled.
/* Registry for abstract types that can be marshalled.
* We need this to look up the constructors when unmarshalling. */
JanetTable *abstract_registry;
@@ -129,6 +136,9 @@ struct JanetVM {
size_t scratch_cap;
size_t scratch_len;
/* Sandbox flags */
uint32_t sandbox_flags;
/* Random number generator */
JanetRNG rng;
@@ -149,19 +159,23 @@ struct JanetVM {
size_t listener_cap;
size_t extra_listeners;
JanetTable threaded_abstracts; /* All abstract types that can be shared between threads (used in this thread) */
JanetTable active_tasks; /* All possibly live task fibers - used just for tracking */
#ifdef JANET_WINDOWS
void **iocp;
#elif defined(JANET_EV_EPOLL)
pthread_attr_t new_thread_attr;
JanetHandle selfpipe[2];
int epoll;
int timerfd;
int timer_enabled;
#elif defined(JANET_EV_KQUEUE)
pthread_attr_t new_thread_attr;
JanetHandle selfpipe[2];
int kq;
int timer;
int timer_enabled;
#else
pthread_attr_t new_thread_attr;
JanetHandle selfpipe[2];
struct pollfd *fds;
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -364,14 +364,13 @@ JANET_CORE_FN(cfun_string_findall,
struct replace_state {
struct kmp_state kmp;
const uint8_t *subst;
int32_t substlen;
Janet subst;
};
static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) {
janet_arity(argc, 3, 4);
JanetByteView pat = janet_getbytes(argv, 0);
JanetByteView subst = janet_getbytes(argv, 1);
Janet subst = argv[1];
JanetByteView text = janet_getbytes(argv, 2);
int32_t start = 0;
if (argc == 4) {
@@ -380,13 +379,14 @@ static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) {
}
kmp_init(&s->kmp, text.bytes, text.len, pat.bytes, pat.len);
s->kmp.i = start;
s->subst = subst.bytes;
s->substlen = subst.len;
s->subst = subst;
}
JANET_CORE_FN(cfun_string_replace,
"(string/replace patt subst str)",
"Replace the first occurrence of `patt` with `subst` in the string `str`. "
"If `subst` is a function, it will be called with `patt` only if a match is found, "
"and should return the actual replacement text to use. "
"Will return the new string if `patt` is found, otherwise returns `str`.") {
int32_t result;
struct replace_state s;
@@ -397,10 +397,11 @@ JANET_CORE_FN(cfun_string_replace,
kmp_deinit(&s.kmp);
return janet_stringv(s.kmp.text, s.kmp.textlen);
}
buf = janet_string_begin(s.kmp.textlen - s.kmp.patlen + s.substlen);
JanetByteView subst = janet_text_substitution(&s.subst, s.kmp.text + result, s.kmp.patlen, NULL);
buf = janet_string_begin(s.kmp.textlen - s.kmp.patlen + subst.len);
safe_memcpy(buf, s.kmp.text, result);
safe_memcpy(buf + result, s.subst, s.substlen);
safe_memcpy(buf + result + s.substlen,
safe_memcpy(buf + result, subst.bytes, subst.len);
safe_memcpy(buf + result + subst.len,
s.kmp.text + result + s.kmp.patlen,
s.kmp.textlen - result - s.kmp.patlen);
kmp_deinit(&s.kmp);
@@ -411,6 +412,8 @@ JANET_CORE_FN(cfun_string_replaceall,
"(string/replace-all patt subst str)",
"Replace all instances of `patt` with `subst` in the string `str`. Overlapping "
"matches will not be counted, only the first match in such a span will be replaced. "
"If `subst` is a function, it will be called with `patt` once for each match, "
"and should return the actual replacement text to use. "
"Will return the new string if `patt` is found, otherwise returns `str`.") {
int32_t result;
struct replace_state s;
@@ -419,8 +422,9 @@ JANET_CORE_FN(cfun_string_replaceall,
replacesetup(argc, argv, &s);
janet_buffer_init(&b, s.kmp.textlen);
while ((result = kmp_next(&s.kmp)) >= 0) {
JanetByteView subst = janet_text_substitution(&s.subst, s.kmp.text + result, s.kmp.patlen, NULL);
janet_buffer_push_bytes(&b, s.kmp.text + lastindex, result - lastindex);
janet_buffer_push_bytes(&b, s.subst, s.substlen);
janet_buffer_push_bytes(&b, subst.bytes, subst.len);
lastindex = result + s.kmp.patlen;
kmp_seti(&s.kmp, lastindex);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -39,9 +39,11 @@
#ifdef JANET_WINDOWS
#ifdef JANET_DYNAMIC_MODULES
#include <psapi.h>
#ifdef JANET_MSVC
#pragma comment (lib, "Psapi.lib")
#endif
#endif
#endif
#ifdef JANET_APPLE
#include <AvailabilityMacros.h>
@@ -90,8 +92,8 @@ const char *const janet_signal_names[14] = {
"user5",
"user6",
"user7",
"user8",
"user9"
"interrupt",
"await"
};
const char *const janet_status_names[16] = {
@@ -107,8 +109,8 @@ const char *const janet_status_names[16] = {
"user5",
"user6",
"user7",
"user8",
"user9",
"interrupted",
"suspended",
"new",
"alive"
};
@@ -116,6 +118,7 @@ const char *const janet_status_names[16] = {
#ifndef JANET_PRF
int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
if (NULL == str) return 5381;
const uint8_t *end = str + len;
uint32_t hash = 5381;
while (str < end)
@@ -660,6 +663,59 @@ JanetBinding janet_binding_from_entry(Janet entry) {
return binding;
}
/* If the value at the given address can be coerced to a byte view,
return that byte view. If it can't, replace the value at the address
with the result of janet_to_string, and return a byte view over that
string. */
static JanetByteView memoize_byte_view(Janet *value) {
JanetByteView result;
if (!janet_bytes_view(*value, &result.bytes, &result.len)) {
JanetString str = janet_to_string(*value);
*value = janet_wrap_string(str);
result.bytes = str;
result.len = janet_string_length(str);
}
return result;
}
static JanetByteView to_byte_view(Janet value) {
JanetByteView result;
if (!janet_bytes_view(value, &result.bytes, &result.len)) {
JanetString str = janet_to_string(value);
result.bytes = str;
result.len = janet_string_length(str);
}
return result;
}
JanetByteView janet_text_substitution(
Janet *subst,
const uint8_t *bytes,
uint32_t len,
JanetArray *extra_argv) {
int32_t extra_argc = extra_argv == NULL ? 0 : extra_argv->count;
JanetType type = janet_type(*subst);
switch (type) {
case JANET_FUNCTION:
case JANET_CFUNCTION: {
int32_t argc = 1 + extra_argc;
Janet *argv = janet_tuple_begin(argc);
argv[0] = janet_stringv(bytes, len);
for (int32_t i = 0; i < extra_argc; i++) {
argv[i + 1] = extra_argv->data[i];
}
janet_tuple_end(argv);
if (type == JANET_FUNCTION) {
return to_byte_view(janet_call(janet_unwrap_function(*subst), argc, argv));
} else {
return to_byte_view(janet_unwrap_cfunction(*subst)(argc, argv));
}
}
default:
return memoize_byte_view(subst);
}
}
JanetBinding janet_resolve_ext(JanetTable *env, const uint8_t *sym) {
Janet entry = janet_table_get(env, janet_wrap_symbol(sym));
return janet_binding_from_entry(entry);
@@ -701,15 +757,25 @@ int janet_indexed_view(Janet seq, const Janet **data, int32_t *len) {
/* Read both strings and buffer as unsigned character array + int32_t len.
* Returns 1 if the view can be constructed and 0 if the type is invalid. */
int janet_bytes_view(Janet str, const uint8_t **data, int32_t *len) {
if (janet_checktype(str, JANET_STRING) || janet_checktype(str, JANET_SYMBOL) ||
janet_checktype(str, JANET_KEYWORD)) {
JanetType t = janet_type(str);
if (t == JANET_STRING || t == JANET_SYMBOL || t == JANET_KEYWORD) {
*data = janet_unwrap_string(str);
*len = janet_string_length(janet_unwrap_string(str));
return 1;
} else if (janet_checktype(str, JANET_BUFFER)) {
} else if (t == JANET_BUFFER) {
*data = janet_unwrap_buffer(str)->data;
*len = janet_unwrap_buffer(str)->count;
return 1;
} else if (t == JANET_ABSTRACT) {
void *abst = janet_unwrap_abstract(str);
const JanetAbstractType *atype = janet_abstract_type(abst);
if (NULL == atype->bytes) {
return 0;
}
JanetByteView view = atype->bytes(abst, janet_abstract_size(abst));
*data = view.bytes;
*len = view.len;
return 1;
}
return 0;
}
@@ -853,13 +919,13 @@ int janet_cryptorand(uint8_t *out, size_t n) {
unsigned int v;
if (rand_s(&v))
return -1;
for (int32_t j = 0; (j < sizeof(unsigned int)) && (i + j < n); j++) {
for (int32_t j = 0; (j < (int32_t) sizeof(unsigned int)) && (i + j < n); j++) {
out[i + j] = v & 0xff;
v = v >> 8;
}
}
return 0;
#elif defined(JANET_LINUX) || ( defined(JANET_APPLE) && !defined(MAC_OS_X_VERSION_10_7) )
#elif defined(JANET_LINUX) || defined(JANET_CYGWIN) || ( defined(JANET_APPLE) && !defined(MAC_OS_X_VERSION_10_7) )
/* We should be able to call getrandom on linux, but it doesn't seem
to be uniformly supported on linux distros.
On Mac, arc4random_buf wasn't available on until 10.7.
@@ -910,6 +976,13 @@ char *get_processed_name(const char *name) {
return ret;
}
#if defined(JANET_NO_DYNAMIC_MODULES)
const char *error_clib(void) {
return "dynamic modules not supported";
}
#else
#if defined(JANET_WINDOWS)
static char error_clib_buf[256];
@@ -957,6 +1030,7 @@ void *symbol_clib(HINSTANCE clib, const char *sym) {
}
}
#endif
#endif
/* Alloc function macro fills */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -93,6 +93,11 @@ void janet_buffer_format(
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);
/* Registry functions */
void janet_registry_put(
@@ -135,7 +140,7 @@ int janet_gettime(struct timespec *spec);
typedef int Clib;
#define load_clib(name) ((void) name, 0)
#define symbol_clib(lib, sym) ((void) lib, (void) sym, NULL)
#define error_clib() "dynamic libraries not supported"
const char *error_clib(void);
#define free_clib(c) ((void) (c), 0)
#elif defined(JANET_WINDOWS)
#include <windows.h>
@@ -150,7 +155,7 @@ typedef void *Clib;
#define load_clib(name) dlopen((name), RTLD_NOW)
#define free_clib(lib) dlclose((lib))
#define symbol_clib(lib, sym) dlsym((lib), (sym))
#define error_clib() dlerror()
#define error_clib dlerror
#endif
char *get_processed_name(const char *name);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -272,6 +272,7 @@ int janet_equals(Janet x, Janet y) {
const Janet *t1 = janet_unwrap_tuple(x);
const Janet *t2 = janet_unwrap_tuple(y);
if (t1 == t2) break;
if (JANET_TUPLE_FLAG_BRACKETCTOR & (janet_tuple_flag(t1) ^ janet_tuple_flag(t2))) return 0;
if (janet_tuple_hash(t1) != janet_tuple_hash(t2)) return 0;
if (janet_tuple_length(t1) != janet_tuple_length(t2)) return 0;
push_traversal_node(janet_tuple_head(t1), janet_tuple_head(t2), 0);
@@ -295,6 +296,15 @@ int janet_equals(Janet x, Janet y) {
return 1;
}
static uint64_t murmur64(uint64_t h) {
h ^= h >> 33;
h *= 0xff51afd7ed558ccdUL;
h ^= h >> 33;
h *= 0xc4ceb9fe1a85ec53UL;
h ^= h >> 33;
return h;
}
/* Computes a hash value for a function */
int32_t janet_hash(Janet x) {
int32_t hash = 0;
@@ -312,6 +322,7 @@ 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;
break;
case JANET_STRUCT:
hash = janet_struct_hash(janet_unwrap_struct(x));
@@ -341,11 +352,8 @@ int32_t janet_hash(Janet x) {
default:
if (sizeof(double) == sizeof(void *)) {
/* Assuming 8 byte pointer (8 byte aligned) */
uint64_t i = janet_u64(x);
uint32_t lo = (uint32_t)(i & 0xFFFFFFFF);
uint32_t hi = (uint32_t)(i >> 32);
uint32_t hilo = (hi ^ lo) * 2654435769u;
hash = (int32_t)((hilo << 16) | (hilo >> 16));
uint64_t i = murmur64(janet_u64(x));
hash = (int32_t)(i >> 32);
} else {
/* Assuming 4 byte pointer (or smaller) */
uintptr_t diff = (uintptr_t) janet_unwrap_pointer(x);
@@ -406,6 +414,9 @@ int janet_compare(Janet x, Janet y) {
case JANET_TUPLE: {
const Janet *lhs = janet_unwrap_tuple(x);
const Janet *rhs = janet_unwrap_tuple(y);
if (JANET_TUPLE_FLAG_BRACKETCTOR & (janet_tuple_flag(lhs) ^ janet_tuple_flag(rhs))) {
return (janet_tuple_flag(lhs) & JANET_TUPLE_FLAG_BRACKETCTOR) ? 1 : -1;
}
push_traversal_node(janet_tuple_head(lhs), janet_tuple_head(rhs), 1);
break;
}
@@ -645,6 +656,15 @@ int32_t janet_length(Janet x) {
case JANET_TABLE:
return janet_unwrap_table(x)->count;
case JANET_ABSTRACT: {
void *abst = janet_unwrap_abstract(x);
const JanetAbstractType *type = janet_abstract_type(abst);
if (type->length != NULL) {
size_t len = type->length(abst, janet_abstract_size(abst));
if (len > INT32_MAX) {
janet_panicf("invalid integer length %u", len);
}
return (int32_t)(len);
}
Janet argv[1] = { x };
Janet len = janet_mcall("length", 1, argv);
if (!janet_checkint(len))
@@ -673,6 +693,16 @@ Janet janet_lengthv(Janet x) {
case JANET_TABLE:
return janet_wrap_integer(janet_unwrap_table(x)->count);
case JANET_ABSTRACT: {
void *abst = janet_unwrap_abstract(x);
const JanetAbstractType *type = janet_abstract_type(abst);
if (type->length != NULL) {
size_t len = type->length(abst, janet_abstract_size(abst));
if ((uint64_t) len <= (uint64_t) JANET_INTMAX_INT64) {
return janet_wrap_number((double) len);
} else {
janet_panicf("integer length %u too large", len);
}
}
Janet argv[1] = { x };
return janet_mcall("length", 1, argv);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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
@@ -918,7 +918,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
int32_t i;
for (i = 0; i < elen; ++i) {
int32_t inherit = fd->environments[i];
if (inherit == -1) {
if (inherit == -1 || inherit >= func->def->environments_length) {
JanetStackFrame *frame = janet_stack_frame(stack);
if (!frame->env) {
/* Lazy capture of current stack frame */
@@ -1559,6 +1559,9 @@ int janet_init(void) {
janet_vm.scratch_len = 0;
janet_vm.scratch_cap = 0;
/* Sandbox flags */
janet_vm.sandbox_flags = 0;
/* Initialize registry */
janet_vm.registry = NULL;
janet_vm.registry_cap = 0;
@@ -1600,6 +1603,18 @@ int janet_init(void) {
return 0;
}
/* Disable some features at runtime with no way to re-enable them */
void janet_sandbox(uint32_t flags) {
janet_sandbox_assert(JANET_SANDBOX_SANDBOX);
janet_vm.sandbox_flags |= flags;
}
void janet_sandbox_assert(uint32_t forbidden_flags) {
if (forbidden_flags & janet_vm.sandbox_flags) {
janet_panic("operation forbidden by sandbox");
}
}
/* Clear all memory associated with the VM */
void janet_deinit(void) {
janet_clear_memory();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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) 2022 Calvin Rose
* Copyright (c) 2023 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,6 +67,11 @@ extern "C" {
#define JANET_LINUX 1
#endif
/* Check for Cygwin */
#if defined(__CYGWIN__)
#define JANET_CYGWIN 1
#endif
/* Check Unix */
#if defined(_AIX) \
|| defined(__APPLE__) /* Darwin */ \
@@ -87,6 +92,16 @@ extern "C" {
#define JANET_WINDOWS 1
#endif
/* Check if compiling with MSVC - else assume a GCC-like compiler by default */
#ifdef _MSC_VER
#define JANET_MSVC
#endif
/* Check Mingw 32-bit and 64-bit */
#ifdef __MINGW32__
#define JANET_MINGW
#endif
/* Check 64-bit vs 32-bit */
#if ((defined(__x86_64__) || defined(_M_X64)) \
&& (defined(JANET_POSIX) || defined(JANET_WINDOWS))) \
@@ -96,7 +111,8 @@ extern "C" {
|| (defined(__sparc__) && defined(__arch64__) || defined (__sparcv9)) /* BE */ \
|| defined(__s390x__) /* S390 64-bit (BE) */ \
|| (defined(__ppc64__) || defined(__PPC64__)) \
|| defined(__aarch64__) /* ARM 64-bit */
|| defined(__aarch64__) /* ARM 64-bit */ \
|| (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */
#define JANET_64 1
#else
#define JANET_32 1
@@ -171,6 +187,13 @@ extern "C" {
#endif
#endif
/* If FFI is enabled and FFI-JIT is not disabled... */
#ifdef JANET_FFI
#ifndef JANET_NO_FFI_JIT
#define JANET_FFI_JIT
#endif
#endif
/* Enable or disable the assembler. Enabled by default. */
#ifndef JANET_NO_ASSEMBLER
#define JANET_ASSEMBLER
@@ -236,7 +259,7 @@ extern "C" {
/* Maximum depth to follow table prototypes before giving up and returning nil. */
#define JANET_MAX_PROTO_DEPTH 200
/* Maximum depth to follow table prototypes before giving up and returning nil. */
/* Prevent macros to expand too deeply and error out. */
#define JANET_MAX_MACRO_EXPAND 200
/* Define default max stack size for stacks before raising a stack overflow error.
@@ -257,10 +280,11 @@ extern "C" {
#ifndef JANET_NO_NANBOX
#ifdef JANET_32
#define JANET_NANBOX_32
#elif defined(__x86_64__) || defined(_WIN64)
#elif defined(__x86_64__) || defined(_WIN64) || defined(__riscv)
/* We will only enable nanboxing by default on 64 bit systems
* on x86. This is mainly because the approach is tied to the
* implicit 47 bit address space. */
* for x64 and risc-v. This is mainly because the approach is tied to the
* implicit 47 bit address space. Many arches allow/require this, but not all,
* and it requires cooperation from the OS. ARM should also work in many configurations. */
#define JANET_NANBOX_64
#endif
#endif
@@ -333,7 +357,7 @@ typedef struct JanetOSRWLock JanetOSRWLock;
/* What to do when out of memory */
#ifndef JANET_OUT_OF_MEMORY
#define JANET_OUT_OF_MEMORY do { fprintf(stderr, "janet out of memory\n"); exit(1); } while (0)
#define JANET_OUT_OF_MEMORY do { fprintf(stderr, "%s:%d - janet out of memory\n", __FILE__, __LINE__); exit(1); } while (0)
#endif
#ifdef JANET_BSD
@@ -425,6 +449,7 @@ typedef struct JanetReg JanetReg;
typedef struct JanetRegExt JanetRegExt;
typedef struct JanetMethod JanetMethod;
typedef struct JanetSourceMapping JanetSourceMapping;
typedef struct JanetSymbolMap JanetSymbolMap;
typedef struct JanetView JanetView;
typedef struct JanetByteView JanetByteView;
typedef struct JanetDictView JanetDictView;
@@ -980,6 +1005,7 @@ struct JanetAbstractHead {
/* Some function definition flags */
#define JANET_FUNCDEF_FLAG_VARARG 0x10000
#define JANET_FUNCDEF_FLAG_NEEDSENV 0x20000
#define JANET_FUNCDEF_FLAG_HASSYMBOLMAP 0x40000
#define JANET_FUNCDEF_FLAG_HASNAME 0x80000
#define JANET_FUNCDEF_FLAG_HASSOURCE 0x100000
#define JANET_FUNCDEF_FLAG_HASDEFS 0x200000
@@ -995,6 +1021,14 @@ struct JanetSourceMapping {
int32_t column;
};
/* Symbol to slot mapping & lifetime structure. */
struct JanetSymbolMap {
uint32_t birth_pc;
uint32_t death_pc;
uint32_t slot_index;
const uint8_t *symbol;
};
/* A function definition. Contains information needed to instantiate closures. */
struct JanetFuncDef {
JanetGCObject gc;
@@ -1008,6 +1042,7 @@ struct JanetFuncDef {
JanetSourceMapping *sourcemap;
JanetString source;
JanetString name;
JanetSymbolMap *symbolmap;
int32_t flags;
int32_t slotcount; /* The amount of stack space required for the function */
@@ -1018,6 +1053,7 @@ struct JanetFuncDef {
int32_t bytecode_length;
int32_t environments_length;
int32_t defs_length;
int32_t symbolmap_length;
};
/* A function environment */
@@ -1093,6 +1129,8 @@ struct JanetAbstractType {
int32_t (*hash)(void *p, size_t len);
Janet(*next)(void *p, Janet key);
Janet(*call)(void *p, int32_t argc, Janet *argv);
size_t (*length)(void *p, size_t len);
JanetByteView(*bytes)(void *p, size_t len);
};
/* Some macros to let us add extra types to JanetAbstract types without
@@ -1110,7 +1148,9 @@ struct JanetAbstractType {
#define JANET_ATEND_COMPARE NULL,JANET_ATEND_HASH
#define JANET_ATEND_HASH NULL,JANET_ATEND_NEXT
#define JANET_ATEND_NEXT NULL,JANET_ATEND_CALL
#define JANET_ATEND_CALL
#define JANET_ATEND_CALL NULL,JANET_ATEND_LENGTH
#define JANET_ATEND_LENGTH NULL,JANET_ATEND_BYTES
#define JANET_ATEND_BYTES
struct JanetReg {
const char *name;
@@ -1545,8 +1585,10 @@ JANET_API Janet janet_array_pop(JanetArray *array);
JANET_API Janet janet_array_peek(JanetArray *array);
/* Buffer functions */
#define JANET_BUFFER_FLAG_NO_REALLOC 0x10000
JANET_API JanetBuffer *janet_buffer(int32_t capacity);
JANET_API JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity);
JANET_API JanetBuffer *janet_pointer_buffer_unsafe(void *memory, int32_t capacity, int32_t count);
JANET_API void janet_buffer_deinit(JanetBuffer *buffer);
JANET_API void janet_buffer_ensure(JanetBuffer *buffer, int32_t capacity, int32_t growth);
JANET_API void janet_buffer_setcount(JanetBuffer *buffer, int32_t count);
@@ -1645,6 +1687,7 @@ JANET_API void janet_table_clear(JanetTable *table);
JANET_API JanetFiber *janet_fiber(JanetFunction *callee, int32_t capacity, int32_t argc, const Janet *argv);
JANET_API JanetFiber *janet_fiber_reset(JanetFiber *fiber, JanetFunction *callee, int32_t argc, const Janet *argv);
JANET_API JanetFiberStatus janet_fiber_status(JanetFiber *fiber);
JANET_API int janet_fiber_can_resume(JanetFiber *fiber);
JANET_API JanetFiber *janet_current_fiber(void);
JANET_API JanetFiber *janet_root_fiber(void);
@@ -1671,6 +1714,7 @@ JANET_API JanetModule janet_native(const char *name, JanetString *error);
/* Marshaling */
#define JANET_MARSHAL_UNSAFE 0x20000
#define JANET_MARSHAL_NO_CYCLES 0x40000
JANET_API void janet_marshal(
JanetBuffer *buf,
@@ -1758,6 +1802,24 @@ JANET_API Janet janet_mcall(const char *name, int32_t argc, Janet *argv);
JANET_API void janet_stacktrace(JanetFiber *fiber, Janet err);
JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix);
/* Sandboxing API */
#define JANET_SANDBOX_SANDBOX 1
#define JANET_SANDBOX_SUBPROCESS 2
#define JANET_SANDBOX_NET_CONNECT 4
#define JANET_SANDBOX_NET_LISTEN 8
#define JANET_SANDBOX_FFI 16
#define JANET_SANDBOX_FS_WRITE 32
#define JANET_SANDBOX_FS_READ 64
#define JANET_SANDBOX_HRTIME 128
#define JANET_SANDBOX_ENV 256
#define JANET_SANDBOX_DYNAMIC_MODULES 512
#define JANET_SANDBOX_FS_TEMP 1024
#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_ALL (UINT32_MAX)
JANET_API void janet_sandbox(uint32_t flags);
JANET_API void janet_sandbox_assert(uint32_t forbidden_flags);
/* Scratch Memory API */
typedef void (*JanetScratchFinalizer)(void *);
@@ -1816,7 +1878,7 @@ JANET_API Janet janet_resolve_core(const char *name);
/* sourcemaps only */
#define JANET_REG_S(JNAME, CNAME) {JNAME, CNAME, NULL, __FILE__, CNAME##_sourceline_}
#define JANET_FN_S(CNAME, USAGE, DOCSTRING) \
static int32_t CNAME##_sourceline_ = __LINE__; \
static const int32_t CNAME##_sourceline_ = __LINE__; \
Janet CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_S(ENV, JNAME, VAL, DOC) \
janet_def_sm(ENV, JNAME, VAL, NULL, __FILE__, __LINE__)
@@ -1832,7 +1894,7 @@ JANET_API Janet janet_resolve_core(const char *name);
/* sourcemaps and docstrings */
#define JANET_REG_SD(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, __FILE__, CNAME##_sourceline_}
#define JANET_FN_SD(CNAME, USAGE, DOCSTRING) \
static int32_t CNAME##_sourceline_ = __LINE__; \
static const int32_t CNAME##_sourceline_ = __LINE__; \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
Janet CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_SD(ENV, JNAME, VAL, DOC) \
@@ -1906,6 +1968,7 @@ JANET_API JanetTable *janet_gettable(const Janet *argv, int32_t n);
JANET_API JanetStruct janet_getstruct(const Janet *argv, int32_t n);
JANET_API JanetString janet_getstring(const Janet *argv, int32_t n);
JANET_API const char *janet_getcstring(const Janet *argv, int32_t n);
JANET_API const char *janet_getcbytes(const Janet *argv, int32_t n);
JANET_API JanetSymbol janet_getsymbol(const Janet *argv, int32_t n);
JANET_API JanetKeyword janet_getkeyword(const Janet *argv, int32_t n);
JANET_API JanetBuffer *janet_getbuffer(const Janet *argv, int32_t n);
@@ -1935,6 +1998,7 @@ JANET_API JanetTuple janet_opttuple(const Janet *argv, int32_t argc, int32_t n,
JANET_API JanetStruct janet_optstruct(const Janet *argv, int32_t argc, int32_t n, JanetStruct dflt);
JANET_API JanetString janet_optstring(const Janet *argv, int32_t argc, int32_t n, JanetString dflt);
JANET_API const char *janet_optcstring(const Janet *argv, int32_t argc, int32_t n, const char *dflt);
JANET_API const char *janet_optcbytes(const Janet *argv, int32_t argc, int32_t n, const char *dflt);
JANET_API JanetSymbol janet_optsymbol(const Janet *argv, int32_t argc, int32_t n, JanetString dflt);
JANET_API JanetKeyword janet_optkeyword(const Janet *argv, int32_t argc, int32_t n, JanetString dflt);
JANET_API JanetFiber *janet_optfiber(const Janet *argv, int32_t argc, int32_t n, JanetFiber *dflt);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Calvin Rose
* Copyright (c) 2023 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,9 +30,13 @@
#ifdef _WIN32
#include <windows.h>
#include <shlwapi.h>
#include <versionhelpers.h>
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif
#endif
void janet_line_init();
@@ -143,9 +147,13 @@ static void setup_console_output(void) {
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (IsWindows10OrGreater()) {
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
}
SetConsoleMode(hOut, dwMode);
SetConsoleOutputCP(65001);
if (IsValidCodePage(65001)) {
SetConsoleOutputCP(65001);
}
}
/* Ansi terminal raw mode */
@@ -157,8 +165,10 @@ static int rawmode(void) {
dwMode &= ~ENABLE_LINE_INPUT;
dwMode &= ~ENABLE_INSERT_MODE;
dwMode &= ~ENABLE_ECHO_INPUT;
dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
dwMode &= ~ENABLE_PROCESSED_INPUT;
if (IsWindows10OrGreater()) {
dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
dwMode &= ~ENABLE_PROCESSED_INPUT;
}
if (!SetConsoleMode(hOut, dwMode)) return 1;
gbl_israwmode = 1;
return 0;
@@ -173,8 +183,10 @@ static void norawmode(void) {
dwMode |= ENABLE_LINE_INPUT;
dwMode |= ENABLE_INSERT_MODE;
dwMode |= ENABLE_ECHO_INPUT;
dwMode &= ~ENABLE_VIRTUAL_TERMINAL_INPUT;
dwMode |= ENABLE_PROCESSED_INPUT;
if (IsWindows10OrGreater()) {
dwMode &= ~ENABLE_VIRTUAL_TERMINAL_INPUT;
dwMode |= ENABLE_PROCESSED_INPUT;
}
SetConsoleMode(hOut, dwMode);
gbl_israwmode = 0;
}
@@ -296,6 +308,7 @@ static char *sdup(const char *s) {
return memcpy(mem, s, len);
}
#ifndef _WIN32
static int curpos(void) {
char buf[32];
int cols, rows;
@@ -311,6 +324,7 @@ static int curpos(void) {
if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) return -1;
return cols;
}
#endif
static int getcols(void) {
#ifdef _WIN32
@@ -950,6 +964,7 @@ static int line() {
break;
#ifndef _WIN32
case 26: /* ctrl-z */
clearlines();
norawmode();
kill(getpid(), SIGSTOP);
rawmode();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Calvin Rose
* Copyright (c) 2023 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

@@ -24,6 +24,11 @@
(def errsym (keyword (gensym)))
~(assert (= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg))
(defn check-compile-error
[form]
(def result (compile form))
(assert (table? result) (string/format "expected compilation error for %j, but compiled without error" form)))
(defmacro assert-no-error
[msg & forms]
(def errsym (keyword (gensym)))

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose
# Copyright (c) 2023 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) 2021 Calvin Rose
# Copyright (c) 2023 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
@@ -228,7 +228,7 @@
(assert (= 14 (sum (map inc @[1 2 3 4]))) "sum map")
(def myfun (juxt + - * /))
(assert (= '[2 -2 2 0.5] (myfun 2)) "juxt")
(assert (= [2 -2 2 0.5] (myfun 2)) "juxt")
# Case statements
(assert
@@ -252,6 +252,9 @@
(def xs (apply tuple (seq [x :down [8 -2] :when (even? x)] (tuple (/ x 2) x))))
(assert (= xs '((4 8) (3 6) (2 4) (1 2) (0 0))) "seq macro 2")
(def xs (catseq [x :range [0 3]] [x x]))
(assert (deep= xs @[0 0 1 1 2 2]) "catseq")
# :range-to and :down-to
(assert (deep= (seq [x :range-to [0 10]] x) (seq [x :range [0 11]] x)) "loop :range-to")
(assert (deep= (seq [x :down-to [10 0]] x) (seq [x :down [10 -1]] x)) "loop :down-to")
@@ -342,6 +345,8 @@
(assert (= (and 0 1 nil) nil) "and 0 1 nil")
(assert (= (and 1) 1) "and 1")
(assert (= (and) true) "and with no arguments")
(assert (= (and 1 true) true) "and with trailing true")
(assert (= (and 1 true 2) 2) "and with internal true")
(assert (= (or true true) true) "or true true")
(assert (= (or true false) true) "or true false")

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose
# Copyright (c) 2023 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
@@ -41,10 +41,10 @@
# Looping idea
(def xs
(seq [x :in '[-1 0 1] y :in '[-1 0 1] :when (not= x y 0)] (tuple x y)))
(seq [x :in [-1 0 1] y :in [-1 0 1] :when (not= x y 0)] (tuple x y)))
(def txs (apply tuple xs))
(assert (= txs '[[-1 -1] [-1 0] [-1 1] [0 -1] [0 1] [1 -1] [1 0] [1 1]]) "nested seq")
(assert (= txs [[-1 -1] [-1 0] [-1 1] [0 -1] [0 1] [1 -1] [1 0] [1 1]]) "nested seq")
# Generators
(def gen (generate [x :range [0 100] :when (pos? (% x 4))] x))
@@ -72,6 +72,10 @@
(assert (= (string/replace "X" "." "XXX...XXX...XXX") ".XX...XXX...XXX") "string/replace 1")
(assert (= (string/replace-all "X" "." "XXX...XXX...XXX") "...............") "string/replace-all 1")
(assert (= (string/replace-all "XX" "." "XXX...XXX...XXX") ".X....X....X") "string/replace-all 2")
(assert (= (string/replace "xx" string/ascii-upper "xxyxyxyxxxy") "XXyxyxyxxxy") "string/replace function")
(assert (= (string/replace-all "xx" string/ascii-upper "xxyxyxyxxxy") "XXyxyxyXXxy") "string/replace-all function")
(assert (= (string/replace "x" 12 "xyx") "12yx") "string/replace stringable")
(assert (= (string/replace-all "x" 12 "xyx") "12y12") "string/replace-all stringable")
(assert (= (string/ascii-lower "ABCabc&^%!@:;.") "abcabc&^%!@:;.") "string/ascii-lower")
(assert (= (string/ascii-upper "ABCabc&^%!@:;.") "ABCABC&^%!@:;.") "string/ascii-lower")
(assert (= (string/reverse "") "") "string/reverse 1")

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose
# Copyright (c) 2023 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
@@ -349,7 +349,7 @@
(def janet-longstring
~{:delim (some "`")
:open (capture :delim :n)
:close (cmt (* (not (> -1 "`")) (-> :n) (<- :delim)) ,=)
:close (cmt (* (not (> -1 "`")) (-> :n) (<- (backmatch :n))) ,=)
:main (* :open (any (if-not :close 1)) :close -1)})
(check-match janet-longstring "`john" false)
@@ -359,6 +359,7 @@
(check-match janet-longstring "`` ``" true)
(check-match janet-longstring "``` `` ```" true)
(check-match janet-longstring "`` ```" false)
(check-match janet-longstring "`a``b`" false)
# Line and column capture

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose
# Copyright (c) 2023 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) 2021 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -83,8 +83,13 @@
(assert (deep= (drop 10 []) []) "drop 2")
(assert (deep= (drop 0 [1 2 3 4 5]) [1 2 3 4 5]) "drop 3")
(assert (deep= (drop 10 [1 2 3]) []) "drop 4")
(assert (deep= (drop -2 [:a :b :c]) [:a :b :c]) "drop 5")
(assert-error :invalid-type (drop 3 {}) "drop 6")
(assert (deep= (drop -1 [1 2 3]) [1 2]) "drop 5")
(assert (deep= (drop -10 [1 2 3]) []) "drop 6")
(assert (deep= (drop 1 "abc") "bc") "drop 7")
(assert (deep= (drop 10 "abc") "") "drop 8")
(assert (deep= (drop -1 "abc") "ab") "drop 9")
(assert (deep= (drop -10 "abc") "") "drop 10")
(assert-error :invalid-type (drop 3 {}) "drop 11")
# drop-until
@@ -98,4 +103,18 @@
# Quasiquote bracketed tuples
(assert (= (tuple/type ~[1 2 3]) (tuple/type '[1 2 3])) "quasiquote bracket tuples")
# No useless splices
(check-compile-error '((splice [1 2 3]) 0))
(check-compile-error '(if ;[1 2] 5))
(check-compile-error '(while ;[1 2 3] (print :hi)))
(check-compile-error '(def x ;[1 2 3]))
(check-compile-error '(fn [x] ;[x 1 2 3]))
# No splice propagation
(check-compile-error '(+ 1 (do ;[2 3 4]) 5))
(check-compile-error '(+ 1 (upscope ;[2 3 4]) 5))
# compiler inlines when condition is constant, ensure that optimization doesn't break
(check-compile-error '(+ 1 (if true ;[3 4])))
(check-compile-error '(+ 1 (if false nil ;[3 4])))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -261,4 +261,12 @@
(modcheck -10 3)
(modcheck -10 -3)
# Check for issue #1130
(var d (int/s64 7))
(mod 0 d)
(var d (int/s64 7))
(def result (seq [n :in (range -21 0)] (mod n d)))
(assert (deep= result (map int/s64 @[0 1 2 3 4 5 6 0 1 2 3 4 5 6 0 1 2 3 4 5 6])) "issue #1130")
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -60,7 +60,7 @@
:buffer (/ '(* "@" :bytes) (constant :string))
:long-bytes {:delim (some "`")
:open (capture :delim :n)
:close (cmt (* (not (> -1 "`")) (-> :n) ':delim) ,=)
:close (cmt (* (not (> -1 "`")) (-> :n) '(backmatch :n)) ,=)
:main (drop (* :open (any (if-not :close 1)) :close))}
:long-string (/ ':long-bytes (constant :string))
:long-buffer (/ '(* "@" :long-bytes) (constant :string))
@@ -239,6 +239,12 @@
(assert (= (os/mktime (os/date now true) true) now) "local os/mktime")
(assert (= (os/mktime {:year 1970}) 0) "os/mktime default values")
# OS strftime test
(assert (= (os/strftime "%Y-%m-%d %H:%M:%S" 0) "1970-01-01 00:00:00") "strftime UTC epoch")
(assert (= (os/strftime "%Y-%m-%d %H:%M:%S" 1388608200) "2014-01-01 20:30:00") "strftime january 2014")
(assert (= (try (os/strftime "%%%d%t") ([err] err)) "invalid conversion specifier '%t'") "invalid conversion specifier")
# Appending buffer to self
(with-dyns [:out @""]
@@ -294,9 +300,12 @@
(assert-error "comptime issue" (eval '(comptime (error "oops"))))
(with [f (file/temp)]
(assert (= 0 (file/tell f)) "start of file")
(file/write f "foo\n")
(assert (= 4 (file/tell f)) "after written string")
(file/flush f)
(file/seek f :set 0)
(assert (= 0 (file/tell f)) "start of file again")
(assert (= (string (file/read f :all)) "foo\n") "temp files work"))
(var counter 0)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -330,7 +330,6 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (deep= (peg/find-all '"/" p) @[0 4 10 14]) "peg find-all")
# Peg replace and replace-all
(var ti 0)
(defn check-replacer
[x y z]
(assert (= (string/replace x y z) (string (peg/replace x y z))) "replacer test replace")
@@ -339,6 +338,32 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(check-replacer "abc" "Z" "")
(check-replacer "aba" "ZZZZZZ" "ababababababa")
(check-replacer "aba" "" "ababababababa")
(check-replacer "aba" string/ascii-upper "ababababababa")
(check-replacer "aba" 123 "ababababababa")
(assert (= (string (peg/replace-all ~(set "ab") string/ascii-upper "abcaa"))
"ABcAA")
"peg/replace-all cfunction")
(assert (= (string (peg/replace-all ~(set "ab") |$ "abcaa"))
"abcaa")
"peg/replace-all function")
(defn peg-test [name f peg subst text expected]
(assert (= (string (f peg subst text)) expected) name))
(peg-test "peg/replace has access to captures"
peg/replace
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b.c")
(peg-test "peg/replace-all has access to captures"
peg/replace-all
~(sequence "." (capture (set "ab")))
(fn [str char] (string/format "%s -> %s, " str (string/ascii-upper char)))
".a.b.c"
".a -> A, .b -> B, .c")
# Peg bug
(assert (deep= @[] (peg/match '(any 1) @"")) "peg empty pattern 1")

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -265,4 +265,15 @@
(ev/do-thread
(assert (ev/take ch) "channel packing bug for threaded abstracts on threaded channels."))
# marshal channels
(def ch (ev/chan 10))
(ev/give ch "hello")
(ev/give ch "world")
(def ch2 (-> ch marshal unmarshal))
(def item1 (ev/take ch2))
(def item2 (ev/take ch2))
(assert (= item1 "hello"))
(assert (= item2 "world"))
(end-suite)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -44,6 +44,43 @@
(assert (= :brackets (tuple/type (1 (macex1 '~[1 2 3 4])))) "macex1 qq bracket tuple")
(assert (deep= (macex1 '~@[1 2 3 4 ,blah]) '~@[1 2 3 4 ,blah]) "macex1 qq array")
# Sourcemaps in threading macros
(defn check-threading [macro expansion]
(def expanded (macex1 (tuple macro 0 '(x) '(y))))
(assert (= expanded expansion) (string macro " expansion value"))
(def smap-x (tuple/sourcemap (get expanded 1)))
(def smap-y (tuple/sourcemap expanded))
(def line first)
(defn column [t] (t 1))
(assert (not= smap-x [-1 -1]) (string macro " x sourcemap existence"))
(assert (not= smap-y [-1 -1]) (string macro " y sourcemap existence"))
(assert (or (< (line smap-x) (line smap-y))
(and (= (line smap-x) (line smap-y))
(< (column smap-x) (column smap-y))))
(string macro " relation between x and y sourcemap")))
(check-threading '-> '(y (x 0)))
(check-threading '->> '(y (x 0)))
# keep-syntax
(let [brak '[1 2 3]
par '(1 2 3)]
(tuple/setmap brak 2 1)
(assert (deep= (keep-syntax brak @[1 2 3]) @[1 2 3]) "keep-syntax brackets ignore array")
(assert (= (keep-syntax! brak @[1 2 3]) '[1 2 3]) "keep-syntax! brackets replace array")
(assert (= (keep-syntax! par (map inc @[1 2 3])) '(2 3 4)) "keep-syntax! parens coerce array")
(assert (not= (keep-syntax! brak @[1 2 3]) '(1 2 3)) "keep-syntax! brackets not parens")
(assert (not= (keep-syntax! par @[1 2 3]) '[1 2 3]) "keep-syntax! parens not brackets")
(assert (= (tuple/sourcemap brak)
(tuple/sourcemap (keep-syntax! brak @[1 2 3]))) "keep-syntax! brackets source map")
(keep-syntax par brak)
(assert (not= (tuple/sourcemap brak) (tuple/sourcemap par)) "keep-syntax no mutate")
(assert (= (keep-syntax 1 brak) brak) "keep-syntax brackets ignore type"))
# Cancel test
(def f (fiber/new (fn [&] (yield 1) (yield 2) (yield 3) 4) :yti))
(assert (= 1 (resume f)) "cancel resume 1")
@@ -144,7 +181,7 @@
(assert (< 1000 1e23) "greater than immediate 2")
# os/execute with environment variables
(assert (= 0 (os/execute [(dyn :executable) "-e" "(+ 1 2 3)"] :pe {"HELLO" "WORLD"})) "os/execute with env")
(assert (= 0 (os/execute [(dyn :executable) "-e" "(+ 1 2 3)"] :pe (merge (os/environ) {"HELLO" "WORLD"}))) "os/execute with env")
# Regression #638
(compwhen

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2022 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -102,5 +102,7 @@
(assert (= 6 (with-dyns [*err* errout] (dummy 1 2 3))) "trace to custom err function")
(assert (deep= @"trace (dummy 1 2 3)\n" b) "trace buffer correct"))
(end-suite)
(def f (asm (disasm (fn [x] (fn [y] (+ x y))))))
(assert (= ((f 10) 37) 47) "asm environment tables")
(end-suite)

View File

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

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2022 Calvin Rose & contributors
# Copyright (c) 2023 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
@@ -27,4 +27,17 @@
(assert (deep= (tabseq [i :in (range 3)] i)
@{}))
(def- sym-prefix-peg
(peg/compile
~{:symchar (+ (range "\x80\xff" "AZ" "az" "09") (set "!$%&*+-./:<?=>@^_"))
:anchor (drop (cmt ($) ,|(= $ 0)))
:cap (* (+ (> -1 (not :symchar)) :anchor) (* ($) '(some :symchar)))
:recur (+ :cap (> -1 :recur))
:main (> -1 :recur)}))
(assert (deep= (peg/match sym-prefix-peg @"123" 3) @[0 "123"]) "peg lookback")
(assert (deep= (peg/match sym-prefix-peg @"1234" 4) @[0 "1234"]) "peg lookback 2")
(assert (deep= (peg/replace-all '(* (<- 1) 1 (backmatch)) "xxx" "aba cdc efa") @"xxx xxx efa") "peg replace-all 1")
(end-suite)

20
test/suite0014.janet Normal file
View File

@@ -0,0 +1,20 @@
(import ./helper :prefix "" :exit true)
(start-suite 14)
(assert (deep=
(peg/match '(not (* (constant 7) "a")) "hello")
@[]) "peg not")
(assert (deep=
(peg/match '(if-not (* (constant 7) "a") "hello") "hello")
@[]) "peg if-not")
(assert (deep=
(peg/match '(if-not (drop (* (constant 7) "a")) "hello") "hello")
@[]) "peg if-not drop")
(assert (deep=
(peg/match '(if (not (* (constant 7) "a")) "hello") "hello")
@[]) "peg if not")
(end-suite)

48
test/suite0015.janet Normal file
View File

@@ -0,0 +1,48 @@
# test *debug* flags
(import ./helper :prefix "" :exit true)
(start-suite 15)
(assert (deep= (in (disasm (defn a [] (def x 10) x)) :symbolmap)
@[[0 3 0 'a] [1 3 1 'x]])
"symbolslots when *debug* is true")
(defn a [arg]
(def x 10)
(do
(def y 20)
(def z 30)
(+ x y z)))
(def symbolslots (in (disasm a) :symbolslots))
(def f (asm (disasm a)))
(assert (deep= (in (disasm f) :symbolslots)
symbolslots)
"symbolslots survive disasm/asm")
(comment
(setdyn *debug* true)
(setdyn :pretty-format "%.40M")
(def f (fn [x] (fn [y] (+ x y))))
(assert (deep= (map last (in (disasm (f 10)) :symbolmap))
@['x 'y])
"symbolslots upvalues"))
(assert (deep= (in (disasm (defn a [arg]
(def x 10)
(do
(def y 20)
(def z 30)
(+ x y z)))) :symbolmap)
@[[0 7 0 'arg]
[0 7 1 'a]
[1 7 2 'x]
[2 7 3 'y]
[3 7 4 'z]])
"arg & inner symbolslots")
# buffer/push-at
(assert (deep= @"abc456" (buffer/push-at @"abc123" 3 "456")) "buffer/push-at 1")
(assert (deep= @"abc456789" (buffer/push-at @"abc123" 3 "456789")) "buffer/push-at 2")
(assert (deep= @"abc423" (buffer/push-at @"abc123" 3 "4")) "buffer/push-at 3")
(end-suite)

0
tools/format.sh Normal file → Executable file
View File

Binary file not shown.

View File

@@ -159,6 +159,7 @@
<Condition>ALLUSERS=1</Condition>
<Environment Id="PATH_PERMACHINE" Name="PATH" Value="[BinDir]" Action="set" Permanent="no" System="yes" Part="last"/>
<Environment Id="JANET_BINPATH_PERMACHINE" Name="JANET_BINPATH" Value="[BinDir]" Action="set" Permanent="no" System="yes"/>
<Environment Id="JANET_MANPATH_PERMACHINE" Name="JANET_MANPATH" Value="[DocsDir]" Action="set" Permanent="no" System="yes"/>
<Environment Id="JANET_PATH_PERMACHINE" Name="JANET_PATH" Value="[LibraryDir]" Action="set" Permanent="no" System="yes" />
<Environment Id="JANET_HEADERPATH_PERMACHINE" Name="JANET_HEADERPATH" Value="[CDir]" Action="set" Permanent="no" System="yes"/>
<Environment Id="JANET_LIBPATH_PERMACHINE" Name="JANET_LIBPATH" Value="[CDir]" Action="set" Permanent="no" System="yes"/>
@@ -167,6 +168,7 @@
<Condition>NOT ALLUSERS=1</Condition>
<Environment Id="PATH_PERUSER" Name="PATH" Value="[BinDir]" Action="set" Permanent="no" System="no" Part="last"/>
<Environment Id="JANET_BINPATH_PERUSER" Name="JANET_BINPATH" Value="[BinDir]" Action="set" Permanent="no" System="no"/>
<Environment Id="JANET_MANPATH_PERUSER" Name="JANET_MANPATH" Value="[DocsDir]" Action="set" Permanent="no" System="no"/>
<Environment Id="JANET_PATH_PERUSER" Name="JANET_PATH" Value="[LibraryDir]" Action="set" Permanent="no" System="no" />
<Environment Id="JANET_HEADERPATH_PERUSER" Name="JANET_HEADERPATH" Value="[CDir]" Action="set" Permanent="no" System="no"/>
<Environment Id="JANET_LIBPATH_PERUSER" Name="JANET_LIBPATH" Value="[CDir]" Action="set" Permanent="no" System="no"/>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Calvin Rose
* Copyright (c) 2023 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