1
0
mirror of https://github.com/janet-lang/janet synced 2025-10-28 22:27:41 +00:00

Compare commits

..

139 Commits

Author SHA1 Message Date
Calvin Rose
a0ddfcb109 Prepare for 1.19.0 release. 2021-11-27 10:00:36 -06:00
Calvin Rose
3df7921fdc Don't call wait twice when closing or gcing. 2021-11-27 09:05:43 -06:00
Calvin Rose
6172a9ca2d Merge branch 'master' of github.com:janet-lang/janet 2021-11-26 18:44:33 -06:00
Calvin Rose
4a40e57cf0 Fix leaking file descriptors to subprocess causing hangs. 2021-11-26 18:44:11 -06:00
Calvin Rose
cdedda4ca1 Merge pull request #880 from pepe/gamma-fix
Fix math/gamma and add math/log-gamma
2021-11-26 12:34:54 -06:00
Josef Pospíšil
e6babd84f7 Fix math/gamma and add math/log-gamma 2021-11-24 10:55:32 +01:00
Calvin Rose
868ec1a7e3 Add test case for missing struct proto 2021-11-23 23:17:24 -06:00
Calvin Rose
e08394c870 Fix struct proto missing when making a struct with a nil value. 2021-11-23 23:16:06 -06:00
Calvin Rose
a99500aebf Update suite0009 assert again. 2021-11-18 20:46:26 -06:00
Calvin Rose
aa5095c23b Update assert message in suite0009 2021-11-18 20:39:29 -06:00
Calvin Rose
9e0f36e5a7 Fix unused variable warnings. 2021-11-18 20:35:41 -06:00
Calvin Rose
d481d079ba Try bsd fix. 2021-11-18 20:30:06 -06:00
Calvin Rose
bc9ec7ac4a Fix unitialized memory access in net/ module. 2021-11-18 20:10:10 -06:00
Calvin Rose
6f7e81067c Address #876 Don't allow scheduling a fiber once it has been canceled already.
We were effectively cancelling the cancellation.
2021-11-18 20:06:29 -06:00
Calvin Rose
af946f398e Turn off raw mode in shell on ctrl-C. 2021-11-18 19:58:52 -06:00
Calvin Rose
c7ca26e9c7 Merge branch 'master' of github.com:janet-lang/janet 2021-11-18 19:04:43 -06:00
Calvin Rose
ef7129f45d Address #874 - Call waitpid on waiter thread with WNOWAIT.
This doesn't destory the pid until the original thread decides to
call waitpid again. Since the pid is exposed in the C API and now
in the Janet API, we don't want to destroy it until we are ready.
2021-11-18 19:03:08 -06:00
Calvin Rose
a20bdd334a Merge pull request #873 from andrewchambers/procsig
Use kill instead of raise for SIGINT.
2021-11-15 07:44:30 -06:00
Andrew Chambers
2ef49a92cc Use kill instead of raise for SIGINT.
Raise signals can only be handled by the current thread while
kill signals can be handled by background threads.
2021-11-15 20:38:23 +13:00
Calvin Rose
75f56b68c6 Merge pull request #872 from jgarte/jgarte-patch-aesthetics
Typo fix
2021-11-14 14:03:21 -06:00
jgart
d34d319d89 Typo fix 2021-11-14 14:19:04 -05:00
Calvin Rose
6660c1da38 Don't truncate test output on failures. 2021-11-12 20:06:28 -06:00
Calvin Rose
3cb604df02 Merge pull request #870 from andrewchambers/exposepid
Expose process :pid on unix like platforms.
2021-11-12 08:20:39 -06:00
Calvin Rose
af9dc7a69e Merge pull request #869 from andrewchambers/typo2
Fix os/proc-kill doc typo.
2021-11-12 08:20:00 -06:00
Calvin Rose
1247e69c78 Merge pull request #868 from andrewchambers/sigint
Allow C code to block SIGINT.
2021-11-12 08:19:43 -06:00
Andrew Chambers
aab0e4315d Expose process :pid on unix like platforms.
This at least means users can use something like jsys
or the kill command to signal processes when they want
to send unsupported signals (like SIGTERM).
2021-11-12 23:43:36 +13:00
Andrew Chambers
14f6517733 Fix os/proc-kill doc typo. 2021-11-12 23:29:13 +13:00
Andrew Chambers
5d75effb37 Allow C code to block SIGINT.
Previously the repl always exits on SIGINT, this change
means the repl will only exit on SIGINT if the SIGINT handler
causes it to exit.
2021-11-12 23:24:33 +13:00
Calvin Rose
ab4f18954b Merge pull request #866 from pyrmont/fix.example-urlloader
Fix URL loader example to use os/spawn
2021-11-11 14:55:24 -06:00
Michael Camilleri
e1460c65e8 Fix URL loader example to use os/spawn 2021-11-11 17:33:25 +09:00
Calvin Rose
425a0fcf07 Add quoted literal support in the match macro. 2021-11-08 15:33:11 -06:00
Calvin Rose
7205ee5e0a Update test output. 2021-11-06 19:29:15 -05:00
Calvin Rose
72c5db8910 Update test suite to better distinguish functional errors with
localname/peername.
2021-11-06 19:19:49 -05:00
bakpakin
3067f4be3a Address #815 - gc mark issue in windows accept state machine.
We were casting a pointer to the wrong type, which caused all sorts of
wonderful chaos, but only on windows and only when the garbage collector
ran after setting up a server in a specific configuration. We were
casting a closure pointer to an abstract type during the mark phase,
        which resulted in memory corruption.
