]> git.kaiwu.me - quickjs.git/commitdiff
allow 'await' in the REPL and added os.sleepAsync()
authorFabrice Bellard <fabrice@bellard.org>
Tue, 2 Jan 2024 15:09:30 +0000 (16:09 +0100)
committerFabrice Bellard <fabrice@bellard.org>
Tue, 2 Jan 2024 15:09:30 +0000 (16:09 +0100)
doc/quickjs.texi
quickjs-libc.c
quickjs.c
quickjs.h
repl.js

index 5731f04bb5b573300e59cf7563004ed96027d626..996836b31c70fe50680ccddbd22331bdb97c3a2f 100644 (file)
@@ -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
index d99bbf403c1f89945d756ea8f871f146aef51b0f..ea73ee8e7f5d2adaf510088b8872bd3960c1cce3 100644 (file)
@@ -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, "<evalScript>", 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 ),
index 06cf581ddb3daa298b625b876f981b5882d89fe7..bac4aea89c53b16f1f7ebcd8966d171d244151ab 100644 (file)
--- 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;
     }
index 41c388245d9be91d05b41d52133ab8ff163c2b53..700ee61d6c89800ddc869d69f2e86f25d78444a4 100644 (file)
--- 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 484269e16b73c0e757823518029d129bf6d60a75..62714a99fb335f7bc1d024d1cbda1308d66fd87b 100644 (file)
--- 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) {