commit 831f5d512bd18a168ee724c195a826bc440d9bea
parent c9f9ef0a9c443dcd1e3d471c8b3fb1635f0960b4
Author: Fabrice Bellard <fabrice@bellard.org>
Date: Tue, 2 Jan 2024 16:09:30 +0100
allow 'await' in the REPL and added os.sleepAsync()
Diffstat:
5 files changed, 123 insertions(+), 48 deletions(-)
diff --git a/quickjs/doc/quickjs.texi b/quickjs/doc/quickjs.texi
@@ -368,6 +368,9 @@ optional properties:
@item backtrace_barrier
Boolean (default = false). If true, error backtraces do not list the
stack frames below the evalScript.
+ @item async
+ Boolean (default = false). If true, @code{await} is accepted in the
+ script and a promise is returned.
@end table
@item loadScript(filename)
@@ -769,6 +772,12 @@ write_fd]} or null in case of error.
@item sleep(delay_ms)
Sleep during @code{delay_ms} milliseconds.
+@item sleepAsync(delay_ms)
+Asynchronouse sleep during @code{delay_ms} milliseconds. Returns a promise. Example:
+@example
+await os.sleepAsync(500);
+@end example
+
@item now()
Return a timestamp in milliseconds with more precision than
@code{Date.now()}. The time origin is unspecified and is normally not
diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c
@@ -862,6 +862,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
JSValue ret;
JSValueConst options_obj;
BOOL backtrace_barrier = FALSE;
+ BOOL is_async = FALSE;
int flags;
if (argc >= 2) {
@@ -869,6 +870,9 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
if (get_bool_option(ctx, &backtrace_barrier, options_obj,
"backtrace_barrier"))
return JS_EXCEPTION;
+ if (get_bool_option(ctx, &is_async, options_obj,
+ "async"))
+ return JS_EXCEPTION;
}
str = JS_ToCStringLen(ctx, &len, argv[0]);
@@ -881,6 +885,8 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
flags = JS_EVAL_TYPE_GLOBAL;
if (backtrace_barrier)
flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER;
+ if (is_async)
+ flags |= JS_EVAL_FLAG_ASYNC;
ret = JS_Eval(ctx, str, len, "<evalScript>", flags);
JS_FreeCString(ctx, str);
if (!ts->is_worker_thread && --ts->eval_script_recurse == 0) {
@@ -2593,6 +2599,38 @@ static JSClassDef js_os_timer_class = {
.gc_mark = js_os_timer_mark,
};
+/* return a promise */
+static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ JSRuntime *rt = JS_GetRuntime(ctx);
+ JSThreadState *ts = JS_GetRuntimeOpaque(rt);
+ int64_t delay;
+ JSOSTimer *th;
+ JSValue promise, resolving_funcs[2];
+
+ if (JS_ToInt64(ctx, &delay, argv[0]))
+ return JS_EXCEPTION;
+ promise = JS_NewPromiseCapability(ctx, resolving_funcs);
+ if (JS_IsException(promise))
+ return JS_EXCEPTION;
+
+ th = js_mallocz(ctx, sizeof(*th));
+ if (!th) {
+ JS_FreeValue(ctx, promise);
+ JS_FreeValue(ctx, resolving_funcs[0]);
+ JS_FreeValue(ctx, resolving_funcs[1]);
+ return JS_EXCEPTION;
+ }
+ th->has_object = FALSE;
+ th->timeout = get_time_ms() + delay;
+ th->func = JS_DupValue(ctx, resolving_funcs[0]);
+ list_add_tail(&th->link, &ts->os_timers);
+ JS_FreeValue(ctx, resolving_funcs[0]);
+ JS_FreeValue(ctx, resolving_funcs[1]);
+ return promise;
+}
+
static void call_handler(JSContext *ctx, JSValueConst func)
{
JSValue ret, func1;
@@ -4555,6 +4593,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JS_CFUNC_DEF("fetchHttp", 2, js_os_fetchHttp ),
#endif
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
+ JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ),
JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ),
JS_CFUNC_DEF("chdir", 0, js_os_chdir ),
diff --git a/quickjs/quickjs.c b/quickjs/quickjs.c
@@ -34016,7 +34016,7 @@ static __exception int js_parse_program(JSParseState *s)
emit_op(s, OP_get_loc);
emit_u16(s, fd->eval_ret_idx);
- emit_op(s, OP_return);
+ emit_return(s, TRUE);
} else {
emit_return(s, FALSE);
}
@@ -34150,7 +34150,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
}
}
fd->module = m;
- if (m != NULL) {
+ if (m != NULL || (flags & JS_EVAL_FLAG_ASYNC)) {
fd->in_function_body = TRUE;
fd->func_kind = JS_FUNC_ASYNC;
}
diff --git a/quickjs/quickjs.h b/quickjs/quickjs.h
@@ -307,6 +307,9 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v)
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5)
/* don't include the stack frames before this eval in the Error() backtraces */
#define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6)
+/* allow top-level await in normal script. JS_Eval() returns a
+ promise. Only allowed with JS_EVAL_TYPE_GLOBAL */
+#define JS_EVAL_FLAG_ASYNC (1 << 7)
typedef JSValue JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic);
diff --git a/quickjs/repl.js b/quickjs/repl.js
@@ -120,6 +120,7 @@ import * as os from "os";
var utf8 = true;
var show_time = false;
var show_colors = true;
+ var eval_start_time;
var eval_time = 0;
var mexpr = "";
@@ -817,10 +818,8 @@ import * as os from "os";
prompt += ps2;
} else {
if (show_time) {
- var t = Math.round(eval_time) + " ";
- eval_time = 0;
- t = dupstr("0", 5 - t.length) + t;
- prompt += t.substring(0, t.length - 4) + "." + t.substring(t.length - 4);
+ var t = eval_time / 1000;
+ prompt += t.toFixed(6) + " ";
}
plen = prompt.length;
prompt += ps1;
@@ -1227,37 +1226,6 @@ import * as os from "os";
}
}
- function eval_and_print(expr) {
- var result;
-
- try {
- if (eval_mode === "math")
- expr = '"use math"; void 0;' + expr;
- var now = (new Date).getTime();
- /* eval as a script */
- result = std.evalScript(expr, { backtrace_barrier: true });
- eval_time = (new Date).getTime() - now;
- std.puts(colors[styles.result]);
- print(result);
- std.puts("\n");
- std.puts(colors.none);
- /* set the last result */
- g._ = result;
- } catch (error) {
- std.puts(colors[styles.error_msg]);
- if (error instanceof Error) {
- console.log(error);
- if (error.stack) {
- std.puts(error.stack);
- }
- } else {
- std.puts("Throw: ");
- console.log(error);
- }
- std.puts(colors.none);
- }
- }
-
function cmd_start() {
if (!config_numcalc) {
if (has_jscalc)
@@ -1284,29 +1252,32 @@ import * as os from "os";
}
function readline_handle_cmd(expr) {
- handle_cmd(expr);
- cmd_readline_start();
+ if (!handle_cmd(expr)) {
+ cmd_readline_start();
+ }
}
+ /* return true if async termination */
function handle_cmd(expr) {
var colorstate, cmd;
if (expr === null) {
expr = "";
- return;
+ return false;
}
if (expr === "?") {
help();
- return;
+ return false;
}
cmd = extract_directive(expr);
if (cmd.length > 0) {
- if (!handle_directive(cmd, expr))
- return;
+ if (!handle_directive(cmd, expr)) {
+ return false;
+ }
expr = expr.substring(cmd.length + 1);
}
if (expr === "")
- return;
+ return false;
if (mexpr)
expr = mexpr + '\n' + expr;
@@ -1315,20 +1286,73 @@ import * as os from "os";
level = colorstate[1];
if (pstate) {
mexpr = expr;
- return;
+ return false;
}
mexpr = "";
if (has_bignum) {
- BigFloatEnv.setPrec(eval_and_print.bind(null, expr),
+ /* XXX: async is not supported in this case */
+ BigFloatEnv.setPrec(eval_and_print_start.bind(null, expr, false),
prec, expBits);
} else {
- eval_and_print(expr);
+ eval_and_print_start(expr, true);
}
- level = 0;
+ return true;
+ }
+
+ function eval_and_print_start(expr, is_async) {
+ var result;
+ try {
+ if (eval_mode === "math")
+ expr = '"use math"; void 0;' + expr;
+ eval_start_time = os.now();
+ /* eval as a script */
+ result = std.evalScript(expr, { backtrace_barrier: true, async: is_async });
+ if (is_async) {
+ /* result is a promise */
+ result.then(print_eval_result, print_eval_error);
+ } else {
+ print_eval_result(result);
+ }
+ } catch (error) {
+ print_eval_error(error);
+ }
+ }
+
+ function print_eval_result(result) {
+ eval_time = os.now() - eval_start_time;
+ std.puts(colors[styles.result]);
+ print(result);
+ std.puts("\n");
+ std.puts(colors.none);
+ /* set the last result */
+ g._ = result;
+
+ handle_cmd_end();
+ }
+
+ function print_eval_error(error) {
+ std.puts(colors[styles.error_msg]);
+ if (error instanceof Error) {
+ console.log(error);
+ if (error.stack) {
+ std.puts(error.stack);
+ }
+ } else {
+ std.puts("Throw: ");
+ console.log(error);
+ }
+ std.puts(colors.none);
+
+ handle_cmd_end();
+ }
+
+ function handle_cmd_end() {
+ level = 0;
/* run the garbage collector after each command */
std.gc();
+ cmd_readline_start();
}
function colorize_js(str) {