1
0
mirror of https://github.com/janet-lang/janet synced 2024-11-04 15:56:17 +00:00

Merge branch 'master' of github.com:janet-lang/janet

This commit is contained in:
Calvin Rose 2021-07-25 20:21:59 -05:00
commit 030dd747e9
17 changed files with 486 additions and 184 deletions

View File

@ -2,6 +2,8 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 1.17.0 - Unreleased ## 1.17.0 - Unreleased
- Add `JANET_FN` and `JANET_REG` macros to more easily define C functions that export their source mapping information.
- Add `janet_interpreter_interupt` and `janet_loop1_interrupt` to interrupt the interpreter while running.
- Add `table/clear` - Add `table/clear`
- Add build option to disable the threading library without disabling all threads. - Add build option to disable the threading library without disabling all threads.
- Remove JPM from the main Janet distribution. Instead, JPM must be installed - Remove JPM from the main Janet distribution. Instead, JPM must be installed

View File

@ -74,6 +74,7 @@ conf.set('JANET_NO_PROCESSES', not get_option('processes'))
conf.set('JANET_SIMPLE_GETLINE', get_option('simple_getline')) conf.set('JANET_SIMPLE_GETLINE', get_option('simple_getline'))
conf.set('JANET_EV_NO_EPOLL', not get_option('epoll')) conf.set('JANET_EV_NO_EPOLL', not get_option('epoll'))
conf.set('JANET_NO_THREADS', get_option('threads')) conf.set('JANET_NO_THREADS', get_option('threads'))
conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt'))
if get_option('os_name') != '' if get_option('os_name') != ''
conf.set('JANET_OS_NAME', get_option('os_name')) conf.set('JANET_OS_NAME', get_option('os_name'))
endif endif

View File

@ -18,6 +18,7 @@ option('umask', type : 'boolean', value : true)
option('realpath', type : 'boolean', value : true) option('realpath', type : 'boolean', value : true)
option('simple_getline', type : 'boolean', value : false) option('simple_getline', type : 'boolean', value : false)
option('epoll', type : 'boolean', value : false) option('epoll', type : 'boolean', value : false)
option('interpreter_interrupt', type : 'boolean', value : false)
option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024) option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024)
option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200) option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200)

View File

@ -1032,7 +1032,7 @@
ret) ret)
(defn take (defn take
"Take first n elements in an indexed type. Returns new indexed instance." "Take the first n elements of an indexed or bytes type. Returns a new tuple or string, respectively."
[n ind] [n ind]
(def use-str (bytes? ind)) (def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice)) (def f (if use-str string/slice tuple/slice))
@ -1043,7 +1043,7 @@
(f ind 0 end)) (f ind 0 end))
(defn take-until (defn take-until
"Same as (take-while (complement pred) ind)." "Same as `(take-while (complement pred) ind)`."
[pred ind] [pred ind]
(def use-str (bytes? ind)) (def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice)) (def f (if use-str string/slice tuple/slice))
@ -1053,13 +1053,14 @@
(f ind 0 end)) (f ind 0 end))
(defn take-while (defn take-while
`Given a predicate, take only elements from an indexed type that satisfy `Given a predicate, take only elements from an indexed or bytes type that satisfy
the predicate, and abort on first failure. Returns a new array.` the predicate, and abort on first failure. Returns a new tuple or string, respectively.`
[pred ind] [pred ind]
(take-until (complement pred) ind)) (take-until (complement pred) ind))
(defn drop (defn drop
"Drop first n elements in an indexed type. Returns new indexed instance." ``Drop the first n elements in an indexed or bytes type. Returns a new tuple or string
instance, respectively.``
[n ind] [n ind]
(def use-str (bytes? ind)) (def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice)) (def f (if use-str string/slice tuple/slice))
@ -1070,7 +1071,7 @@
(f ind start -1)) (f ind start -1))
(defn drop-until (defn drop-until
"Same as (drop-while (complement pred) ind)." "Same as `(drop-while (complement pred) ind)`."
[pred ind] [pred ind]
(def use-str (bytes? ind)) (def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice)) (def f (if use-str string/slice tuple/slice))
@ -1080,8 +1081,8 @@
(f ind start)) (f ind start))
(defn drop-while (defn drop-while
`Given a predicate, remove elements from an indexed type that satisfy `Given a predicate, remove elements from an indexed or bytes type that satisfy
the predicate, and abort on first failure. Returns a new array.` the predicate, and abort on first failure. Returns a new tuple or string, respectively.`
[pred ind] [pred ind]
(drop-until (complement pred) ind)) (drop-until (complement pred) ind))
@ -3620,6 +3621,9 @@
(put flat :doc nil)) (put flat :doc nil))
(when (boot/config :no-sourcemaps) (when (boot/config :no-sourcemaps)
(put flat :source-map nil)) (put flat :source-map nil))
# Fix directory separators on windows to make image identical between windows and non-windows
(when-let [sm (get flat :source-map)]
(put flat :source-map [(string/replace-all "\\" "/" (sm 0)) (sm 1) (sm 2)]))
(if (v :private) (if (v :private)
(put root-env k nil) (put root-env k nil)
(put root-env k flat))) (put root-env k flat)))

View File

@ -48,6 +48,7 @@
/* #define JANET_OS_NAME my-custom-os */ /* #define JANET_OS_NAME my-custom-os */
/* #define JANET_ARCH_NAME pdp-8 */ /* #define JANET_ARCH_NAME pdp-8 */
/* #define JANET_EV_NO_EPOLL */ /* #define JANET_EV_NO_EPOLL */
/* #define JANET_NO_INTERPRETER_INTERRUPT */
/* Custom vm allocator support */ /* Custom vm allocator support */
/* #include <mimalloc.h> */ /* #include <mimalloc.h> */

View File

