summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Hayden <andyhayden1@gmail.com>2018-11-06 11:19:16 -0800
committerRyan Dahl <ry@tinyclouds.org>2018-11-06 11:19:16 -0800
commite9327be8318188db0983963e801effe8833e36e3 (patch)
tree3eeed42a572905988da910882536b7738213cd38
parent13e1eb2b87c146a8594e7e6f24ba738bff116246 (diff)
Support repl multiline input (#1165)
-rw-r--r--js/repl.ts75
-rw-r--r--tools/repl_test.py6
2 files changed, 63 insertions, 18 deletions
diff --git a/js/repl.ts b/js/repl.ts
index b7c516110..3139330f6 100644
--- a/js/repl.ts
+++ b/js/repl.ts
@@ -51,15 +51,12 @@ export function replLoop(): void {
window.deno = deno; // FIXME use a new scope (rather than window).
const historyFile = "deno_history.txt";
- const prompt = "> ";
-
const rid = startRepl(historyFile);
- let line = "";
+ let code = "";
while (true) {
try {
- line = readline(rid, prompt);
- line = line.trim();
+ code = readBlock(rid, "> ", " ");
} catch (err) {
if (err.message === "EOF") {
break;
@@ -67,23 +64,65 @@ export function replLoop(): void {
console.error(err);
exit(1);
}
- if (!line) {
+ if (!code) {
continue;
- }
- if (line === ".exit") {
+ } else if (code.trim() === ".exit") {
break;
}
- try {
- const result = eval.call(window, line); // FIXME use a new scope.
- console.log(result);
- } catch (err) {
- if (err instanceof Error) {
- console.error(`${err.constructor.name}: ${err.message}`);
- } else {
- console.error("Thrown:", err);
- }
- }
+
+ evaluate(code);
}
close(rid);
}
+
+function evaluate(code: string): void {
+ try {
+ const result = eval.call(window, code); // FIXME use a new scope.
+ console.log(result);
+ } catch (err) {
+ if (err instanceof Error) {
+ console.error(`${err.constructor.name}: ${err.message}`);
+ } else {
+ console.error("Thrown:", err);
+ }
+ }
+}
+
+function readBlock(
+ rid: number,
+ prompt: string,
+ continuedPrompt: string
+): string {
+ let code = "";
+ do {
+ code += readline(rid, prompt);
+ prompt = continuedPrompt;
+ } while (parenthesesAreOpen(code));
+ return code;
+}
+
+// modified from
+// https://codereview.stackexchange.com/a/46039/148556
+function parenthesesAreOpen(code: string): boolean {
+ const parentheses = "[]{}()";
+ const stack = [];
+
+ for (const ch of code) {
+ const bracePosition = parentheses.indexOf(ch);
+
+ if (bracePosition === -1) {
+ // not a paren
+ continue;
+ }
+
+ if (bracePosition % 2 === 0) {
+ stack.push(bracePosition + 1); // push next expected brace position
+ } else {
+ if (stack.length === 0 || stack.pop() !== bracePosition) {
+ return false;
+ }
+ }
+ }
+ return stack.length > 0;
+}
diff --git a/tools/repl_test.py b/tools/repl_test.py
index 5b3172edb..db51ec511 100644
--- a/tools/repl_test.py
+++ b/tools/repl_test.py
@@ -80,6 +80,12 @@ class Repl(object):
assertEqual(err, 'TypeError: console is not a function\n')
assertEqual(code, 0)
+ def test_multiline(self):
+ out, err, code = self.input("(\n1 + 2\n)")
+ assertEqual(out, '3\n')
+ assertEqual(err, '')
+ assertEqual(code, 0)
+
def test_exit_command(self):
out, err, code = self.input(".exit", "'ignored'", exit=False)
assertEqual(out, '')