1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

REPL multiline support with recoverable errors (#1731)

This commit is contained in:
Kevin (Kun) "Kassimo" Qian 2019-02-11 11:01:28 -08:00 committed by Ryan Dahl
parent d26655371b
commit 489c69f8e1
3 changed files with 83 additions and 60 deletions

View file

@ -79,31 +79,74 @@ export async function replLoop(): Promise<void> {
const historyFile = "deno_history.txt";
const rid = startRepl(historyFile);
let code = "";
while (true) {
const quitRepl = (exitCode: number) => {
// Special handling in case user calls deno.close(3).
try {
code = await readBlock(rid, "> ", " ");
close(rid); // close signals Drop on REPL and saves history.
} catch {}
exit(exitCode);
};
while (true) {
let code = "";
// Top level read
try {
code = await readline(rid, "> ");
if (code.trim() === "") {
continue;
}
} catch (err) {
if (err.message === "EOF") {
break;
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 = formatError(
libdeno.errorToJSON(err));
console.error(formattedError);
}
// Quit REPL anyways.
quitRepl(1);
}
}
// Start continued read
while (!evaluate(code)) {
code += "\n";
try {
code += await readline(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 = formatError(
libdeno.errorToJSON(err));
console.error(formattedError);
quitRepl(1);
}
}
console.error(err);
exit(1);
}
evaluate(code);
}
close(rid);
}
function evaluate(code: string): void {
if (code.trim() === "") {
return;
}
// Evaluate code.
// Returns true if code is consumed (no error/irrecoverable error).
// Returns false if error is recoverable
function evaluate(code: string): boolean {
const [result, errInfo] = libdeno.evalContext(code);
if (!errInfo) {
console.log(result);
} else if (errInfo.isCompileError &&
isRecoverableError(errInfo.thrown)) {
// Recoverable compiler error
return false; // don't consume code.
} else {
if (errInfo.isNativeError) {
const formattedError = formatError(
@ -114,42 +157,23 @@ function evaluate(code: string): void {
console.error("Thrown:", errInfo.thrown);
}
}
return true;
}
async function readBlock(
rid: number,
prompt: string,
continuedPrompt: string
): Promise<string> {
let code = "";
do {
code += await 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.pop() !== bracePosition) {
return false;
}
}
}
return stack.length > 0;
// 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: Error): boolean {
return recoverableErrorMessages.includes(e.message);
}

View file

@ -1,8 +1,6 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use rustyline;
use rustyline::error::ReadlineError::Interrupted;
use crate::msg::ErrorKind;
use std::error::Error;
@ -10,7 +8,6 @@ use crate::deno_dir::DenoDir;
use crate::errors::new as deno_error;
use crate::errors::DenoResult;
use std::path::PathBuf;
use std::process::exit;
#[cfg(not(windows))]
use rustyline::Editor;
@ -99,13 +96,8 @@ impl Repl {
.map(|line| {
self.editor.add_history_entry(line.as_ref());
line
}).map_err(|e| match e {
Interrupted => {
self.save_history().unwrap();
exit(1)
}
e => deno_error(ErrorKind::Other, e.description().to_string()),
})
}).map_err(|e| deno_error(ErrorKind::Other, e.description().to_string()))
// Forward error to TS side for processing
}
}

View file

@ -84,6 +84,13 @@ class Repl(object):
assertEqual(err, '')
assertEqual(code, 0)
# This should print error instead of wait for input
def test_eval_unterminated(self):
out, err, code = self.input("eval('{')")
assertEqual(out, '')
assert "Unexpected end of input" in err
assertEqual(code, 0)
def test_reference_error(self):
out, err, code = self.input("not_a_variable")
assertEqual(out, '')