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

Compare commits

..

32 Commits

Author SHA1 Message Date
Calvin Rose
47bb7fd21b Begin implementing async subproccesses for windows. 2021-01-03 11:21:44 -06:00
Calvin Rose
1c7ed8ca48 Use PostQueuedCompletionStatus for threaded calls on windows.
Ore efficient than using a self pipe.
2021-01-03 11:08:12 -06:00
Calvin Rose
61c65f3df1 Fix valgrind warning. 2020-12-31 16:30:54 -06:00
Calvin Rose
05166b3673 Fix proc getter bug. 2020-12-31 16:23:20 -06:00
Calvin Rose
0a1c93b869 Add ev api for making threaded calls.
Easy way to make arbitrary functions in C async.
2020-12-31 16:12:42 -06:00
Calvin Rose
788f91a36f Remove unneeded book keeping for sub processes.
Since we are not using signals we no longer need some bookkeeping.
2020-12-31 11:52:12 -06:00
Calvin Rose
c831ecf5d2 Working implementation of process waiting with threads.
Does not require all sorts of signal handling code
that is not thread-safe and can "steal processes".

However, there is a much simpler way to add this functionality
by creating a new stream and thread for each subprocess when it is
waited on. This is perhaps _slightly_ less efficient but oh so much
simpler, since we can reuse all of our concepts from streams and there
is no need to implement a whole system around the selfpipe.
2020-12-31 11:22:18 -06:00
Calvin Rose
9e42ee153c Merge branch 'master' into HEAD 2020-12-30 12:19:13 -06:00
Calvin Rose
d457aa5951 Deprecate file/popen.
os/spawn is the prefered way of creating a subprocess and
communicating with it.
2020-12-30 10:22:45 -06:00
Calvin Rose
ab37ee6ebb Add :all option to ev/read.
Brings ev/read more in line with file/read.
2020-12-29 20:37:59 -06:00
Calvin Rose
8655530b19 Rename predicates in module/paths 2020-12-29 19:52:26 -06:00
Calvin Rose
27b1f59aa9 Change Ctrl-C and move old behavior to Ctrl-Q
This lets Janet be a better unix citizen and lets Ctrl-C
raise an interrupt. Trying to make Janet behave superficially
like a shell by overriding Ctrl-C is not helpful.
2020-12-29 16:20:37 -06:00
Calvin Rose
cc2cc4db43 Merge pull request #541 from sogaiu/match-doc-formatting
Tweak match docstring
2020-12-29 13:10:35 -06:00
Calvin Rose
20bcd95279 Merge commit '0ea77cabfb30afc15433581f5888171c1f65aafd' 2020-12-28 12:20:21 -06:00
Calvin Rose
d7954be5e5 Update docstring for os/open. 2020-12-28 11:00:15 -06:00
Felix Riedel
0ea77cabfb Tweak sort: use insertion sort for small arrays 2020-12-28 16:06:48 +00:00
Felix Riedel
0d46352ff4 Revert to better performing number hash. 2020-12-27 14:05:40 +00:00
sogaiu
ffa0d5fe45 Tweak match docstring 2020-12-27 13:42:22 +09:00
Calvin Rose
a2c837a99c Merge remote-tracking branch 'felixr/master' into master 2020-12-26 20:06:34 -06:00
Calvin Rose
13d8d11011 Try new number hashing with frexp.
This may be a bit slower in some cases but generally should
have much better hashing for numbers.
2020-12-26 16:54:14 -06:00
Calvin Rose
2357b6162f Update test-install target. 2020-12-26 15:42:13 -06:00
Calvin Rose
b4f242193d Improve hash function for numbers. 2020-12-26 15:38:04 -06:00
Calvin Rose
7242ee0186 Merge pull request #540 from felixr/better-quicksort
Improve quicksort to avoid worst case performance on sorted input
2020-12-26 15:23:01 -06:00
Felix Riedel
3e742ffc4c Improve quicksort to avoid worst case performance.
The current implementation will have quadratic behaviour for already
sorted arrays because it picks the last element as pivot. In an sorted
array this splits the array repeatedly into the biggest value and all
other values.

The implementation in this commit uses the *median of three* as pivot.

