diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/repl.ts | 122 |
1 files changed, 73 insertions, 49 deletions
diff --git a/js/repl.ts b/js/repl.ts index 162c6f42f..e452eb191 100644 --- a/js/repl.ts +++ b/js/repl.ts @@ -79,31 +79,74 @@ export async function replLoop(): Promise<void> { const historyFile = "deno_history.txt"; const rid = startRepl(historyFile); - let code = ""; + const quitRepl = (exitCode: number) => { + // Special handling in case user calls deno.close(3). + try { + close(rid); // close signals Drop on REPL and saves history. + } catch {} + exit(exitCode); + }; + while (true) { + let code = ""; + // Top level read try { - code = await readBlock(rid, "> ", " "); + 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); } |