summaryrefslogtreecommitdiff
path: root/cli/rt/40_repl.js
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2020-07-23 10:27:26 -0400
committerGitHub <noreply@github.com>2020-07-23 16:27:26 +0200
commitca4dcb36dd5be0b14a2fafa059ea02ee7e0a0262 (patch)
tree393fc85f19e97bb66402676f67e63690d7a29e00 /cli/rt/40_repl.js
parent090455936c892b6f2dfa425be9b1cdfb4c63af4a (diff)
Rename cli/js2 to cli/rt (#6857)
Diffstat (limited to 'cli/rt/40_repl.js')
-rw-r--r--cli/rt/40_repl.js186
1 files changed, 186 insertions, 0 deletions
diff --git a/cli/rt/40_repl.js b/cli/rt/40_repl.js
new file mode 100644
index 000000000..4966c45be
--- /dev/null
+++ b/cli/rt/40_repl.js
@@ -0,0 +1,186 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const exit = window.__bootstrap.os.exit;
+ const version = window.__bootstrap.version.version;
+ const dispatchJson = window.__bootstrap.dispatchJson;
+ const close = window.__bootstrap.resources.close;
+ const inspectArgs = window.__bootstrap.console.inspectArgs;
+
+ function opStartRepl(historyFile) {
+ return dispatchJson.sendSync("op_repl_start", { historyFile });
+ }
+
+ function opReadline(rid, prompt) {
+ return dispatchJson.sendAsync("op_repl_readline", { rid, prompt });
+ }
+
+ function replLog(...args) {
+ core.print(inspectArgs(args) + "\n");
+ }
+
+ function replError(...args) {
+ core.print(inspectArgs(args) + "\n", true);
+ }
+
+ // Error messages that allow users to continue input
+ // instead of throwing an error to REPL
+ // ref: https://github.com/v8/v8/blob/master/src/message-template.h
+ // TODO(kevinkassimo): this list might not be comprehensive
+ const recoverableErrorMessages = [
+ "Unexpected end of input", // { or [ or (
+ "Missing initializer in const declaration", // const a
+ "Missing catch or finally after try", // try {}
+ "missing ) after argument list", // console.log(1
+ "Unterminated template literal", // `template
+ // TODO(kevinkassimo): need a parser to handling errors such as:
+ // "Missing } in template expression" // `${ or `${ a 123 }`
+ ];
+
+ function isRecoverableError(e) {
+ return recoverableErrorMessages.includes(e.message);
+ }
+
+ // Returns `true` if `close()` is called in REPL.
+ // We should quit the REPL when this function returns `true`.
+ function isCloseCalled() {
+ return globalThis.closed;
+ }
+
+ let lastEvalResult = undefined;
+ let lastThrownError = undefined;
+
+ // Evaluate code.
+ // Returns true if code is consumed (no error/irrecoverable error).
+ // Returns false if error is recoverable
+ function evaluate(code) {
+ // each evalContext is a separate function body, and we want strict mode to
+ // work, so we should ensure that the code starts with "use strict"
+ const [result, errInfo] = core.evalContext(`"use strict";\n\n${code}`);
+ if (!errInfo) {
+ // when a function is eval'ed with just "use strict" sometimes the result
+ // is "use strict" which should be discarded
+ lastEvalResult = typeof result === "string" && result === "use strict"
+ ? undefined
+ : result;
+ if (!isCloseCalled()) {
+ replLog(lastEvalResult);
+ }
+ } else if (errInfo.isCompileError && isRecoverableError(errInfo.thrown)) {
+ // Recoverable compiler error
+ return false; // don't consume code.
+ } else {
+ lastThrownError = errInfo.thrown;
+ if (errInfo.isNativeError) {
+ const formattedError = core.formatError(errInfo.thrown);
+ replError(formattedError);
+ } else {
+ replError("Thrown:", errInfo.thrown);
+ }
+ }
+ return true;
+ }
+
+ async function replLoop() {
+ const { console } = globalThis;
+
+ const historyFile = "deno_history.txt";
+ const rid = opStartRepl(historyFile);
+
+ const quitRepl = (exitCode) => {
+ // Special handling in case user calls deno.close(3).
+ try {
+ close(rid); // close signals Drop on REPL and saves history.
+ } catch {}
+ exit(exitCode);
+ };
+
+ // Configure globalThis._ to give the last evaluation result.
+ Object.defineProperty(globalThis, "_", {
+ configurable: true,
+ get: () => lastEvalResult,
+ set: (value) => {
+ Object.defineProperty(globalThis, "_", {
+ value: value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+ console.log("Last evaluation result is no longer saved to _.");
+ },
+ });
+
+ // Configure globalThis._error to give the last thrown error.
+ Object.defineProperty(globalThis, "_error", {
+ configurable: true,
+ get: () => lastThrownError,
+ set: (value) => {
+ Object.defineProperty(globalThis, "_error", {
+ value: value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+ console.log("Last thrown error is no longer saved to _error.");
+ },
+ });
+
+ replLog(`Deno ${version.deno}`);
+ replLog("exit using ctrl+d or close()");
+
+ while (true) {
+ if (isCloseCalled()) {
+ quitRepl(0);
+ }
+
+ let code = "";
+ // Top level read
+ try {
+ code = await opReadline(rid, "> ");
+ if (code.trim() === "") {
+ continue;
+ }
+ } catch (err) {
+ if (err.message === "EOF") {
+ quitRepl(0);
+ } else {
+ // If interrupted, don't print error.
+ if (err.message !== "Interrupted") {
+ // e.g. this happens when we have deno.close(3).
+ // We want to display the problem.
+ const formattedError = core.formatError(err);
+ replError(formattedError);
+ }
+ // Quit REPL anyways.
+ quitRepl(1);
+ }
+ }
+ // Start continued read
+ while (!evaluate(code)) {
+ code += "\n";
+ try {
+ code += await opReadline(rid, " ");
+ } catch (err) {
+ // If interrupted on continued read,
+ // abort this read instead of quitting.
+ if (err.message === "Interrupted") {
+ break;
+ } else if (err.message === "EOF") {
+ quitRepl(0);
+ } else {
+ // e.g. this happens when we have deno.close(3).
+ // We want to display the problem.
+ const formattedError = core.formatError(err);
+ replError(formattedError);
+ quitRepl(1);
+ }
+ }
+ }
+ }
+ }
+
+ window.__bootstrap.repl = {
+ replLoop,
+ };
+})(this);