@ -307,12 +307,12 @@ static const JanetReg array_cfuns[] = {
{ {
"array/new-filled", cfun_array_new_filled, "array/new-filled", cfun_array_new_filled,
JDOC("(array/new-filled count &opt value)\n\n" JDOC("(array/new-filled count &opt value)\n\n"
"Creates a new array of count elements, all set to value, which defaults to nil. Returns the new array.") "Creates a new array of `count` elements, all set to `value`, which defaults to nil. Returns the new array.")
}, },
{ {
"array/fill", cfun_array_fill, "array/fill", cfun_array_fill,
JDOC("(array/fill arr &opt value)\n\n" JDOC("(array/fill arr &opt value)\n\n"
"Replace all elements of an array with value (defaulting to nil) without changing the length of the array. " "Replace all elements of an array with `value` (defaulting to nil) without changing the length of the array. "
"Returns the modified array.") "Returns the modified array.")
}, },
{ {
@ -334,7 +334,7 @@ static const JanetReg array_cfuns[] = {
{ {
"array/ensure", cfun_array_ensure, "array/ensure", cfun_array_ensure,
JDOC("(array/ensure arr capacity growth)\n\n" JDOC("(array/ensure arr capacity growth)\n\n"
"Ensures that the memory backing the array is large enough for capacity " "Ensures that the memory backing the array is large enough for `capacity` "
"items at the given rate of growth. Capacity and growth must be integers. " "items at the given rate of growth. Capacity and growth must be integers. "
"If the backing capacity is already enough, then this function does nothing. " "If the backing capacity is already enough, then this function does nothing. "
"Otherwise, the backing memory will be reallocated so that there is enough space.") "Otherwise, the backing memory will be reallocated so that there is enough space.")
@ -342,34 +342,34 @@ static const JanetReg array_cfuns[] = {
{ {
"array/slice", cfun_array_slice, "array/slice", cfun_array_slice,
JDOC("(array/slice arrtup &opt start end)\n\n" JDOC("(array/slice arrtup &opt start end)\n\n"
"Takes a slice of array or tuple from start to end. The range is half open, " "Takes a slice of array or tuple from `start` to `end`. The range is half open, "
"[start, end). Indexes can also be negative, indicating indexing from the end of the " "[start, end). Indexes can also be negative, indicating indexing from the "
"end of the array. By default, start is 0 and end is the length of the array. " "end of the array. By default, `start` is 0 and `end` is the length of the array. "
"Note that index -1 is synonymous with index (length arrtup) to allow a full " "Note that index -1 is synonymous with index `(length arrtup)` to allow a full "
"negative slice range. Returns a new array.") "negative slice range. Returns a new array.")
}, },
{ {
"array/concat", cfun_array_concat, "array/concat", cfun_array_concat,
JDOC("(array/concat arr & parts)\n\n" JDOC("(array/concat arr & parts)\n\n"
"Concatenates a variable number of arrays (and tuples) into the first argument " "Concatenates a variable number of arrays (and tuples) into the first argument, "
"which must be an array. If any of the parts are arrays or tuples, their elements will " "which must be an array. If any of the parts are arrays or tuples, their elements will "
"be inserted into the array. Otherwise, each part in parts will be appended to arr in order. " "be inserted into the array. Otherwise, each part in `parts` will be appended to `arr` in order. "
"Return the modified array arr.") "Return the modified array `arr`.")
}, },
{ {
"array/insert", cfun_array_insert, "array/insert", cfun_array_insert,
JDOC("(array/insert arr at & xs)\n\n" JDOC("(array/insert arr at & xs)\n\n"
"Insert all xs into array arr at index at. at should be an integer between " "Insert all `xs` into array `arr` at index `at`. `at` should be an integer between "
"0 and the length of the array. A negative value for at will index backwards from " "0 and the length of the array. A negative value for `at` will index backwards from "
"the end of the array, such that inserting at -1 appends to the array. " "the end of the array, such that inserting at -1 appends to the array. "
"Returns the array.") "Returns the array.")
}, },
{ {
"array/remove", cfun_array_remove, "array/remove", cfun_array_remove,
JDOC("(array/remove arr at &opt n)\n\n" JDOC("(array/remove arr at &opt n)\n\n"
"Remove up to n elements starting at index at in array arr. at can index from " "Remove up to `n` elements starting at index `at` in array `arr`. `at` can index from "
"the end of the array with a negative index, and n must be a non-negative integer. " "the end of the array with a negative index, and `n` must be a non-negative integer. "
"By default, n is 1. " "By default, `n` is 1. "
"Returns the array.") "Returns the array.")
}, },
{ {

View File

@ -942,8 +942,12 @@ Janet janet_disasm(JanetFuncDef *def) {
return janet_wrap_struct(janet_table_to_struct(ret)); return janet_wrap_struct(janet_table_to_struct(ret));
} }
/* C Function for assembly */ JANET_CORE_FN(cfun_asm,
static Janet cfun_asm(int32_t argc, Janet *argv) { "(asm assembly)",
"Returns a new function that is the compiled result of the assembly.\n"
"The syntax for the assembly can be found on the Janet website, and should correspond\n"
"to the return value of disasm. Will throw an\n"
"error on invalid assembly.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
JanetAssembleResult res; JanetAssembleResult res;
res = janet_asm(argv[0], 0); res = janet_asm(argv[0], 0);
@ -953,7 +957,24 @@ static Janet cfun_asm(int32_t argc, Janet *argv) {
return janet_wrap_function(janet_thunk(res.funcdef)); return janet_wrap_function(janet_thunk(res.funcdef));
} }
static Janet cfun_disasm(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_disasm,
"(disasm func &opt field)",
"Returns assembly that could be used to compile the given function. "
"func must be a function, not a c function. Will throw on error on a badly "
"typed argument. If given a field name, will only return that part of the function assembly. "
"Possible fields are:\n\n"
"* :arity - number of required and optional arguments.\n"
"* :min-arity - minimum number of arguments function can be called with.\n"
"* :max-arity - maximum number of arguments function can be called with.\n"
"* :vararg - true if function can take a variable number of arguments.\n"
"* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n"
"* :source - name of source file that this function was compiled from.\n"
"* :name - name of function.\n"
"* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n"
"* :constants - an array of constants referenced by this function.\n"
"* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n"
"* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n"
"* :defs - other function definitions that this function may instantiate.\n") {
janet_arity(argc, 1, 2); janet_arity(argc, 1, 2);
JanetFunction *f = janet_getfunction(argv, 0); JanetFunction *f = janet_getfunction(argv, 0);
if (argc == 2) { if (argc == 2) {
@ -976,41 +997,14 @@ static Janet cfun_disasm(int32_t argc, Janet *argv) {
} }
} }
static const JanetReg asm_cfuns[] = {
{
"asm", cfun_asm,
JDOC("(asm assembly)\n\n"
"Returns a new function that is the compiled result of the assembly.\n"
"The syntax for the assembly can be found on the Janet website, and should correspond\n"
"to the return value of disasm. Will throw an\n"
"error on invalid assembly.")
},
{
"disasm", cfun_disasm,
JDOC("(disasm func &opt field)\n\n"
"Returns assembly that could be used to compile the given function.\n"
"func must be a function, not a c function. Will throw on error on a badly\n"
"typed argument. If given a field name, will only return that part of the function assembly.\n"
"Possible fields are:\n\n"
"* :arity - number of required and optional arguments.\n\n"
"* :min-arity - minimum number of arguments function can be called with.\n\n"
"* :max-arity - maximum number of arguments function can be called with.\n\n"
"* :vararg - true if function can take a variable number of arguments.\n\n"
"* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n\n"
"* :source - name of source file that this function was compiled from.\n\n"
"* :name - name of function.\n\n"
"* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n\n"
"* :constants - an array of constants referenced by this function.\n\n"
"* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n\n"
"* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n\n"
"* :defs - other function definitions that this function may instantiate.\n")
},
{NULL, NULL, NULL}
};
/* Load the library */ /* Load the library */
void janet_lib_asm(JanetTable *env) { void janet_lib_asm(JanetTable *env) {
janet_core_cfuns(env, NULL, asm_cfuns); JanetRegExt asm_cfuns[] = {
JANET_CORE_REG("asm", cfun_asm),
JANET_CORE_REG("disasm", cfun_disasm),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, asm_cfuns);
} }
#endif #endif

View File

@ -520,21 +520,6 @@ static Janet make_supervisor_event(const char *name, JanetFiber *fiber) {
return janet_wrap_tuple(janet_tuple_n(tup, 2)); return janet_wrap_tuple(janet_tuple_n(tup, 2));
} }
/* Run a top level task */
static void run_one(JanetFiber *fiber, Janet value, JanetSignal sigin) {
fiber->flags &= ~JANET_FIBER_FLAG_SCHEDULED;
Janet res;
JanetSignal sig = janet_continue_signal(fiber, value, &res, sigin);
JanetChannel *chan = (JanetChannel *)(fiber->supervisor_channel);
if (NULL == chan) {
if (sig != JANET_SIGNAL_EVENT && sig != JANET_SIGNAL_YIELD) {
janet_stacktrace(fiber, res);
}
} else if (sig == JANET_SIGNAL_OK || (fiber->flags & (1 << sig))) {
janet_channel_push(chan, make_supervisor_event(janet_signal_names[sig], fiber), 2);
}
}
/* Common init code */ /* Common init code */
void janet_ev_init_common(void) { void janet_ev_init_common(void) {
janet_q_init(&janet_vm.spawn); janet_q_init(&janet_vm.spawn);
@ -864,7 +849,14 @@ static Janet janet_chanat_next(void *p, Janet key) {
void janet_loop1_impl(int has_timeout, JanetTimestamp timeout); void janet_loop1_impl(int has_timeout, JanetTimestamp timeout);
void janet_loop1(void) { int janet_loop_done(void) {
return !(janet_vm.listener_count ||
(janet_vm.spawn.head != janet_vm.spawn.tail) ||
janet_vm.tq_count ||
janet_vm.extra_listeners);
}
JanetFiber *janet_loop1(void) {
/* Schedule expired timers */ /* Schedule expired timers */
JanetTimeout to; JanetTimeout to;
JanetTimestamp now = ts_now(); JanetTimestamp now = ts_now();
@ -899,7 +891,21 @@ void janet_loop1(void) {
while (janet_vm.spawn.head != janet_vm.spawn.tail) { 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};
janet_q_pop(&janet_vm.spawn, &task, sizeof(task)); janet_q_pop(&janet_vm.spawn, &task, sizeof(task));
run_one(task.fiber, task.value, task.sig); task.fiber->flags &= ~JANET_FIBER_FLAG_SCHEDULED;
Janet res;
JanetSignal sig = janet_continue_signal(task.fiber, task.value, &res, task.sig);
JanetChannel *chan = (JanetChannel *)(task.fiber->supervisor_channel);
if (NULL == chan) {
if (sig != JANET_SIGNAL_EVENT && sig != JANET_SIGNAL_YIELD && sig != JANET_SIGNAL_INTERRUPT) {
janet_stacktrace(task.fiber, res);
}
} else if (sig == JANET_SIGNAL_OK || (task.fiber->flags & (1 << sig))) {
janet_channel_push(chan, make_supervisor_event(janet_signal_names[sig], task.fiber), 2);
}
if (sig == JANET_SIGNAL_INTERRUPT) {
/* On interrupts, return the interrupted fiber immediately */
return task.fiber;
}
} }
/* Poll for events */ /* Poll for events */
@ -916,11 +922,28 @@ void janet_loop1(void) {
janet_loop1_impl(has_timeout, to.when); janet_loop1_impl(has_timeout, to.when);
} }
} }
/* No fiber was interrupted */
return NULL;
}
/* Same as janet_interpreter_interrupt, but will also
* break out of the event loop if waiting for an event
* (say, waiting for ev/sleep to finish). Does this by pushing
* an empty event to the event loop. */
void janet_loop1_interrupt(JanetVM *vm) {
janet_interpreter_interrupt(vm);
JanetEVGenericMessage msg = {0};
JanetCallback cb = NULL;
janet_ev_post_event(vm, cb, msg);
} }
void janet_loop(void) { void janet_loop(void) {
while (janet_vm.listener_count || (janet_vm.spawn.head != janet_vm.spawn.tail) || janet_vm.tq_count || janet_vm.extra_listeners) { while (!janet_loop_done()) {
janet_loop1(); JanetFiber *interrupted_fiber = janet_loop1();
if (NULL != interrupted_fiber) {
janet_schedule(interrupted_fiber, janet_wrap_nil());
}
} }
} }
@ -945,8 +968,9 @@ static void janet_ev_setup_selfpipe(void) {
static void janet_ev_handle_selfpipe(void) { static void janet_ev_handle_selfpipe(void) {
JanetSelfPipeEvent response; JanetSelfPipeEvent response;
while (read(janet_vm.selfpipe[0], &response, sizeof(response)) > 0) { while (read(janet_vm.selfpipe[0], &response, sizeof(response)) > 0) {
if (NULL != response.cb) {
response.cb(response.msg); response.cb(response.msg);
janet_ev_dec_refcount(); }
} }
} }
@ -1014,9 +1038,10 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) {
if (0 == completionKey) { if (0 == completionKey) {
/* Custom event */ /* Custom event */
JanetSelfPipeEvent *response = (JanetSelfPipeEvent *)(overlapped); JanetSelfPipeEvent *response = (JanetSelfPipeEvent *)(overlapped);
if (NULL != response->cb) {
response->cb(response->msg); response->cb(response->msg);
}
janet_free(response); janet_free(response);
janet_ev_dec_refcount();
} else { } else {
/* Normal event */ /* Normal event */
JanetStream *stream = (JanetStream *) completionKey; JanetStream *stream = (JanetStream *) completionKey;
@ -1310,6 +1335,45 @@ void janet_ev_deinit(void) {
* End poll implementation * End poll implementation
*/ */
/*
* Generic Callback system. Post a function pointer + data to the event loop (from another
* thread or even a signal handler). Allows posting events from another thread or signal handler.
*/
void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGenericMessage msg) {
vm = vm ? vm : &janet_vm;
#ifdef JANET_WINDOWS
JanetHandle iocp = vm->iocp;
JanetSelfPipeEvent *event = janet_malloc(sizeof(JanetSelfPipeEvent));
if (NULL == event) {
JANET_OUT_OF_MEMORY;
}
event->msg = msg;
event->cb = cb;
janet_assert(PostQueuedCompletionStatus(iocp,
sizeof(JanetSelfPipeEvent),
0,
(LPOVERLAPPED) event),
"failed to post completion event");
#else
JanetSelfPipeEvent event;
event.msg = msg;
event.cb = cb;
int fd = vm->selfpipe;
/* handle a bit of back pressure before giving up. */
int tries = 4;
while (tries > 0) {
int status;
do {
status = write(fd, &event, sizeof(event));
} while (status == -1 && errno == EINTR);
if (status > 0) break;
sleep(1);
tries--;
}
janet_assert(tries > 0, "failed to write event to self-pipe");
#endif
}
/* /*
* Threaded calls * Threaded calls
*/ */
@ -1391,6 +1455,7 @@ void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage ar
/* Default callback for janet_ev_threaded_await. */ /* Default callback for janet_ev_threaded_await. */
void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) { void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value) {
janet_ev_dec_refcount();
if (return_value.fiber == NULL) { if (return_value.fiber == NULL) {
return; return;
} }
@ -1808,6 +1873,18 @@ JanetAsyncStatus ev_machine_write(JanetListenerState *s, JanetAsyncEvent event)
} else } else
#endif #endif
{ {
/*
* File handles in IOCP need to specify this if they are writing to the
* ends of files, like how this is used here.
* If the underlying resource doesn't support seeking
* byte offsets, they will be ignored
* but this otherwise writes to the end of the file in question
* Right now, os/open streams aren't seekable, so this works.
* for more details see the lpOverlapped parameter in
* https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
*/
state->overlapped.Offset = (DWORD) 0xFFFFFFFF;
state->overlapped.OffsetHigh = (DWORD) 0xFFFFFFFF;
status = WriteFile(s->stream->handle, bytes, len, NULL, &state->overlapped); status = WriteFile(s->stream->handle, bytes, len, NULL, &state->overlapped);
if (!status && (ERROR_IO_PENDING != WSAGetLastError())) { if (!status && (ERROR_IO_PENDING != WSAGetLastError())) {
janet_cancel(s->fiber, janet_ev_lasterr()); janet_cancel(s->fiber, janet_ev_lasterr());

View File

@ -353,6 +353,7 @@ static const JanetAbstractType ProcAT;
#define JANET_PROC_OWNS_STDIN 16 #define JANET_PROC_OWNS_STDIN 16
#define JANET_PROC_OWNS_STDOUT 32 #define JANET_PROC_OWNS_STDOUT 32
#define JANET_PROC_OWNS_STDERR 64 #define JANET_PROC_OWNS_STDERR 64
#define JANET_PROC_ALLOW_ZOMBIE 128
typedef struct { typedef struct {
int flags; int flags;
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
@ -410,6 +411,7 @@ static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
/* Callback that is called in main thread when subroutine completes. */ /* Callback that is called in main thread when subroutine completes. */
static void janet_proc_wait_cb(JanetEVGenericMessage args) { static void janet_proc_wait_cb(JanetEVGenericMessage args) {
janet_ev_dec_refcount();
int status = args.argi; int status = args.argi;
JanetProc *proc = (JanetProc *) args.argp; JanetProc *proc = (JanetProc *) args.argp;
if (NULL != proc) { if (NULL != proc) {
@ -434,12 +436,14 @@ static int janet_proc_gc(void *p, size_t s) {
JanetProc *proc = (JanetProc *) p; JanetProc *proc = (JanetProc *) p;
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
if (!(proc->flags & JANET_PROC_CLOSED)) { if (!(proc->flags & JANET_PROC_CLOSED)) {
if (!(proc->flags & JANET_PROC_ALLOW_ZOMBIE)) {
TerminateProcess(proc->pHandle, 1); TerminateProcess(proc->pHandle, 1);
}
CloseHandle(proc->pHandle); CloseHandle(proc->pHandle);
CloseHandle(proc->tHandle); CloseHandle(proc->tHandle);
} }
#else #else
if (!(proc->flags & JANET_PROC_WAITED)) { if (!(proc->flags & (JANET_PROC_WAITED | JANET_PROC_ALLOW_ZOMBIE))) {
/* Kill and wait to prevent zombies */ /* Kill and wait to prevent zombies */
kill(proc->pid, SIGKILL); kill(proc->pid, SIGKILL);
int status; int status;
@ -759,7 +763,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
/* Get flags */ /* Get flags */
uint64_t flags = 0; uint64_t flags = 0;
if (argc > 1) { if (argc > 1) {
flags = janet_getflags(argv, 1, "epx"); flags = janet_getflags(argv, 1, "epxd");
} }
/* Get environment */ /* Get environment */
@ -777,7 +781,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE; JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE; JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
int pipe_errflag = 0; /* Track errors setting up pipes */ int pipe_errflag = 0; /* Track errors setting up pipes */
int pipe_owner_flags = 0; int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0;
/* Get optional redirections */ /* Get optional redirections */
if (argc > 2) { if (argc > 2) {
@ -2094,12 +2098,13 @@ static const JanetReg os_cfuns[] = {
"os/execute", os_execute, "os/execute", os_execute,
JDOC("(os/execute args &opt flags env)\n\n" JDOC("(os/execute args &opt flags env)\n\n"
"Execute a program on the system and pass it string arguments. `flags` " "Execute a program on the system and pass it string arguments. `flags` "
"is a keyword that modifies how the program will execute.\n\n" "is a keyword that modifies how the program will execute.\n"
"* :e - enables passing an environment to the program. Without :e, the " "* :e - enables passing an environment to the program. Without :e, the "
"current environment is inherited.\n\n" "current environment is inherited.\n"
"* :p - allows searching the current PATH for the binary to execute. " "* :p - allows searching the current PATH for the binary to execute. "
"Without this flag, binaries must use absolute paths.\n\n" "Without this flag, binaries must use absolute paths.\n"
"* :x - raise error if exit code is non-zero.\n\n" "* :x - raise error if exit code is non-zero.\n"
"* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n"
"`env` is a table or struct mapping environment variables to values. It can also " "`env` is a table or struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. " "contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. "
"These arguments should be core/file values. " "These arguments should be core/file values. "

View File

@ -1541,7 +1541,11 @@ static JanetPeg *compile_peg(Janet x) {
* C Functions * C Functions
*/ */
static Janet cfun_peg_compile(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_peg_compile,
"(peg/compile peg)",
"Compiles a peg source data structure into a <core/peg>. This will speed up matching "
"if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to suppliment "
"the grammar of the peg for otherwise undefined peg keywords.") {
janet_fixarity(argc, 1); janet_fixarity(argc, 1);
JanetPeg *peg = compile_peg(argv[0]); JanetPeg *peg = compile_peg(argv[0]);
return janet_wrap_abstract(peg); return janet_wrap_abstract(peg);
@ -1604,13 +1608,18 @@ static void peg_call_reset(PegCall *c) {
c->s.tags->count = 0; c->s.tags->count = 0;
} }
static Janet cfun_peg_match(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_peg_match,
"(peg/match peg text &opt start & args)",
"Match a Parsing Expression Grammar to a byte string and return an array of captured values. "
"Returns nil if text does not match the language defined by peg. The syntax of PEGs is documented on the Janet website.") {
PegCall c = peg_cfun_init(argc, argv, 0); PegCall c = peg_cfun_init(argc, argv, 0);
const uint8_t *result = peg_rule(&c.s, c.s.bytecode, c.bytes.bytes + c.start); const uint8_t *result = peg_rule(&c.s, c.s.bytecode, c.bytes.bytes + c.start);
return result ? janet_wrap_array(c.s.captures) : janet_wrap_nil(); return result ? janet_wrap_array(c.s.captures) : janet_wrap_nil();
} }
static Janet cfun_peg_find(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_peg_find,
"(peg/find peg text &opt start & args)",
"Find first index where the peg matches in text. Returns an integer, or nil if not found.") {
PegCall c = peg_cfun_init(argc, argv, 0); PegCall c = peg_cfun_init(argc, argv, 0);
for (int32_t i = c.start; i < c.bytes.len; i++) { for (int32_t i = c.start; i < c.bytes.len; i++) {
peg_call_reset(&c); peg_call_reset(&c);
@ -1620,7 +1629,9 @@ static Janet cfun_peg_find(int32_t argc, Janet *argv) {
return janet_wrap_nil(); return janet_wrap_nil();
} }
static Janet cfun_peg_find_all(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_peg_find_all,
"(peg/find-all peg text &opt start & args)",
"Find all indexes where the peg matches in text. Returns an array of integers.") {
PegCall c = peg_cfun_init(argc, argv, 0); PegCall c = peg_cfun_init(argc, argv, 0);
JanetArray *ret = janet_array(0); JanetArray *ret = janet_array(0);
for (int32_t i = c.start; i < c.bytes.len; i++) { for (int32_t i = c.start; i < c.bytes.len; i++) {
@ -1659,11 +1670,16 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) {
return janet_wrap_buffer(ret); return janet_wrap_buffer(ret);
} }
static Janet cfun_peg_replace_all(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_peg_replace_all,
"(peg/replace-all peg repl text &opt start & args)",
"Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.") {
return cfun_peg_replace_generic(argc, argv, 0); return cfun_peg_replace_generic(argc, argv, 0);
} }
static Janet cfun_peg_replace(int32_t argc, Janet *argv) { JANET_CORE_FN(cfun_peg_replace,
"(peg/replace peg repl text &opt start & args)",
"Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. "
"If no matches are found, returns the input string in a new buffer.") {
return cfun_peg_replace_generic(argc, argv, 1); return cfun_peg_replace_generic(argc, argv, 1);
} }
@ -1688,47 +1704,18 @@ static Janet peg_next(void *p, Janet key) {
return janet_nextmethod(peg_methods, key); return janet_nextmethod(peg_methods, key);
} }
static const JanetReg peg_cfuns[] = {
{
"peg/compile", cfun_peg_compile,
JDOC("(peg/compile peg)\n\n"
"Compiles a peg source data structure into a <core/peg>. This will speed up matching "
"if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to suppliment "
"the grammar of the peg for otherwise undefined peg keywords.")
},
{
"peg/match", cfun_peg_match,
JDOC("(peg/match peg text &opt start & args)\n\n"
"Match a Parsing Expression Grammar to a byte string and return an array of captured values. "
"Returns nil if text does not match the language defined by peg. The syntax of PEGs is documented on the Janet website.")
},
{
"peg/find", cfun_peg_find,
JDOC("(peg/find peg text &opt start & args)\n\n"
"Find first index where the peg matches in text. Returns an integer, or nil if not found.")
},
{
"peg/find-all", cfun_peg_find_all,
JDOC("(peg/find-all peg text &opt start & args)\n\n"
"Find all indexes where the peg matches in text. Returns an array of integers.")
},
{
"peg/replace", cfun_peg_replace,
JDOC("(peg/replace peg repl text &opt start & args)\n\n"
"Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. "
"If no matches are found, returns the input string in a new buffer.")
},
{
"peg/replace-all", cfun_peg_replace_all,
JDOC("(peg/replace-all peg repl text &opt start & args)\n\n"
"Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.")
},
{NULL, NULL, NULL}
};
/* Load the peg module */ /* Load the peg module */
void janet_lib_peg(JanetTable *env) { void janet_lib_peg(JanetTable *env) {
janet_core_cfuns(env, NULL, peg_cfuns); JanetRegExt cfuns[] = {
JANET_CORE_REG("peg/compile", cfun_peg_compile),
JANET_CORE_REG("peg/match", cfun_peg_match),
JANET_CORE_REG("peg/find", cfun_peg_find),
JANET_CORE_REG("peg/find-all", cfun_peg_find_all),
JANET_CORE_REG("peg/replace", cfun_peg_replace),
JANET_CORE_REG("peg/replace-all", cfun_peg_replace_all),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, cfuns);
janet_register_abstract_type(&janet_peg_type); janet_register_abstract_type(&janet_peg_type);
} }

View File

@ -28,6 +28,10 @@
JANET_THREAD_LOCAL JanetVM janet_vm; JANET_THREAD_LOCAL JanetVM janet_vm;
JanetVM *janet_local_vm(void) {
return &janet_vm;
}
JanetVM *janet_vm_alloc(void) { JanetVM *janet_vm_alloc(void) {
JanetVM *mem = janet_malloc(sizeof(JanetVM)); JanetVM *mem = janet_malloc(sizeof(JanetVM));
if (NULL == mem) { if (NULL == mem) {
@ -47,3 +51,11 @@ void janet_vm_save(JanetVM *into) {
void janet_vm_load(JanetVM *from) { void janet_vm_load(JanetVM *from) {
janet_vm = *from; janet_vm = *from;
} }
/* Trigger suspension of the Janet vm by trying to
* exit the interpeter loop when convenient. You can optionally
* use NULL to interrupt the current VM when convenient */
void janet_interpreter_interrupt(JanetVM *vm) {
vm = vm ? vm : &janet_vm;
vm->auto_suspend = 1;
}

View File

@ -72,6 +72,10 @@ struct JanetVM {
/* How many VM stacks have been entered */ /* How many VM stacks have been entered */
int stackn; int stackn;
/* If this flag is true, suspend on function calls and backwards jumps.
* When this occurs, this flag will be reset to 0. */
int auto_suspend;
/* The current running fiber on the current thread. /* The current running fiber on the current thread.
* Set and unset by janet_run. */ * Set and unset by janet_run. */
JanetFiber *fiber; JanetFiber *fiber;

View File

@ -369,82 +369,148 @@ void janet_register(const char *name, JanetCFunction cfun) {
janet_table_put(janet_vm.registry, key, value); janet_table_put(janet_vm.registry, key, value);
} }
/* Add sourcemapping and documentation to a binding table */
static void janet_add_meta(JanetTable *table, const char *doc, const char *source_file, int32_t source_line) {
if (doc) {
janet_table_put(table, janet_ckeywordv("doc"), janet_cstringv(doc));
}
if (source_file && source_line) {
Janet triple[3];
triple[0] = janet_cstringv(source_file);
triple[1] = janet_wrap_integer(source_line);
triple[2] = janet_wrap_integer(1);
Janet value = janet_wrap_tuple(janet_tuple_n(triple, 3));
janet_table_put(table, janet_ckeywordv("source-map"), value);
}
}
/* Add a def to an environment */ /* Add a def to an environment */
void janet_def(JanetTable *env, const char *name, Janet val, const char *doc) { void janet_def_sm(JanetTable *env, const char *name, Janet val, const char *doc, const char *source_file, int32_t source_line) {
JanetTable *subt = janet_table(2); JanetTable *subt = janet_table(2);
janet_table_put(subt, janet_ckeywordv("value"), val); janet_table_put(subt, janet_ckeywordv("value"), val);
if (doc) janet_add_meta(subt, doc, source_file, source_line);
janet_table_put(subt, janet_ckeywordv("doc"), janet_cstringv(doc));
janet_table_put(env, janet_csymbolv(name), janet_wrap_table(subt)); janet_table_put(env, janet_csymbolv(name), janet_wrap_table(subt));
} }
void janet_def(JanetTable *env, const char *name, Janet value, const char *doc) {
janet_def_sm(env, name, value, doc, NULL, 0);
}
/* Add a var to the environment */ /* Add a var to the environment */
void janet_var(JanetTable *env, const char *name, Janet val, const char *doc) { void janet_var_sm(JanetTable *env, const char *name, Janet val, const char *doc, const char *source_file, int32_t source_line) {
JanetArray *array = janet_array(1); JanetArray *array = janet_array(1);
JanetTable *subt = janet_table(2); JanetTable *subt = janet_table(2);
janet_array_push(array, val); janet_array_push(array, val);
janet_table_put(subt, janet_ckeywordv("ref"), janet_wrap_array(array)); janet_table_put(subt, janet_ckeywordv("ref"), janet_wrap_array(array));
if (doc) janet_add_meta(subt, doc, source_file, source_line);
janet_table_put(subt, janet_ckeywordv("doc"), janet_cstringv(doc));
janet_table_put(env, janet_csymbolv(name), janet_wrap_table(subt)); janet_table_put(env, janet_csymbolv(name), janet_wrap_table(subt));
} }
void janet_var(JanetTable *env, const char *name, Janet val, const char *doc) {
janet_var_sm(env, name, val, doc, NULL, 0);
}
/* Load many cfunctions at once */ /* Load many cfunctions at once */
static void _janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns, int defprefix) { typedef struct {
uint8_t *longname_buffer = NULL; uint8_t *longname_buffer;
size_t prefixlen = 0; size_t prefixlen;
size_t bufsize = 0; size_t bufsize;
} JanetNameBuffer;
static void cfuns_namebuf_init(JanetNameBuffer *nb, const char *regprefix) {
nb->longname_buffer = NULL;
nb->prefixlen = 0;
nb->bufsize = 0;
if (NULL != regprefix) { if (NULL != regprefix) {
prefixlen = strlen(regprefix); nb->prefixlen = strlen(regprefix);
bufsize = prefixlen + 256; nb->bufsize = nb->prefixlen + 256;
longname_buffer = janet_malloc(bufsize); nb->longname_buffer = janet_malloc(nb->bufsize);
if (NULL == longname_buffer) { if (NULL == nb->longname_buffer) {
JANET_OUT_OF_MEMORY; JANET_OUT_OF_MEMORY;
} }
safe_memcpy(longname_buffer, regprefix, prefixlen); safe_memcpy(nb->longname_buffer, regprefix, nb->prefixlen);
longname_buffer[prefixlen] = '/'; nb->longname_buffer[nb->prefixlen] = '/';
prefixlen++; nb->prefixlen++;
} }
while (cfuns->name) { }
Janet name;
if (NULL != regprefix) { static Janet cfuns_namebuf_getname(JanetNameBuffer *nb, const char *suffix) {
if (nb->prefixlen) {
int32_t nmlen = 0; int32_t nmlen = 0;
while (cfuns->name[nmlen]) nmlen++; while (suffix[nmlen]) nmlen++;
int32_t totallen = (int32_t) prefixlen + nmlen; int32_t totallen = (int32_t) nb->prefixlen + nmlen;
if ((size_t) totallen > bufsize) { if ((size_t) totallen > nb->bufsize) {
bufsize = (size_t)(totallen) + 128; nb->bufsize = (size_t)(totallen) + 128;
longname_buffer = janet_realloc(longname_buffer, bufsize); nb->longname_buffer = janet_realloc(nb->longname_buffer, nb->bufsize);
if (NULL == longname_buffer) { if (NULL == nb->longname_buffer) {
JANET_OUT_OF_MEMORY; JANET_OUT_OF_MEMORY;
} }
} }
safe_memcpy(longname_buffer + prefixlen, cfuns->name, nmlen); safe_memcpy(nb->longname_buffer + nb->prefixlen, suffix, nmlen);
name = janet_wrap_symbol(janet_symbol(longname_buffer, totallen)); return janet_wrap_symbol(janet_symbol(nb->longname_buffer, totallen));
} else { } else {
name = janet_csymbolv(cfuns->name); return janet_csymbolv(suffix);
} }
Janet fun = janet_wrap_cfunction(cfuns->cfun); }
if (defprefix) {
JanetTable *subt = janet_table(2); static void cfuns_namebuf_deinit(JanetNameBuffer *nb) {
janet_table_put(subt, janet_ckeywordv("value"), fun); janet_free(nb->longname_buffer);
if (cfuns->documentation)
janet_table_put(subt, janet_ckeywordv("doc"), janet_cstringv(cfuns->documentation));
janet_table_put(env, name, janet_wrap_table(subt));
} else {
janet_def(env, cfuns->name, fun, cfuns->documentation);
}
janet_table_put(janet_vm.registry, fun, name);
cfuns++;
}
(janet_free)(longname_buffer);
} }
void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns) { void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
_janet_cfuns_prefix(env, regprefix, cfuns, 1); JanetNameBuffer nb;
cfuns_namebuf_init(&nb, regprefix);
while (cfuns->name) {
Janet name = cfuns_namebuf_getname(&nb, cfuns->name);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
janet_def(env, cfuns->name, fun, cfuns->documentation);
janet_table_put(janet_vm.registry, fun, name);
cfuns++;
}
cfuns_namebuf_deinit(&nb);
} }
void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) { void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
_janet_cfuns_prefix(env, regprefix, cfuns, 0); JanetNameBuffer nb;
cfuns_namebuf_init(&nb, regprefix);
while (cfuns->name) {
Janet name = cfuns_namebuf_getname(&nb, cfuns->name);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
JanetTable *subt = janet_table(2);
janet_table_put(subt, janet_ckeywordv("value"), fun);
janet_add_meta(subt, cfuns->documentation, NULL, 0);
janet_table_put(env, name, janet_wrap_table(subt));
janet_table_put(janet_vm.registry, fun, name);
cfuns++;
}
cfuns_namebuf_deinit(&nb);
}
void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
JanetNameBuffer nb;
cfuns_namebuf_init(&nb, regprefix);
while (cfuns->name) {
Janet name = cfuns_namebuf_getname(&nb, cfuns->name);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
janet_def_sm(env, cfuns->name, fun, cfuns->documentation, cfuns->source_file, cfuns->source_line);
janet_table_put(janet_vm.registry, fun, name);
cfuns++;
}
cfuns_namebuf_deinit(&nb);
}
void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
JanetNameBuffer nb;
cfuns_namebuf_init(&nb, regprefix);
while (cfuns->name) {
Janet name = cfuns_namebuf_getname(&nb, cfuns->name);
Janet fun = janet_wrap_cfunction(cfuns->cfun);
JanetTable *subt = janet_table(2);
janet_table_put(subt, janet_ckeywordv("value"), fun);
janet_add_meta(subt, cfuns->documentation, cfuns->source_file, cfuns->source_line);
janet_table_put(env, name, janet_wrap_table(subt));
janet_table_put(janet_vm.registry, fun, name);
cfuns++;
}
cfuns_namebuf_deinit(&nb);
} }
/* Abstract type introspection */ /* Abstract type introspection */
@ -485,6 +551,20 @@ void janet_core_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cf
cfuns++; cfuns++;
} }
} }
void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p, const void *sf, int32_t sl) {
(void) sf, sl;
janet_core_def(env, name, x, p);
}
void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
(void) regprefix;
while (cfuns->name) {
Janet fun = janet_wrap_cfunction(cfuns->cfun);
janet_core_def(env, cfuns->name, fun, cfuns->documentation);
cfuns++;
}
}
#endif #endif
JanetBinding janet_resolve_ext(JanetTable *env, const uint8_t *sym) { JanetBinding janet_resolve_ext(JanetTable *env, const uint8_t *sym) {

View File

@ -90,11 +90,19 @@ Janet janet_next_impl(Janet ds, Janet key, int is_interpreter);
/* Inside the janet core, defining globals is different /* Inside the janet core, defining globals is different
* at bootstrap time and normal runtime */ * at bootstrap time and normal runtime */
#ifdef JANET_BOOTSTRAP #ifdef JANET_BOOTSTRAP
#define JANET_CORE_REG JANET_REG
#define JANET_CORE_FN JANET_FN
#define janet_core_def janet_def #define janet_core_def janet_def
#define janet_core_cfuns janet_cfuns #define janet_core_cfuns janet_cfuns
#define janet_core_def_sm janet_def_sm
#define janet_core_cfuns_ext janet_cfuns_ext
#else #else
#define JANET_CORE_REG JANET_REG_
#define JANET_CORE_FN JANET_FN_
void janet_core_def(JanetTable *env, const char *name, Janet x, const void *p); void janet_core_def(JanetTable *env, const char *name, Janet x, const void *p);
void janet_core_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns); void janet_core_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns);
void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p, const void *sf, int32_t sl);
void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns);
#endif #endif
/* Clock gettime */ /* Clock gettime */

View File

@ -111,6 +111,17 @@
janet_panicf("expected %T, got %v", (TS), (X)); \ janet_panicf("expected %T, got %v", (TS), (X)); \
} \ } \
} while (0) } while (0)
#ifdef JANET_NO_INTERPRETER_INTERRUPT
#define vm_maybe_auto_suspend(COND)
#else
#define vm_maybe_auto_suspend(COND) do { \
if ((COND) && janet_vm.auto_suspend) { \
janet_vm.auto_suspend = 0; \
fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \
vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \
} \
} while (0)
#endif
/* Templates for certain patterns in opcodes */ /* Templates for certain patterns in opcodes */
#define vm_binop_immediate(op)\ #define vm_binop_immediate(op)\
@ -746,11 +757,13 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
VM_OP(JOP_JUMP) VM_OP(JOP_JUMP)
pc += DS; pc += DS;
vm_maybe_auto_suspend(DS < 0);
vm_next(); vm_next();
VM_OP(JOP_JUMP_IF) VM_OP(JOP_JUMP_IF)
if (janet_truthy(stack[A])) { if (janet_truthy(stack[A])) {
pc += ES; pc += ES;
vm_maybe_auto_suspend(ES < 0);
} else { } else {
pc++; pc++;
} }
@ -761,12 +774,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
pc++; pc++;
} else { } else {
pc += ES; pc += ES;
vm_maybe_auto_suspend(ES < 0);
} }
vm_next(); vm_next();
VM_OP(JOP_JUMP_IF_NIL) VM_OP(JOP_JUMP_IF_NIL)
if (janet_checktype(stack[A], JANET_NIL)) { if (janet_checktype(stack[A], JANET_NIL)) {
pc += ES; pc += ES;
vm_maybe_auto_suspend(ES < 0);
} else { } else {
pc++; pc++;
} }
@ -777,6 +792,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
pc++; pc++;
} else { } else {
pc += ES; pc += ES;
vm_maybe_auto_suspend(ES < 0);
} }
vm_next(); vm_next();
@ -950,6 +966,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_checkgc_pcnext(); vm_checkgc_pcnext();
VM_OP(JOP_CALL) { VM_OP(JOP_CALL) {
vm_maybe_auto_suspend(1);
Janet callee = stack[E]; Janet callee = stack[E];
if (fiber->stacktop > fiber->maxstack) { if (fiber->stacktop > fiber->maxstack) {
vm_throw("stack overflow"); vm_throw("stack overflow");
@ -989,6 +1006,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
} }
VM_OP(JOP_TAILCALL) { VM_OP(JOP_TAILCALL) {
vm_maybe_auto_suspend(1);
Janet callee = stack[D]; Janet callee = stack[D];
if (fiber->stacktop > fiber->maxstack) { if (fiber->stacktop > fiber->maxstack) {
vm_throw("stack overflow"); vm_throw("stack overflow");
@ -1035,6 +1053,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
VM_OP(JOP_RESUME) { VM_OP(JOP_RESUME) {
Janet retreg; Janet retreg;
vm_maybe_auto_suspend(1);
vm_assert_type(stack[B], JANET_FIBER); vm_assert_type(stack[B], JANET_FIBER);
JanetFiber *child = janet_unwrap_fiber(stack[B]); JanetFiber *child = janet_unwrap_fiber(stack[B]);
if (janet_check_can_resume(child, &retreg)) { if (janet_check_can_resume(child, &retreg)) {
@ -1519,6 +1538,9 @@ int janet_init(void) {
/* Core env */ /* Core env */
janet_vm.core_env = NULL; janet_vm.core_env = NULL;
/* Auto suspension */
janet_vm.auto_suspend = 0;
/* Dynamic bindings */ /* Dynamic bindings */
janet_vm.top_dyns = NULL; janet_vm.top_dyns = NULL;

View File

@ -355,6 +355,7 @@ typedef enum {
} JanetSignal; } JanetSignal;
#define JANET_SIGNAL_EVENT JANET_SIGNAL_USER9 #define JANET_SIGNAL_EVENT JANET_SIGNAL_USER9
#define JANET_SIGNAL_INTERRUPT JANET_SIGNAL_USER8
/* Fiber statuses - mostly corresponds to signals. */ /* Fiber statuses - mostly corresponds to signals. */
typedef enum { typedef enum {
@ -402,6 +403,7 @@ typedef struct JanetKV JanetKV;
typedef struct JanetStackFrame JanetStackFrame; typedef struct JanetStackFrame JanetStackFrame;
typedef struct JanetAbstractType JanetAbstractType; typedef struct JanetAbstractType JanetAbstractType;
typedef struct JanetReg JanetReg; typedef struct JanetReg JanetReg;
typedef struct JanetRegExt JanetRegExt;
typedef struct JanetMethod JanetMethod; typedef struct JanetMethod JanetMethod;
typedef struct JanetSourceMapping JanetSourceMapping; typedef struct JanetSourceMapping JanetSourceMapping;
typedef struct JanetView JanetView; typedef struct JanetView JanetView;
@ -1092,6 +1094,14 @@ struct JanetReg {
const char *documentation; const char *documentation;
}; };
struct JanetRegExt {
const char *name;
JanetCFunction cfun;
const char *documentation;
const char *source_file;
int32_t source_line;
};
struct JanetMethod { struct JanetMethod {
const char *name; const char *name;
JanetCFunction cfun; JanetCFunction cfun;
@ -1281,6 +1291,31 @@ extern JANET_API const JanetAbstractType janet_stream_type;
/* Run the event loop */ /* Run the event loop */
JANET_API void janet_loop(void); JANET_API void janet_loop(void);
/* Run the event loop, but allow for user scheduled interrupts triggered
* by janet_loop1_interrupt being called in library code, a signal handler, or
* another thread.
*
* Example:
*
* while (!janet_loop_done()) {
* // One turn of the event loop
* JanetFiber *interrupted_fiber = janet_loop1();
* // interrupted_fiber may be NULL
* // do some work here periodically...
* if (NULL != interrupted_fiber) {
* if (cancel_interrupted_fiber) {
* janet_cancel(interrupted_fiber, janet_cstringv("fiber was interrupted for [reason]"));
* } else {
* janet_schedule(interrupted_fiber, janet_wrap_nil());
* }
* }
* }
*
*/
JANET_API int janet_loop_done(void);
JANET_API JanetFiber *janet_loop1(void);
JANET_API void janet_loop1_interrupt(JanetVM *vm);
/* Wrapper around streams */ /* Wrapper around streams */
JANET_API JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods); JANET_API JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods);
JANET_API void janet_stream_close(JanetStream *stream); JANET_API void janet_stream_close(JanetStream *stream);
@ -1344,13 +1379,20 @@ typedef struct {
/* Function pointer that is run in the thread pool */ /* Function pointer that is run in the thread pool */
typedef JanetEVGenericMessage(*JanetThreadedSubroutine)(JanetEVGenericMessage arguments); typedef JanetEVGenericMessage(*JanetThreadedSubroutine)(JanetEVGenericMessage arguments);
/* Handler that is run in the main thread with the result of the JanetAsyncSubroutine */ /* Handler for events posted to the event loop */
typedef void (*JanetCallback)(JanetEVGenericMessage return_value);
/* Handler that is run in the main thread with the result of the JanetAsyncSubroutine (same as JanetCallback) */
typedef void (*JanetThreadedCallback)(JanetEVGenericMessage return_value); typedef void (*JanetThreadedCallback)(JanetEVGenericMessage return_value);
/* API calls for quickly offloading some work in C to a new thread or thread pool. */ /* 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_call(JanetThreadedSubroutine fp, JanetEVGenericMessage arguments, JanetThreadedCallback cb);
JANET_NO_RETURN JANET_API void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp); JANET_NO_RETURN JANET_API void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp);
/* Post callback + userdata to an event loop. Takes the vm parameter to allow posting from other
* threads or signal handlers. Use NULL to post to the current thread. */
JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGenericMessage msg);
/* Callback used by janet_ev_threaded_await */ /* Callback used by janet_ev_threaded_await */
JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value); JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value);
@ -1660,9 +1702,11 @@ JANET_API int32_t janet_sorted_keys(const JanetKV *dict, int32_t cap, int32_t *i
JANET_API int janet_init(void); JANET_API int janet_init(void);
JANET_API void janet_deinit(void); JANET_API void janet_deinit(void);
JANET_API JanetVM *janet_vm_alloc(void); JANET_API JanetVM *janet_vm_alloc(void);
JANET_API JanetVM *janet_local_vm(void);
JANET_API void janet_vm_free(JanetVM *vm); JANET_API void janet_vm_free(JanetVM *vm);
JANET_API void janet_vm_save(JanetVM *into); JANET_API void janet_vm_save(JanetVM *into);
JANET_API void janet_vm_load(JanetVM *from); JANET_API void janet_vm_load(JanetVM *from);
JANET_API void janet_interpreter_interrupt(JanetVM *vm);
JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out); JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out);
JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig); JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig);
JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f); JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f);
@ -1715,6 +1759,54 @@ JANET_API Janet janet_resolve_core(const char *name);
/* Shorthand for janet C function declarations */ /* Shorthand for janet C function declarations */
#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv) #define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv)
/* Declare a C function with documentation and source mapping */
#define JANET_REG_END {NULL, NULL, NULL, NULL, 0}
/* no docstrings or sourcemaps */
#define JANET_REG_(JNAME, CNAME) {JNAME, CNAME, NULL, NULL, 0}
#define JANET_FN_(CNAME, USAGE, DOCSTRING) \
static Janet CNAME (int32_t argc, Janet *argv)
/* sourcemaps only */
#define JANET_REG_S(JNAME, CNAME) {JNAME, CNAME, NULL, __FILE__, CNAME##_sourceline_}
#define JANET_FN_S(CNAME, USAGE, DOCSTRING) \
static int32_t CNAME##_sourceline_ = __LINE__; \
static Janet CNAME (int32_t argc, Janet *argv)
/* docstring only */
#define JANET_REG_D(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, NULL, 0}
#define JANET_FN_D(CNAME, USAGE, DOCSTRING) \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
static Janet CNAME (int32_t argc, Janet *argv)
/* sourcemaps and docstrings */
#define JANET_REG_SD(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, __FILE__, CNAME##_sourceline_}
#define JANET_FN_SD(CNAME, USAGE, DOCSTRING) \
static int32_t CNAME##_sourceline_ = __LINE__; \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
static Janet CNAME (int32_t argc, Janet *argv)
/* Choose defaults for source mapping and docstring based on config defs */
#if defined(JANET_NO_SOURCEMAPS) && defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_
#define JANET_FN JANET_FN_
#elif defined(JANET_NO_SOURCEMAPS) && !defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_D
#define JANET_FN JANET_FN_D
#elif !defined(JANET_NO_SOURCEMAPS) && defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_S
#define JANET_FN JANET_FN_S
#elif !defined(JANET_NO_SOURCEMAPS) && !defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_SD
#define JANET_FN JANET_FN_SD
#endif
/* Define things with source mapping information */
JANET_API void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns);
JANET_API void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns);
JANET_API void janet_def_sm(JanetTable *env, const char *name, Janet val, const char *documentation, const char *source_file, int32_t source_line);
JANET_API void janet_var_sm(JanetTable *env, const char *name, Janet val, const char *documentation, const char *source_file, int32_t source_line);
/* Allow setting entry name for static libraries */ /* Allow setting entry name for static libraries */
#ifdef __cplusplus #ifdef __cplusplus
#define JANET_MODULE_PREFIX extern "C" #define JANET_MODULE_PREFIX extern "C"

View File

@ -105,6 +105,18 @@
(file/close outfile) (file/close outfile)
(os/rm "unique.txt")) (os/rm "unique.txt"))
# Ensure that the stream created by os/open works
(assert-no-error "File writing 4.1"
(def outstream (os/open "unique.txt" :wct))
(defer (:close outstream)
(:write outstream "123\n")
(:write outstream "456\n"))
# Cast to string to enable comparison
(assert (= "123\n456\n" (string (slurp "unique.txt"))) "File writing 4.2")
(os/rm "unique.txt"))
# ev/gather # ev/gather
(assert (deep= @[1 2 3] (ev/gather 1 2 3)) "ev/gather 1") (assert (deep= @[1 2 3] (ev/gather 1 2 3)) "ev/gather 1")