`janet -e "(sort (range 10000))"` to reproduce quadratic behaviour.
2020-12-26 19:18:17 +00:00
Felix Riedel
2ec12fe06f Improve hashing of numbers
Using an integer hash (https://stackoverflow.com/a/12996028/60617) on
the number casted to int32 combined with lower bits of the number.
2020-12-26 13:09:11 +00:00
Felix Riedel
c76e0ae685 Use boost's way of combining hash values for arrays and kv pairs.
`seed ^= hash_value(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);`
from https://www.boost.org/doc/libs/1_35_0/doc/html/boost/hash_combine_id241013.html

The current way of combining hashes peforms poorly on hash values of
numbers. Changing the way hashes are combined canlead to a significant speed up:

```
time janet_new -e '(def tbl @{}) (loop [x :in (range 1000) y :in (range 1000)] (put tbl {0 x 1 y} true))'
3.77s user 0.08s system 99% cpu 3.843 total

time janet_orig -e '(def tbl @{}) (loop [x :in (range 1000) y :in (range 1000)] (put tbl {0 x 1 y} true))'
48.98s user 0.15s system 99% cpu 49.136 total
```
2020-12-26 13:05:03 +00:00
Calvin Rose
cae4f19629 Merge pull request #532 from pyrmont/feature.parser-line-col-setting
Update (parser/where) to support optional line and column
2020-12-15 19:03:25 -06:00
Michael Camilleri
04f6c7b156 Clarify docstring of parser/where 2020-12-15 16:41:45 +09:00
Michael Camilleri
77b79e9899 Update (parser/where) to support optional line and column 2020-12-15 14:12:33 +09:00
Calvin Rose
a55354357c Make dofile error if source file errors.
This should make dofile a bit easier to use.
It also means that import properly raises errors when
things go bad.
2020-12-14 08:23:06 -06:00
Calvin Rose
392d5d51df Fix build info for 1.13.1 2020-12-13 11:59:52 -06:00
Calvin Rose
cbdea8f331 Make os/execute cooperate with ev module.
os/execute, os/proc-wait do not block (currently posix only).
This uses the self-pipe trick to turn signals into a pollable entity.
2020-11-29 15:36:21 -06:00
21 changed files with 688 additions and 179 deletions

View File

@@ -1,7 +1,18 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.13.0 - 2020-12-13
## Unreleased - ???
- Deprecate `file/popen` in favor of `os/spawn`.
- Add `:all` keyword to `ev/read` and `net/read` to make them more like `file/read`. However, we
do not provide any `:line` option as that requires buffering.
- Change repl behavior to make Ctrl-C raise SIGINT on posix. The old behavior for Ctrl-C,
to clear the current line buffer, has been moved to Ctrl-Q.
- Importing modules that start with `/` is now the only way to import from project root.
Before, this would import from / on disk.
- Change hash function for numbers.
- Improve error handling of `dofile`.
## 1.13.1 - 2020-12-13
- Pretty printing a table with a prototype will look for `:_name` instead of `:name`
in the prototype table to tag the output.
- `match` macro implementation changed to be tail recursive.

View File

@@ -157,7 +157,7 @@ build/janet.c: build/janet_boot src/boot/boot.janet
##### Amalgamation #####
########################
SONAME=libjanet.so.1.12
SONAME=libjanet.so.1.13
build/shell.c: src/mainclient/shell.c
cp $< $@

View File

@@ -0,0 +1,22 @@
(defn dowork [name n]
(print name " starting work...")
(os/execute [(dyn :executable) "-e" (string "(os/sleep " n ")")])
(print name " finished work!"))
# Will be done in parallel
(print "starting group A")
(ev/call dowork "A 2" 2)
(ev/call dowork "A 1" 1)
(ev/call dowork "A 3" 3)
(ev/sleep 4)
# Will also be done in parallel
(print "starting group B")
(ev/call dowork "B 2" 2)
(ev/call dowork "B 1" 1)
(ev/call dowork "B 3" 3)
(ev/sleep 4)
(print "all work done")

View File

@@ -64,6 +64,10 @@ Move cursor to the beginning of input line.
.BR Ctrl\-B
Move cursor one character to the left.
.TP 16
.BR Ctrl\-D
If on a newline, indicate end of stream and exit the repl.
.TP 16
.BR Ctrl\-E
Move cursor to the end of input line.
@@ -100,6 +104,10 @@ Delete one word before the cursor.
.BR Ctrl\-G
Show documentation for the current symbol under the cursor.
.TP 16
.BR Ctrl\-Q
Clear the current command, including already typed lines.
.TP 16
.BR Alt\-B/Alt\-F
Move cursor backwards and forwards one word.

View File

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

View File

@@ -790,36 +790,50 @@
###
###
(defn- sort-part
[a lo hi by]
(def pivot (in a hi))
(var i lo)
(forv j lo hi
(def aj (in a j))
(when (by aj pivot)
(def ai (in a i))
(set (a i) aj)
(set (a j) ai)
(++ i)))
(set (a hi) (in a i))
(set (a i) pivot)
i)
(defn- median-of-three [a b c]
(if (not= (> a b) (> a c))
a
(if (not= (> b a) (> b c)) b c)))
(defn- sort-help
[a lo hi by]
(when (> hi lo)
(def piv (sort-part a lo hi by))
(sort-help a lo (- piv 1) by)
(sort-help a (+ piv 1) hi by))
(defn- insertion-sort [a lo hi by]
(for i (+ lo 1) (+ hi 1)
(def temp (in a i))
(var j (- i 1))
(while (and (>= j lo) (by temp (in a j)))
(set (a (+ j 1)) (in a j))
(-- j))
(set (a (+ j 1)) temp))
a)
(defn sort
"Sort an array in-place. Uses quick-sort and is not a stable sort."
[a &opt by]
(sort-help a 0 (- (length a) 1) (or by <)))
(default by <)
(def stack @[[0 (- (length a) 1)]])
(while (not (empty? stack))
(def [lo hi] (array/pop stack))
(when (< lo hi)
(when (< (- hi lo) 32) (insertion-sort a lo hi by) (break))
(def pivot (median-of-three (in a hi) (in a lo) (in a (math/floor (/ (+ lo hi) 2)))))
(var left lo)
(var right hi)
(while true
(while (by (in a left) pivot) (++ left))
(while (by pivot (in a right)) (-- right))
(when (<= left right)
(def tmp (in a left))
(set (a left) (in a right))
(set (a right) tmp)
(++ left)
(-- right))
(if (>= left right) (break)))
(array/push stack [lo right])
(array/push stack [left hi])))
a)
(undef sort-part)
(undef sort-help)
(undef median-of-three)
(undef insertion-sort)
(defn sort-by
`Returns a new sorted array that compares elements by invoking
@@ -1561,16 +1575,26 @@
(defmacro match
```
Pattern matching. Match an expression x against
any number of cases. Each case is a pattern to match against, followed
by an expression to evaluate to if that case is matched. A pattern that is
a symbol will match anything, binding x's value to that symbol. An array
will match only if all of it's elements match the corresponding elements in
x. A table or struct will match if all values match with the corresponding
values in x. A tuple pattern will match if it's first element matches, and the following
elements are treated as predicates and are true. The last special case is
the '_ symbol, which is a wildcard that will match any value without creating a binding.
Any other value pattern will only match if it is equal to x.
Pattern matching. Match an expression `x` against any number of cases.
Each case is a pattern to match against, followed by an expression to
evaluate to if that case is matched. Legal patterns are:
* symbol -- a pattern that is a symbol will match anything, binding `x`'s
value to that symbol.
* array -- an array will match only if all of its elements match the
corresponding elements in `x`.
* table or struct -- a table or struct will match if all values match with
the corresponding values in `x`.
* tuple -- a tuple pattern will match if its first element matches, and the
following elements are treated as predicates and are true.
* `_` symbol -- the last special case is the `_` symbol, which is a wildcard
that will match any value without creating a binding.
Any other value pattern will only match if it is equal to `x`.
```
[x & cases]
@@ -1684,7 +1708,7 @@
(visit-pattern-2 anda gun preds nil nil pattern)
# Local unification
(def unify @[])
(each syms b2g
(each syms b2g
(when (< 1 (length syms))
(array/push unify [= ;syms])))
# Global unification
@@ -1706,7 +1730,7 @@
(if (= :branch el)
(let [condition (array/pop stack)
truthy (array/pop stack)
if-form ~(if ,condition ,truthy
if-form ~(if ,condition ,truthy
,(case (length stack)
0 nil
1 (stack 0)
@@ -2416,9 +2440,6 @@
(default where "<anonymous>")
(default guard :ydt)
# Are we done yet?
(var going true)
# Evaluate 1 source form in a protected manner
(defn eval1 [source]
(def source (if expand (expand source) source))
@@ -2442,7 +2463,7 @@
(fiber/setenv f env)
(while (fiber/can-resume? f)
(def res (resume f resumeval))
(when good (when going (set resumeval (onstatus f res))))))
(when good (set resumeval (onstatus f res)))))
# Reader version
(when read
@@ -2467,11 +2488,11 @@
# Loop
(def buf @"")
(while going
(var parser-not-done true)
(while parser-not-done
(if (env :exit) (break))
(buffer/clear buf)
(if (= (chunks buf p)
:cancel)
(if (= (chunks buf p) :cancel)
(do
# A :cancel chunk represents a cancelled form in the REPL, so reset.
(:flush p)
@@ -2482,19 +2503,23 @@
(def len (length buf))
(when (= len 0)
(:eof p)
(set going false))
(set parser-not-done false))
(while (> len pindex)
(+= pindex (p-consume p buf pindex))
(while (p-has-more p)
(eval1 (p-produce p)))
(eval1 (p-produce p))
(if (env :exit) (break)))
(when (= (p-status p) :error)
(parse-err p where))))))
(parse-err p where)
(if (env :exit) (break)))))))
# Check final parser state
(while (p-has-more p)
(eval1 (p-produce p)))
(when (= (p-status p) :error)
(parse-err p where))
(unless (env :exit)
(while (p-has-more p)
(eval1 (p-produce p))
(if (env :exit) (break)))
(when (= (p-status p) :error)
(parse-err p where)))
(in env :exit-value env))
@@ -2593,8 +2618,9 @@
[image]
(unmarshal image load-image-dict))
(defn- check-. [x] (if (string/has-prefix? "." x) x))
(defn- not-check-. [x] (unless (string/has-prefix? "." x) x))
(defn- check-relative [x] (if (string/has-prefix? "." x) x))
(defn- check-is-dep [x] (unless (or (string/has-prefix? "/" x) (string/has-prefix? "." x)) x))
(defn- check-project-relative [x] (if (string/has-prefix? "/" x) x))
(def module/paths
```
@@ -2630,12 +2656,12 @@
(defn- find-prefix
[pre]
(or (find-index |(and (string? ($ 0)) (string/has-prefix? pre ($ 0))) module/paths) 0))
(def all-index (find-prefix ":all:"))
(array/insert module/paths all-index [(string ":all:" ext) loader not-check-.])
(def all-index (find-prefix ".:all:"))
(array/insert module/paths all-index [(string ".:all:" ext) loader check-project-relative])
(def sys-index (find-prefix ":sys:"))
(array/insert module/paths sys-index [(string ":sys:/:all:" ext) loader not-check-.])
(array/insert module/paths sys-index [(string ":sys:/:all:" ext) loader check-is-dep])
(def curall-index (find-prefix ":cur:/:all:"))
(array/insert module/paths curall-index [(string ":cur:/:all:" ext) loader check-.])
(array/insert module/paths curall-index [(string ":cur:/:all:" ext) loader check-relative])
module/paths)
(module/add-paths ":native:" :native)
@@ -2694,8 +2720,9 @@
(undef fexists)
(undef mod-filter)
(undef check-.)
(undef not-check-.)
(undef check-relative)
(undef check-project-relative)
(undef check-is-dep)
(def module/loading
`Table mapping currently loading modules to true. Used to prevent
@@ -2723,15 +2750,25 @@
(def spath (string path))
(put env :current-file (or src (if-not path-is-file spath)))
(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 2048 buf))
(defn bp [&opt x y]
(def ret (bad-parse x y))
(if exit (os/exit 1))
ret)
(when exit
(bad-parse x y)
(os/exit 1))
(put env :exit true)
(def [line col] (:where x))
(def pe (string (:error x) " in " y " around line " line ", column " col))
(set exit-error pe))
(defn bc [&opt x y z]
(def ret (bad-compile x y z))
(if exit (os/exit 1))
ret)
(when exit
(bad-compile x y z)
(os/exit 1))
(put env :exit true)
(def ce (string x " while compiling " z))
(set exit-error ce)
(set exit-fiber y))
(unless f
(error (string "could not find file " path)))
(def nenv
@@ -2741,32 +2778,40 @@
:on-compile-error bc
:on-status (fn [f x]
(when (not= (fiber/status f) :dead)
(debug/stacktrace f x)
(if exit (os/exit 1) (eflush))))
(when exit
(debug/stacktrace f x)
(eflush)
(os/exit 1))
(put env :exit true)
(set exit-error x)
(set exit-fiber f)))
:evaluator evaluator
:expander expander
:read read
:parser parser
:source (or src (if path-is-file "<anonymous>" spath))}))
(if-not path-is-file (file/close f))
(when exit-error
(if exit-fiber
(propagate exit-error exit-fiber)
(error exit-error)))
nenv)
(def module/loaders
`A table of loading method names to loading functions.
This table lets require and import load many different kinds
of files as modules.`
@{:native (fn [path &] (native path (make-env)))
:source (fn [path args]
@{:native (fn native-loader [path &] (native path (make-env)))
:source (fn source-loader [path args]
(put module/loading path true)
(def newenv (dofile path ;args))
(put module/loading path nil)
newenv)
:preload (fn [path & args]
(defer (put module/loading path nil)
(dofile path ;args)))
:preload (fn preload-loader [path & args]
(when-let [m (in module/cache path)]
(if (function? m)
(set (module/cache path) (m path ;args))
m)))
:image (fn [path &] (load-image (slurp path)))})
:image (fn image-loader [path &] (load-image (slurp path)))})
(defn require-1
[path args kargs]

View File

@@ -5,9 +5,9 @@
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 13
#define JANET_VERSION_PATCH 0
#define JANET_VERSION_PATCH 2
#define JANET_VERSION_EXTRA "-dev"
#define JANET_VERSION "1.13.0-dev"
#define JANET_VERSION "1.13.2-dev"
/* #define JANET_BUILD "local" */

View File

@@ -31,12 +31,12 @@
#ifdef JANET_EV
/* Includes */
#include <math.h>
#ifdef JANET_WINDOWS
#include <winsock2.h>
#include <windows.h>
#else
#include <pthread.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
@@ -48,6 +48,7 @@
#include <netinet/tcp.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#ifdef JANET_EV_EPOLL
#include <sys/epoll.h>
#include <sys/timerfd.h>
@@ -148,6 +149,7 @@ JANET_THREAD_LOCAL JanetRNG janet_vm_ev_rng;
JANET_THREAD_LOCAL JanetListenerState **janet_vm_listeners = NULL;
JANET_THREAD_LOCAL size_t janet_vm_listener_count = 0;
JANET_THREAD_LOCAL size_t janet_vm_listener_cap = 0;
JANET_THREAD_LOCAL size_t janet_vm_extra_listeners = 0;
/* Get current timestamp (millisecond precision) */
static JanetTimestamp ts_now(void);
@@ -519,6 +521,7 @@ void janet_ev_init_common(void) {
/* Common deinit code */
void janet_ev_deinit_common(void) {
janet_q_deinit(&janet_vm_spawn);
free(janet_vm_tq);
free(janet_vm_listeners);
janet_vm_listeners = NULL;
}
@@ -540,6 +543,14 @@ void janet_addtimeout(double sec) {
add_timeout(to);
}
void janet_ev_inc_refcount(void) {
janet_vm_extra_listeners++;
}
void janet_ev_dec_refcount(void) {
janet_vm_extra_listeners--;
}
/* Channels */
typedef struct {
@@ -839,14 +850,16 @@ void janet_loop1(void) {
}
}
}
/* Run scheduled fibers */
while (janet_vm_spawn.head != janet_vm_spawn.tail) {
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK};
janet_q_pop(&janet_vm_spawn, &task, sizeof(task));
run_one(task.fiber, task.value, task.sig);
}
/* Poll for events */
if (janet_vm_listener_count || janet_vm_tq_count) {
if (janet_vm_listener_count || janet_vm_tq_count || janet_vm_extra_listeners) {
JanetTimeout to;
memset(&to, 0, sizeof(to));
int has_timeout;
@@ -855,18 +868,69 @@ void janet_loop1(void) {
pop_timeout(0);
}
/* Run polling implementation only if pending timeouts or pending events */
if (janet_vm_tq_count || janet_vm_listener_count) {
if (janet_vm_tq_count || janet_vm_listener_count || janet_vm_extra_listeners) {
janet_loop1_impl(has_timeout, to.when);
}
}
}
void janet_loop(void) {
while (janet_vm_listener_count || (janet_vm_spawn.head != janet_vm_spawn.tail) || janet_vm_tq_count) {
while (janet_vm_listener_count || (janet_vm_spawn.head != janet_vm_spawn.tail) || janet_vm_tq_count || janet_vm_extra_listeners) {
janet_loop1();
}
}
/*
* Self-pipe handling code.
*/
/* Wrap return value by pairing it with the callback used to handle it
* in the main thread */
typedef struct {
JanetEVGenericMessage msg;
JanetThreadedCallback cb;
} JanetSelfPipeEvent;
/* Structure used to initialize threads in the thread pool
* (same head structure as self pipe event)*/
typedef struct {
JanetEVGenericMessage msg;
JanetThreadedCallback cb;
JanetThreadedSubroutine subr;
JanetHandle write_pipe;
} JanetEVThreadInit;
#ifdef JANET_WINDOWS
/* On windows, use PostQueuedCompletionStatus instead for
* custom events */
#else
static JANET_THREAD_LOCAL JanetHandle janet_vm_selfpipe[2];
static void janet_ev_setup_selfpipe(void) {
if (janet_make_pipe(janet_vm_selfpipe)) {
JANET_EXIT("failed to initialize self pipe in event loop");
}
}
/* Handle events from the self pipe inside the event loop */
static void janet_ev_handle_selfpipe(void) {
JanetSelfPipeEvent response;
while (read(janet_vm_selfpipe[0], &response, sizeof(response)) > 0) {
response.cb(response.msg);
janet_ev_dec_refcount();
}
}
static void janet_ev_cleanup_selfpipe(void) {
close(janet_vm_selfpipe[0]);
close(janet_vm_selfpipe[1]);
}
#endif
#ifdef JANET_WINDOWS
JANET_THREAD_LOCAL HANDLE janet_vm_iocp = NULL;
@@ -926,6 +990,12 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) {
if (!has_timeout) {
/* queue emptied */
}
} else if (0 == completionKey) {
/* Custom event */
JanetSelfPipeEvent *response = (JanetSelfPipeEvent *)(overlapped);
response->cb(response->msg);
free(response);
janet_ev_dec_refcount();
} else {
/* Normal event */
JanetStream *stream = (JanetStream *) completionKey;
@@ -1034,8 +1104,14 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
/* Step state machines */
for (int i = 0; i < ready; i++) {
JanetStream *stream = events[i].data.ptr;
if (NULL != stream) { /* If NULL, is a timeout */
void *p = events[i].data.ptr;
if (&janet_vm_timerfd == p) {
/* Timer expired, ignore */;
} else if (janet_vm_selfpipe == p) {
/* Self-pipe handling */
janet_ev_handle_selfpipe();
} else {
JanetStream *stream = p;
int mask = events[i].events;
JanetListenerState *state = stream->state;
state->event = events + i;
@@ -1066,14 +1142,18 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
void janet_ev_init(void) {
janet_ev_init_common();
janet_ev_setup_selfpipe();
janet_vm_epoll = epoll_create1(EPOLL_CLOEXEC);
janet_vm_timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
janet_vm_timer_enabled = 0;
if (janet_vm_epoll == -1 || janet_vm_timerfd == -1) goto error;
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = NULL;
ev.data.ptr = &janet_vm_timerfd;
if (-1 == epoll_ctl(janet_vm_epoll, EPOLL_CTL_ADD, janet_vm_timerfd, &ev)) goto error;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = janet_vm_selfpipe;
if (-1 == epoll_ctl(janet_vm_epoll, EPOLL_CTL_ADD, janet_vm_selfpipe[0], &ev)) goto error;
return;
error:
JANET_EXIT("failed to initialize event loop");
@@ -1083,6 +1163,7 @@ void janet_ev_deinit(void) {
janet_ev_deinit_common();
close(janet_vm_epoll);
close(janet_vm_timerfd);
janet_ev_cleanup_selfpipe();
janet_vm_epoll = 0;
}
@@ -1119,7 +1200,7 @@ JanetListenerState *janet_listen(JanetStream *stream, JanetListener behavior, in
JanetListenerState *state = janet_listen_impl(stream, behavior, mask, size, user);
size_t newsize = janet_vm_listener_cap;
if (newsize > oldsize) {
janet_vm_fds = realloc(janet_vm_fds, newsize * sizeof(struct pollfd));
janet_vm_fds = realloc(janet_vm_fds, (newsize + 1) * sizeof(struct pollfd));
if (NULL == janet_vm_fds) {
JANET_OUT_OF_MEMORY;
}
@@ -1128,12 +1209,12 @@ JanetListenerState *janet_listen(JanetStream *stream, JanetListener behavior, in
ev.fd = stream->handle;
ev.events = make_poll_events(state->stream->_mask);
ev.revents = 0;
janet_vm_fds[state->_index] = ev;
janet_vm_fds[state->_index + 1] = ev;
return state;
}
static void janet_unlisten(JanetListenerState *state) {
janet_vm_fds[state->_index] = janet_vm_fds[janet_vm_listener_count - 1];
janet_vm_fds[state->_index + 1] = janet_vm_fds[janet_vm_listener_count];
janet_unlisten_impl(state);
}
@@ -1146,19 +1227,25 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
JanetTimestamp now = ts_now();
to = now > timeout ? 0 : (int)(timeout - now);
}
ready = poll(janet_vm_fds, janet_vm_listener_count, to);
ready = poll(janet_vm_fds, janet_vm_listener_count + 1, to);
} while (ready == -1 && errno == EINTR);
if (ready == -1) {
JANET_EXIT("failed to poll events");
}
/* Check selfpipe */
if (janet_vm_fds[0].revents & POLLIN) {
janet_vm_fds[0].revents = 0;
janet_ev_handle_selfpipe();
}
/* Step state machines */
for (size_t i = 0; i < janet_vm_listener_count; i++) {
struct pollfd *pfd = janet_vm_fds + i;
struct pollfd *pfd = janet_vm_fds + i + 1;
/* Skip fds where nothing interesting happened */
JanetListenerState *state = janet_vm_listeners[i];
/* Normal event */
int mask = janet_vm_fds[i].revents;
int mask = pfd->revents;
JanetAsyncStatus status1 = JANET_ASYNC_STATUS_NOT_DONE;
JanetAsyncStatus status2 = JANET_ASYNC_STATUS_NOT_DONE;
JanetAsyncStatus status3 = JANET_ASYNC_STATUS_NOT_DONE;
@@ -1183,20 +1270,157 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
void janet_ev_init(void) {
janet_ev_init_common();
janet_vm_fds = NULL;
janet_ev_setup_selfpipe();
janet_vm_fds = malloc(sizeof(struct pollfd));
if (NULL == janet_vm_fds) {
JANET_OUT_OF_MEMORY;
}
janet_vm_fds[0].fd = janet_vm_selfpipe[0];
janet_vm_fds[0].events = POLLIN;
janet_vm_fds[0].revents = 0;
return;
}
void janet_ev_deinit(void) {
janet_ev_deinit_common();
janet_ev_cleanup_selfpipe();
free(janet_vm_fds);
janet_vm_fds = NULL;
}
#endif
/* C API helpers for reading and writing from streams.
/*
* End poll implementation
*/
/*
* Threaded calls
*/
#ifdef JANET_WINDOWS
static DWORD WINAPI janet_thread_body(LPVOID ptr) {
JanetEVThreadInit *init = (JanetEVThreadInit *)ptr;
JanetEVGenericMessage msg = init->msg;
JanetThreadedSubroutine subr = init->subr;
JanetThreadedCallback cb = init->cb;
JanetHandle iocp = init->write_pipe;
/* Reuse memory from thread init for returning data */
init->msg = subr(msg);
init->cb = cb;
janet_assert(PostQueuedCompletionStatus(iocp,
sizeof(JanetSelfPipeEvent),
0,
(LPOVERLAPPED) init),
"failed to post completion event");
return 0;
}
#else
static void *janet_thread_body(void *ptr) {
JanetEVThreadInit *init = (JanetEVThreadInit *)ptr;
JanetEVGenericMessage msg = init->msg;
JanetThreadedSubroutine subr = init->subr;
JanetThreadedCallback cb = init->cb;
int fd = init->write_pipe;
free(init);
JanetSelfPipeEvent response;
response.msg = subr(msg);
response.cb = cb;
/* handle a bit of back pressure before giving up. */
int tries = 4;
while (tries > 0) {
int status;
do {
status = write(fd, &response, sizeof(response));
} while (status == -1 && errno == EINTR);
if (status > 0) break;
sleep(1);
tries--;
}
return NULL;
}
#endif
void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage arguments, JanetThreadedCallback cb) {
JanetEVThreadInit *init = malloc(sizeof(JanetEVThreadInit));
if (NULL == init) {
JANET_OUT_OF_MEMORY;
}
init->msg = arguments;
init->subr = fp;
init->cb = cb;
#ifdef JANET_WINDOWS
init->write_pipe = janet_vm_iocp;
HANDLE thread_handle = CreateThread(NULL, 0, janet_thread_body, init, 0, NULL);
if (NULL == thread_handle) {
free(init);
janet_panic("failed to create thread");
}
CloseHandle(thread_handle); /* detach from thread */
#else
init->write_pipe = janet_vm_selfpipe[1];
pthread_t waiter_thread;
int err = pthread_create(&waiter_thread, NULL, janet_thread_body, init);
if (err) {
free(init);
janet_panicf("%s", strerror(err));
}
pthread_detach(waiter_thread);
#endif
/* Increment ev refcount so we don't quit while waiting for a subprocess */
janet_ev_inc_refcount();
}
/* Default callback for janet_ev_threaded_await. */
void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) {
switch (return_value.tag) {
default:
case JANET_EV_TCTAG_NIL:
janet_schedule(return_value.fiber, janet_wrap_nil());
break;
case JANET_EV_TCTAG_INTEGER:
janet_schedule(return_value.fiber, janet_wrap_integer(return_value.argi));
break;
case JANET_EV_TCTAG_STRING:
case JANET_EV_TCTAG_STRINGF:
janet_schedule(return_value.fiber, janet_cstringv((const char *) return_value.argp));
if (return_value.tag == JANET_EV_TCTAG_STRINGF) free(return_value.argp);
break;
case JANET_EV_TCTAG_KEYWORD:
janet_schedule(return_value.fiber, janet_ckeywordv((const char *) return_value.argp));
break;
case JANET_EV_TCTAG_ERR_STRING:
case JANET_EV_TCTAG_ERR_STRINGF:
janet_cancel(return_value.fiber, janet_cstringv((const char *) return_value.argp));
if (return_value.tag == JANET_EV_TCTAG_STRINGF) free(return_value.argp);
break;
case JANET_EV_TCTAG_ERR_KEYWORD:
janet_cancel(return_value.fiber, janet_ckeywordv((const char *) return_value.argp));
break;
}
janet_gcunroot(janet_wrap_fiber(return_value.fiber));
}
/* Convenience method for common case */
void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp) {
JanetEVGenericMessage arguments;
arguments.tag = tag;
arguments.argi = argi;
arguments.argp = argp;
arguments.fiber = janet_root_fiber();
janet_gcroot(janet_wrap_fiber(arguments.fiber));
janet_ev_threaded_call(fp, arguments, janet_ev_default_threaded_callback);
janet_await();
}
/*
* C API helpers for reading and writing from streams.
* There is some networking code in here as well as generic
* reading and writing primitives. */
* reading and writing primitives.
*/
void janet_stream_flags(JanetStream *stream, uint32_t flags) {
if (stream->flags & JANET_STREAM_CLOSED) {
@@ -1297,7 +1521,7 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
janet_buffer_push_bytes(state->buf, state->chunk_buf, s->bytes);
state->bytes_left -= s->bytes;
if (state->bytes_left <= 0 || !state->is_chunk || s->bytes == 0) {
if (state->bytes_left == 0 || !state->is_chunk || s->bytes == 0) {
Janet resume_val;
#ifdef JANET_NET
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
@@ -1360,12 +1584,11 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
}
return JANET_ASYNC_STATUS_DONE;
}
case JANET_ASYNC_EVENT_READ:
/* Read in bytes */
{
case JANET_ASYNC_EVENT_READ: {
JanetBuffer *buffer = state->buf;
int32_t bytes_left = state->bytes_left;
janet_buffer_extra(buffer, bytes_left);
int32_t read_limit = bytes_left < 0 ? 4096 : bytes_left;
janet_buffer_extra(buffer, read_limit);
ssize_t nread;
#ifdef JANET_NET
char saddr[256];
@@ -1374,14 +1597,14 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
do {
#ifdef JANET_NET
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
nread = recvfrom(s->stream->handle, buffer->data + buffer->count, bytes_left, state->flags,
nread = recvfrom(s->stream->handle, buffer->data + buffer->count, read_limit, state->flags,
(struct sockaddr *)&saddr, &socklen);
} else if (state->mode == JANET_ASYNC_READMODE_RECV) {
nread = recv(s->stream->handle, buffer->data + buffer->count, bytes_left, state->flags);
nread = recv(s->stream->handle, buffer->data + buffer->count, read_limit, state->flags);
} else
#endif
{
nread = read(s->stream->handle, buffer->data + buffer->count, bytes_left);
nread = read(s->stream->handle, buffer->data + buffer->count, read_limit);
}
} while (nread == -1 && errno == EINTR);
@@ -1731,7 +1954,13 @@ int janet_make_pipe(JanetHandle handles[2]) {
return 0;
#else
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;
return 0;
error:
close(handles[0]);
close(handles[1]);
return -1;
#endif
}
@@ -1756,9 +1985,7 @@ static Janet cfun_ev_call(int32_t argc, Janet *argv) {
return janet_wrap_fiber(fiber);
}
static Janet cfun_ev_sleep(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
double sec = janet_getnumber(argv, 0);
JANET_NO_RETURN void janet_sleep_await(double sec) {
JanetTimeout to;
to.when = ts_delta(ts_now(), sec);
to.fiber = janet_vm_root_fiber;
@@ -1769,6 +1996,12 @@ static Janet cfun_ev_sleep(int32_t argc, Janet *argv) {
janet_await();
}
static Janet cfun_ev_sleep(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
double sec = janet_getnumber(argv, 0);
janet_sleep_await(sec);
}
static Janet cfun_ev_deadline(int32_t argc, Janet *argv) {
janet_arity(argc, 1, 3);
double sec = janet_getnumber(argv, 0);
@@ -1803,11 +2036,16 @@ Janet janet_cfun_stream_read(int32_t argc, Janet *argv) {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE);
int32_t n = janet_getnat(argv, 1);
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, 10);
double to = janet_optnumber(argv, argc, 3, INFINITY);
if (to != INFINITY) janet_addtimeout(to);
janet_ev_read(stream, buffer, n);
if (janet_keyeq(argv[1], "all")) {
if (to != INFINITY) janet_addtimeout(to);
janet_ev_readchunk(stream, buffer, -1);
} else {
int32_t n = janet_getnat(argv, 1);
if (to != INFINITY) janet_addtimeout(to);
janet_ev_read(stream, buffer, n);
}
janet_await();
}
@@ -1922,7 +2160,8 @@ static const JanetReg ev_cfuns[] = {
{
"ev/read", janet_cfun_stream_read,
JDOC("(ev/read stream n &opt buffer timeout)\n\n"
"Read up to n bytes into a buffer asynchronously from a stream. "
"Read up to n bytes into a buffer asynchronously from a stream. `n` can also be the keyword "
"`:all` to read into the buffer until end of stream. "
"Optionally provide a buffer to write into "
"as well as a timeout in seconds after which to cancel the operation and raise an error. "
"Returns the buffer if the read was successful or nil if end-of-stream reached. Will raise an "

View File

@@ -777,7 +777,7 @@ static const JanetReg io_cfuns[] = {
#ifndef JANET_NO_PROCESSES
{
"file/popen", cfun_io_popen,
JDOC("(file/popen command &opt mode)\n\n"
JDOC("(file/popen command &opt mode) (DEPRECATED for os/spawn)\n\n"
"Open a file that is backed by a process. The file must be opened in either "
"the :r (read) or the :w (write) mode. In :r mode, the stdout of the "
"process can be read from the file. In :w mode, the stdin of the process "

View File

@@ -509,11 +509,16 @@ static Janet cfun_stream_read(int32_t argc, Janet *argv) {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
int32_t n = janet_getnat(argv, 1);
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, 10);
double to = janet_optnumber(argv, argc, 3, INFINITY);
if (to != INFINITY) janet_addtimeout(to);
janet_ev_recv(stream, buffer, n, MSG_NOSIGNAL);
if (janet_keyeq(argv[1], "all")) {
if (to != INFINITY) janet_addtimeout(to);
janet_ev_recvchunk(stream, buffer, -1, MSG_NOSIGNAL);
} else {
int32_t n = janet_getnat(argv, 1);
if (to != INFINITY) janet_addtimeout(to);
janet_ev_recv(stream, buffer, n, MSG_NOSIGNAL);
}
janet_await();
}
@@ -643,6 +648,7 @@ static const JanetReg net_cfuns[] = {
"net/read", cfun_stream_read,
JDOC("(net/read stream nbytes &opt buf timeout)\n\n"
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
"`n` can also be the keyword `:all` to read into the buffer until end of stream. "
"If less than n bytes are available (and more than 0), will push those bytes and return early. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.")

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* 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
@@ -185,6 +185,7 @@ static Janet os_exit(int32_t argc, Janet *argv) {
#ifndef JANET_REDUCED_OS
#ifndef JANET_NO_PROCESSES
/* Get env for os_execute */
static char **os_execute_env(int32_t argc, const Janet *argv) {
char **envp = NULL;
@@ -319,13 +320,15 @@ static JanetBuffer *os_exec_escape(JanetView args) {
static const JanetAbstractType ProcAT;
#define JANET_PROC_CLOSED 1
#define JANET_PROC_WAITED 2
#define JANET_PROC_WAITING 4
#define JANET_PROC_ERROR_NONZERO 8
typedef struct {
int flags;
#ifdef JANET_WINDOWS
HANDLE pHandle;
HANDLE tHandle;
#else
int pid;
pid_t pid;
#endif
int return_code;
#ifdef JANET_EV
@@ -339,6 +342,62 @@ typedef struct {
#endif
} JanetProc;
#ifdef JANET_EV
#ifdef JANET_WINDOWS
static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
JanetProc *proc = (JanetProc *) args.argp;
WaitForSingleObject(proc->pHandle, INFINITE);
GetExitCodeProcess(proc->pHandle, &args.argi);
return 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;
int status = 0;
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)) {
status = WSTOPSIG(status) + 128;
} else {
status = WTERMSIG(status) + 128;
}
args.argi = status;
return args;
}
#endif /* End windows check */
/* Callback that is called in main thread when subroutine completes. */
static void janet_proc_wait_cb(JanetEVGenericMessage args) {
int status = args.argi;
JanetProc *proc = (JanetProc *) args.argp;
if (NULL != proc) {
proc->return_code = (int32_t) status;
proc->flags |= JANET_PROC_WAITED;
proc->flags &= ~JANET_PROC_WAITING;
janet_gcunroot(janet_wrap_abstract(proc));
janet_gcunroot(janet_wrap_fiber(args.fiber));
if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) {
JanetString s = janet_formatc("command failed with non-zero exit code %d", status);
janet_cancel(args.fiber, janet_wrap_string(s));
} else {
janet_schedule(args.fiber, janet_wrap_integer(status));
}
}
}
#endif /* End ev check */
static int janet_proc_gc(void *p, size_t s) {
(void) s;
JanetProc *proc = (JanetProc *) p;
@@ -367,10 +426,26 @@ static int janet_proc_mark(void *p, size_t s) {
return 0;
}
#ifdef JANET_EV
JANET_NO_RETURN
#endif
static Janet os_proc_wait_impl(JanetProc *proc) {
if (proc->flags & JANET_PROC_WAITED) {
janet_panicf("cannot wait on process that has already finished");
if (proc->flags & (JANET_PROC_WAITED | JANET_PROC_WAITING)) {
janet_panicf("cannot wait twice on a process");
}
#ifdef JANET_EV
/* Event loop implementation - threaded call */
proc->flags |= JANET_PROC_WAITING;
JanetEVGenericMessage targs;
memset(&targs, 0, sizeof(targs));
targs.argp = proc;
targs.fiber = janet_root_fiber();
janet_gcroot(janet_wrap_abstract(proc));
janet_gcroot(janet_wrap_fiber(targs.fiber));
janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb);
janet_await();
#else
/* Non evented implementation */
proc->flags |= JANET_PROC_WAITED;
int status = 0;
#ifdef JANET_WINDOWS
@@ -386,6 +461,7 @@ static Janet os_proc_wait_impl(JanetProc *proc) {
#endif
proc->return_code = (int32_t) status;
return janet_wrap_integer(proc->return_code);
#endif
}
static Janet os_proc_wait(int32_t argc, Janet *argv) {
@@ -481,7 +557,7 @@ static int janet_proc_get(void *p, Janet key, Janet *out) {
return 1;
}
if (janet_keyeq(key, "err")) {
*out = (NULL == proc->out) ? janet_wrap_nil() : janet_wrap_abstract(proc->err);
*out = (NULL == proc->err) ? janet_wrap_nil() : janet_wrap_abstract(proc->err);
return 1;
}
if ((-1 != proc->return_code) && janet_keyeq(key, "return-code")) {
@@ -575,7 +651,7 @@ static JanetFile *get_stdio_for_handle(JanetHandle handle, void *orig, int iswri
}
#endif
static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
janet_arity(argc, 1, 3);
/* Get flags */
@@ -713,7 +789,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
tHandle = processInfo.hThread;
/* Wait and cleanup immedaitely */
if (!is_async) {
if (!is_spawn) {
DWORD code;
WaitForSingleObject(pHandle, INFINITE);
GetExitCodeProcess(pHandle, &code);
@@ -781,45 +857,51 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
if (status) {
os_execute_cleanup(envp, child_argv);
janet_panicf("%p: %s", argv[0], strerror(errno));
} else if (is_async) {
} else if (is_spawn) {
/* Get process handle */
os_execute_cleanup(envp, child_argv);
} else {
/* Wait to complete */
waitpid(pid, &status, 0);
os_execute_cleanup(envp, child_argv);
/* Use POSIX shell semantics for interpreting signals */
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
} else if (WIFSTOPPED(status)) {
status = WSTOPSIG(status) + 128;
} else {
status = WTERMSIG(status) + 128;
}
}
#endif
if (is_async) {
JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc));
proc->return_code = -1;
JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc));
proc->return_code = -1;
#ifdef JANET_WINDOWS
proc->pHandle = pHandle;
proc->tHandle = tHandle;
proc->pHandle = pHandle;
proc->tHandle = tHandle;
#else
proc->pid = pid;
proc->pid = pid;
#endif
proc->in = get_stdio_for_handle(new_in, orig_in, 1);
proc->out = get_stdio_for_handle(new_out, orig_out, 0);
proc->err = get_stdio_for_handle(new_err, orig_err, 0);
proc->flags = 0;
if (proc->in == NULL || proc->out == NULL || proc->err == NULL) {
janet_panic("failed to construct proc");
}
proc->in = NULL;
proc->out = NULL;
proc->err = NULL;
if (new_in != JANET_HANDLE_NONE) {
proc->in = get_stdio_for_handle(new_in, orig_in, 0);
if (NULL == proc->in) janet_panic("failed to construct proc");
}
if (new_out != JANET_HANDLE_NONE) {
proc->out = get_stdio_for_handle(new_out, orig_out, 1);
if (NULL == proc->out) janet_panic("failed to construct proc");
}
if (new_err != JANET_HANDLE_NONE) {
proc->err = get_stdio_for_handle(new_err, orig_err, 1);
if (NULL == proc->err) janet_panic("failed to construct proc");
}
proc->flags = 0;
if (janet_flag_at(flags, 2)) {
proc->flags |= JANET_PROC_ERROR_NONZERO;
}
if (is_spawn) {
return janet_wrap_abstract(proc);
} else if (janet_flag_at(flags, 2) && status) {
janet_panicf("command failed with non-zero exit code %d", status);
} else {
return janet_wrap_integer(status);
#ifdef JANET_EV
os_proc_wait_impl(proc);
#else
return os_proc_wait_impl(proc);
#endif
}
}
@@ -2037,25 +2119,25 @@ static const JanetReg os_cfuns[] = {
"mode should be a file mode as passed to os/chmod, but only if the create flag is given. "
"The default mode is 8r666. "
"Allowed flags are as follows:\n\n"
"\t:r - open this file for reading\n"
"\t:w - open this file for writing\n"
"\t:c - create a new file (O_CREATE)\n"
"\t:e - fail if the file exists (O_EXCL)\n"
"\t:t - shorten an existing file to length 0 (O_TRUNC)\n\n"
"Posix only flags:\n"
"\t:a - append to a file (O_APPEND)\n"
"\t:x - O_SYNC\n"
"\t:C - O_NOCTTY\n\n"
"Windows only flags:\n"
"\t:R - share reads (FILE_SHARE_READ)\n"
"\t:W - share writes (FILE_SHARE_WRITE)\n"
"\t:D - share deletes (FILE_SHARE_DELETE)\n"
"\t:H - FILE_ATTRIBUTE_HIDDEN\n"
"\t:O - FILE_ATTRIBUTE_READONLY\n"
"\t:F - FILE_ATTRIBUTE_OFFLINE\n"
"\t:T - FILE_ATTRIBUTE_TEMPORARY\n"
"\t:d - FILE_FLAG_DELETE_ON_CLOSE\n"
"\t:b - FILE_FLAG_NO_BUFFERING\n")
" * :r - open this file for reading\n"
" * :w - open this file for writing\n"
" * :c - create a new file (O_CREATE)\n"
" * :e - fail if the file exists (O_EXCL)\n"
" * :t - shorten an existing file to length 0 (O_TRUNC)\n\n"
"Posix only flags:\n\n"
" * :a - append to a file (O_APPEND)\n"
" * :x - O_SYNC\n"
" * :C - O_NOCTTY\n\n"
"Windows only flags:\n\n"
" * :R - share reads (FILE_SHARE_READ)\n"
" * :W - share writes (FILE_SHARE_WRITE)\n"
" * :D - share deletes (FILE_SHARE_DELETE)\n"
" * :H - FILE_ATTRIBUTE_HIDDEN\n"
" * :O - FILE_ATTRIBUTE_READONLY\n"
" * :F - FILE_ATTRIBUTE_OFFLINE\n"
" * :T - FILE_ATTRIBUTE_TEMPORARY\n"
" * :d - FILE_FLAG_DELETE_ON_CLOSE\n"
" * :b - FILE_FLAG_NO_BUFFERING\n")
},
{
"os/pipe", os_pipe,
@@ -2078,6 +2160,8 @@ void janet_lib_os(JanetTable *env) {
InitializeCriticalSection(&env_lock);
env_lock_initialized = 1;
}
#endif
#ifndef JANET_NO_PROCESSES
#endif
janet_core_cfuns(env, NULL, os_cfuns);
}

View File

@@ -985,8 +985,20 @@ static Janet cfun_parse_flush(int32_t argc, Janet *argv) {
}
static Janet cfun_parse_where(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
janet_arity(argc, 1, 3);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
if (argc > 1) {
int32_t line = janet_getinteger(argv, 1);
if (line < 1)
janet_panicf("invalid line number %d", line);
p->line = (size_t) line;
}
if (argc > 2) {
int32_t column = janet_getinteger(argv, 2);
if (column < 0)
janet_panicf("invalid column number %d", column);
p->column = (size_t) column;
}
Janet *tup = janet_tuple_begin(2);
tup[0] = janet_wrap_integer(p->line);
tup[1] = janet_wrap_integer(p->column);
@@ -1247,8 +1259,10 @@ static const JanetReg parse_cfuns[] = {
},
{
"parser/where", cfun_parse_where,
JDOC("(parser/where parser)\n\n"
"Returns the current line number and column of the parser's internal state.")
JDOC("(parser/where parser &opt line col)\n\n"
"Returns the current line number and column of the parser's internal state. If line is "
"provided, the current line number of the parser is first set to that value. If column is "
"also provided, the current column number of the parser is also first set to that value.")
},
{
"parser/eof", cfun_parse_eof,

View File

@@ -227,19 +227,21 @@ int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
/* Computes hash of an array of values */
int32_t janet_array_calchash(const Janet *array, int32_t len) {
const Janet *end = array + len;
uint32_t hash = 5381;
while (array < end)
hash = (hash << 5) + hash + janet_hash(*array++);
uint32_t hash = 0;
while (array < end) {
uint32_t elem = janet_hash(*array++);
hash ^= elem + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}
return (int32_t) hash;
}
/* Computes hash of an array of values */
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len) {
const JanetKV *end = kvs + len;
uint32_t hash = 5381;
uint32_t hash = 0;
while (kvs < end) {
hash = (hash << 5) + hash + janet_hash(kvs->key);
hash = (hash << 5) + hash + janet_hash(kvs->value);
hash ^= janet_hash(kvs->key) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
hash ^= janet_hash(kvs->value) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
kvs++;
}
return (int32_t) hash;

View File

@@ -28,6 +28,8 @@
#include <janet.h>
#endif
#include <math.h>
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal = NULL;
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_top = NULL;
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_base = NULL;
@@ -261,6 +263,21 @@ int32_t janet_hash(Janet x) {
case JANET_STRUCT:
hash = janet_struct_hash(janet_unwrap_struct(x));
break;
case JANET_NUMBER: {
double num = janet_unwrap_number(x);
if (isnan(num) || isinf(num) || num == 0) {
hash = 0;
} else {
hash = (int32_t)num;
hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
hash = (hash >> 16) ^ hash;
uint32_t lo = (uint32_t)(janet_u64(x) & 0xFFFFFFFF);
hash ^= lo + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}
break;
}
case JANET_ABSTRACT: {
JanetAbstract xx = janet_unwrap_abstract(x);
const JanetAbstractType *at = janet_abstract_type(xx);

View File

@@ -1478,7 +1478,6 @@ int janet_init(void) {
janet_vm_fiber = NULL;
janet_vm_root_fiber = NULL;
janet_vm_stackn = 0;
/* Threads */
#ifdef JANET_THREADS
janet_threads_init();
#endif

View File

@@ -1279,14 +1279,59 @@ JANET_API JanetListenerState *janet_listen(JanetStream *stream, JanetListener be
/* Shorthand for yielding to event loop in C */
JANET_NO_RETURN JANET_API void janet_await(void);
JANET_NO_RETURN JANET_API void janet_sleep_await(double sec);
/* For use inside listeners - adds a timeout to the current fiber, such that
* it will be resumed after sec seconds if no other event schedules the current fiber. */
JANET_API void janet_addtimeout(double sec);
JANET_API void janet_ev_inc_refcount(void);
JANET_API void janet_ev_dec_refcount(void);
/* Get last error from a an IO operation */
JANET_API Janet janet_ev_lasterr(void);
/* Async service for calling a function or syscall in a background thread. This is not
* as efficient in the slightest as using Streams but can be used for arbitrary blocking
* functions and syscalls. */
/* Used to pass data between the main thread and worker threads for simple tasks.
* We could just use a pointer but this prevents malloc/free in the common case
* of only a handful of arguments. */
typedef struct {
int tag;
int argi;
void *argp;
JanetFiber *fiber;
} JanetEVGenericMessage;
/* How to resume or cancel after a threaded call. Not exhaustive of the possible
* ways one might want to resume after returning from a threaded call, but should
* cover most of the common cases. For something more complicated, such as resuming
* with an abstract type or a struct, one should use janet_ev_threaded_call instead
* of janet_ev_threaded_await with a custom callback. */
#define JANET_EV_TCTAG_NIL 0 /* resume with nil */
#define JANET_EV_TCTAG_INTEGER 1 /* resume with janet_wrap_integer(argi) */
#define JANET_EV_TCTAG_STRING 2 /* resume with janet_cstringv((const char *) argp) */
#define JANET_EV_TCTAG_STRINGF 3 /* resume with janet_cstringv((const char *) argp), then call free on argp. */
#define JANET_EV_TCTAG_KEYWORD 4 /* resume with janet_ckeywordv((const char *) argp) */
#define JANET_EV_TCTAG_ERR_STRING 5 /* cancel with janet_cstringv((const char *) argp) */
#define JANET_EV_TCTAG_ERR_STRINGF 6 /* cancel with janet_cstringv((const char *) argp), then call free on argp. */
#define JANET_EV_TCTAG_ERR_KEYWORD 7 /* cancel with janet_ckeywordv((const char *) argp) */
/* Function pointer that is run in the thread pool */
typedef JanetEVGenericMessage(*JanetThreadedSubroutine)(JanetEVGenericMessage arguments);
/* Handler that is run in the main thread with the result of the JanetAsyncSubroutine */
typedef void (*JanetThreadedCallback)(JanetEVGenericMessage return_value);
/* API calls for quickly offloading some work in C to a new thread or thread pool. */
JANET_API void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage arguments, JanetThreadedCallback cb);
JANET_API void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp);
/* Callback used by janet_ev_threaded_await */
JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value);
/* Read async from a stream */
JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes);
JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes);

View File

@@ -758,6 +758,10 @@ static int line() {
kleft();
break;
case 3: /* ctrl-c */
clearlines();
gbl_sigint_flag = 1;
return -1;
case 17: /* ctrl-q */
gbl_cancel_current_repl_form = 1;
clearlines();
return -1;

View File

@@ -1,3 +1,3 @@
(import build/testmod :as testmod)
(import /build/testmod :as testmod)
(if (not= 5 (testmod/get5)) (error "testmod/get5 failed"))

View File

@@ -1,7 +1,7 @@
(use build/testmod)
(use build/testmod2)
(use build/testmod3)
(use build/test-mod-4)
(use /build/testmod)
(use /build/testmod2)
(use /build/testmod3)
(use /build/test-mod-4)
(defn main [&]
(print "Hello from executable!")

View File

@@ -128,6 +128,18 @@
(assert (not= nil (parse-error @"\xc3\x28")) "reject invalid utf-8 symbol")
(assert (not= nil (parse-error @":\xc3\x28")) "reject invalid utf-8 keyword")
# Parser line and column numbers
(defn parser-location [input &opt location]
(def p (parser/new))
(parser/consume p input)
(if location
(parser/where p ;location)
(parser/where p)))
(assert (= [1 7] (parser-location @"(+ 1 2)")) "parser location 1")
(assert (= [5 7] (parser-location @"(+ 1 2)" [5])) "parser location 2")
(assert (= [10 10] (parser-location @"(+ 1 2)" [10 10])) "parser location 3")
# String check-set
(assert (string/check-set "abc" "a") "string/check-set 1")
(assert (not (string/check-set "abc" "z")) "string/check-set 2")

View File

@@ -40,7 +40,7 @@
# or else the first read can fail. Might be a strange windows
# "bug", but needs further investigating. Otherwise, `build_win test`
# can sometimes fail on windows, leading to flaky testing.
(ev/sleep 0.2)
(ev/sleep 0.3)
(defn test-echo [msg]
(with [conn (net/connect "127.0.0.1" "8000")]
@@ -59,6 +59,7 @@
(var pipe-counter 0)
(def chan (ev/chan 10))
(let [[reader writer] (os/pipe)]
(ev/sleep 0.3)
(ev/spawn
(while (ev/read reader 3)
(++ pipe-counter))