2021-11-06 17:50:54 -05:00
Calvin Rose
2aa1ccdd76 Update test helper to be even les noisy. 2021-11-06 11:20:09 -05:00
Calvin Rose
0284df503f Make test output less verbose 2021-11-06 11:01:21 -05:00
Calvin Rose
2833a983d8 Merge pull request #860 from sogaiu/short-fn-docstring-tweak
Tweak short-fn docstring
2021-11-04 19:39:55 -05:00
Calvin Rose
39c6be7cb7 Fix #861 - parser/produce caused state to be invalid for parser/state.
parser/produce was leaving a counter in the root state undecremented.
2021-11-04 19:38:37 -05:00
sogaiu
fdc94c1353 Tweak short-fn docstring 2021-11-04 18:11:53 +09:00
Calvin Rose
9cc4e48124 Update changelog and allow evaluating streams with dofile. 2021-10-30 14:43:06 -05:00
Calvin Rose
34c7f15d6d Always return port in peername and localname 2021-10-30 10:56:40 -05:00
Calvin Rose
899a9b025e Merge branch 'struct-proto' 2021-10-30 09:31:22 -05:00
Calvin Rose
deb4315383 Fix parse.c parser/state :args 2021-10-30 09:30:56 -05:00
Calvin Rose
9a06660fdb fix call to table/proto-flatten 2021-10-30 09:15:23 -05:00
Calvin Rose
5c35d24e13 Fix nil check issue. 2021-10-29 19:29:54 -05:00
Calvin Rose
03f99752a7 Merge branch 'master' into struct-proto 2021-10-29 16:42:34 -05:00
Calvin Rose
fd37567c18 Docstring fix. 2021-10-29 11:13:07 -05:00
Calvin Rose
6e38bf1578 Use more inclusive check for the %j formatter for valid symbols.
We did not allow arbitrary utf8 to be printed with %j, even though the parser
allows. Thos changes uses the existing built in utf8 detectiotion to
exclude only unprintable symbols from the docstring.
2021-10-29 11:08:53 -05:00
Calvin Rose
8b2d278840 Add min-of and max-of. 2021-10-26 17:46:24 -05:00
Calvin Rose
06aa0a124d Add math/gcd and math/lcm to the core. 2021-10-24 11:43:07 -05:00
Calvin Rose
eb4595158d Allow compiling tables as peg grammars. 2021-10-23 09:59:36 -05:00
Calvin Rose
32103441f1 Merge pull request #850 from pyrmont/bugfix.anonymous-union
Use named union in JanetGCObject
2021-10-22 07:56:38 -05:00
Michael Camilleri
7ed0aa6630 Use named union in JanetGCObject 2021-10-22 09:52:57 +09:00
Calvin Rose
f690229f31 Merge pull request #848 from pyrmont/bugfix.uname-switches
Fix error for uname switch -o on macOS
2021-10-21 18:17:41 -05:00
Michael Camilleri
f3bab72a86 Add comment to explain Linux check in Makefile 2021-10-21 14:23:23 +09:00
Michael Camilleri
2bd63c2d27 Fix error for uname switch -o on macOS 2021-10-21 14:13:51 +09:00
Calvin Rose
545d9e85e9 Update CHANGELOG.md 2021-10-20 19:57:02 -05:00
Calvin Rose
21a4ab4ec7 Hang forever instead of exit early on channel deadlock.
While not technically needed, the behavior is more intuitive and will
prevent people from writing bad scripts.
2021-10-20 19:53:29 -05:00
Calvin Rose
66fbbeb5ec Why is the copyright gone... 2021-10-20 18:05:32 -05:00
Calvin Rose
55879c7b6d Fix checked for fiber being dead. 2021-10-19 09:03:24 -05:00
Calvin Rose
66c4e5a5e2 Prepare for patch release. 2021-10-16 15:05:48 -05:00
Calvin Rose
884139e246 Merge pull request #843 from jgarte/jgarte-patch-typo
Fix typo in docstring
2021-10-16 12:51:45 -05:00
Calvin Rose
c3d7b1541e Merge branch 'master' into jgarte-patch-typo 2021-10-16 12:51:38 -05:00
Calvin Rose
51ada4d70b Merge pull request #840 from bradms/android-termux
Support Android (termux)
2021-10-16 12:50:24 -05:00
jgart
e3a5d52c5e Fix typo in docstring 2021-10-16 11:48:59 -04:00
Brad Svercl
559fd70737 Add android-spawn to meson.build if found 2021-10-15 21:39:03 -05:00
Brad Svercl
e0dba85cbb Support Android (termux) 2021-10-14 21:18:35 -05:00
Calvin Rose
74c9cf03d0 Fix -r switch in repl 2021-10-14 17:25:12 -05:00
Calvin Rose
0774e79e4f Pass non-blocking pipes to subprocesses on non-windows platform. 2021-10-14 13:57:51 -05:00
Calvin Rose
a3ec37741a Merge pull request #835 from MorganPeterson/update-string-docs
Updated string/bytes docs to reflect return value as tuple
2021-10-12 00:22:50 -05:00
MorganPeterson
9bf5cd83c3 updated string/bytes docs to reflect return value as tuple 2021-10-11 13:45:31 -04:00
Calvin Rose
f0da793f99 Prepare for 1.18.0 release 2021-10-10 09:27:31 -05:00
Calvin Rose
684f3ac172 Add optional base to scan-number. 2021-10-10 09:07:56 -05:00
Calvin Rose
3e5bd460a5 Add line/col info to parse error in janet_[dobytes, dostring] 2021-10-08 09:25:00 -05:00
Calvin Rose
3b1d787fbe Address #829 - Set state->event inside linked list traversal for epoll. 2021-10-08 08:35:47 -05:00
Calvin Rose
980f55ff69 Merge pull request #828 from sogaiu/add-argument-to-janet-panicf-call
Add argument to some janet_panicf calls
2021-10-08 08:23:55 -05:00
sogaiu
52ed68bfeb Add argument to janet_panicf call (2) 2021-10-07 20:58:50 +09:00
sogaiu
be0d4c28e4 Add argument to janet_panicf call 2021-10-07 16:38:40 +09:00
Calvin Rose
79807bf2ab Merge pull request #827 from sogaiu/tweak-format-strings
Tweak format strings
2021-10-06 17:48:53 -05:00
Calvin Rose
e48ca1a03f Merge pull request #826 from rick2600/fix-uaf-cfun_array_concat
fix issue #825
2021-10-06 17:48:27 -05:00
sogaiu
eae18ce973 Tweak format strings 2021-10-06 20:34:33 +09:00
rick2600
591344ca9d fix issue #825 2021-10-05 01:45:59 -03:00
Calvin Rose
fbe067823e Merge pull request #824 from GrayJack/rnd-double-capi
Expose `janet_rng_double` to the C API
2021-10-04 13:54:10 -05:00
GrayJack
ffece911e6 Expose janet_rng_double to the C API 2021-10-03 00:52:34 -03:00
Calvin Rose
186afa9651 Merge pull request #823 from llmII/fix-kqueue-hang
Fix #822 - kqueue hang in suite 9.
2021-09-30 10:50:04 -05:00
llmII
6b3037106a Fix #822 - kqueue hang in suite 9.
Priorly we only checked exactly one state when an event was received.
This was incorrect. A state may have a next state, an action to take
after the first in the list of states has been taken. This change
acknowledges that and makes the code work with the state list vs just
the head of the list.
2021-09-30 06:56:09 -05:00
Calvin Rose
1bf22288ee Merge pull request #821 from llmII/fix-kqueue
Fix #819 timeouts under kqueue on FreeBSD broken
2021-09-29 15:51:46 -05:00
llmII
3cec470f25 Change the way timeouts work in kqueue.
Don't use a timer filter, just set the timeout on each call to kevent.
Should hopefully work around the 1ms minimum on NetBSD and be possibly
more performant.
2021-09-29 15:19:30 -05:00
llmII
e1ec0d13ae Fixing kqueue under the BSDs.
FreeBSD is the only BSD supporting ABSTIME timers, whereas the rest
demand intervals. Janet operates on timestamps, which are absolute
times, as per ABSTIME. The idea was to use that under FreeBSD but not
the other BSDs. This commit changes that since ABSTIME breaks when the
timeout supplied is for a time prior to whatever the time is
now (invalid argument). We now utilize the same logic we use on the
other BSDs with FreeBSD to effect interval timeouts since intervals are
absolutely sometime beyond now, be it now and less than a millisecond,
or more than a millisecond. This will hopefully unbreak BSD builds when
running the test suite.
2021-09-28 17:40:38 -05:00
bakpakin
924fe97fc3 Address #820 - ev/cancel to work on already scheduled fibers. 2021-09-28 15:42:16 -05:00
bakpakin
504411eade Update suite 009 to do read and write in parallel. 2021-09-28 12:05:28 -05:00
Calvin Rose
038ca1b9ca Update README.md 2021-09-27 21:20:06 -05:00
Calvin Rose
544b192f8c Fix bad docstring change. 2021-09-25 14:32:23 -05:00
Calvin Rose
7748ccdb8e Fix network byte order port. 2021-09-25 14:31:19 -05:00
Calvin Rose
64e29c6fce More simplification and removal of macros. 2021-09-25 13:53:27 -05:00
Calvin Rose
acdf097998 Refactor of peername and localname to be much more direct.
Also remove a lot of overly general code from cqueues. Janet has more
opinionated error handling so we can avoid a lot error propagation code.
2021-09-25 13:38:36 -05:00
Calvin Rose
ba3107c1fa Merge branch 'master' of github.com:janet-lang/janet 2021-09-21 18:03:25 -05:00
Calvin Rose
9985f787eb Add custom base option to number peg combinator.
Allows parsing custom bases without needed Janet specific prefixes.
2021-09-21 18:02:42 -05:00
Calvin Rose
d6f41bcf98 Merge pull request #811 from sogaiu/use-deprecation-mechanism-for-file-popen
Apply deprecation machinery to file/popen
2021-09-21 15:16:20 -05:00
Calvin Rose
50bced49ad Merge branch 'master' into use-deprecation-mechanism-for-file-popen 2021-09-21 15:15:36 -05:00
Calvin Rose
4fd7470bbf Remove accidental limit on max read size. 2021-09-21 09:55:40 -05:00
Calvin Rose
033c6f1fdb Add -i flag to run .jimage files as scripts. 2021-09-19 19:47:57 -05:00
Calvin Rose
6c58347916 Remove thread module.
Instead, use the more general and non-blocing `ev/` module.
2021-09-19 14:19:32 -05:00
Calvin Rose
cccbdc164c Add (number combinator to peg).
This allows using Janet's number parser without creating
intermediate strings.
2021-09-19 13:02:16 -05:00
Calvin Rose
cea14a6869 Fix typo in changelog. 2021-09-19 00:16:08 -05:00
Calvin Rose
9b4b24edf7 Prepare for 1.17.2 release. 2021-09-18 13:42:26 -05:00
Calvin Rose
8b10a5fb7c Format and update CHANGELOG.md 2021-09-18 13:40:32 -05:00
Calvin Rose
b0d0d9cad2 Address #809 - treat first docstring line different from others.
Only do this if the docstring starts with an open parentheses.
2021-09-18 12:41:11 -05:00
sogaiu
d5c8eb048a Apply deprecation machinery to file/popen 2021-09-18 17:08:56 +09:00
Calvin Rose
9abee3f29a Add semi-colon. 2021-09-17 19:20:59 -05:00
Calvin Rose
bf9b6b1301 Avoid including windows.h in janet.h for JanetOSMutex. 2021-09-17 16:59:50 -05:00
Calvin Rose
8cd57025a0 Add makefile var to fix jpm to a tag/branch 2021-09-17 16:38:11 -05:00
Calvin Rose
faf60b6b1f Pass DESTDIR directly to jpm bootstrap script. 2021-09-16 18:36:29 -05:00
Calvin Rose
da2c1be49c Fix #801 threaded abstract cyclic references in marshalling.
We forgot to mark threaded abstract types as "seen" when marshalling so
we would mistakenly marshal them twice. This messed up unmarshalling.
2021-09-14 21:12:02 -05:00
Calvin Rose
92c02449f4 Merge pull request #800 from marler8997/fixUbInGC
add NULL check in gc.c to avoid UB
2021-09-14 16:04:12 -05:00
Jonathan Marler
e381622a9a add NULL check in gc.c to avoid UB
After the UB was fixed in value.c, I tried running the build again and encoutered another instance of UB in gc.c.  With this fixed I can now build janet with ubsan enabled, meaning there's no more UB encountered in janet_boot during the build.
2021-09-11 19:50:52 -06:00
bakpakin
b799223ebc Merge branch 'master' of github.com:janet-lang/janet 2021-09-11 10:34:22 -05:00
bakpakin
40ef224a95 Update test code. 2021-09-11 10:34:08 -05:00
Calvin Rose
a4c20b6e1c Merge pull request #797 from sogaiu/math-nan-doc-tweak
Tweak math/nan docstring
2021-09-11 09:13:38 -05:00
sogaiu
e6ee867f72 Tweak math/nan docstring 2021-09-11 16:54:35 +09:00
bakpakin
468a31f515 Address #795 - add NULL check to avoid UB 2021-09-09 07:20:26 -05:00
bakpakin
4d746794cc Merge branch 'bindport' 2021-09-07 22:45:22 -05:00
bakpakin
02d2a66ef2 Merge branch 'master' of github.com:janet-lang/janet 2021-09-07 22:44:59 -05:00
Calvin Rose
4638baf545 Merge pull request #790 from llmII/feature-getsockname-getpeername
Work in progress - more socket functions
2021-09-07 22:44:40 -05:00
llmII
325d5399fa Code cleanup and attribution set.
All that is left is to test unix sockets.
2021-09-07 20:00:00 -05:00
llmII
21b3e4052c Don't print stuff in tests, CI looks wacky 2021-09-07 17:21:10 -05:00
llmII
bf2928805e Had an extra plen definition, removed. 2021-09-07 17:03:34 -05:00
llmII
077bf5ebae Create test case for localname/peername 2021-09-07 07:12:43 -05:00
llmII
3740eadb7d Seeing if this fixes the last warnings for Windows 2021-09-06 19:57:56 -05:00
llmII
e29fa66a74 More Windows fixes 2021-09-06 19:42:45 -05:00
llmII
ca5406c8e4 More windows fixes
MSVC's output via appveyor is a little lacking in indication of all
issues so I'm hitting them as I can find them.
2021-09-06 19:31:16 -05:00
llmII
7217caacd1 Attempting some more windows related fixes. 2021-09-06 19:26:33 -05:00
llmII
1597ca0de5 Cleanup code a bit
Inconsistent indentation and such fixed, superfluous newlines removed,
documentation of new functions.
2021-09-06 18:32:23 -05:00
llmII
8c938ceff9 Fix for Windows (possibly)
Windows does not have <arpa/inet.h> so only include it when not Windows.
2021-09-06 17:48:17 -05:00
llmII
65a6945ea5 Finalize peername and localname
Both now do the right thing and give back all information (host and
port) when possible as a tuple of (host port).
2021-09-06 17:35:49 -05:00
llmII
02640812af Add getsockname (net/localname) 2021-09-06 17:01:09 -05:00
llmII
ba761d5c35 Work in progress - more socket functions
When this is complete we'll have getpeername, getsockname and possibly
getpeerid in the net/* API.
2021-09-06 16:15:01 -05:00
Calvin Rose
fab65d6c40 Merge branch 'master' into struct-proto 2021-05-30 09:33:59 -05:00
Calvin Rose
4d983e54b5 Initial struct prototype code.
Also add a number of cfunctions for manipulating structs
with prototypes.
2021-05-29 11:43:18 -05:00
44 changed files with 1116 additions and 1153 deletions

View File

@@ -1,16 +1,48 @@
# Changelog
All notable changes to this project will be documented in this file.
## Unlreleased - ???
- Supervisor channels in threads will no longer include a wastful copy of the fiber in every
## 1.19.0 - 2021-11-27
- Add `math/log-gamma` to replace `math/gamma`, and change `math/gamma` to be the expected gamma function.
- Fix leaking file-descriptors in os/spawn and os/execute.
- Ctrl-C will now raise SIGINT.
- Allow quoted literals in the `match` macro to behave as expected in patterns.
- Fix windows net related bug for TCP servers.
- Allow evaluating ev streams with dofile.
- Fix `ev` related bug with operations on already closed file descriptors.
- Add struct and table agnostic `getproto` function.
- Add a number of functions related to structs.
- Add prototypes to structs. Structs can now inherit from other structs, just like tables.
- Create a struct with a prototype with `struct/with-proto`.
- Deadlocked channels will no longer exit early - instead they will hang, which is more intuitive.
## 1.18.1 - 2021-10-16
- Fix some documentation typos
- Fix - Set pipes passed to subprocess to blocking mode.
- Fix `-r` switch in repl.
## 1.18.0 - 2021-10-10
- Allow `ev/cancel` to work on already scheduled fibers.
- Fix bugs with ev/ module.
- Add optional `base` argument to scan-number
- Add `-i` flag to janet binary to make it easier to run image files from the command line
- Remove `thread/` module.
- Add `(number ...)` pattern to peg for more efficient number parsing using Janet's
scan-number function without immediate string creation.
## 1.17.2 - 2021-09-18
- Remove include of windows.h from janet.h. This caused issues on certain projects.
- Fix formatting in doc-format to better handle special characters in signatures.
- Fix some marshalling bugs.
- Add optional Makefile target to install jpm as well.
- Supervisor channels in threads will no longer include a wasteful copy of the fiber in every
message across a thread.
- Allow passing a closure to `ev/thead` as well as a whole fiber.
- Allow passing a closure to `ev/thread` as well as a whole fiber.
- Allow passing a closure directly to `ev/go` to spawn fibers on the event loop.
## 1.17.1 - 2021-08-29
- Fix docstring typos
- Add `make install-jpm-git` to make jpm co-install simpler if using makefile.
- Fix bugs with starting ev/threads and fiber marshling.
- Add `make install-jpm-git` to make jpm co-install simpler if using the Makefile.
- Fix bugs with starting ev/threads and fiber marshaling.
## 1.17.0 - 2021-08-21
- Add the `-E` flag for one-liners with the `short-fn` syntax for argument passing.

View File

@@ -36,6 +36,7 @@ JANET_PATH?=$(LIBDIR)/janet
JANET_MANPATH?=$(PREFIX)/share/man/man1/
JANET_PKG_CONFIG_PATH?=$(LIBDIR)/pkgconfig
JANET_DIST_DIR?=janet-dist
JPM_TAG?=master
DEBUGGER=gdb
SONAME_SETTER=-Wl,-soname,
@@ -61,11 +62,18 @@ ifeq ($(UNAME), Darwin)
else ifeq ($(UNAME), Linux)
CLIBS:=$(CLIBS) -lrt -ldl
endif
# For other unix likes, add flags here!
ifeq ($(UNAME), Haiku)
LDCONFIG:=true
LDFLAGS=-Wl,--export-dynamic
endif
# For Android (termux)
ifeq ($(UNAME), Linux) # uname on Darwin doesn't recognise -o
ifeq ($(shell uname -o), Android)
CLIBS:=$(CLIBS) -landroid-spawn
endif
endif
$(shell mkdir -p build/core build/c build/boot)
all: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h
@@ -119,7 +127,6 @@ JANET_CORE_SOURCES=src/core/abstract.c \
src/core/struct.c \
src/core/symcache.c \
src/core/table.c \
src/core/thread.c \
src/core/tuple.c \
src/core/util.c \
src/core/value.c \
@@ -158,7 +165,7 @@ build/c/janet.c: build/janet_boot src/boot/boot.janet
##### Amalgamation #####
########################
SONAME=libjanet.so.1.17
SONAME=libjanet.so.1.19
build/c/shell.c: src/mainclient/shell.c
cp $< $@
@@ -284,12 +291,13 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc
install-jpm-git: $(JANET_TARGET)
mkdir -p build
rm -rf build/jpm
git clone --depth=1 https://github.com/janet-lang/jpm.git build/jpm
cd build/jpm && PREFIX='$(DESTDIR)$(PREFIX)' \
JANET_MANPATH='$(DESTDIR)$(JANET_MANPATH)' \
JANET_HEADERPATH='$(DESTDIR)$(INCLUDEDIR)/janet' \
JANET_BINPATH='$(DESTDIR)$(BINDIR)' \
JANET_LIBPATH='$(DESTDIR)$(LIBDIR)' \
git clone --depth=1 --branch='$(JPM_TAG)' https://github.com/janet-lang/jpm.git build/jpm
cd build/jpm && PREFIX='$(PREFIX)' \
DESTDIR=$(DESTDIR) \
JANET_MANPATH='$(JANET_MANPATH)' \
JANET_HEADERPATH='$(INCLUDEDIR)/janet' \
JANET_BINPATH='$(BINDIR)' \
JANET_LIBPATH='$(LIBDIR)' \
../../$(JANET_TARGET) ./bootstrap.janet
uninstall:

View File

@@ -30,6 +30,7 @@ Lua, but smaller than GNU Guile or Python.
## Features
* Configurable at build time - turn features on or off for a smaller or more featureful build
* Minimal setup - one binary and you are good to go!
* First-class closures
* Garbage collection
@@ -39,6 +40,8 @@ Lua, but smaller than GNU Guile or Python.
* Mutable and immutable hashtables (table/struct)
* Mutable and immutable strings (buffer/string)
* 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
* Direct interop with C via abstract types and C functions
@@ -238,6 +241,52 @@ 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
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.
### Is this Scheme/Common Lisp? Where are the cons cells?
Nope. There are no cons cells here.
### Is this a Clojure port?
No. It's similar to Clojure superficially because I like Lisps and I like the aesthetics.
Internally, Janet is not at all like Clojure.
### Are the immutable data structures (tuples and structs) implemented as hash tries?
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?
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.
```
(def Car @{:honk (fn [self msg] (print "car " self " goes " msg)) })
(def my-car (table/setproto @{} Car))
(:honk my-car "Beep!")
```
### Why can't we add (feature from Clojure) into the core?
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
to the core also makes it a bit more difficult keep Janet maximally portable.
### Why is my terminal spitting out junk when I run the REPL?
Make sure your terminal supports ANSI escape codes. Most modern terminals will
@@ -246,35 +295,6 @@ 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
ensue.
### 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
of 5 others by making the language more complicated.
### Where is the example code?
In the examples directory.
### Is this a Clojure port?
No. It's similar to Clojure superficially because I like Lisps and I like the asthetics.
Internally, Janet is not at all like Clojure.
### Are the immutable data structures (tuples and structs) implemented as hash tries?
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.
### Why can't we add (feature from Clojure) into the core?
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. We admittedly have a much more primitive GC.
- 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.
## Why is it called "Janet"?
Janet is named after the almost omniscient and friendly artificial being in [The Good Place](https://en.wikipedia.org/wiki/The_Good_Place).

View File

@@ -18,8 +18,14 @@
@rem Set compile and link options here
@setlocal
@rem Example use asan
@rem set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD /fsanitize=address /Zi
@rem set JANET_LINK=link /nologo clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
@set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD
@set JANET_LINK=link /nologo
@set JANET_LINK_STATIC=lib /nologo
@rem Add janet build tag
@@ -81,7 +87,7 @@ exit /b 1
@echo command prompt.
exit /b 0
@rem Clean build artifacts
@rem Clean build artifacts
:CLEAN
del *.exe *.lib *.exp
rd /s /q build

View File

@@ -1,10 +1,10 @@
# An example of using Janet's extensible module system
# to import files from URL. To try this, run `janet -l examples/urlloader.janet`
# from the repl, and then:
# An example of using Janet's extensible module system to import files from
# URL. To try this, run `janet -l ./examples/urlloader.janet` from the command
# line, and then at the REPL type:
#
# (import https://raw.githubusercontent.com/janet-lang/janet/master/examples/colors.janet :as c)
#
# This will import a file using curl. You can then try
# This will import a file using curl. You can then try:
#
# (print (c/color :green "Hello!"))
#
@@ -13,9 +13,9 @@
(defn- load-url
[url args]
(def f (file/popen (string "curl " url)))
(def res (dofile f :source url ;args))
(try (file/close f) ([err] nil))
(def p (os/spawn ["curl" url "-s"] :p {:out :pipe}))
(def res (dofile (p :out) :source url ;args))
(:wait p)
res)
(defn- check-http-url

View File

@@ -3,7 +3,7 @@
janet \- run the Janet language abstract machine
.SH SYNOPSIS
.B janet
[\fB\-hvsrpnqk\fR]
[\fB\-hvsrpnqik\fR]
[\fB\-e\fR \fISOURCE\fR]
[\fB\-E\fR \fISOURCE ...ARGUMENTS\fR]
[\fB\-l\fR \fIMODULE\fR]
@@ -213,6 +213,11 @@ Precompiles Janet source code into an image, a binary dump that can be efficient
Source should be a path to the Janet module to compile, and output should be the file path of
resulting image. Output should usually end with the .jimage extension.
.TP
.BR \-i
When this flag is passed, a script passed to the interpreter will be treated as a janet image file
rather than a janet source file.
.TP
.BR \-l\ lib
Import a Janet module before running a script or repl. Multiple files can be loaded

View File

@@ -20,7 +20,7 @@
project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.17.1')
version : '1.19.0')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
@@ -30,6 +30,7 @@ header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet'
cc = meson.get_compiler('c')
m_dep = cc.find_library('m', required : false)
dl_dep = cc.find_library('dl', required : false)
android_spawn_dep = cc.find_library('android-spawn', required : false)
thread_dep = dependency('threads')
# Link options
@@ -74,7 +75,6 @@ conf.set('JANET_NO_PROCESSES', not get_option('processes'))
conf.set('JANET_SIMPLE_GETLINE', get_option('simple_getline'))
conf.set('JANET_EV_NO_EPOLL', not get_option('epoll'))
conf.set('JANET_EV_NO_KQUEUE', not get_option('kqueue'))
conf.set('JANET_NO_THREADS', get_option('threads'))
conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt'))
if get_option('os_name') != ''
conf.set('JANET_OS_NAME', get_option('os_name'))
@@ -136,7 +136,6 @@ core_src = [
'src/core/struct.c',
'src/core/symcache.c',
'src/core/table.c',
'src/core/thread.c',
'src/core/tuple.c',
'src/core/util.c',
'src/core/value.c',
@@ -162,7 +161,7 @@ mainclient_src = [
janet_boot = executable('janet-boot', core_src, boot_src,
include_directories : incdir,
c_args : '-DJANET_BOOTSTRAP',
dependencies : [m_dep, dl_dep, thread_dep],
dependencies : [m_dep, dl_dep, thread_dep, android_spawn_dep],
native : true)
# Build janet.c
@@ -175,7 +174,7 @@ janetc = custom_target('janetc',
'JANET_PATH', janet_path
])
janet_dependencies = [m_dep, dl_dep]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
endif

View File

@@ -1,7 +1,6 @@
option('git_hash', type : 'string', value : 'meson')
option('single_threaded', type : 'boolean', value : false)
option('threads', type : 'boolean', value : true)
option('nanbox', type : 'boolean', value : true)
option('dynamic_modules', type : 'boolean', value : true)
option('docstrings', type : 'boolean', value : true)

View File

@@ -51,7 +51,7 @@
``Use a function or macro literal `f` as a macro. This lets
any function be used as a macro. Inside a quasiquote, the
idiom `(as-macro ,my-custom-macro arg1 arg2...)` can be used
to avoid unwanted variable capture.``
to avoid unwanted variable capture of `my-custom-macro`.``
[f & args]
(f ;args))
@@ -698,6 +698,14 @@
"Returns the numeric minimum of the arguments."
[& args] (extreme < args))
(defn max-of
"Returns the numeric maximum of the argument sequence."
[args] (extreme > args))
(defn min-of
"Returns the numeric minimum of the argument sequence."
[args] (extreme < args))
(defn first
"Get the first element from an indexed data structure."
[xs]
@@ -1677,6 +1685,8 @@
already bound to `<sym>`, rather than matching and rebinding it.
Any other value pattern will only match if it is equal to `x`.
Quoting a pattern with `'` will also treat the value as a literal value to match against.
```
[x & cases]
@@ -1728,6 +1738,10 @@
(array/push x s)
(put b2g pattern @[s]))
# match quoted literal
(and (= t :tuple) (= 2 (length pattern)) (= 'quote (pattern 0)))
(break)
# match data structure template
(or isarr (= t :struct) (= t :table))
(do
@@ -1765,6 +1779,10 @@
# match local binding
(= t :symbol) (break)
# match quoted literal
(and (= t :tuple) (= 2 (length pattern)) (= 'quote (pattern 0)))
(array/push anda ['= s pattern])
# match global unification
(and (= t :tuple) (= 2 (length pattern)) (= '@ (pattern 0)))
(if-let [x (in gun (pattern 1))]
@@ -2047,7 +2065,7 @@
```
Shorthand for fn. Arguments are given as $n, where n is the 0-indexed
argument of the function. $ is also an alias for the first (index 0) argument.
The $& symbol will make the anonymous function variadic if it apears in the
The $& symbol will make the anonymous function variadic if it appears in the
body of the function - it can be combined with positional arguments.
Example usage:
@@ -2582,7 +2600,7 @@
@{})
(defn dofile
`Evaluate a file and return the resulting environment. :env, :expander,
`Evaluate a file, file path, or stream and return the resulting environment. :env, :expander,
:evaluator, :read, and :parser are passed through to the underlying
run-context call. If exit is true, any top level errors will trigger a
call to (os/exit 1) after printing the error.`
@@ -2594,8 +2612,9 @@
:evaluator evaluator
:read read
:parser parser}]
(def f (if (= (type path) :core/file)
path
(def f (case (type path)
:core/file path
:core/stream path
(file/open path :rb)))
(def path-is-file (= f path))
(default env (make-env))
@@ -2604,7 +2623,7 @@
(put env :source (or src (if-not path-is-file spath path)))
(var exit-error nil)
(var exit-fiber nil)
(defn chunks [buf _] (file/read f 4096 buf))
(defn chunks [buf _] (:read f 4096 buf))
(defn bp [&opt x y]
(when exit
(bad-parse x y)
@@ -2646,7 +2665,7 @@
:read read
:parser parser
:source (or src (if path-is-file "<anonymous>" spath))}))
(if-not path-is-file (file/close f))
(if-not path-is-file (:close f))
(when exit-error
(if exit-fiber
(propagate exit-error exit-fiber)
@@ -2786,8 +2805,8 @@
(def delimiters
(if has-color
{:underline ["\e[4m" "\e[24m"]
:code ["\e[3;97m" "\e[39;23m"]
:italics ["\e[3m" "\e[23m"]
:code ["\e[97m" "\e[39m"]
:italics ["\e[4m" "\e[24m"]
:bold ["\e[1m" "\e[22m"]}
{:underline ["_" "_"]
:code ["`" "`"]
@@ -2820,7 +2839,7 @@
(c++)
(- cursor x))
# Detection helpers - return number of characters natched
# Detection helpers - return number of characters matched
(defn ul? []
(let [x (c) x1 (cn 1)]
(and
@@ -2954,6 +2973,14 @@
(finish-p)
new-indent))
# Handle first line specially for defn, defmacro, etc.
(when (= (chr "(") (in str 0))
(skipline)
(def first-line (string/slice str 0 (- cursor 1)))
(def fl-open (if has-color "\e[97m" ""))
(def fl-close (if has-color "\e[39m" ""))
(push [[(string fl-open first-line fl-close) (length first-line)]]))
(parse-blocks 0)
# Emission state
@@ -3500,6 +3527,12 @@
# conditional compilation for reduced os
(def- getenv-alias (if-let [entry (in root-env 'os/getenv)] (entry :value) (fn [&])))
(defn- run-main
[env subargs arg]
(if-let [main (get (in env 'main) :value)]
(let [thunk (compile [main ;subargs] env arg)]
(if (function? thunk) (thunk) (error (thunk :error))))))
(defn cli-main
`Entrance for the Janet CLI tool. Call this function with the command line
arguments as an array or tuple of strings to invoke the CLI interface.`
@@ -3507,17 +3540,18 @@
(setdyn :args args)
(var *should-repl* false)
(var *no-file* true)
(var *quiet* false)
(var *raw-stdin* false)
(var *handleopts* true)
(var *exit-on-error* true)
(var *colorize* true)
(var *debug* false)
(var *compile-only* false)
(var *warn-level* nil)
(var *error-level* nil)
(var should-repl false)
(var no-file true)
(var quiet false)
(var raw-stdin false)
(var handleopts true)
(var exit-on-error true)
(var colorize true)
(var debug-flag false)
(var compile-only false)
(var warn-level nil)
(var error-level nil)
(var expect-image false)
(if-let [jp (getenv-alias "JANET_PATH")] (setdyn :syspath jp))
(if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn :profilepath jprofile))
@@ -3547,8 +3581,9 @@
-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 lib : Import a module before processing more arguments
-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
@@ -3556,29 +3591,31 @@
(os/exit 0)
1)
"v" (fn [&] (print janet/version "-" janet/build) (os/exit 0) 1)
"s" (fn [&] (set *raw-stdin* true) (set *should-repl* true) 1)
"r" (fn [&] (set *should-repl* true) 1)
"p" (fn [&] (set *exit-on-error* false) 1)
"q" (fn [&] (set *quiet* true) 1)
"k" (fn [&] (set *compile-only* true) (set *exit-on-error* false) 1)
"n" (fn [&] (set *colorize* false) 1)
"s" (fn [&] (set raw-stdin true) (set should-repl true) 1)
"r" (fn [&] (set should-repl true) 1)
"p" (fn [&] (set exit-on-error false) 1)
"q" (fn [&] (set quiet true) 1)
"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)
"m" (fn [i &] (setdyn :syspath (in args (+ i 1))) 2)
"c" (fn c-switch [i &]
(def e (dofile (in args (+ i 1))))
(def path (in args (+ i 1)))
(def e (dofile path))
(spit (in args (+ i 2)) (make-image e))
(set *no-file* false)
(set no-file false)
3)
"-" (fn [&] (set *handleopts* false) 1)
"-" (fn [&] (set handleopts false) 1)
"l" (fn l-switch [i &]
(import* (in args (+ i 1))
:prefix "" :exit *exit-on-error*)
:prefix "" :exit exit-on-error)
2)
"e" (fn e-switch [i &]
(set *no-file* false)
(set no-file false)
(eval-string (in args (+ i 1)))
2)
"E" (fn E-switch [i &]
(set *no-file* false)
(set no-file false)
(def subargs (array/slice args (+ i 2)))
(def src ~|,(parse (in args (+ i 1))))
(def thunk (compile src))
@@ -3586,9 +3623,9 @@
((thunk) ;subargs)
(error (get thunk :error)))
math/inf)
"d" (fn [&] (set *debug* true) 1)
"w" (fn [i &] (set *warn-level* (get-lint-level i)) 2)
"x" (fn [i &] (set *error-level* (get-lint-level i)) 2)
"d" (fn [&] (set debug-flag true) 1)
"w" (fn [i &] (set warn-level (get-lint-level i)) 2)
"x" (fn [i &] (set error-level (get-lint-level i)) 2)
"R" (fn [&] (setdyn :profilepath nil) 1)})
(defn- dohandler [n i &]
@@ -3600,29 +3637,37 @@
(def lenargs (length args))
(while (< i lenargs)
(def arg (in args i))
(if (and *handleopts* (= "-" (string/slice arg 0 1)))
(if (and handleopts (= "-" (string/slice arg 0 1)))
(+= i (dohandler (string/slice arg 1) i))
(do
(set *no-file* false)
(def env (make-env))
(def subargs (array/slice args i))
(put env :args subargs)
(put env :lint-error *error-level*)
(put env :lint-warn *warn-level*)
(if *compile-only*
(flycheck arg :exit *exit-on-error* :env env)
(set no-file false)
(if expect-image
(do
(dofile arg :exit *exit-on-error* :env env)
(if-let [main (get (in env 'main) :value)]
(let [thunk (compile [main ;(tuple/slice args i)] env arg)]
(if (function? thunk) (thunk) (error (thunk :error)))))))
(def env (load-image (slurp arg)))
(put env :args subargs)
(put env :lint-error error-level)
(put env :lint-warn warn-level)
(if debug-flag (put env :debug true))
(run-main env subargs arg))
(do
(def env (make-env))
(put env :args subargs)
(put env :lint-error error-level)
(put env :lint-warn warn-level)
(if debug-flag (put env :debug true))
(if compile-only
(flycheck arg :exit exit-on-error :env env)
(do
(dofile arg :exit exit-on-error :env env)
(run-main env subargs arg)))))
(set i lenargs))))
(if (or *should-repl* *no-file*)
(if (or should-repl no-file)
(if
*compile-only* (flycheck stdin :source "stdin" :exit *exit-on-error*)
compile-only (flycheck stdin :source "stdin" :exit exit-on-error)
(do
(if-not *quiet*
(if-not quiet
(print "Janet " janet/version "-" janet/build " " (os/which) "/" (os/arch) " - '(doc)' for help"))
(flush)
(defn getprompt [p]
@@ -3636,15 +3681,15 @@
(when-let [profile.janet (dyn :profilepath)]
(def new-env (dofile profile.janet :exit true))
(merge-module env new-env "" false))
(if *debug* (put env :debug true))
(def getter (if *raw-stdin* getstdin getline))
(if debug-flag (put env :debug true))
(def getter (if raw-stdin getstdin getline))
(defn getchunk [buf p]
(getter (getprompt p) buf env))
(setdyn :pretty-format (if *colorize* "%.20Q" "%.20q"))
(setdyn :err-color (if *colorize* true))
(setdyn :doc-color (if *colorize* true))
(setdyn :lint-error *error-level*)
(setdyn :lint-warn *error-level*)
(setdyn :pretty-format (if colorize "%.20Q" "%.20q"))
(setdyn :err-color (if colorize true))
(setdyn :doc-color (if colorize true))
(setdyn :lint-error error-level)
(setdyn :lint-warn error-level)
(repl getchunk nil env)))))
###
@@ -3655,26 +3700,15 @@
(do
(defn proto-flatten
"Flatten a table and its prototypes into a single table."
[into x]
(when x
(proto-flatten into (table/getproto x))
(loop [k :keys x]
(put into k (x k))))
into)
# Deprecate thread library
(loop [[k v] :in (pairs root-env)
:when (symbol? k)
:when (string/has-prefix? "thread/" k)]
# Deprecate file/popen
(when-let [v (get root-env 'file/popen)]
(put v :deprecated true))
# Modify root-env to remove private symbols and
# flatten nested tables.
(loop [[k v] :in (pairs root-env)
:when (symbol? k)]
(def flat (proto-flatten @{} v))
(def flat (table/proto-flatten v))
(when (boot/config :no-docstrings)
(put flat :doc nil))
(when (boot/config :no-sourcemaps)
@@ -3754,7 +3788,6 @@
"src/core/struct.c"
"src/core/symcache.c"
"src/core/table.c"
"src/core/thread.c"
"src/core/tuple.c"
"src/core/util.c"
"src/core/value.c"

View File

@@ -4,10 +4,10 @@
#define JANETCONF_H
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 17
#define JANET_VERSION_PATCH 1
#define JANET_VERSION_MINOR 19
#define JANET_VERSION_PATCH 0
#define JANET_VERSION_EXTRA ""
#define JANET_VERSION "1.17.1"
#define JANET_VERSION "1.19.0"
/* #define JANET_BUILD "local" */

