allow 'await' in the REPL and added os.sleepAsync()

This commit is contained in:
Fabrice Bellard 2024-01-02 16:09:30 +01:00
parent 8de4538ff3
commit e44b793e38
5 changed files with 123 additions and 48 deletions

View file

@ -368,6 +368,9 @@ optional properties:
@item backtrace_barrier @item backtrace_barrier
Boolean (default = false). If true, error backtraces do not list the Boolean (default = false). If true, error backtraces do not list the
stack frames below the evalScript. 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 @end table
@item loadScript(filename) @item loadScript(filename)
@ -769,6 +772,12 @@ write_fd]} or null in case of error.
@item sleep(delay_ms) @item sleep(delay_ms)
Sleep during @code{delay_ms} milliseconds. 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() @item now()
Return a timestamp in milliseconds with more precision than Return a timestamp in milliseconds with more precision than
@code{Date.now()}. The time origin is unspecified and is normally not @code{Date.now()}. The time origin is unspecified and is normally not

View file

@ -751,6 +751,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
JSValue ret; JSValue ret;
JSValueConst options_obj; JSValueConst options_obj;
BOOL backtrace_barrier = FALSE; BOOL backtrace_barrier = FALSE;
BOOL is_async = FALSE;
int flags; int flags;
if (argc >= 2) { 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, if (get_bool_option(ctx, &backtrace_barrier, options_obj,
"backtrace_barrier")) "backtrace_barrier"))
return JS_EXCEPTION; return JS_EXCEPTION;
if (get_bool_option(ctx, &is_async, options_obj,
"async"))
return JS_EXCEPTION;
} }
str = JS_ToCStringLen(ctx, &len, argv[0]); 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; flags = JS_EVAL_TYPE_GLOBAL;
if (backtrace_barrier) if (backtrace_barrier)
flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER;
if (is_async)
flags |= JS_EVAL_FLAG_ASYNC;
ret = JS_Eval(ctx, str, len, "<evalScript>", flags); ret = JS_Eval(ctx, str, len, "<evalScript>", flags);
JS_FreeCString(ctx, str); JS_FreeCString(ctx, str);
if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { 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, .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) static void call_handler(JSContext *ctx, JSValueConst func)
{ {
JSValue ret, func1; JSValue ret, func1;
@ -3648,6 +3686,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JS_CFUNC_DEF("now", 0, js_os_now ), JS_CFUNC_DEF("now", 0, js_os_now ),
JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), 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_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ),
JS_CFUNC_DEF("chdir", 0, js_os_chdir ), JS_CFUNC_DEF("chdir", 0, js_os_chdir ),

View file

@ -33827,7 +33827,7 @@ static __exception int js_parse_program(JSParseState *s)
emit_op(s, OP_get_loc); emit_op(s, OP_get_loc);
emit_u16(s, fd->eval_ret_idx); emit_u16(s, fd->eval_ret_idx);
emit_op(s, OP_return); emit_return(s, TRUE);
} else { } else {
emit_return(s, FALSE); emit_return(s, FALSE);
} }
@ -33959,7 +33959,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
goto fail; goto fail;
} }
fd->module = m; fd->module = m;
if (m != NULL) { if (m != NULL || (flags & JS_EVAL_FLAG_ASYNC)) {
fd->in_function_body = TRUE; fd->in_function_body = TRUE;
fd->func_kind = JS_FUNC_ASYNC; fd->func_kind = JS_FUNC_ASYNC;
} }

View file

@ -307,6 +307,9 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v)
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) #define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5)
/* don't include the stack frames before this eval in the Error() backtraces */ /* don't include the stack frames before this eval in the Error() backtraces */
#define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6) #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 JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic);

116
repl.js
View file

@ -118,6 +118,7 @@ import * as os from "os";
var utf8 = true; var utf8 = true;
var show_time = false; var show_time = false;
var show_colors = true; var show_colors = true;
var eval_start_time;
var eval_time = 0; var eval_time = 0;
var mexpr = ""; var mexpr = "";
@ -814,10 +815,8 @@ import * as os from "os";
prompt += ps2; prompt += ps2;
} else { } else {
if (show_time) { if (show_time) {
var t = Math.round(eval_time) + " "; var t = eval_time / 1000;
eval_time = 0; prompt += t.toFixed(6) + " ";
t = dupstr("0", 5 - t.length) + t;
prompt += t.substring(0, t.length - 4) + "." + t.substring(t.length - 4);
} }
plen = prompt.length; plen = prompt.length;
prompt += ps1; 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() { function cmd_start() {
if (!config_numcalc) { if (!config_numcalc) {
if (has_jscalc) if (has_jscalc)
@ -1281,29 +1249,32 @@ import * as os from "os";
} }
function readline_handle_cmd(expr) { function readline_handle_cmd(expr) {
handle_cmd(expr); if (!handle_cmd(expr)) {
cmd_readline_start(); cmd_readline_start();
}
} }
/* return true if async termination */
function handle_cmd(expr) { function handle_cmd(expr) {
var colorstate, cmd; var colorstate, cmd;
if (expr === null) { if (expr === null) {
expr = ""; expr = "";
return; return false;
} }
if (expr === "?") { if (expr === "?") {
help(); help();
return; return false;
} }
cmd = extract_directive(expr); cmd = extract_directive(expr);
if (cmd.length > 0) { if (cmd.length > 0) {
if (!handle_directive(cmd, expr)) if (!handle_directive(cmd, expr)) {
return; return false;
}
expr = expr.substring(cmd.length + 1); expr = expr.substring(cmd.length + 1);
} }
if (expr === "") if (expr === "")
return; return false;
if (mexpr) if (mexpr)
expr = mexpr + '\n' + expr; expr = mexpr + '\n' + expr;
@ -1312,20 +1283,73 @@ import * as os from "os";
level = colorstate[1]; level = colorstate[1];
if (pstate) { if (pstate) {
mexpr = expr; mexpr = expr;
return; return false;
} }
mexpr = ""; mexpr = "";
if (has_bignum) { 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); prec, expBits);
} else { } 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 */ /* run the garbage collector after each command */
std.gc(); std.gc();
cmd_readline_start();
} }
function colorize_js(str) { function colorize_js(str) {