From e44b793e3817766d9045ed2a776658fcbe0f2790 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 2 Jan 2024 16:09:30 +0100 Subject: [PATCH] allow 'await' in the REPL and added os.sleepAsync() --- doc/quickjs.texi | 9 ++++ quickjs-libc.c | 39 ++++++++++++++++ quickjs.c | 4 +- quickjs.h | 3 ++ repl.js | 116 ++++++++++++++++++++++++++++------------------- 5 files changed, 123 insertions(+), 48 deletions(-) diff --git a/doc/quickjs.texi b/doc/quickjs.texi index 5731f04..996836b 100644 --- a/doc/quickjs.texi +++ b/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-libc.c b/quickjs-libc.c index d99bbf4..ea73ee8 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -751,6 +751,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) { @@ -758,6 +759,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]); @@ -770,6 +774,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, "", flags); JS_FreeCString(ctx, str); if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { @@ -2082,6 +2088,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; @@ -3648,6 +3686,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("now", 0, js_os_now ), JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), 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.c b/quickjs.c index 06cf581..bac4aea 100644 --- a/quickjs.c +++ b/quickjs.c @@ -33827,7 +33827,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); } @@ -33959,7 +33959,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, goto fail; } 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.h b/quickjs.h index 41c3882..700ee61 100644 --- a/quickjs.h +++ b/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/repl.js b/repl.js index 484269e..62714a9 100644 --- a/repl.js +++ b/repl.js @@ -118,6 +118,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 = ""; @@ -814,10 +815,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; @@ -1224,37 +1223,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) @@ -1281,29 +1249,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; @@ -1312,20 +1283,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) {