View File

@@ -63,8 +63,8 @@ void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size)
}
janet_vm.next_collection += size + sizeof(JanetAbstractHead);
header->gc.flags = JANET_MEMORY_THREADED_ABSTRACT;
header->gc.next = NULL; /* Clear memory for address sanitizers */
header->gc.refcount = 1;
header->gc.data.next = NULL; /* Clear memory for address sanitizers */
header->gc.data.refcount = 1;
header->size = size;
header->type = atype;
void *abstract = (void *) & (header->data);
@@ -86,37 +86,37 @@ void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size) {
#ifdef JANET_WINDOWS
static int32_t janet_incref(JanetAbstractHead *ab) {
return InterlockedIncrement(&ab->gc.refcount);
return InterlockedIncrement(&ab->gc.data.refcount);
}
static int32_t janet_decref(JanetAbstractHead *ab) {
return InterlockedDecrement(&ab->gc.refcount);
return InterlockedDecrement(&ab->gc.data.refcount);
}
void janet_os_mutex_init(JanetOSMutex *mutex) {
InitializeCriticalSection(mutex);
InitializeCriticalSection((CRITICAL_SECTION *) mutex);
}
void janet_os_mutex_deinit(JanetOSMutex *mutex) {
DeleteCriticalSection(mutex);
DeleteCriticalSection((CRITICAL_SECTION *) mutex);
}
void janet_os_mutex_lock(JanetOSMutex *mutex) {
EnterCriticalSection(mutex);
EnterCriticalSection((CRITICAL_SECTION *) mutex);
}
void janet_os_mutex_unlock(JanetOSMutex *mutex) {
LeaveCriticalSection(mutex);
LeaveCriticalSection((CRITICAL_SECTION *) mutex);
}
#else
static int32_t janet_incref(JanetAbstractHead *ab) {
return __atomic_add_fetch(&ab->gc.refcount, 1, __ATOMIC_RELAXED);
return __atomic_add_fetch(&ab->gc.data.refcount, 1, __ATOMIC_RELAXED);
}
static int32_t janet_decref(JanetAbstractHead *ab) {
return __atomic_add_fetch(&ab->gc.refcount, -1, __ATOMIC_RELAXED);
return __atomic_add_fetch(&ab->gc.data.refcount, -1, __ATOMIC_RELAXED);
}
void janet_os_mutex_init(JanetOSMutex *mutex) {

View File

@@ -241,6 +241,11 @@ JANET_CORE_FN(cfun_array_concat,
int32_t j, len = 0;
const Janet *vals = NULL;
janet_indexed_view(argv[i], &vals, &len);
if (array->data == vals) {
int32_t newcount = array->count + len;
janet_array_ensure(array, newcount, 2);
janet_indexed_view(argv[i], &vals, &len);
}
for (j = 0; j < len; j++)
janet_array_push(array, vals[j]);
}

View File

@@ -137,7 +137,7 @@ static const char *janet_dyncstring(const char *name, const char *dflt) {
const uint8_t *jstr = janet_unwrap_string(x);
const char *cstr = (const char *)jstr;
if (strlen(cstr) != (size_t) janet_string_length(jstr)) {
janet_panicf("string %v contains embedded 0s");
janet_panicf("string %v contains embedded 0s", x);
}
return cstr;
}
@@ -398,15 +398,21 @@ JANET_CORE_FN(janet_core_is_abstract,
}
JANET_CORE_FN(janet_core_scannumber,
"(scan-number str)",
"Parse a number from a byte sequence an return that number, either and integer "
"(scan-number str &opt base)",
"Parse a number from a byte sequence and return that number, either an integer "
"or a real. The number "
"must be in the same format as numbers in janet source code. Will return nil "
"on an invalid number.") {
"on an invalid number. Optionally provide a base - if a base is provided, no "
"radix specifier is expected at the beginning of the number.") {
double number;
janet_fixarity(argc, 1);
janet_arity(argc, 1, 2);
JanetByteView view = janet_getbytes(argv, 0);
if (janet_scan_number(view.bytes, view.len, &number))
int32_t base = janet_optinteger(argv, argc, 1, 0);
int valid = base == 0 || (base >= 2 && base <= 36);
if (!valid) {
janet_panicf("expected base between 2 and 36, got %d", base);
}
if (janet_scan_number_base(view.bytes, view.len, base, &number))
return janet_wrap_nil();
return janet_wrap_number(number);
}
@@ -459,6 +465,25 @@ JANET_CORE_FN(janet_core_table,
return janet_wrap_table(table);
}
JANET_CORE_FN(janet_core_getproto,
"(getproto x)",
"Get the prototype of a table or struct. Will return nil if `x` has no prototype.") {
janet_fixarity(argc, 1);
if (janet_checktype(argv[0], JANET_TABLE)) {
JanetTable *t = janet_unwrap_table(argv[0]);
return t->proto
? janet_wrap_table(t->proto)
: janet_wrap_nil();
}
if (janet_checktype(argv[0], JANET_STRUCT)) {
JanetStruct st = janet_unwrap_struct(argv[0]);
return janet_struct_proto(st)
? janet_wrap_struct(janet_struct_proto(st))
: janet_wrap_nil();
}
janet_panicf("expected struct|table, got %v", argv[0]);
}
JANET_CORE_FN(janet_core_struct,
"(struct & kvs)",
"Create a new struct from a sequence of key value pairs. "
@@ -466,8 +491,9 @@ JANET_CORE_FN(janet_core_struct,
"an odd number of elements, an error will be thrown. Returns the "
"new struct.") {
int32_t i;
if (argc & 1)
if (argc & 1) {
janet_panic("expected even number of arguments");
}
JanetKV *st = janet_struct_begin(argc >> 1);
for (i = 0; i < argc; i += 2) {
janet_struct_put(st, argv[i], argv[i + 1]);
@@ -954,6 +980,7 @@ 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("getproto", janet_core_getproto),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, corelib_cfuns);
@@ -963,6 +990,7 @@ static void janet_load_libs(JanetTable *env) {
janet_lib_tuple(env);
janet_lib_buffer(env);
janet_lib_table(env);
janet_lib_struct(env);
janet_lib_fiber(env);
janet_lib_os(env);
janet_lib_parse(env);
@@ -979,9 +1007,6 @@ static void janet_load_libs(JanetTable *env) {
#ifdef JANET_INT_TYPES
janet_lib_inttypes(env);
#endif
#ifdef JANET_THREADS
janet_lib_thread(env);
#endif
#ifdef JANET_EV
janet_lib_ev(env);
#endif

View File

@@ -86,7 +86,7 @@ void janet_debug_find(
}
}
}
current = current->next;
current = current->data.next;
}
if (best_def) {
*def_out = best_def;

View File

@@ -1,4 +1,14 @@
/* The above copyright notice and this permission notice shall be included in
/*
* Copyright (c) 2021 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
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
@@ -10,6 +20,7 @@
* IN THE SOFTWARE.
*/
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
@@ -75,6 +86,7 @@ typedef struct {
JanetFiber *fiber;
Janet value;
JanetSignal sig;
uint32_t expected_sched_id; /* If the fiber has been rescheduled this loop, don't run first scheduling. */
} JanetTask;
/* Wrap return value by pairing it with the callback used to handle it
@@ -222,6 +234,9 @@ static void add_timeout(JanetTimeout to) {
/* Create a new event listener */
static JanetListenerState *janet_listen_impl(JanetStream *stream, JanetListener behavior, int mask, size_t size, void *user) {
if (stream->flags & JANET_STREAM_CLOSED) {
janet_panic("cannot listen on closed stream");
}
if (stream->_mask & mask) {
janet_panic("cannot listen for duplicate event on stream");
}
@@ -332,8 +347,10 @@ static void janet_stream_close_impl(JanetStream *stream, int is_gc) {
{
CloseHandle(stream->handle);
}
stream->handle = INVALID_HANDLE_VALUE;
#else
close(stream->handle);
stream->handle = -1;
#endif
}
@@ -447,10 +464,9 @@ const JanetAbstractType janet_stream_type = {
/* Register a fiber to resume with value */
void janet_schedule_signal(JanetFiber *fiber, Janet value, JanetSignal sig) {
if (fiber->flags & JANET_FIBER_FLAG_SCHEDULED) return;
fiber->flags |= JANET_FIBER_FLAG_SCHEDULED;
fiber->sched_id++;
JanetTask t = { fiber, value, sig };
if (fiber->flags & JANET_FIBER_FLAG_CANCELED) return;
JanetTask t = { fiber, value, sig, ++fiber->sched_id };
if (sig == JANET_SIGNAL_ERROR) fiber->flags |= JANET_FIBER_FLAG_CANCELED;
janet_q_push(&janet_vm.spawn, &t, sizeof(t));
}
@@ -821,8 +837,8 @@ static int janet_channel_push(JanetChannel *channel, Janet x, int mode) {
pending.mode = mode ? JANET_CP_MODE_CHOICE_WRITE : JANET_CP_MODE_WRITE;
janet_q_push(&channel->write_pending, &pending, sizeof(pending));
janet_chan_unlock(channel);
janet_ev_inc_refcount();
if (is_threaded) {
janet_ev_inc_refcount();
janet_gcroot(janet_wrap_fiber(pending.fiber));
}
return 1;
@@ -839,6 +855,7 @@ static int janet_channel_push(JanetChannel *channel, Janet x, int mode) {
msg.argj = x;
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
} else {
janet_ev_dec_refcount();
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
janet_schedule(reader.fiber, make_read_result(channel, x));
} else {
@@ -871,8 +888,8 @@ static int janet_channel_pop(JanetChannel *channel, Janet *item, int is_choice)
pending.mode = is_choice ? JANET_CP_MODE_CHOICE_READ : JANET_CP_MODE_READ;
janet_q_push(&channel->read_pending, &pending, sizeof(pending));
janet_chan_unlock(channel);
janet_ev_inc_refcount();
if (is_threaded) {
janet_ev_inc_refcount();
janet_gcroot(janet_wrap_fiber(pending.fiber));
}
return 0;
@@ -890,6 +907,7 @@ static int janet_channel_pop(JanetChannel *channel, Janet *item, int is_choice)
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
} else {
janet_ev_dec_refcount();
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
janet_schedule(writer.fiber, make_write_result(channel));
} else {
@@ -1093,6 +1111,7 @@ JANET_CORE_FN(cfun_channel_close,
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
} else {
janet_ev_dec_refcount();
if (writer.mode == JANET_CP_MODE_CHOICE_WRITE) {
janet_schedule(writer.fiber, janet_wrap_nil());
} else {
@@ -1112,6 +1131,7 @@ JANET_CORE_FN(cfun_channel_close,
msg.argj = janet_wrap_nil();
janet_ev_post_event(vm, janet_thread_chan_cb, msg);
} else {
janet_ev_dec_refcount();
if (reader.mode == JANET_CP_MODE_CHOICE_READ) {
janet_schedule(reader.fiber, janet_wrap_nil());
} else {
@@ -1182,13 +1202,13 @@ JanetFiber *janet_loop1(void) {
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);
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) {
janet_cancel(to.fiber, janet_cstringv("deadline expired"));
}
@@ -1206,9 +1226,10 @@ JanetFiber *janet_loop1(void) {
/* Run scheduled fibers */
while (janet_vm.spawn.head != janet_vm.spawn.tail) {
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK};
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0};
janet_q_pop(&janet_vm.spawn, &task, sizeof(task));
task.fiber->flags &= ~JANET_FIBER_FLAG_SCHEDULED;
task.fiber->flags &= ~JANET_FIBER_FLAG_CANCELED;
if (task.expected_sched_id != task.fiber->sched_id) continue;
Janet res;
JanetSignal sig = janet_continue_signal(task.fiber, task.value, &res, task.sig);
void *sv = task.fiber->supervisor_channel;
@@ -1513,8 +1534,8 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
JanetStream *stream = p;
int mask = events[i].events;
JanetListenerState *state = stream->state;
state->event = events + i;
while (NULL != state) {
state->event = events + i;
JanetListenerState *next_state = state->_next;
JanetAsyncStatus status1 = JANET_ASYNC_STATUS_NOT_DONE;
JanetAsyncStatus status2 = JANET_ASYNC_STATUS_NOT_DONE;
@@ -1576,32 +1597,19 @@ void janet_ev_deinit(void) {
* NetBSD uses intptr_t while others use void * for .udata */
#define EV_SETx(ev, a, b, c, d, e, f) EV_SET((ev), (a), (b), (c), (d), (e), ((__typeof__((ev)->udata))(f)))
#define JANET_KQUEUE_TF (EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT)
/* NOTE:
* NetBSD doesn't like intervals less than 1 millisecond so lets make that the
* default anywhere JANET_KQUEUE_TS will be used. */
#ifdef __NetBSD__
#define JANET_KQUEUE_MIN_INTERVAL 1
#else
#define JANET_KQUEUE_MIN_INTERVAL 0
#endif
#ifdef __FreeBSD__
#define JANET_KQUEUE_TS(timestamp) (timestamp)
#else
/* NOTE:
* NetBSD and OpenBSD expect things are always intervals, so fake that we have
* abstime capability by changing how a timestamp is used in all kqueue calls
* and defining absent macros. Additionally NetBSD expects intervals be
* greater than 1 millisecond, so correct all intervals to be at least 1
* millisecond under NetBSD. */
JanetTimestamp fix_interval(const JanetTimestamp ts) {
* NetBSD and OpenBSD expect things are always intervals, and FreeBSD doesn't
* like an ABSTIME in the past so just use intervals always. Introduces a
* calculation to determine the minimum timeout per timeout requested of
* kqueue. Also note that NetBSD doesn't accept timeout intervals less than 1
* millisecond, so correct all intervals on that platform to be at least 1
* millisecond.*/
JanetTimestamp to_interval(const JanetTimestamp ts) {
return ts >= JANET_KQUEUE_MIN_INTERVAL ? ts : JANET_KQUEUE_MIN_INTERVAL;
}
#define JANET_KQUEUE_TS(timestamp) (fix_interval((timestamp - ts_now())))
#define NOTE_MSECONDS 0
#define NOTE_ABSTIME 0
#endif
#define JANET_KQUEUE_INTERVAL(timestamp) (to_interval((timestamp - ts_now())))
/* TODO: make this available be we using kqueue or epoll, instead of
@@ -1614,6 +1622,12 @@ static JanetTimestamp ts_now(void) {
return res;
}
/* NOTE: Assumes Janet's timestamp precision is in milliseconds. */
static void timestamp2timespec(struct timespec *t, JanetTimestamp ts) {
t->tv_sec = ts == 0 ? 0 : ts / 1000;
t->tv_nsec = ts == 0 ? 0 : (ts % 1000) * 1000000;
}
void add_kqueue_events(const struct kevent *events, int length) {
/* NOTE: Status should be equal to the amount of events added, which isn't
* always known since deletions or modifications occur. Can't use the
@@ -1680,45 +1694,46 @@ static void janet_unlisten(JanetListenerState *state, int is_gc) {
janet_unlisten_impl(state, is_gc);
}
#define JANET_KQUEUE_TIMER_IDENT 1
#define JANET_KQUEUE_MAX_EVENTS 64
void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
/* Construct our timer which is a definite time on the clock, not an
* interval, in milliseconds as that is `JanetTimestamp`'s precision. */
struct kevent timer;
if (janet_vm.timer_enabled || has_timeout) {
EV_SETx(&timer,
JANET_KQUEUE_TIMER_IDENT,
EVFILT_TIMER,
JANET_KQUEUE_TF,
NOTE_MSECONDS | NOTE_ABSTIME,
JANET_KQUEUE_TS(timeout), &janet_vm.timer);
add_kqueue_events(&timer, 1);
}
janet_vm.timer_enabled = has_timeout;
/* Poll for events */
/* NOTE:
* We calculate the timeout interval per iteration. When the interval
* drops to 0 or negative, we effect a timeout of 0. Effecting a timeout
* of infinity will not work and could make other fibers with timeouts
* miss their timeouts if we did so.
* JANET_KQUEUE_INTERVAL insures we have a timeout of no less than 0. */
int status;
struct timespec ts;
struct kevent events[JANET_KQUEUE_MAX_EVENTS];
do {
status = kevent(janet_vm.kq, NULL, 0, events, JANET_KQUEUE_MAX_EVENTS, NULL);
if (janet_vm.timer_enabled || has_timeout) {
timestamp2timespec(&ts, JANET_KQUEUE_INTERVAL(timeout));
status = kevent(janet_vm.kq, NULL, 0, events,
JANET_KQUEUE_MAX_EVENTS, &ts);
} else {
status = kevent(janet_vm.kq, NULL, 0, events,
JANET_KQUEUE_MAX_EVENTS, NULL);
}
} while (status == -1 && errno == EINTR);
if (status == -1)
JANET_EXIT("failed to poll events");
/* Make sure timer is set accordingly. */
janet_vm.timer_enabled = has_timeout;
/* Step state machines */
for (int i = 0; i < status; i++) {
void *p = (void*) events[i].udata;
if (&janet_vm.timer == p) {
/* Timer expired, ignore */;
} else if (janet_vm.selfpipe == p) {
void *p = (void *) events[i].udata;
if (janet_vm.selfpipe == p) {
/* Self-pipe handling */
janet_ev_handle_selfpipe();
} else {
JanetStream *stream = p;
JanetListenerState *state = stream->state;
if (NULL != state) {
while (NULL != state) {
JanetListenerState *next_state = state->_next;
state->event = events + i;
JanetAsyncStatus statuses[4];
for (int i = 0; i < 4; i++)
@@ -1739,6 +1754,8 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
statuses[2] == JANET_ASYNC_STATUS_DONE ||
statuses[3] == JANET_ASYNC_STATUS_DONE)
janet_unlisten(state, 0);
state = next_state;
}
}
}
@@ -1750,16 +1767,9 @@ void janet_ev_init(void) {
janet_vm.kq = kqueue();
janet_vm.timer_enabled = 0;
if (janet_vm.kq == -1) goto error;
struct kevent events[2];
/* Don't use JANET_KQUEUE_TS here, as even under FreeBSD we use intervals
* here. */
EV_SETx(&events[0],
JANET_KQUEUE_TIMER_IDENT,
EVFILT_TIMER,
JANET_KQUEUE_TF,
NOTE_MSECONDS, JANET_KQUEUE_MIN_INTERVAL, &janet_vm.timer);
EV_SETx(&events[1], janet_vm.selfpipe[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, janet_vm.selfpipe);
add_kqueue_events(events, 2);
struct kevent event;
EV_SETx(&event, janet_vm.selfpipe[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, janet_vm.selfpipe);
add_kqueue_events(&event, 1);
return;
error:
JANET_EXIT("failed to initialize event loop");
@@ -2235,7 +2245,7 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
case JANET_ASYNC_EVENT_READ: {
JanetBuffer *buffer = state->buf;
int32_t bytes_left = state->bytes_left;
int32_t read_limit = bytes_left > 4096 ? 4096 : bytes_left;
int32_t read_limit = state->is_chunk ? (bytes_left > 4096 ? 4096 : bytes_left) : bytes_left;
janet_buffer_extra(buffer, read_limit);
ssize_t nread;
#ifdef JANET_NET
@@ -2571,15 +2581,16 @@ void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, in
static volatile long PipeSerialNumber;
#endif
/*
* mode = 0: both sides non-blocking.
* mode = 1: only read side non-blocking: write side sent to subprocess
* mode = 2: only write side non-blocking: read side sent to subprocess
*/
int janet_make_pipe(JanetHandle handles[2], int mode) {
#ifdef JANET_WINDOWS
/*
* On windows, the built in CreatePipe function doesn't support overlapped IO
* so we lift from the windows source code and modify for our own version.
*
* mode = 0: both sides non-blocking.
* mode = 1: only read side non-blocking: write side sent to subprocess
* mode = 2: only write side non-blocking: read side sent to subprocess
*/
JanetHandle shandle, chandle;
UCHAR PipeNameBuffer[MAX_PATH];
@@ -2629,10 +2640,11 @@ int janet_make_pipe(JanetHandle handles[2], int mode) {
}
return 0;
#else
(void) mode;
if (pipe(handles)) return -1;
if (fcntl(handles[0], F_SETFL, O_NONBLOCK)) goto error;
if (fcntl(handles[1], F_SETFL, O_NONBLOCK)) goto error;
if (mode != 2 && fcntl(handles[0], F_SETFD, FD_CLOEXEC)) goto error;
if (mode != 1 && fcntl(handles[1], F_SETFD, FD_CLOEXEC)) goto error;
if (mode != 2 && fcntl(handles[0], F_SETFL, O_NONBLOCK)) goto error;
if (mode != 1 && fcntl(handles[1], F_SETFL, O_NONBLOCK)) goto error;
return 0;
error:
close(handles[0]);
@@ -2903,7 +2915,7 @@ JANET_CORE_FN(cfun_ev_deadline,
JANET_CORE_FN(cfun_ev_cancel,
"(ev/cancel fiber err)",
"Cancel a suspended fiber in the event loop. Differs from cancel in that it returns the canceled fiber immediately") {
"Cancel a suspended fiber in the event loop. Differs from cancel in that it returns the canceled fiber immediately.") {
janet_fixarity(argc, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
Janet err = argv[1];

View File

@@ -47,8 +47,8 @@
#define JANET_FIBER_MASK_USER 0x3FF0
#define JANET_FIBER_STATUS_MASK 0x3F0000
#define JANET_FIBER_FLAG_SCHEDULED 0x800000
#define JANET_FIBER_RESUME_SIGNAL 0x400000
#define JANET_FIBER_FLAG_CANCELED 0x400000
#define JANET_FIBER_STATUS_OFFSET 16
#define JANET_FIBER_BREAKPOINT 0x1000000

View File

@@ -123,6 +123,8 @@ static void janet_mark_abstract(void *adata) {
/* Mark a bunch of items in memory */
static void janet_mark_many(const Janet *values, int32_t n) {
if (values == NULL)
return;
const Janet *end = values + n;
while (values < end) {
janet_mark(*values);
@@ -160,10 +162,13 @@ recur: /* Manual tail recursion */
}
static void janet_mark_struct(const JanetKV *st) {
recur:
if (janet_gc_reachable(janet_struct_head(st)))
return;
janet_gc_mark(janet_struct_head(st));
janet_mark_kvs(st, janet_struct_capacity(st));
st = janet_struct_proto(st);
if (st) goto recur;
}
static void janet_mark_tuple(const Janet *tuple) {
@@ -321,7 +326,7 @@ void janet_sweep() {
JanetGCObject *current = janet_vm.blocks;
JanetGCObject *next;
while (NULL != current) {
next = current->next;
next = current->data.next;
if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) {
previous = current;
current->flags &= ~JANET_MEM_REACHABLE;
@@ -329,7 +334,7 @@ void janet_sweep() {
janet_vm.block_count--;
janet_deinit_block(current);
if (NULL != previous) {
previous->next = next;
previous->data.next = next;
} else {
janet_vm.blocks = next;
}
@@ -393,7 +398,7 @@ void *janet_gcalloc(enum JanetMemoryType type, size_t size) {
/* Prepend block to heap list */
janet_vm.next_collection += size;
mem->next = janet_vm.blocks;
mem->data.next = janet_vm.blocks;
janet_vm.blocks = mem;
janet_vm.block_count++;
@@ -530,7 +535,7 @@ void janet_clear_memory(void) {
JanetGCObject *current = janet_vm.blocks;
while (NULL != current) {
janet_deinit_block(current);
JanetGCObject *next = current->next;
JanetGCObject *next = current->data.next;
janet_free(current);
current = next;
}

View File

@@ -64,8 +64,9 @@ enum {
LB_FUNCDEF_REF, /* 220 */
LB_UNSAFE_CFUNCTION, /* 221 */
LB_UNSAFE_POINTER, /* 222 */
LB_STRUCT_PROTO, /* 223 */
#ifdef JANET_EV
LB_THREADED_ABSTRACT/* 223 */
LB_THREADED_ABSTRACT/* 224 */
#endif
} LeadBytes;
@@ -384,6 +385,7 @@ static void marshal_one_abstract(MarshalState *st, Janet x, int flags) {
janet_abstract_incref(abstract);
pushbyte(st, LB_THREADED_ABSTRACT);
pushbytes(st, (uint8_t *) &abstract, sizeof(abstract));
MARK_SEEN();
return;
}
#endif
@@ -541,8 +543,10 @@ static void marshal_one(MarshalState *st, Janet x, int flags) {
int32_t count;
const JanetKV *struct_ = janet_unwrap_struct(x);
count = janet_struct_length(struct_);
pushbyte(st, LB_STRUCT);
pushbyte(st, janet_struct_proto(struct_) ? LB_STRUCT_PROTO : LB_STRUCT);
pushint(st, count);
if (janet_struct_proto(struct_))
marshal_one(st, janet_wrap_struct(janet_struct_proto(struct_)), flags + 1);
for (int32_t i = 0; i < janet_struct_capacity(struct_); i++) {
if (janet_checktype(struct_[i].key, JANET_NIL))
continue;
@@ -1280,6 +1284,7 @@ static const uint8_t *unmarshal_one(
case LB_ARRAY:
case LB_TUPLE:
case LB_STRUCT:
case LB_STRUCT_PROTO:
case LB_TABLE:
case LB_TABLE_PROTO:
/* Things that open with integers */
@@ -1309,9 +1314,15 @@ static const uint8_t *unmarshal_one(
}
*out = janet_wrap_tuple(janet_tuple_end(tup));
janet_v_push(st->lookup, *out);
} else if (lead == LB_STRUCT) {
} else if (lead == LB_STRUCT || lead == LB_STRUCT_PROTO) {
/* Struct */
JanetKV *struct_ = janet_struct_begin(len);
if (lead == LB_STRUCT_PROTO) {
Janet proto;
data = unmarshal_one(st, data, &proto, flags + 1);
janet_asserttype(proto, JANET_STRUCT);
janet_struct_proto(struct_) = janet_unwrap_struct(proto);
}
for (int32_t i = 0; i < len; i++) {
Janet key, value;
data = unmarshal_one(st, data, &key, flags + 1);

View File

@@ -286,7 +286,8 @@ 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, lgamma, "Returns gamma(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.")
@@ -309,6 +310,42 @@ JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") {
return janet_wrap_boolean(!janet_truthy(argv[0]));
}
static double janet_gcd(double x, double y) {
if (isnan(x) || isnan(y)) {
#ifdef NAN
return NAN;
#else
return 0.0 \ 0.0;
#endif
}
if (isinf(x) || isinf(y)) return INFINITY;
while (y != 0) {
double temp = y;
y = fmod(x, y);
x = temp;
}
return x;
}
static double janet_lcm(double x, double y) {
return (x / janet_gcd(x, y)) * y;
}
JANET_CORE_FN(janet_cfun_gcd, "(math/gcd x y)",
"Returns the greatest common divisor between x and y.") {
janet_fixarity(argc, 2);
double x = janet_getnumber(argv, 0);
double y = janet_getnumber(argv, 1);
return janet_wrap_number(janet_gcd(x, y));
}
JANET_CORE_FN(janet_cfun_lcm, "(math/lcm x y)",
"Returns the least common multiple of x and y.") {
janet_fixarity(argc, 2);
double x = janet_getnumber(argv, 0);
double y = janet_getnumber(argv, 1);
return janet_wrap_number(janet_lcm(x, y));
}
/* Module entry point */
void janet_lib_math(JanetTable *env) {
@@ -347,12 +384,15 @@ void janet_lib_math(JanetTable *env) {
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/log-gamma", janet_lgamma),
JANET_CORE_REG("math/erfc", janet_erfc),
JANET_CORE_REG("math/erf", janet_erf),
JANET_CORE_REG("math/expm1", janet_expm1),
JANET_CORE_REG("math/trunc", janet_trunc),
JANET_CORE_REG("math/round", janet_round),
JANET_CORE_REG("math/next", janet_nextafter),
JANET_CORE_REG("math/gcd", janet_cfun_gcd),
JANET_CORE_REG("math/lcm", janet_cfun_lcm),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, math_cfuns);
@@ -375,7 +415,7 @@ void janet_lib_math(JanetTable *env) {
JANET_CORE_DEF(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE),
"The maximum contiguous integer represtenable by a double (-(2^53))");
#ifdef NAN
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN");
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN)");
#else
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(0.0 / 0.0), "Not a number (IEEE-754 NaN)");
#endif

View File

@@ -38,6 +38,7 @@
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "Advapi32.lib")
#else
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
@@ -73,6 +74,15 @@ const JanetAbstractType janet_address_type = {
#endif
#endif
/* maximum number of bytes in a socket address host (post name resolution) */
#ifdef JANET_WINDOWS
#define SA_ADDRSTRLEN (INET6_ADDRSTRLEN + 1)
typedef unsigned short in_port_t;
#else
#define JANET_SA_MAX(a, b) (((a) > (b))? (a) : (b))
#define SA_ADDRSTRLEN JANET_SA_MAX(INET6_ADDRSTRLEN + 1, (sizeof ((struct sockaddr_un *)0)->sun_path) + 1)
#endif
static JanetStream *make_stream(JSock handle, uint32_t flags);
/* We pass this flag to all send calls to prevent sigpipe */
@@ -122,22 +132,20 @@ JanetAsyncStatus net_machine_accept(JanetListenerState *s, JanetAsyncEvent event
case JANET_ASYNC_EVENT_MARK: {
if (state->lstream) janet_mark(janet_wrap_abstract(state->lstream));
if (state->astream) janet_mark(janet_wrap_abstract(state->astream));
if (state->function) janet_mark(janet_wrap_abstract(state->function));
if (state->function) janet_mark(janet_wrap_function(state->function));
break;
}
case JANET_ASYNC_EVENT_CLOSE:
janet_schedule(s->fiber, janet_wrap_nil());
return JANET_ASYNC_STATUS_DONE;
case JANET_ASYNC_EVENT_COMPLETE: {
int seconds;
int bytes = sizeof(seconds);
if (NO_ERROR != getsockopt((SOCKET) state->astream->handle, SOL_SOCKET, SO_CONNECT_TIME,
(char *)&seconds, &bytes)) {
if (state->astream->flags & JANET_STREAM_CLOSED) {
janet_cancel(s->fiber, janet_cstringv("failed to accept connection"));
return JANET_ASYNC_STATUS_DONE;
}
SOCKET lsock = (SOCKET) state->lstream->handle;
if (NO_ERROR != setsockopt((SOCKET) state->astream->handle, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
(char *) & (state->lstream->handle), sizeof(SOCKET))) {
(char *) &lsock, sizeof(lsock))) {
janet_cancel(s->fiber, janet_cstringv("failed to accept connection"));
return JANET_ASYNC_STATUS_DONE;
}
@@ -313,12 +321,13 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
*/
JANET_CORE_FN(cfun_net_sockaddr,
"(net/address host port &opt type)",
"(net/address host port &opt type multi)",
"Look up the connection information for a given hostname, port, and connection type. Returns "
"a handle that can be used to send datagrams over network without establishing a connection. "
"On Posix platforms, you can use :unix for host to connect to a unix domain socket, where the name is "
"given in the port argument. On Linux, abstract "
"unix domain sockets are specified with a leading '@' character in port.") {
"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_arity(argc, 2, 4);
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
@@ -371,7 +380,7 @@ JANET_CORE_FN(cfun_net_connect,
int is_unix = 0;
char *bindhost = (char *) janet_optcstring(argv, argc, 3, NULL);
char *bindport = NULL;
if (janet_checkint(argv[4])) {
if (argc >= 5 && janet_checkint(argv[4])) {
bindport = (char *)janet_to_string(argv[4]);
} else {
bindport = (char *)janet_optcstring(argv, argc, 4, NULL);
@@ -400,6 +409,7 @@ JANET_CORE_FN(cfun_net_connect,
}
}
/* Create socket */
JSock sock = JSOCKDEFAULT;
void *addr = NULL;
@@ -420,7 +430,7 @@ JANET_CORE_FN(cfun_net_connect,
struct addrinfo *rp = NULL;
for (rp = ai; rp != NULL; rp = rp->ai_next) {
#ifdef JANET_WINDOWS
sock = WSASocketW(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
#else
sock = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol);
#endif
@@ -630,6 +640,96 @@ JANET_CORE_FN(cfun_net_listen,
}
}
/* Types of socket's we need to deal with - relevant type puns below.
struct sockaddr *sa; // Common base structure
struct sockaddr_storage *ss; // Size of largest socket address type
struct sockaddr_in *sin; // IPv4 address + port
struct sockaddr_in6 *sin6; // IPv6 address + port
struct sockaddr_un *sun; // Unix Domain Socket Address
*/
/* Turn a socket address into a host, port pair.
* For unix domain sockets, returned tuple will have only a single element, the path string. */
static Janet janet_so_getname(const void *sa_any) {
const struct sockaddr *sa = sa_any;
char buffer[SA_ADDRSTRLEN];
switch (sa->sa_family) {
default:
janet_panic("unknown address family");
case AF_INET: {
const struct sockaddr_in *sai = sa_any;
if (!inet_ntop(AF_INET, &(sai->sin_addr), buffer, sizeof(buffer))) {
janet_panic("unable to decode ipv4 host address");
}
Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai->sin_port))};
return janet_wrap_tuple(janet_tuple_n(pair, 2));
}
case AF_INET6: {
const struct sockaddr_in6 *sai6 = sa_any;
if (!inet_ntop(AF_INET6, &(sai6->sin6_addr), buffer, sizeof(buffer))) {
janet_panic("unable to decode ipv4 host address");
}
Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai6->sin6_port))};
return janet_wrap_tuple(janet_tuple_n(pair, 2));
}
#ifndef JANET_WINDOWS
case AF_UNIX: {
const struct sockaddr_un *sun = sa_any;
Janet pathname;
if (sun->sun_path[0] == '\0') {
memcpy(buffer, sun->sun_path, sizeof(sun->sun_path));
buffer[0] = '@';
pathname = janet_cstringv(buffer);
} else {
pathname = janet_cstringv(sun->sun_path);
}
return janet_wrap_tuple(janet_tuple_n(&pathname, 1));
}
#endif
}
}
JANET_CORE_FN(cfun_net_getsockname,
"(net/localname stream)",
"Gets the local address and port in a tuple in that order.") {
janet_fixarity(argc, 1);
JanetStream *js = janet_getabstract(argv, 0, &janet_stream_type);
if (js->flags & JANET_STREAM_CLOSED) janet_panic("stream closed");
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
memset(&ss, 0, slen);
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");
return janet_so_getname(&ss);
}
JANET_CORE_FN(cfun_net_getpeername,
"(net/peername stream)",
"Gets the remote peer's address and port in a tuple in that order.") {
janet_fixarity(argc, 1);
JanetStream *js = janet_getabstract(argv, 0, &janet_stream_type);
if (js->flags & JANET_STREAM_CLOSED) janet_panic("stream closed");
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
memset(&ss, 0, slen);
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");
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 "
"will have only the path in the returned tuple.") {
janet_fixarity(argc, 1);
struct sockaddr *sa = janet_getabstract(argv, 0, &janet_address_type);
return janet_so_getname(sa);
}
JANET_CORE_FN(cfun_stream_accept_loop,
"(net/accept-loop stream handler)",
"Shorthand for running a server stream that will continuously accept new connections. "
@@ -799,6 +899,9 @@ void janet_lib_net(JanetTable *env) {
JANET_CORE_REG("net/flush", cfun_stream_flush),
JANET_CORE_REG("net/connect", cfun_net_connect),
JANET_CORE_REG("net/shutdown", cfun_net_shutdown),
JANET_CORE_REG("net/peername", cfun_net_getpeername),
JANET_CORE_REG("net/localname", cfun_net_getsockname),
JANET_CORE_REG("net/address-unpack", cfun_net_address_unpack),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, net_cfuns);

View File

@@ -409,15 +409,13 @@ static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
#else /* windows check */
/* 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;
pid_t result;
static int proc_get_status(JanetProc *proc) {
/* Use POSIX shell semantics for interpreting signals */
int status = 0;
pid_t result;
do {
result = waitpid(proc->pid, &status, 0);
} while (result == -1 && errno == EINTR);
/* Use POSIX shell semantics for interpreting signals */
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
} else if (WIFSTOPPED(status)) {
@@ -425,7 +423,21 @@ static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
} else {
status = WTERMSIG(status) + 128;
}
args.argi = status;
return status;
}
/* 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;
}
@@ -434,9 +446,13 @@ static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
/* Callback that is called in main thread when subroutine completes. */
static void janet_proc_wait_cb(JanetEVGenericMessage args) {
janet_ev_dec_refcount();
int status = args.argi;
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;
@@ -469,7 +485,9 @@ static int janet_proc_gc(void *p, size_t s) {
/* Kill and wait to prevent zombies */
kill(proc->pid, SIGKILL);
int status;
waitpid(proc->pid, &status, 0);
if (!(proc->flags & JANET_PROC_WAITING)) {
waitpid(proc->pid, &status, 0);
}
}
#endif
return 0;
@@ -540,7 +558,7 @@ JANET_CORE_FN(os_proc_wait,
JANET_CORE_FN(os_proc_kill,
"(os/proc-kill proc &opt wait)",
"Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process "
"handle on windows. If wait is truthy, will wait for the process to finsih and "
"handle on windows. If wait is truthy, will wait for the process to finish and "
"returns the exit code. Otherwise, returns proc.") {
janet_arity(argc, 1, 2);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
@@ -685,6 +703,12 @@ static int janet_proc_get(void *p, Janet key, Janet *out) {
*out = (NULL == proc->err) ? janet_wrap_nil() : janet_wrap_abstract(proc->err);
return 1;
}
#ifndef JANET_WINDOWS
if (janet_keyeq(key, "pid")) {
*out = janet_wrap_number(proc->pid);
return 1;
}
#endif
if ((-1 != proc->return_code) && janet_keyeq(key, "return-code")) {
*out = janet_wrap_integer(proc->return_code);
return 1;
@@ -948,18 +972,24 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
posix_spawn_file_actions_init(&actions);
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) {
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) {
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) {
posix_spawn_file_actions_adddup2(&actions, new_err, 2);
posix_spawn_file_actions_addclose(&actions, new_err);
}
pid_t pid;
@@ -1053,7 +1083,9 @@ JANET_CORE_FN(os_execute,
JANET_CORE_FN(os_spawn,
"(os/spawn args &opt flags env)",
"Execute a program on the system and return a handle to the process. Otherwise, the "
"same arguments as os/execute. Does not wait for the process.") {
"same arguments as os/execute. Does not wait for the process. "
"The returned value has the fields :in, :out, :err, :return-code and "
"the additional field :pid on unix like platforms.") {
return os_execute_impl(argc, argv, 1);
}

View File

@@ -51,15 +51,15 @@ static const uint32_t symchars[8] = {
};
/* Check if a character is a valid symbol character
* symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_~| */
static int is_symbol_char(uint8_t c) {
* symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_| */
int janet_is_symbol_char(uint8_t c) {
return symchars[c >> 5] & ((uint32_t)1 << (c & 0x1F));
}
/* Validate some utf8. Useful for identifiers. Only validates
* the encoding, does not check for valid code points (they
* are less well defined than the encoding). */
static int valid_utf8(const uint8_t *str, int32_t len) {
int janet_valid_utf8(const uint8_t *str, int32_t len) {
int32_t i = 0;
int32_t j;
while (i < len) {
@@ -411,7 +411,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) {
Janet ret;
double numval;
int32_t blen;
if (is_symbol_char(c)) {
if (janet_is_symbol_char(c)) {
push_buf(p, (uint8_t) c);
if (c > 127) state->argn = 1; /* Use to indicate non ascii */
return 1;
@@ -422,7 +422,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) {
int start_num = start_dig || p->buf[0] == '-' || p->buf[0] == '+' || p->buf[0] == '.';
if (p->buf[0] == ':') {
/* Don't do full utf-8 check unless we have seen non ascii characters. */
int valid = (!state->argn) || valid_utf8(p->buf + 1, blen - 1);
int valid = (!state->argn) || janet_valid_utf8(p->buf + 1, blen - 1);
if (!valid) {
p->error = "invalid utf-8 in keyword";
return 0;
@@ -442,7 +442,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) {
return 0;
} else {
/* Don't do full utf-8 check unless we have seen non ascii characters. */
int valid = (!state->argn) || valid_utf8(p->buf, blen);
int valid = (!state->argn) || janet_valid_utf8(p->buf, blen);
if (!valid) {
p->error = "invalid utf-8 in symbol";
return 0;
@@ -582,7 +582,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) {
switch (c) {
default:
if (is_whitespace(c)) return 1;
if (!is_symbol_char(c)) {
if (!janet_is_symbol_char(c)) {
p->error = "unexpected character";
return 1;
}
@@ -746,6 +746,7 @@ Janet janet_parser_produce(JanetParser *parser) {
}
parser->pending--;
parser->argcount--;
parser->states[0].argn--;
return ret;
}
@@ -759,6 +760,7 @@ Janet janet_parser_produce_wrapped(JanetParser *parser) {
}
parser->pending--;
parser->argcount--;
parser->states[0].argn--;
return ret;
}
@@ -1093,8 +1095,9 @@ static Janet janet_wrap_parse_state(JanetParseState *s, Janet *args,
if (s->flags & PFLAG_CONTAINER) {
JanetArray *container_args = janet_array(s->argn);
container_args->count = s->argn;
safe_memcpy(container_args->data, args, sizeof(args[0])*s->argn);
for (int32_t i = 0; i < s->argn; i++) {
janet_array_push(container_args, args[i]);
}
janet_table_put(state, janet_ckeywordv("args"),
janet_wrap_array(container_args));
}
@@ -1189,11 +1192,14 @@ static Janet parser_state_frames(const JanetParser *p) {
JanetArray *states = janet_array(count);
states->count = count;
uint8_t *buf = p->buf;
Janet *args = p->args;
/* Iterate arg stack backwards */
Janet *args = p->args + p->argcount;
for (int32_t i = count - 1; i >= 0; --i) {
JanetParseState *s = p->states + i;
if (s->flags & PFLAG_CONTAINER) {
args -= s->argn;
}
states->data[i] = janet_wrap_parse_state(s, args, buf, (uint32_t) p->bufcount);
args -= s->argn;
}
return janet_wrap_array(states);
}

View File

@@ -387,6 +387,25 @@ tail:
return result;
}
case RULE_CAPTURE_NUM: {
down1(s);
const uint8_t *result = peg_rule(s, s->bytecode + rule[1], text);
up1(s);
if (!result) return NULL;
/* check number parsing */
double x = 0.0;
int32_t base = (int32_t) rule[2];
if (janet_scan_number_base(text, (int32_t)(result - text), base, &x)) return NULL;
/* Specialized pushcap - avoid intermediate string creation */
if (!s->has_backref && s->mode == PEG_MODE_ACCUMULATE) {
janet_buffer_push_bytes(s->scratch, text, (int32_t)(result - text));
} else {
uint32_t tag = rule[3];
pushcap(s, janet_wrap_number(x), tag);
}
return result;
}
case RULE_ACCUMULATE: {
uint32_t tag = rule[2];
int oldmode = s->mode;
@@ -975,6 +994,25 @@ static void spec_unref(Builder *b, int32_t argc, const Janet *argv) {
spec_cap1(b, argc, argv, RULE_UNREF);
}
static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) {
peg_arity(b, argc, 1, 3);
Reserve r = reserve(b, 4);
uint32_t base = 0;
if (argc >= 2) {
if (!janet_checktype(argv[1], JANET_NIL)) {
if (!janet_checkint(argv[1])) goto error;
base = (uint32_t) janet_unwrap_integer(argv[1]);
if (base < 2 || base > 36) goto error;
}
}
uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0;
uint32_t rule = peg_compile1(b, argv[0]);
emit_3(r, RULE_CAPTURE_NUM, rule, base, tag);
return;
error:
peg_panicf(b, "expected integer between 2 and 36, got %v", argv[2]);
}
static void spec_reference(Builder *b, int32_t argc, const Janet *argv) {
peg_arity(b, argc, 1, 2);
Reserve r = reserve(b, 3);
@@ -1118,6 +1156,7 @@ static const SpecialPair peg_specials[] = {
{"line", spec_line},
{"look", spec_look},
{"not", spec_not},
{"number", spec_capture_number},
{"opt", spec_opt},
{"position", spec_position},
{"quote", spec_capture},
@@ -1214,6 +1253,18 @@ static uint32_t peg_compile1(Builder *b, Janet peg) {
emit_bytes(b, RULE_LITERAL, len, str);
break;
}
case JANET_TABLE: {
/* Build grammar table */
JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg));
new_grammar->proto = grammar;
b->grammar = grammar = new_grammar;
/* Run the main rule */
Janet main_rule = janet_table_rawget(grammar, janet_ckeywordv("main"));
if (janet_checktype(main_rule, JANET_NIL))
peg_panic(b, "grammar requires :main rule");
rule = peg_compile1(b, main_rule);
break;
}
case JANET_STRUCT: {
/* Build grammar table */
const JanetKV *st = janet_unwrap_struct(peg);
@@ -1419,6 +1470,12 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
if (rule[1] >= clen) goto bad;
i += 3;
break;
case RULE_CAPTURE_NUM:
/* [rule, base, tag] */
if (rule[1] >= blen) goto bad;
op_flags[rule[1]] |= 0x01;
i += 4;
break;
case RULE_ACCUMULATE:
case RULE_GROUP:
case RULE_CAPTURE:

View File

@@ -261,21 +261,13 @@ void janet_to_string_b(JanetBuffer *buffer, Janet x) {
/* See parse.c for full table */
static const uint32_t pp_symchars[8] = {
0x00000000, 0xf7ffec72, 0xc7ffffff, 0x07fffffe,
0x00000000, 0x00000000, 0x00000000, 0x00000000
};
static int pp_is_symbol_char(uint8_t c) {
return pp_symchars[c >> 5] & ((uint32_t)1 << (c & 0x1F));
}
/* Check if a symbol or keyword contains no symbol characters */
static int contains_bad_chars(const uint8_t *sym, int issym) {
int32_t len = janet_string_length(sym);
if (len && issym && sym[0] >= '0' && sym[0] <= '9') return 1;
if (!janet_valid_utf8(sym, len)) return 1;
for (int32_t i = 0; i < len; i++) {
if (!pp_is_symbol_char(sym[i])) return 1;
if (!janet_is_symbol_char(sym[i])) return 1;
}
return 0;
}
@@ -570,12 +562,12 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
case JANET_STRUCT:
case JANET_TABLE: {
int istable = janet_checktype(x, JANET_TABLE);
janet_buffer_push_cstring(S->buffer, istable ? "@" : "{");
/* For object-like tables, print class name */
if (istable) {
JanetTable *t = janet_unwrap_table(x);
JanetTable *proto = t->proto;
janet_buffer_push_cstring(S->buffer, "@");
if (NULL != proto) {
Janet name = janet_table_get(proto, janet_ckeywordv("_name"));
const uint8_t *n;
@@ -590,8 +582,25 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
}
}
}
janet_buffer_push_cstring(S->buffer, "{");
} else {
JanetStruct st = janet_unwrap_struct(x);
JanetStruct proto = janet_struct_proto(st);
if (NULL != proto) {
Janet name = janet_struct_get(proto, janet_ckeywordv("_name"));
const uint8_t *n;
int32_t len;
if (janet_bytes_view(name, &n, &len)) {
if (S->flags & JANET_PRETTY_COLOR) {
janet_buffer_push_cstring(S->buffer, janet_class_color);
}
janet_buffer_push_bytes(S->buffer, n, len);
if (S->flags & JANET_PRETTY_COLOR) {
janet_buffer_push_cstring(S->buffer, "\x1B[0m");
}
}
}
}
janet_buffer_push_cstring(S->buffer, "{");
S->depth--;
S->indent += 2;

View File

@@ -79,7 +79,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);
janet_eprintf("parse error in %s: %s\n", sourcePath, e);
size_t line = parser.line;
size_t col = parser.column;
janet_eprintf("%s:%lu:%lu: parse error: %s\n", sourcePath, line, col, e);
done = 1;
break;
}

View File

@@ -54,14 +54,6 @@ typedef struct {
int is_error;
} JanetTimeout;
#ifdef JANET_THREADS
typedef struct {
JanetMailbox *original;
JanetMailbox *newbox;
uint64_t flags;
} JanetMailboxPair;
#endif
/* Registry table for C functions - containts metadata that can
* be looked up by cfunction pointer. All strings here are pointing to
* static memory not managed by Janet. */
@@ -145,13 +137,6 @@ struct JanetVM {
JanetTraversalNode *traversal_top;
JanetTraversalNode *traversal_base;
/* Threading */
#ifdef JANET_THREADS
JanetMailbox *mailbox;
JanetThread *thread_current;
JanetTable *thread_decode;
#endif
/* Event loop and scheduler globals */
#ifdef JANET_EV
size_t tq_count;
@@ -186,12 +171,6 @@ struct JanetVM {
extern JANET_THREAD_LOCAL JanetVM janet_vm;
/* Setup / teardown */
#ifdef JANET_THREADS
void janet_threads_init(void);
void janet_threads_deinit(void);
#endif
#ifdef JANET_NET
void janet_net_init(void);
void janet_net_deinit(void);

View File

@@ -218,7 +218,7 @@ JANET_CORE_FN(cfun_string_repeat,
JANET_CORE_FN(cfun_string_bytes,
"(string/bytes str)",
"Returns an array of integers that are the byte values of the string.") {
"Returns a tuple of integers that are the byte values of the string.") {
janet_fixarity(argc, 1);
JanetByteView view = janet_getbytes(argv, 0);
Janet *tup = janet_tuple_begin(view.len);

View File

@@ -246,15 +246,15 @@ static double convert(
}
/* Scan a real (double) from a string. If the string cannot be converted into
* and integer, set *err to 1 and return 0. */
int janet_scan_number(
* and integer, return 0. */
int janet_scan_number_base(
const uint8_t *str,
int32_t len,
int32_t base,
double *out) {
const uint8_t *end = str + len;
int seenadigit = 0;
int ex = 0;
int base = 10;
int seenpoint = 0;
int foundexp = 0;
int neg = 0;
@@ -278,21 +278,28 @@ int janet_scan_number(
}
/* Check for leading 0x or digit digit r */
if (str + 1 < end && str[0] == '0' && str[1] == 'x') {
base = 16;
str += 2;
} else if (str + 1 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] == 'r') {
base = str[0] - '0';
str += 2;
} else if (str + 2 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] >= '0' && str[1] <= '9' &&
str[2] == 'r') {
base = 10 * (str[0] - '0') + (str[1] - '0');
if (base < 2 || base > 36) goto error;
str += 3;
if (base == 0) {
if (str + 1 < end && str[0] == '0' && str[1] == 'x') {
base = 16;
str += 2;
} else if (str + 1 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] == 'r') {
base = str[0] - '0';
str += 2;
} else if (str + 2 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] >= '0' && str[1] <= '9' &&
str[2] == 'r') {
base = 10 * (str[0] - '0') + (str[1] - '0');
if (base < 2 || base > 36) goto error;
str += 3;
}
}
/* If still base is 0, set to default (10) */
if (base == 0) {
base = 10;
}
/* Skip leading zeros */
@@ -376,6 +383,13 @@ error:
return 1;
}
int janet_scan_number(
const uint8_t *str,
int32_t len,
double *out) {
return janet_scan_number_base(str, len, 0, out);
}
#ifdef JANET_INT_TYPES
static int scan_uint64(

View File

@@ -39,13 +39,14 @@ JanetKV *janet_struct_begin(int32_t count) {
head->length = count;
head->capacity = capacity;
head->hash = 0;
head->proto = NULL;
JanetKV *st = (JanetKV *)(head->data);
janet_memempty(st, capacity);
return st;
}
/* Find an item in a struct. Should be similar to janet_dict_find, but
/* Find an item in a struct without looking for prototypes. Should be similar to janet_dict_find, but
* specialized to structs (slightly more compact). */
const JanetKV *janet_struct_find(const JanetKV *st, Janet key) {
int32_t cap = janet_struct_capacity(st);
@@ -68,7 +69,7 @@ const JanetKV *janet_struct_find(const JanetKV *st, Janet key) {
* preforms an in-place insertion sort. This ensures the internal structure of the
* hash map is independent of insertion order.
*/
void janet_struct_put(JanetKV *st, Janet key, Janet value) {
void janet_struct_put_ext(JanetKV *st, Janet key, Janet value, int replace) {
int32_t cap = janet_struct_capacity(st);
int32_t hash = janet_hash(key);
int32_t index = janet_maphash(cap, hash);
@@ -123,13 +124,19 @@ void janet_struct_put(JanetKV *st, Janet key, Janet value) {
dist = otherdist;
hash = otherhash;
} else if (status == 0) {
/* A key was added to the struct more than once - replace old value */
kv->value = value;
if (replace) {
/* A key was added to the struct more than once - replace old value */
kv->value = value;
}
return;
}
}
}
void janet_struct_put(JanetKV *st, Janet key, Janet value) {
janet_struct_put_ext(st, key, value, 1);
}
/* Finish building a struct */
const JanetKV *janet_struct_end(JanetKV *st) {
if (janet_struct_hash(st) != janet_struct_length(st)) {
@@ -143,16 +150,43 @@ const JanetKV *janet_struct_end(JanetKV *st) {
janet_struct_put(newst, kv->key, kv->value);
}
}
janet_struct_proto(newst) = janet_struct_proto(st);
st = newst;
}
janet_struct_hash(st) = janet_kv_calchash(st, janet_struct_capacity(st));
if (janet_struct_proto(st)) {
janet_struct_hash(st) += 2654435761u * janet_struct_hash(janet_struct_proto(st));
}
return (const JanetKV *)st;
}
/* Get an item from a struct without looking into prototypes. */
Janet janet_struct_rawget(const JanetKV *st, Janet key) {
const JanetKV *kv = janet_struct_find(st, key);
return kv ? kv->value : janet_wrap_nil();
}
/* Get an item from a struct */
Janet janet_struct_get(const JanetKV *st, Janet key) {
const JanetKV *kv = janet_struct_find(st, key);
return kv ? kv->value : janet_wrap_nil();
for (int i = JANET_MAX_PROTO_DEPTH; st && i; --i, st = janet_struct_proto(st)) {
const JanetKV *kv = janet_struct_find(st, key);
if (NULL != kv && !janet_checktype(kv->key, JANET_NIL)) {
return kv->value;
}
}
return janet_wrap_nil();
}
/* Get an item from a struct, and record which prototype the item came from. */
Janet janet_struct_get_ex(const JanetKV *st, Janet key, JanetStruct *which) {
for (int i = JANET_MAX_PROTO_DEPTH; st && i; --i, st = janet_struct_proto(st)) {
const JanetKV *kv = janet_struct_find(st, key);
if (NULL != kv && !janet_checktype(kv->key, JANET_NIL)) {
*which = st;
return kv->value;
}
}
return janet_wrap_nil();
}
/* Convert struct to table */
@@ -167,3 +201,107 @@ JanetTable *janet_struct_to_table(const JanetKV *st) {
}
return table;
}
/* C Functions */
JANET_CORE_FN(cfun_struct_with_proto,
"(struct/with-proto proto & kvs)",
"Create a structure, as with the usual struct constructor but set the "
"struct prototype as well.") {
janet_arity(argc, 1, -1);
JanetStruct proto = janet_optstruct(argv, argc, 0, NULL);
if (!(argc & 1))
janet_panic("expected odd number of arguments");
JanetKV *st = janet_struct_begin(argc / 2);
for (int32_t i = 1; i < argc; i += 2) {
janet_struct_put(st, argv[i], argv[i + 1]);
}
janet_struct_proto(st) = proto;
return janet_wrap_struct(janet_struct_end(st));
}
JANET_CORE_FN(cfun_struct_getproto,
"(struct/getproto st)",
"Return the prototype of a struct, or nil if it doesn't have one.") {
janet_fixarity(argc, 1);
JanetStruct st = janet_getstruct(argv, 0);
return janet_struct_proto(st)
? janet_wrap_struct(janet_struct_proto(st))
: janet_wrap_nil();
}
JANET_CORE_FN(cfun_struct_flatten,
"(struct/proto-flatten st)",
"Convert a struct with prototypes to a struct with no prototypes by merging "
"all key value pairs from recursive prototypes into one new struct.") {
janet_fixarity(argc, 1);
JanetStruct st = janet_getstruct(argv, 0);
/* get an upper bounds on the number of items in the final struct */
int64_t pair_count = 0;
JanetStruct cursor = st;
while (cursor) {
pair_count += janet_struct_length(cursor);
cursor = janet_struct_proto(cursor);
}
if (pair_count > INT32_MAX) {
janet_panic("struct too large");
}
JanetKV *accum = janet_struct_begin((int32_t) pair_count);
cursor = st;
while (cursor) {
for (int32_t i = 0; i < janet_struct_capacity(cursor); i++) {
const JanetKV *kv = cursor + i;
if (!janet_checktype(kv->key, JANET_NIL)) {
janet_struct_put_ext(accum, kv->key, kv->value, 0);
}
}
cursor = janet_struct_proto(cursor);
}
return janet_wrap_struct(janet_struct_end(accum));
}
JANET_CORE_FN(cfun_struct_to_table,
"(struct/to-table st &opt recursive)",
"Convert a struct to a table. If recursive is true, also convert the "
"table's prototypes into the new struct's prototypes as well.") {
janet_arity(argc, 1, 2);
JanetStruct st = janet_getstruct(argv, 0);
int recursive = argc > 1 && janet_truthy(argv[1]);
JanetTable *tab = NULL;
JanetStruct cursor = st;
JanetTable *tab_cursor = tab;
do {
if (tab) {
tab_cursor->proto = janet_table(janet_struct_length(cursor));
tab_cursor = tab_cursor->proto;
} else {
tab = janet_table(janet_struct_length(cursor));
tab_cursor = tab;
}
/* TODO - implement as memcpy since struct memory should be compatible
* with table memory */
for (int32_t i = 0; i < janet_struct_capacity(cursor); i++) {
const JanetKV *kv = cursor + i;
if (!janet_checktype(kv->key, JANET_NIL)) {
janet_table_put(tab_cursor, kv->key, kv->value);
}
}
cursor = janet_struct_proto(cursor);
} while (recursive && cursor);
return janet_wrap_table(tab);
}
/* Load the struct module */
void janet_lib_struct(JanetTable *env) {
JanetRegExt struct_cfuns[] = {
JANET_CORE_REG("struct/with-proto", cfun_struct_with_proto),
JANET_CORE_REG("struct/getproto", cfun_struct_getproto),
JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten),
JANET_CORE_REG("struct/to-table", cfun_struct_to_table),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, struct_cfuns);
}

View File

@@ -132,37 +132,21 @@ static void janet_table_rehash(JanetTable *t, int32_t size) {
/* Get a value out of the table */
Janet janet_table_get(JanetTable *t, Janet key) {
JanetKV *bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
return bucket->value;
/* Check prototypes */
{
int i;
for (i = JANET_MAX_PROTO_DEPTH, t = t->proto; t && i; t = t->proto, --i) {
bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
return bucket->value;
}
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
JanetKV *bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
return bucket->value;
}
return janet_wrap_nil();
}
/* Get a value out of the table, and record which prototype it was from. */
Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which) {
JanetKV *bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) {
*which = t;
return bucket->value;
}
/* Check prototypes */
{
int i;
for (i = JANET_MAX_PROTO_DEPTH, t = t->proto; t && i; t = t->proto, --i) {
bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) {
*which = t;
return bucket->value;
}
for (int i = JANET_MAX_PROTO_DEPTH; t && i; t = t->proto, --i) {
JanetKV *bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) {
*which = t;
return bucket->value;
}
}
return janet_wrap_nil();
@@ -217,6 +201,23 @@ void janet_table_put(JanetTable *t, Janet key, Janet value) {
}
}
/* Used internally so don't check arguments
* Put into a table, but if the key already exists do nothing. */
static void janet_table_put_no_overwrite(JanetTable *t, Janet key, Janet value) {
JanetKV *bucket = janet_table_find(t, key);
if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL))
return;
if (NULL == bucket || 2 * (t->count + t->deleted + 1) > t->capacity) {
janet_table_rehash(t, janet_tablen(2 * t->count + 2));
}
bucket = janet_table_find(t, key);
if (janet_checktype(bucket->value, JANET_BOOLEAN))
--t->deleted;
bucket->key = key;
bucket->value = value;
++t->count;
}
/* Clear a table */
void janet_table_clear(JanetTable *t) {
int32_t capacity = t->capacity;
@@ -226,19 +227,6 @@ void janet_table_clear(JanetTable *t) {
t->deleted = 0;
}
/* Convert table to struct */
const JanetKV *janet_table_to_struct(JanetTable *t) {
JanetKV *st = janet_struct_begin(t->count);
JanetKV *kv = t->data;
JanetKV *end = t->data + t->capacity;
while (kv < end) {
if (!janet_checktype(kv->key, JANET_NIL))
janet_struct_put(st, kv->key, kv->value);
kv++;
}
return janet_struct_end(st);
}
/* Clone a table. */
JanetTable *janet_table_clone(JanetTable *table) {
JanetTable *newTable = janet_gcalloc(JANET_MEMORY_TABLE, sizeof(JanetTable));
@@ -275,6 +263,34 @@ void janet_table_merge_struct(JanetTable *table, const JanetKV *other) {
janet_table_mergekv(table, other, janet_struct_capacity(other));
}
/* Convert table to struct */
const JanetKV *janet_table_to_struct(JanetTable *t) {
JanetKV *st = janet_struct_begin(t->count);
JanetKV *kv = t->data;
JanetKV *end = t->data + t->capacity;
while (kv < end) {
if (!janet_checktype(kv->key, JANET_NIL))
janet_struct_put(st, kv->key, kv->value);
kv++;
}
return janet_struct_end(st);
}
JanetTable *janet_table_proto_flatten(JanetTable *t) {
JanetTable *newTable = janet_table(0);
while (t) {
JanetKV *kv = t->data;
JanetKV *end = t->data + t->capacity;
while (kv < end) {
if (!janet_checktype(kv->key, JANET_NIL))
janet_table_put_no_overwrite(newTable, kv->key, kv->value);
kv++;
}
t = t->proto;
}
return newTable;
}
/* C Functions */
JANET_CORE_FN(cfun_table_new,
@@ -349,6 +365,14 @@ JANET_CORE_FN(cfun_table_clear,
return janet_wrap_table(table);
}
JANET_CORE_FN(cfun_table_proto_flatten,
"(table/proto-flatten tab)",
"Create a new table that is the result of merging all prototypes into a new table.") {
janet_fixarity(argc, 1);
JanetTable *table = janet_gettable(argv, 0);
return janet_wrap_table(janet_table_proto_flatten(table));
}
/* Load the table module */
void janet_lib_table(JanetTable *env) {
JanetRegExt table_cfuns[] = {
@@ -359,6 +383,7 @@ void janet_lib_table(JanetTable *env) {
JANET_CORE_REG("table/rawget", cfun_table_rawget),
JANET_CORE_REG("table/clone", cfun_table_clone),
JANET_CORE_REG("table/clear", cfun_table_clear),
JANET_CORE_REG("table/proto-flatten", cfun_table_proto_flatten),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, table_cfuns);

View File

@@ -1,739 +0,0 @@
/*
* Copyright (c) 2021 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
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
#include "gc.h"
#include "util.h"
#include "state.h"
#endif
#ifdef JANET_THREADS
#include <math.h>
#ifdef JANET_WINDOWS
#include <windows.h>
#else
#include <setjmp.h>
#include <time.h>
#include <pthread.h>
#endif
/* typedefed in janet.h */
struct JanetMailbox {
/* Synchronization */
#ifdef JANET_WINDOWS
CRITICAL_SECTION lock;
CONDITION_VARIABLE cond;
#else
pthread_mutex_t lock;
pthread_cond_t cond;
#endif
/* Memory management - reference counting */
int refCount;
int closed;
/* Store messages */
uint16_t messageCapacity;
uint16_t messageCount;
uint16_t messageFirst;
uint16_t messageNext;
/* Buffers to store messages. These buffers are manually allocated, so
* are not owned by any thread's GC. */
JanetBuffer messages[];
};
#define JANET_THREAD_HEAVYWEIGHT 0x1
#define JANET_THREAD_ABSTRACTS 0x2
#define JANET_THREAD_CFUNCTIONS 0x4
static const char janet_thread_flags[] = "hac";
static JanetTable *janet_thread_get_decode(void) {
if (janet_vm.thread_decode == NULL) {
janet_vm.thread_decode = janet_get_core_table("load-image-dict");
if (NULL == janet_vm.thread_decode) {
janet_vm.thread_decode = janet_table(0);
}
janet_gcroot(janet_wrap_table(janet_vm.thread_decode));
}
return janet_vm.thread_decode;
}
static JanetMailbox *janet_mailbox_create(int refCount, uint16_t capacity) {
JanetMailbox *mailbox = janet_malloc(sizeof(JanetMailbox) + sizeof(JanetBuffer) * (size_t) capacity);
if (NULL == mailbox) {
JANET_OUT_OF_MEMORY;
}
#ifdef JANET_WINDOWS
InitializeCriticalSection(&mailbox->lock);
InitializeConditionVariable(&mailbox->cond);
#else
pthread_mutex_init(&mailbox->lock, NULL);
pthread_cond_init(&mailbox->cond, NULL);
#endif
mailbox->refCount = refCount;
mailbox->closed = 0;
mailbox->messageCount = 0;
mailbox->messageCapacity = capacity;
mailbox->messageFirst = 0;
mailbox->messageNext = 0;
for (uint16_t i = 0; i < capacity; i++) {
janet_buffer_init(mailbox->messages + i, 0);
}
return mailbox;
}
static void janet_mailbox_destroy(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
DeleteCriticalSection(&mailbox->lock);
#else
pthread_mutex_destroy(&mailbox->lock);
pthread_cond_destroy(&mailbox->cond);
#endif
for (uint16_t i = 0; i < mailbox->messageCapacity; i++) {
janet_buffer_deinit(mailbox->messages + i);
}
janet_free(mailbox);
}
static void janet_mailbox_lock(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
EnterCriticalSection(&mailbox->lock);
#else
pthread_mutex_lock(&mailbox->lock);
#endif
}
static void janet_mailbox_unlock(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
LeaveCriticalSection(&mailbox->lock);
#else
pthread_mutex_unlock(&mailbox->lock);
#endif
}
/* Assumes you have the mailbox lock already */
static void janet_mailbox_ref_with_lock(JanetMailbox *mailbox, int delta) {
mailbox->refCount += delta;
if (mailbox->refCount <= 0) {
janet_mailbox_unlock(mailbox);
janet_mailbox_destroy(mailbox);
} else {
janet_mailbox_unlock(mailbox);
}
}
static void janet_mailbox_ref(JanetMailbox *mailbox, int delta) {
janet_mailbox_lock(mailbox);
janet_mailbox_ref_with_lock(mailbox, delta);
}
static void janet_close_thread(JanetThread *thread) {
if (thread->mailbox) {
janet_mailbox_ref(thread->mailbox, -1);
thread->mailbox = NULL;
}
}
static int thread_gc(void *p, size_t size) {
(void) size;
JanetThread *thread = (JanetThread *)p;
janet_close_thread(thread);
return 0;
}
static int thread_mark(void *p, size_t size) {
(void) size;
JanetThread *thread = (JanetThread *)p;
if (thread->encode) {
janet_mark(janet_wrap_table(thread->encode));
}
return 0;
}
static JanetMailboxPair *make_mailbox_pair(JanetMailbox *original, uint64_t flags) {
JanetMailboxPair *pair = janet_malloc(sizeof(JanetMailboxPair));
if (NULL == pair) {
JANET_OUT_OF_MEMORY;
}
pair->original = original;
janet_mailbox_ref(original, 1);
pair->newbox = janet_mailbox_create(1, 16);
pair->flags = flags;
return pair;
}
static void destroy_mailbox_pair(JanetMailboxPair *pair) {
janet_mailbox_ref(pair->original, -1);
janet_mailbox_ref(pair->newbox, -1);
janet_free(pair);
}
/* Abstract waiting for timeout across windows/posix */
typedef struct {
int timedwait;
int nowait;
#ifdef JANET_WINDOWS
DWORD interval;
DWORD ticksLeft;
#else
struct timespec ts;
#endif
} JanetWaiter;
static void janet_waiter_init(JanetWaiter *waiter, double sec) {
waiter->timedwait = 0;
waiter->nowait = 0;
if (sec <= 0.0 || isnan(sec)) {
waiter->nowait = 1;
return;
}
waiter->timedwait = sec > 0.0 && !isinf(sec);
/* Set maximum wait time to 30 days */
if (sec > (60.0 * 60.0 * 24.0 * 30.0)) {
sec = 60.0 * 60.0 * 24.0 * 30.0;
}
#ifdef JANET_WINDOWS
if (waiter->timedwait) {
waiter->ticksLeft = waiter->interval = (DWORD) floor(1000.0 * sec);
}
#else
if (waiter->timedwait) {
/* N seconds -> timespec of (now + sec) */
struct timespec now;
janet_gettime(&now);
time_t tvsec = (time_t) floor(sec);
long tvnsec = (long) floor(1000000000.0 * (sec - ((double) tvsec)));
tvsec += now.tv_sec;
tvnsec += now.tv_nsec;
if (tvnsec >= 1000000000L) {
tvnsec -= 1000000000L;
tvsec += 1;
}
waiter->ts.tv_sec = tvsec;
waiter->ts.tv_nsec = tvnsec;
}
#endif
}
static int janet_waiter_wait(JanetWaiter *wait, JanetMailbox *mailbox) {
if (wait->nowait) return 1;
#ifdef JANET_WINDOWS
if (wait->timedwait) {
if (wait->ticksLeft == 0) return 1;
DWORD startTime = GetTickCount();
int status = !SleepConditionVariableCS(&mailbox->cond, &mailbox->lock, wait->ticksLeft);
DWORD dTick = GetTickCount() - startTime;
/* Be careful about underflow */
wait->ticksLeft = dTick > wait->ticksLeft ? 0 : dTick;
return status;
} else {
SleepConditionVariableCS(&mailbox->cond, &mailbox->lock, INFINITE);
return 0;
}
#else
if (wait->timedwait) {
return pthread_cond_timedwait(&mailbox->cond, &mailbox->lock, &wait->ts);
} else {
pthread_cond_wait(&mailbox->cond, &mailbox->lock);
return 0;
}
#endif
}
static void janet_mailbox_wakeup(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
WakeConditionVariable(&mailbox->cond);
#else
pthread_cond_signal(&mailbox->cond);
#endif
}
static int mailbox_at_capacity(JanetMailbox *mailbox) {
return mailbox->messageCount >= mailbox->messageCapacity;
}
/* Returns 1 if could not send (encode error or timeout), 2 for mailbox closed, and
* 0 otherwise. Will not panic. */
int janet_thread_send(JanetThread *thread, Janet msg, double timeout) {
/* Ensure mailbox is not closed. */
JanetMailbox *mailbox = thread->mailbox;
if (NULL == mailbox) return 2;
janet_mailbox_lock(mailbox);
if (mailbox->closed) {
janet_mailbox_ref_with_lock(mailbox, -1);
thread->mailbox = NULL;
return 2;
}
/* Back pressure */
if (mailbox_at_capacity(mailbox)) {
JanetWaiter wait;
janet_waiter_init(&wait, timeout);
if (wait.nowait) {
janet_mailbox_unlock(mailbox);
return 1;
}
/* Retry loop, as there can be multiple writers */
while (mailbox_at_capacity(mailbox)) {
if (janet_waiter_wait(&wait, mailbox)) {
janet_mailbox_unlock(mailbox);
janet_mailbox_wakeup(mailbox);
return 1;
}
}
}
/* Hack to capture all panics from marshalling. This works because
* we know janet_marshal won't mess with other essential global state. */
jmp_buf buf;
jmp_buf *old_buf = janet_vm.signal_buf;
janet_vm.signal_buf = &buf;
int32_t oldmcount = mailbox->messageCount;
int ret = 0;
if (setjmp(buf)) {
ret = 1;
mailbox->messageCount = oldmcount;
} else {
JanetBuffer *msgbuf = mailbox->messages + mailbox->messageNext;
msgbuf->count = 0;
/* Start panic zone */
janet_marshal(msgbuf, msg, thread->encode, JANET_MARSHAL_UNSAFE);
/* End panic zone */
mailbox->messageNext = (mailbox->messageNext + 1) % mailbox->messageCapacity;
mailbox->messageCount++;
}
/* Cleanup */
janet_vm.signal_buf = old_buf;
janet_mailbox_unlock(mailbox);
/* Potentially wake up a blocked thread */
janet_mailbox_wakeup(mailbox);
return ret;
}
/* Returns 0 on successful message. Returns 1 if timedout */
int janet_thread_receive(Janet *msg_out, double timeout) {
JanetMailbox *mailbox = janet_vm.mailbox;
janet_mailbox_lock(mailbox);
/* For timeouts */
JanetWaiter wait;
janet_waiter_init(&wait, timeout);
for (;;) {
/* Check for messages waiting for us */
if (mailbox->messageCount > 0) {
/* Hack to capture all panics from marshalling. This works because
* we know janet_marshal won't mess with other essential global state. */
jmp_buf buf;
jmp_buf *old_buf = janet_vm.signal_buf;
janet_vm.signal_buf = &buf;
/* Handle errors */
if (setjmp(buf)) {
/* Cleanup jmp_buf, return error.
* Do not ignore bad messages as before. */
janet_vm.signal_buf = old_buf;
*msg_out = *janet_vm.return_reg;
janet_mailbox_unlock(mailbox);
return 2;
} else {
JanetBuffer *msgbuf = mailbox->messages + mailbox->messageFirst;
mailbox->messageCount--;
mailbox->messageFirst = (mailbox->messageFirst + 1) % mailbox->messageCapacity;
/* Read from beginning of channel */
const uint8_t *nextItem = NULL;
Janet item = janet_unmarshal(
msgbuf->data, msgbuf->count,
JANET_MARSHAL_UNSAFE, janet_thread_get_decode(), &nextItem);
*msg_out = item;
/* Cleanup */
janet_vm.signal_buf = old_buf;
janet_mailbox_unlock(mailbox);
/* Potentially wake up pending threads */
janet_mailbox_wakeup(mailbox);
return 0;
}
}
if (wait.nowait) {
janet_mailbox_unlock(mailbox);
return 1;
}
/* Wait for next message */
if (janet_waiter_wait(&wait, mailbox)) {
janet_mailbox_unlock(mailbox);
return 1;
}
}
}
static int janet_thread_getter(void *p, Janet key, Janet *out);
static Janet janet_thread_next(void *p, Janet key);
const JanetAbstractType janet_thread_type = {
"core/thread",
thread_gc,
thread_mark,
janet_thread_getter,
NULL, /* put */
NULL, /* marshal */
NULL, /* unmarshal */
NULL, /* tostring */
NULL, /* compare */
NULL, /* hash */
janet_thread_next,
JANET_ATEND_NEXT
};
static JanetThread *janet_make_thread(JanetMailbox *mailbox, JanetTable *encode) {
JanetThread *thread = janet_abstract(&janet_thread_type, sizeof(JanetThread));
janet_mailbox_ref(mailbox, 1);
thread->mailbox = mailbox;
thread->encode = encode;
return thread;
}
JanetThread *janet_getthread(const Janet *argv, int32_t n) {
return (JanetThread *) janet_getabstract(argv, n, &janet_thread_type);
}
/* Runs in new thread */
static int thread_worker(JanetMailboxPair *pair) {
JanetFiber *fiber = NULL;
Janet out;
/* Init VM */
janet_init();
/* Use the mailbox we were given */
janet_vm.mailbox = pair->newbox;
janet_mailbox_ref(pair->newbox, 1);
/* Get dictionaries for default encode/decode */
JanetTable *encode;
if (pair->flags & JANET_THREAD_HEAVYWEIGHT) {
encode = janet_get_core_table("make-image-dict");
} else {
encode = NULL;
janet_vm.thread_decode = janet_table(0);
janet_gcroot(janet_wrap_table(janet_vm.thread_decode));
}
/* Create parent thread */
JanetThread *parent = janet_make_thread(pair->original, encode);
Janet parentv = janet_wrap_abstract(parent);
/* Unmarshal the abstract registry */
if (pair->flags & JANET_THREAD_ABSTRACTS) {
Janet reg;
int status = janet_thread_receive(&reg, INFINITY);
if (status) goto error;
if (!janet_checktype(reg, JANET_TABLE)) goto error;
janet_gcunroot(janet_wrap_table(janet_vm.abstract_registry));
janet_vm.abstract_registry = janet_unwrap_table(reg);
janet_gcroot(janet_wrap_table(janet_vm.abstract_registry));
}
/* Unmarshal the function */
Janet funcv;
int status = janet_thread_receive(&funcv, INFINITY);
if (status) goto error;
if (!janet_checktype(funcv, JANET_FUNCTION)) goto error;
JanetFunction *func = janet_unwrap_function(funcv);
/* Arity check */
if (func->def->min_arity > 1 || func->def->max_arity < 1) {
goto error;
}
/* Call function */
Janet argv[1] = { parentv };
fiber = janet_fiber(func, 64, 1, argv);
if (pair->flags & JANET_THREAD_HEAVYWEIGHT) {
fiber->env = janet_table(0);
fiber->env->proto = janet_core_env(NULL);
}
JanetSignal sig = janet_continue(fiber, janet_wrap_nil(), &out);
if (sig != JANET_SIGNAL_OK && sig < JANET_SIGNAL_USER0) {
janet_eprintf("in thread %v: ", janet_wrap_abstract(janet_make_thread(pair->newbox, encode)));
janet_stacktrace(fiber, out);
}
#ifdef JANET_EV
janet_loop();
#endif
/* Normal exit */
destroy_mailbox_pair(pair);
janet_deinit();
return 0;
/* Fail to set something up */
error:
destroy_mailbox_pair(pair);
janet_eprintf("\nthread failed to start\n");
janet_deinit();
return 1;
}
#ifdef JANET_WINDOWS
static DWORD WINAPI janet_create_thread_wrapper(LPVOID param) {
thread_worker((JanetMailboxPair *)param);
return 0;
}
static int janet_thread_start_child(JanetMailboxPair *pair) {
HANDLE handle = CreateThread(NULL, 0, janet_create_thread_wrapper, pair, 0, NULL);
int ret = NULL == handle;
/* Does not kill thread, simply detatches */
if (!ret) CloseHandle(handle);
return ret;
}
#else
static void *janet_pthread_wrapper(void *param) {
thread_worker((JanetMailboxPair *)param);
return NULL;
}
static int janet_thread_start_child(JanetMailboxPair *pair) {
pthread_t handle;
int error = pthread_create(&handle, NULL, janet_pthread_wrapper, pair);
if (error) {
return 1;
} else {
pthread_detach(handle);
return 0;
}
}
#endif
/*
* Setup/Teardown
*/
void janet_threads_init(void) {
janet_vm.mailbox = janet_mailbox_create(1, 10);
janet_vm.thread_decode = NULL;
janet_vm.thread_current = NULL;
}
void janet_threads_deinit(void) {
janet_mailbox_lock(janet_vm.mailbox);
janet_vm.mailbox->closed = 1;
janet_mailbox_ref_with_lock(janet_vm.mailbox, -1);
janet_vm.mailbox = NULL;
janet_vm.thread_current = NULL;
janet_vm.thread_decode = NULL;
}
JanetThread *janet_thread_current(void) {
if (NULL == janet_vm.thread_current) {
janet_vm.thread_current = janet_make_thread(janet_vm.mailbox, janet_get_core_table("make-image-dict"));
janet_gcroot(janet_wrap_abstract(janet_vm.thread_current));
}
return janet_vm.thread_current;
}
/*
* Cfuns
*/
JANET_CORE_FN(cfun_thread_current,
"(thread/current)",
"Get the current running thread.") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_abstract(janet_thread_current());
}
JANET_CORE_FN(cfun_thread_new,
"(thread/new func &opt capacity flags)",
"Start a new thread that will start immediately. "
"If capacity is provided, that is how many messages can be stored in the thread's mailbox before blocking senders. "
"The capacity must be between 1 and 65535 inclusive, and defaults to 10. "
"Can optionally provide flags to the new thread - supported flags are:\n\n"
"* `:h` - Start a heavyweight thread. This loads the core environment by default, so may use more memory initially. Messages may compress better, though.\n"
"* `:a` - Allow sending over registered abstract types to the new thread\n"
"* `:c` - Send over cfunction information to the new thread (no longer supported).\n"
"Returns a handle to the new thread.") {
janet_arity(argc, 1, 3);
/* Just type checking */
janet_getfunction(argv, 0);
int32_t cap = janet_optinteger(argv, argc, 1, 10);
if (cap < 1 || cap > UINT16_MAX) {
janet_panicf("bad slot #1, expected integer in range [1, 65535], got %d", cap);
}
uint64_t flags = argc >= 3 ? janet_getflags(argv, 2, janet_thread_flags) : JANET_THREAD_ABSTRACTS;
JanetTable *encode;
if (flags & JANET_THREAD_HEAVYWEIGHT) {
encode = janet_get_core_table("make-image-dict");
} else {
encode = NULL;
}
JanetMailboxPair *pair = make_mailbox_pair(janet_vm.mailbox, flags);
JanetThread *thread = janet_make_thread(pair->newbox, encode);
if (janet_thread_start_child(pair)) {
destroy_mailbox_pair(pair);
janet_panic("could not start thread");
}
if (flags & JANET_THREAD_ABSTRACTS) {
if (janet_thread_send(thread, janet_wrap_table(janet_vm.abstract_registry), INFINITY)) {
janet_panic("could not send abstract registry to thread");
}
}
/* If thread started, send the worker function. */
if (janet_thread_send(thread, argv[0], INFINITY)) {
janet_panicf("could not send worker function %v to thread", argv[0]);
}
return janet_wrap_abstract(thread);
}
JANET_CORE_FN(cfun_thread_send,
"(thread/send thread msgi &opt timeout)",
"Send a message to the thread. By default, the timeout is 1 second, but an optional timeout "
"in seconds can be provided. Use math/inf for no timeout. "
"Will throw an error if there is a problem sending the message.") {
janet_arity(argc, 2, 3);
JanetThread *thread = janet_getthread(argv, 0);
int status = janet_thread_send(thread, argv[1], janet_optnumber(argv, argc, 2, 1.0));
switch (status) {
default:
break;
case 1:
janet_panicf("failed to send message %v", argv[1]);
case 2:
janet_panic("thread mailbox is closed");
}
return argv[0];
}
JANET_CORE_FN(cfun_thread_receive,
"(thread/receive &opt timeout)",
"Get a message sent to this thread. If timeout (in seconds) is provided, an error "
"will be thrown after the timeout has elapsed but "
"no messages are received. The default timeout is 1 second, and math/inf cam be passed to "
"turn off the timeout.") {
janet_arity(argc, 0, 1);
double wait = janet_optnumber(argv, argc, 0, 1.0);
Janet out;
int status = janet_thread_receive(&out, wait);
switch (status) {
default:
break;
case 1:
janet_panicf("timeout after %f seconds", wait);
case 2:
janet_panicf("failed to receive message: %v", out);
}
return out;
}
JANET_CORE_FN(cfun_thread_close,
"(thread/close thread)",
"Close a thread, unblocking it and ending communication with it. Note that closing "
"a thread is idempotent and does not cancel the thread's operation. Returns nil.") {
janet_fixarity(argc, 1);
JanetThread *thread = janet_getthread(argv, 0);
janet_close_thread(thread);
return janet_wrap_nil();
}
JANET_CORE_FN(cfun_thread_exit,
"(thread/exit &opt code)",
"Exit from the current thread. If no more threads are running, ends the process, but otherwise does "
"not end the current process.") {
(void) argv;
janet_arity(argc, 0, 1);
#if defined(JANET_WINDOWS)
int32_t flag = janet_optinteger(argv, argc, 0, 0);
ExitThread(flag);
#else
pthread_exit(NULL);
#endif
return janet_wrap_nil();
}
static const JanetMethod janet_thread_methods[] = {
{"send", cfun_thread_send},
{"close", cfun_thread_close},
{NULL, NULL}
};
static int janet_thread_getter(void *p, Janet key, Janet *out) {
(void) p;
if (!janet_checktype(key, JANET_KEYWORD)) return 0;
return janet_getmethod(janet_unwrap_keyword(key), janet_thread_methods, out);
}
static Janet janet_thread_next(void *p, Janet key) {
(void) p;
return janet_nextmethod(janet_thread_methods, key);
}
/* Module entry point */
void janet_lib_thread(JanetTable *env) {
JanetRegExt threadlib_cfuns[] = {
JANET_CORE_REG("thread/current", cfun_thread_current),
JANET_CORE_REG("thread/new", cfun_thread_new),
JANET_CORE_REG("thread/send", cfun_thread_send),
JANET_CORE_REG("thread/receive", cfun_thread_receive),
JANET_CORE_REG("thread/close", cfun_thread_close),
JANET_CORE_REG("thread/exit", cfun_thread_exit),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, threadlib_cfuns);
janet_register_abstract_type(&janet_thread_type);
}
#endif

View File

@@ -57,6 +57,8 @@
/* Utils */
#define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1))
int janet_valid_utf8(const uint8_t *str, int32_t len);
int janet_is_symbol_char(uint8_t c);
extern const char janet_base64[65];
int32_t janet_array_calchash(const Janet *array, int32_t len);
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len);
@@ -126,6 +128,7 @@ void janet_lib_array(JanetTable *env);
void janet_lib_tuple(JanetTable *env);
void janet_lib_buffer(JanetTable *env);
void janet_lib_table(JanetTable *env);
void janet_lib_struct(JanetTable *env);
void janet_lib_fiber(JanetTable *env);
void janet_lib_os(JanetTable *env);
void janet_lib_string(JanetTable *env);
@@ -145,9 +148,6 @@ void janet_lib_typed_array(JanetTable *env);
#ifdef JANET_INT_TYPES
void janet_lib_inttypes(JanetTable *env);
#endif
#ifdef JANET_THREADS
void janet_lib_thread(JanetTable *env);
#endif
#ifdef JANET_NET
void janet_lib_net(JanetTable *env);
extern const JanetAbstractType janet_address_type;

View File

@@ -37,8 +37,9 @@ static void push_traversal_node(void *lhs, void *rhs, int32_t index2) {
node.other = (JanetGCObject *) rhs;
node.index = 0;
node.index2 = index2;
if (janet_vm.traversal + 1 >= janet_vm.traversal_top) {
size_t oldsize = janet_vm.traversal - janet_vm.traversal_base;
int is_new = janet_vm.traversal_base == NULL;
if (is_new || (janet_vm.traversal + 1 >= janet_vm.traversal_top)) {
size_t oldsize = is_new ? 0 : (janet_vm.traversal - janet_vm.traversal_base);
size_t newsize = 2 * oldsize + 1;
if (newsize < 128) {
newsize = 128;
@@ -100,6 +101,17 @@ static int traversal_next(Janet *x, Janet *y) {
janet_vm.traversal = t;
return 0;
}
/* Traverse prototype */
JanetStruct sproto = sself->proto;
JanetStruct oproto = sother->proto;
if (sproto && !oproto) return 3;
if (!sproto && oproto) return 1;
if (oproto && sproto) {
*x = janet_wrap_struct(sproto);
*y = janet_wrap_struct(oproto);
janet_vm.traversal = t - 1;
return 0;
}
}
t--;
}
@@ -272,6 +284,8 @@ int janet_equals(Janet x, Janet y) {
if (s1 == s2) break;
if (janet_struct_hash(s1) != janet_struct_hash(s2)) return 0;
if (janet_struct_length(s1) != janet_struct_length(s2)) return 0;
if (janet_struct_proto(s1) && !janet_struct_proto(s2)) return 0;
if (!janet_struct_proto(s1) && janet_struct_proto(s2)) return 0;
push_traversal_node(janet_struct_head(s1), janet_struct_head(s2), 0);
break;
}

View File

@@ -1493,7 +1493,9 @@ JanetSignal janet_pcall(
Janet janet_mcall(const char *name, int32_t argc, Janet *argv) {
/* At least 1 argument */
if (argc < 1) janet_panicf("method :%s expected at least 1 argument");
if (argc < 1) {
janet_panicf("method :%s expected at least 1 argument", name);
}
/* Find method */
Janet method = janet_method_lookup(argv[0], name);
if (janet_checktype(method, JANET_NIL)) {
@@ -1557,9 +1559,6 @@ int janet_init(void) {
janet_vm.root_fiber = NULL;
janet_vm.stackn = 0;
#ifdef JANET_THREADS
janet_threads_init();
#endif
#ifdef JANET_EV
janet_ev_init();
#endif
@@ -1586,9 +1585,6 @@ void janet_deinit(void) {
janet_vm.root_fiber = NULL;
janet_free(janet_vm.registry);
janet_vm.registry = NULL;
#ifdef JANET_THREADS
janet_threads_deinit();
#endif
#ifdef JANET_EV
janet_ev_deinit();
#endif

View File

@@ -144,11 +144,6 @@ extern "C" {
#define JANET_NO_UTC_MKTIME
#endif
/* Check thread library */
#ifndef JANET_NO_THREADS
#define JANET_THREADS
#endif
/* Define how global janet state is declared */
/* Also enable the thread library only if not single-threaded */
#ifdef JANET_SINGLE_THREADED
@@ -330,11 +325,16 @@ typedef struct {
/* Some extra includes if EV is enabled */
#ifdef JANET_EV
#ifdef JANET_WINDOWS
#ifdef JANET_NET
#include <winsock2.h>
#endif
#include <windows.h>
typedef CRITICAL_SECTION JanetOSMutex;
typedef struct JanetDudCriticalSection {
/* Avoid including windows.h here - instead, create a structure of the same size */
/* Needs to be same size as crtical section see WinNT.h for CRITCIAL_SECTION definition */
void *debug_info;
long lock_count;
long recursion_count;
void *owning_thread;
void *lock_semaphore;
unsigned long spin_count;
} JanetOSMutex;
#else
#include <pthread.h>
typedef pthread_mutex_t JanetOSMutex;
@@ -866,7 +866,7 @@ struct JanetGCObject {
union {
JanetGCObject *next;
int32_t refcount; /* For threaded abstract types */
};
} data;
};
/* A lightweight green thread in janet. Does not correspond to
@@ -961,6 +961,7 @@ struct JanetStructHead {
int32_t length;
int32_t hash;
int32_t capacity;
const JanetKV *proto;
const JanetKV data[];
};
@@ -1522,6 +1523,7 @@ JANET_API int janet_loop_fiber(JanetFiber *fiber);
/* Number scanning */
JANET_API int janet_scan_number(const uint8_t *str, int32_t len, double *out);
JANET_API int janet_scan_number_base(const uint8_t *str, int32_t len, int32_t base, double *out);
JANET_API int janet_scan_int64(const uint8_t *str, int32_t len, int64_t *out);
JANET_API int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out);
@@ -1538,6 +1540,7 @@ JANET_API JanetRNG *janet_default_rng(void);
JANET_API void janet_rng_seed(JanetRNG *rng, uint32_t seed);
JANET_API void janet_rng_longseed(JanetRNG *rng, const uint8_t *bytes, int32_t len);
JANET_API uint32_t janet_rng_u32(JanetRNG *rng);
JANET_API double janet_rng_double(JanetRNG *rng);
/* Array functions */
JANET_API JanetArray *janet_array(int32_t capacity);
@@ -1618,10 +1621,13 @@ JANET_API JanetSymbol janet_symbol_gen(void);
#define janet_struct_length(t) (janet_struct_head(t)->length)
#define janet_struct_capacity(t) (janet_struct_head(t)->capacity)
#define janet_struct_hash(t) (janet_struct_head(t)->hash)
#define janet_struct_proto(t) (janet_struct_head(t)->proto)
JANET_API JanetKV *janet_struct_begin(int32_t count);
JANET_API void janet_struct_put(JanetKV *st, Janet key, Janet value);
JANET_API JanetStruct janet_struct_end(JanetKV *st);
JANET_API Janet janet_struct_get(JanetStruct st, Janet key);
JANET_API Janet janet_struct_rawget(JanetStruct st, Janet key);
JANET_API Janet janet_struct_get_ex(JanetStruct st, Janet key, JanetStruct *which);
JANET_API JanetTable *janet_struct_to_table(JanetStruct st);
JANET_API const JanetKV *janet_struct_find(JanetStruct st, Janet key);
@@ -2035,7 +2041,8 @@ typedef enum {
RULE_READINT, /* [(signedness << 4) | (endianess << 5) | bytewidth, tag] */
RULE_LINE, /* [tag] */
RULE_COLUMN, /* [tag] */
RULE_UNREF /* [rule, tag] */
RULE_UNREF, /* [rule, tag] */
RULE_CAPTURE_NUM /* [rule, tag] */
} JanetPegOpcod;
typedef struct {

View File

@@ -136,7 +136,6 @@ static JANET_THREAD_LOCAL int gbl_cols = 80;
static JANET_THREAD_LOCAL char *gbl_history[JANET_HISTORY_MAX];
static JANET_THREAD_LOCAL int gbl_history_count = 0;
static JANET_THREAD_LOCAL int gbl_historyi = 0;
static JANET_THREAD_LOCAL int gbl_sigint_flag = 0;
static JANET_THREAD_LOCAL struct termios gbl_termios_start;
static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
static JANET_THREAD_LOCAL int gbl_match_count = 0;
@@ -758,9 +757,9 @@ static int line() {
kleft();
break;
case 3: /* ctrl-c */
clearlines();
gbl_sigint_flag = 1;
return -1;
norawmode();
kill(getpid(), SIGINT);
/* fallthrough */
case 17: /* ctrl-q */
gbl_cancel_current_repl_form = 1;
clearlines();
@@ -962,11 +961,7 @@ void janet_line_get(const char *p, JanetBuffer *buffer) {
}
if (line()) {
norawmode();
if (gbl_sigint_flag) {
raise(SIGINT);
} else {
fputc('\n', out);
}
fputc('\n', out);
return;
}
fflush(stdin);

View File

@@ -5,6 +5,8 @@
(var suite-num 0)
(var start-time 0)
(def is-verbose (os/getenv "VERBOSE"))
(defn assert
"Override's the default assert with some nice error handling."
[x &opt e]
@@ -12,11 +14,9 @@
(++ num-tests-run)
(when x (++ num-tests-passed))
(def str (string e))
(def truncated
(if (> (length e) 40) (string (string/slice e 0 35) "...") (describe e)))
(if x
(eprintf "\e[32m✔\e[0m %s: %v" truncated x)
(eprintf "\n\e[31m✘\e[0m %s: %v" truncated x))
(when is-verbose (eprintf "\e[32m✔\e[0m %s: %v" (describe e) x))
(eprintf "\n\e[31m✘\e[0m %s: %v" (describe e) x))
x)
(defmacro assert-error
@@ -32,10 +32,10 @@
(defn start-suite [x]
(set suite-num x)
(set start-time (os/clock))
(eprint "\nRunning test suite " x " tests...\n "))
(eprint "Starting suite " x "..."))
(defn end-suite []
(def delta (- (os/clock) start-time))
(eprintf "\n\nTest suite %d finished in %.3f seconds" suite-num delta)
(eprint num-tests-passed " of " num-tests-run " tests passed.\n")
(eprinf "Finished suite %d in %.3f seconds - " suite-num delta)
(eprint num-tests-passed " of " num-tests-run " tests passed.")
(if (not= num-tests-passed num-tests-run) (os/exit 1)))

View File

@@ -202,6 +202,7 @@
#🐙🐙🐙🐙
(defn foo [Θa Θb Θc] 0)
(def 🦊 :fox)
(def 🐮 :cow)
(assert (= (string "🐼" 🦊 🐮) "🐼foxcow") "emojis 🙉 :)")

View File

@@ -308,8 +308,9 @@
(assert (deep= (range 4) a) "eachk 1")
(tracev (def my-unique-var-name true))
(assert my-unique-var-name "tracev upscopes")
(with-dyns [:err @""]
(tracev (def my-unique-var-name true))
(assert my-unique-var-name "tracev upscopes"))
(assert (pos? (length (gensym))) "gensym not empty, regression #753")

View File

@@ -344,4 +344,12 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (deep= @[] (peg/match '(* "test" (any 1)) @"test")) "peg empty pattern 5")
(assert (deep= @[] (peg/match '(* "test" (any 1)) (buffer "test"))) "peg empty pattern 6")
# number pattern
(assert (deep= @[111] (peg/match '(number :d+) "111")) "simple number capture 1")
(assert (deep= @[255] (peg/match '(number :w+) "0xff")) "simple number capture 2")
# quoted match test
(assert (= :yes (match 'john 'john :yes _ :nope)) "quoted literal match 1")
(assert (= :nope (match 'john ''john :yes _ :nope)) "quoted literal match 2")
(end-suite)

View File

@@ -151,6 +151,38 @@
(:close s))
(defn check-matching-names [stream]
(def ln (net/localname stream))
(def pn (net/peername stream))
(def [my-ip my-port] ln)
(def [remote-ip remote-port] pn)
(def msg (string my-ip " " my-port " " remote-ip " " remote-port))
(def buf @"")
(ev/gather
(net/write stream msg)
(net/read stream 1024 buf))
(def comparison (string/split " " buf))
(assert (and (= my-ip (get comparison 2))
(= (string my-port) (get comparison 3))
(= remote-ip (get comparison 0))
(= (string remote-port) (get comparison 1)))
(string/format "localname should match peername: msg=%j, buf=%j" msg buf)))
# Test on both server and client
(defn names-handler
[stream]
(defer (:close stream)
(check-matching-names stream)))
# Test localname and peername
(repeat 20
(with [s (net/server "127.0.0.1" "8000" names-handler)]
(defn test-names []
(with [conn (net/connect "127.0.0.1" "8000")]
(check-matching-names conn)))
(repeat 20 (test-names)))
(gccollect))
# Create pipe
(var pipe-counter 0)

View File

@@ -161,10 +161,59 @@
([err] :caught))))
"regression #638"))
# Struct prototypes
(def x (struct/with-proto {1 2 3 4} 5 6))
(def y (-> x marshal unmarshal))
(def z {1 2 3 4})
(assert (= 2 (get x 1)) "struct get proto value 1")
(assert (= 4 (get x 3)) "struct get proto value 2")
(assert (= 6 (get x 5)) "struct get proto value 3")
(assert (= x y) "struct proto marshal equality 1")
(assert (= (getproto x) (getproto y)) "struct proto marshal equality 2")
(assert (= 0 (cmp x y)) "struct proto comparison 1")
(assert (= 0 (cmp (getproto x) (getproto y))) "struct proto comparison 2")
(assert (not= (cmp x z) 0) "struct proto comparison 3")
(assert (not= (cmp y z) 0) "struct proto comparison 4")
(assert (not= x z) "struct proto comparison 5")
(assert (not= y z) "struct proto comparison 6")
(assert (= (x 5) 6) "struct proto get 1")
(assert (= (y 5) 6) "struct proto get 1")
(assert (deep= x y) "struct proto deep= 1")
(assert (deep-not= x z) "struct proto deep= 2")
(assert (deep-not= y z) "struct proto deep= 3")
# Issue #751
(def t {:side false})
(assert (nil? (get-in t [:side :note])) "get-in with false value")
(assert (= (get-in t [:side :note] "dflt") "dflt")
"get-in with false value and default")
(assert (= (math/gcd 462 1071) 21) "math/gcd 1")
(assert (= (math/lcm 462 1071) 23562) "math/lcm 1")
# Evaluate stream with `dofile`
(def [r w] (os/pipe))
(:write w "(setdyn :x 10)")
(:close w)
(def stream-env (dofile r))
(assert (= (stream-env :x) 10) "dofile stream 1")
# Issue #861 - should be valgrind clean
(def step1 "(a b c d)\n")
(def step2 "(a b)\n")
(def p1 (parser/new))
(parser/state p1)
(parser/consume p1 step1)
(loop [v :iterate (parser/produce p1)])
(parser/state p1)
(def p2 (parser/clone p1))
(parser/state p2)
(parser/consume p2 step2)
(loop [v :iterate (parser/produce p2)])
(parser/state p2)
# Check missing struct proto bug.
(assert (struct/getproto (struct/with-proto {:a 1} :b 2 :c nil)) "missing struct proto")
(end-suite)

31
test/suite0011.janet Normal file
View File

@@ -0,0 +1,31 @@
# Copyright (c) 2021 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
(import ./helper :prefix "" :exit true)
(start-suite 11)
(assert (< 11899423.08 (math/gamma 11.5) 11899423.085)
"math/gamma")
(assert (< 2605.1158 (math/log-gamma 500) 2605.1159)
"math/log-gamma")
(end-suite)

View File

@@ -3,8 +3,11 @@
# Format all code with astyle
STYLEOPTS="--style=attach --indent-switches --convert-tabs \
--align-pointer=name --pad-header --pad-oper --unpad-paren --indent-labels"
--align-pointer=name --pad-header --pad-oper --unpad-paren --indent-labels --formatted"
astyle $STYLEOPTS */*.c
astyle $STYLEOPTS */*/*.c
astyle $STYLEOPTS */*/*.h
rm -f */*.c.orig
rm -f */*/*.c.orig
rm -f */*/*.h.orig