summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin (Kun) "Kassimo" Qian <kevinkassimo@gmail.com>2019-02-09 13:55:40 -0800
committerRyan Dahl <ry@tinyclouds.org>2019-02-09 16:55:40 -0500
commit1d36eb47eb882cb9305a6338019fa2a2b375d7b1 (patch)
tree4584aff9da784f3e05c4fb5314d5911772b4d655
parent1502051453bf16787a59f43004b24d553d39bd26 (diff)
Support scoped variables, unblock REPL async op, and REPL error colors (#1721)
-rw-r--r--BUILD.gn1
-rw-r--r--js/console.ts5
-rw-r--r--js/console_test.ts5
-rw-r--r--js/format_error.ts21
-rw-r--r--js/libdeno.ts47
-rw-r--r--js/repl.ts19
-rw-r--r--libdeno/api.cc1
-rw-r--r--libdeno/binding.cc96
-rw-r--r--libdeno/exceptions.cc28
-rw-r--r--libdeno/exceptions.h7
-rw-r--r--libdeno/internal.h7
-rw-r--r--libdeno/libdeno_test.cc14
-rw-r--r--libdeno/libdeno_test.js47
-rw-r--r--src/libdeno.rs1
-rw-r--r--src/msg.fbs10
-rw-r--r--src/ops.rs40
-rw-r--r--src/resources.rs13
-rw-r--r--tools/repl_test.py23
18 files changed, 330 insertions, 55 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 239a0f995..4f8550d0d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -68,6 +68,7 @@ ts_sources = [
"js/event.ts",
"js/event_target.ts",
"js/fetch.ts",
+ "js/format_error.ts",
"js/dom_file.ts",
"js/file_info.ts",
"js/files.ts",
diff --git a/js/console.ts b/js/console.ts
index 0797ab8e9..e5dafceee 100644
--- a/js/console.ts
+++ b/js/console.ts
@@ -3,6 +3,8 @@ import { isTypedArray } from "./util";
import { TextEncoder } from "./text_encoding";
import { File, stdout } from "./files";
import { cliTable } from "./console_table";
+import { formatError } from "./format_error";
+import { libdeno } from "./libdeno";
// tslint:disable-next-line:no-any
type ConsoleContext = Set<any>;
@@ -263,7 +265,8 @@ function createObjectString(
...args: [ConsoleContext, number, number]
): string {
if (value instanceof Error) {
- return value.stack! || "";
+ const errorJSON = libdeno.errorToJSON(value);
+ return formatError(errorJSON);
} else if (Array.isArray(value)) {
return createArrayString(value, ...args);
} else if (value instanceof Number) {
diff --git a/js/console_test.ts b/js/console_test.ts
index 577c8bf5d..f84dc247a 100644
--- a/js/console_test.ts
+++ b/js/console_test.ts
@@ -1,6 +1,6 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { Console, libdeno, stringifyArgs, inspect, write, stdout } from "deno";
-import { test, assertEqual } from "./test_util.ts";
+import { test, assertEqual, assert } from "./test_util.ts";
const console = new Console(libdeno.print);
@@ -245,7 +245,8 @@ test(function consoleTestError() {
try {
throw new MyError("This is an error");
} catch (e) {
- assertEqual(stringify(e).split("\n")[0], "MyError: This is an error");
+ assert(stringify(e).split("\n")[3]
+ .includes("MyError: This is an error"));
}
});
diff --git a/js/format_error.ts b/js/format_error.ts
new file mode 100644
index 000000000..ebc579355
--- /dev/null
+++ b/js/format_error.ts
@@ -0,0 +1,21 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import * as msg from "gen/msg_generated";
+import * as flatbuffers from "./flatbuffers";
+import { sendSync } from "./dispatch";
+import { assert } from "./util";
+
+export function formatError(errString: string): string {
+ const builder = flatbuffers.createBuilder();
+ const errString_ = builder.createString(errString);
+ msg.FormatError.startFormatError(builder);
+ msg.FormatError.addError(builder, errString_);
+ const offset = msg.FormatError.endFormatError(builder);
+ const baseRes = sendSync(builder, msg.Any.FormatError, offset);
+ assert(baseRes != null);
+ assert(msg.Any.FormatErrorRes === baseRes!.innerType());
+ const formatErrorResMsg = new msg.FormatErrorRes();
+ assert(baseRes!.inner(formatErrorResMsg) != null);
+ const formattedError = formatErrorResMsg.error();
+ assert(formatError != null);
+ return formattedError!;
+}
diff --git a/js/libdeno.ts b/js/libdeno.ts
index 401ffae40..7d2bc3ede 100644
--- a/js/libdeno.ts
+++ b/js/libdeno.ts
@@ -3,11 +3,17 @@ import { globalEval } from "./global_eval";
// The libdeno functions are moved so that users can't access them.
type MessageCallback = (msg: Uint8Array) => void;
-export type PromiseRejectEvent =
- | "RejectWithNoHandler"
- | "HandlerAddedAfterReject"
- | "ResolveAfterResolved"
- | "RejectAfterResolved";
+
+interface EvalErrorInfo {
+ // Is the object thrown a native Error?
+ isNativeError: boolean;
+ // Was the error happened during compilation?
+ isCompileError: boolean;
+ // The actual thrown entity
+ // (might be an Error or anything else thrown by the user)
+ // If isNativeError is true, this is an Error
+ thrown: any; // tslint:disable-line:no-any
+}
interface Libdeno {
recv(cb: MessageCallback): void;
@@ -20,26 +26,17 @@ interface Libdeno {
builtinModules: { [s: string]: object };
- setGlobalErrorHandler: (
- handler: (
- message: string,
- source: string,
- line: number,
- col: number,
- error: Error
- ) => void
- ) => void;
-
- setPromiseRejectHandler: (
- handler: (
- error: Error | string,
- event: PromiseRejectEvent,
- /* tslint:disable-next-line:no-any */
- promise: Promise<any>
- ) => void
- ) => void;
-
- setPromiseErrorExaminer: (handler: () => boolean) => void;
+ /** Evaluate provided code in the current context.
+ * It differs from eval(...) in that it does not create a new context.
+ * Returns an array: [output, errInfo].
+ * If an error occurs, `output` becomes null and `errInfo` is non-null.
+ */
+ evalContext(
+ code: string
+ ): [any, EvalErrorInfo | null] /* tslint:disable-line:no-any */;
+
+ // tslint:disable-next-line:no-any
+ errorToJSON: (e: Error) => string;
}
const window = globalEval("this");
diff --git a/js/repl.ts b/js/repl.ts
index 6fb395e1b..6676721fc 100644
--- a/js/repl.ts
+++ b/js/repl.ts
@@ -7,6 +7,8 @@ import { close } from "./files";
import * as dispatch from "./dispatch";
import { exit } from "./os";
import { globalEval } from "./global_eval";
+import { libdeno } from "./libdeno";
+import { formatError } from "./format_error";
const window = globalEval("this");
@@ -96,14 +98,19 @@ export async function replLoop(): Promise<void> {
}
function evaluate(code: string): void {
- try {
- const result = eval.call(window, code); // FIXME use a new scope.
+ if (code.trim() === "") {
+ return;
+ }
+ const [result, errInfo] = libdeno.evalContext(code);
+ if (!errInfo) {
console.log(result);
- } catch (err) {
- if (err instanceof Error) {
- console.error(`${err.constructor.name}: ${err.message}`);
+ } else {
+ if (errInfo.isNativeError) {
+ const formattedError = formatError(
+ libdeno.errorToJSON(errInfo.thrown as Error));
+ console.error(formattedError);
} else {
- console.error("Thrown:", err);
+ console.error("Thrown:", errInfo.thrown);
}
}
}
diff --git a/libdeno/api.cc b/libdeno/api.cc
index e540c95fc..21ece13a6 100644
--- a/libdeno/api.cc
+++ b/libdeno/api.cc
@@ -9,6 +9,7 @@
#include "third_party/v8/src/base/logging.h"
#include "deno.h"
+#include "exceptions.h"
#include "internal.h"
extern "C" {
diff --git a/libdeno/binding.cc b/libdeno/binding.cc
index 78e4cad29..f7ee977e8 100644
--- a/libdeno/binding.cc
+++ b/libdeno/binding.cc
@@ -120,6 +120,16 @@ void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
fflush(file);
}
+void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ auto* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::HandleScope handle_scope(isolate);
+ auto json_string = EncodeExceptionAsJSON(context, args[0]);
+ args.GetReturnValue().Set(v8_str(json_string.c_str()));
+}
+
v8::Local<v8::Uint8Array> ImportBuf(DenoIsolate* d, deno_buf buf) {
if (buf.alloc_ptr == nullptr) {
// If alloc_ptr isn't set, we memcpy.
@@ -368,6 +378,80 @@ bool Execute(v8::Local<v8::Context> context, const char* js_filename,
return true;
}
+static inline v8::Local<v8::Boolean> v8_bool(bool v) {
+ return v8::Boolean::New(v8::Isolate::GetCurrent(), v);
+}
+
+void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::EscapableHandleScope handleScope(isolate);
+ auto context = d->context_.Get(isolate);
+ v8::Context::Scope context_scope(context);
+
+ CHECK(args[0]->IsString());
+ auto source = args[0].As<v8::String>();
+
+ auto output = v8::Array::New(isolate, 2);
+ /**
+ * output[0] = result
+ * output[1] = ErrorInfo | null
+ * ErrorInfo = {
+ * thrown: Error | any,
+ * isNativeError: boolean,
+ * isCompileError: boolean,
+ * }
+ */
+
+ v8::TryCatch try_catch(isolate);
+
+ auto name = v8_str("<unknown>");
+ v8::ScriptOrigin origin(name);
+ auto script = v8::Script::Compile(context, source, &origin);
+
+ if (script.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ output->Set(0, v8::Null(isolate));
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ errinfo_obj->Set(v8_str("isCompileError"), v8_bool(true));
+ errinfo_obj->Set(v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()));
+ errinfo_obj->Set(v8_str("thrown"), exception);
+
+ output->Set(1, errinfo_obj);
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ auto result = script.ToLocalChecked()->Run(context);
+
+ if (result.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ output->Set(0, v8::Null(isolate));
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ errinfo_obj->Set(v8_str("isCompileError"), v8_bool(false));
+ errinfo_obj->Set(v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()));
+ errinfo_obj->Set(v8_str("thrown"), exception);
+
+ output->Set(1, errinfo_obj);
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ output->Set(0, result.ToLocalChecked());
+ output->Set(1, v8::Null(isolate));
+ args.GetReturnValue().Set(output);
+}
+
void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) {
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);
@@ -389,6 +473,18 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) {
auto send_val = send_tmpl->GetFunction(context).ToLocalChecked();
CHECK(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust());
+ auto eval_context_tmpl = v8::FunctionTemplate::New(isolate, EvalContext);
+ auto eval_context_val =
+ eval_context_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(deno_val->Set(context, deno::v8_str("evalContext"), eval_context_val)
+ .FromJust());
+
+ auto error_to_json_tmpl = v8::FunctionTemplate::New(isolate, ErrorToJSON);
+ auto error_to_json_val =
+ error_to_json_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(deno_val->Set(context, deno::v8_str("errorToJSON"), error_to_json_val)
+ .FromJust());
+
CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared)
.FromJust());
diff --git a/libdeno/exceptions.cc b/libdeno/exceptions.cc
index 0d7bbed8b..51f81bfce 100644
--- a/libdeno/exceptions.cc
+++ b/libdeno/exceptions.cc
@@ -4,10 +4,10 @@
namespace deno {
-std::string EncodeMessageAsJSON(v8::Local<v8::Context> context,
- v8::Local<v8::Message> message) {
+v8::Local<v8::Object> EncodeMessageAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
auto* isolate = context->GetIsolate();
- v8::HandleScope handle_scope(isolate);
+ v8::EscapableHandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);
auto stack_trace = message->GetStackTrace();
@@ -134,12 +134,33 @@ std::string EncodeMessageAsJSON(v8::Local<v8::Context> context,
}
CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust());
+ json_obj = handle_scope.Escape(json_obj);
+ return json_obj;
+}
+std::string EncodeMessageAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ auto* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+ auto json_obj = EncodeMessageAsObject(context, message);
auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked();
v8::String::Utf8Value json_string_(isolate, json_string);
return std::string(ToCString(json_string_));
}
+v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ auto* isolate = context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto message = v8::Exception::CreateMessage(isolate, exception);
+ auto json_obj = EncodeMessageAsObject(context, message);
+ json_obj = handle_scope.Escape(json_obj);
+ return json_obj;
+}
+
std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
v8::Local<v8::Value> exception) {
auto* isolate = context->GetIsolate();
@@ -167,5 +188,4 @@ void HandleExceptionMessage(v8::Local<v8::Context> context,
CHECK_NOT_NULL(d);
d->last_exception_ = json_str;
}
-
} // namespace deno
diff --git a/libdeno/exceptions.h b/libdeno/exceptions.h
index 362bbc0e6..e07ff183a 100644
--- a/libdeno/exceptions.h
+++ b/libdeno/exceptions.h
@@ -2,10 +2,17 @@
#ifndef EXCEPTIONS_H_
#define EXCEPTIONS_H_
+#include <string>
#include "third_party/v8/include/v8.h"
namespace deno {
+v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
void HandleException(v8::Local<v8::Context> context,
v8::Local<v8::Value> exception);
diff --git a/libdeno/internal.h b/libdeno/internal.h
index 0cd50162c..a87ec0fdc 100644
--- a/libdeno/internal.h
+++ b/libdeno/internal.h
@@ -133,6 +133,8 @@ static inline v8::Local<v8::String> v8_str(const char* x) {
void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
void Recv(const v8::FunctionCallbackInfo<v8::Value>& args);
void Send(const v8::FunctionCallbackInfo<v8::Value>& args);
+void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args);
void Shared(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
void BuiltinModules(v8::Local<v8::Name> property,
@@ -142,6 +144,8 @@ static intptr_t external_references[] = {
reinterpret_cast<intptr_t>(Print),
reinterpret_cast<intptr_t>(Recv),
reinterpret_cast<intptr_t>(Send),
+ reinterpret_cast<intptr_t>(EvalContext),
+ reinterpret_cast<intptr_t>(ErrorToJSON),
reinterpret_cast<intptr_t>(Shared),
reinterpret_cast<intptr_t>(BuiltinModules),
reinterpret_cast<intptr_t>(MessageCallback),
@@ -153,9 +157,6 @@ Deno* NewFromSnapshot(void* user_data, deno_recv_cb cb);
void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context);
-void HandleException(v8::Local<v8::Context> context,
- v8::Local<v8::Value> exception);
-
void DeserializeInternalFields(v8::Local<v8::Object> holder, int index,
v8::StartupData payload, void* data);
diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc
index 0936c53b4..3193e7677 100644
--- a/libdeno/libdeno_test.cc
+++ b/libdeno/libdeno_test.cc
@@ -290,6 +290,20 @@ TEST(LibDenoTest, Utf8Bug) {
deno_delete(d);
}
+TEST(LibDenoTest, LibDenoEvalContext) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContext();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContextError) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContextError();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
TEST(LibDenoTest, SharedAtomics) {
int32_t s[] = {0, 1, 2};
deno_buf shared = {nullptr, 0, reinterpret_cast<uint8_t*>(s), sizeof s};
diff --git a/libdeno/libdeno_test.js b/libdeno/libdeno_test.js
index 50f5c03d4..d6ea5f983 100644
--- a/libdeno/libdeno_test.js
+++ b/libdeno/libdeno_test.js
@@ -147,3 +147,50 @@ global.Shared = () => {
ui8[1] = 43;
ui8[2] = 44;
};
+
+global.LibDenoEvalContext = () => {
+ const [result, errInfo] = libdeno.evalContext("let a = 1; a");
+ assert(result === 1);
+ assert(!errInfo);
+ const [result2, errInfo2] = libdeno.evalContext("a = a + 1; a");
+ assert(result2 === 2);
+ assert(!errInfo2);
+};
+
+global.LibDenoEvalContextError = () => {
+ const [result, errInfo] = libdeno.evalContext("not_a_variable");
+ assert(!result);
+ assert(!!errInfo);
+ assert(errInfo.isNativeError); // is a native error (ReferenceError)
+ assert(!errInfo.isCompileError); // is NOT a compilation error
+ assert(errInfo.thrown.message === "not_a_variable is not defined");
+
+ const [result2, errInfo2] = libdeno.evalContext("throw 1");
+ assert(!result2);
+ assert(!!errInfo2);
+ assert(!errInfo2.isNativeError); // is NOT a native error
+ assert(!errInfo2.isCompileError); // is NOT a compilation error
+ assert(errInfo2.thrown === 1);
+
+ const [result3, errInfo3] =
+ libdeno.evalContext("class AError extends Error {}; throw new AError('e')");
+ assert(!result3);
+ assert(!!errInfo3);
+ assert(errInfo3.isNativeError); // extend from native error, still native error
+ assert(!errInfo3.isCompileError); // is NOT a compilation error
+ assert(errInfo3.thrown.message === "e");
+
+ const [result4, errInfo4] = libdeno.evalContext("{");
+ assert(!result4);
+ assert(!!errInfo4);
+ assert(errInfo4.isNativeError); // is a native error (SyntaxError)
+ assert(errInfo4.isCompileError); // is a compilation error! (braces not closed)
+ assert(errInfo4.thrown.message === "Unexpected end of input");
+
+ const [result5, errInfo5] = libdeno.evalContext("eval('{')");
+ assert(!result5);
+ assert(!!errInfo5);
+ assert(errInfo5.isNativeError); // is a native error (SyntaxError)
+ assert(!errInfo5.isCompileError); // is NOT a compilation error! (just eval)
+ assert(errInfo5.thrown.message === "Unexpected end of input");
+};
diff --git a/src/libdeno.rs b/src/libdeno.rs
index ddf025aba..a3a711b65 100644
--- a/src/libdeno.rs
+++ b/src/libdeno.rs
@@ -178,5 +178,4 @@ extern "C" {
user_data: *const c_void,
id: deno_mod,
);
-
}
diff --git a/src/msg.fbs b/src/msg.fbs
index de69c31ad..7d8b89175 100644
--- a/src/msg.fbs
+++ b/src/msg.fbs
@@ -1,6 +1,8 @@
union Any {
Start,
StartRes,
+ FormatError,
+ FormatErrorRes,
WorkerGetMessage,
WorkerGetMessageRes,
WorkerPostMessage,
@@ -163,6 +165,14 @@ table StartRes {
no_color: bool;
}
+table FormatError {
+ error: string;
+}
+
+table FormatErrorRes {
+ error: string;
+}
+
table WorkerGetMessage {
unused: int8;
}
diff --git a/src/ops.rs b/src/ops.rs
index 2b4136208..a0646e923 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -10,6 +10,7 @@ use crate::isolate::Buf;
use crate::isolate::Isolate;
use crate::isolate::IsolateState;
use crate::isolate::Op;
+use crate::js_errors::JSError;
use crate::libdeno;
use crate::msg;
use crate::msg_util;
@@ -97,6 +98,7 @@ pub fn dispatch(
msg::Any::Environ => op_env,
msg::Any::Exit => op_exit,
msg::Any::Fetch => op_fetch,
+ msg::Any::FormatError => op_format_error,
msg::Any::Listen => op_listen,
msg::Any::MakeTempDir => op_make_temp_dir,
msg::Any::Metrics => op_metrics,
@@ -283,6 +285,41 @@ fn op_start(
))
}
+fn op_format_error(
+ state: &Arc<IsolateState>,
+ base: &msg::Base<'_>,
+ data: libdeno::deno_buf,
+) -> Box<Op> {
+ assert_eq!(data.len(), 0);
+ let inner = base.inner_as_format_error().unwrap();
+ let orig_error = String::from(inner.error().unwrap());
+
+ let js_error = JSError::from_v8_exception(&orig_error).unwrap();
+ let js_error_mapped = js_error.apply_source_map(&state.dir);
+ let js_error_string = js_error_mapped.to_string();
+
+ let mut builder = FlatBufferBuilder::new();
+ let new_error = builder.create_string(&js_error_string);
+
+ let inner = msg::FormatErrorRes::create(
+ &mut builder,
+ &msg::FormatErrorResArgs {
+ error: Some(new_error),
+ ..Default::default()
+ },
+ );
+
+ ok_future(serialize_response(
+ base.cmd_id(),
+ &mut builder,
+ msg::BaseArgs {
+ inner_type: msg::Any::FormatErrorRes,
+ inner: Some(inner.as_union_value()),
+ ..Default::default()
+ },
+ ))
+}
+
fn serialize_response(
cmd_id: u32,
builder: &mut FlatBufferBuilder<'_>,
@@ -1271,7 +1308,8 @@ fn op_repl_readline(
debug!("op_repl_readline {} {}", rid, prompt);
blocking(base.sync(), move || -> OpResult {
- let line = resources::readline(rid, &prompt)?;
+ let repl = resources::get_repl(rid)?;
+ let line = repl.lock().unwrap().readline(&prompt)?;
let builder = &mut FlatBufferBuilder::new();
let line_off = builder.create_string(&line);
diff --git a/src/resources.rs b/src/resources.rs
index 1f5a121de..59167275b 100644
--- a/src/resources.rs
+++ b/src/resources.rs
@@ -35,7 +35,7 @@ use std::net::{Shutdown, SocketAddr};
use std::process::ExitStatus;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
-use std::sync::Mutex;
+use std::sync::{Arc, Mutex};
use tokio;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
@@ -95,7 +95,7 @@ enum Repr {
TcpListener(tokio::net::TcpListener, Option<futures::task::Task>),
TcpStream(tokio::net::TcpStream),
HttpBody(HttpBody),
- Repl(Repl),
+ Repl(Arc<Mutex<Repl>>),
// Enum size is bounded by the largest variant.
// Use `Box` around large `Child` struct.
// https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
@@ -334,7 +334,7 @@ pub fn add_hyper_body(body: hyper::Body) -> Resource {
pub fn add_repl(repl: Repl) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
- let r = tg.insert(rid, Repr::Repl(repl));
+ let r = tg.insert(rid, Repr::Repl(Arc::new(Mutex::new(repl))));
assert!(r.is_none());
Resource { rid }
}
@@ -462,14 +462,11 @@ pub fn child_status(rid: ResourceId) -> DenoResult<ChildStatus> {
}
}
-pub fn readline(rid: ResourceId, prompt: &str) -> DenoResult<String> {
+pub fn get_repl(rid: ResourceId) -> DenoResult<Arc<Mutex<Repl>>> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&rid);
match maybe_repr {
- Some(Repr::Repl(ref mut r)) => {
- let line = r.readline(&prompt)?;
- Ok(line)
- }
+ Some(Repr::Repl(ref mut r)) => Ok(r.clone()),
_ => Err(bad_resource()),
}
}
diff --git a/tools/repl_test.py b/tools/repl_test.py
index 1b589de67..2ce692b99 100644
--- a/tools/repl_test.py
+++ b/tools/repl_test.py
@@ -19,7 +19,7 @@ class Repl(object):
def input(self, *lines, **kwargs):
exit_ = kwargs.pop("exit", True)
sleep_ = kwargs.pop("sleep", 0)
- p = Popen([self.deno_exe], stdout=PIPE, stderr=PIPE, stdin=PIPE)
+ p = Popen([self.deno_exe, "-A"], stdout=PIPE, stderr=PIPE, stdin=PIPE)
try:
# Note: The repl takes a >100ms until it's ready.
time.sleep(sleep_)
@@ -87,7 +87,7 @@ class Repl(object):
def test_reference_error(self):
out, err, code = self.input("not_a_variable")
assertEqual(out, '')
- assertEqual(err, 'ReferenceError: not_a_variable is not defined\n')
+ assert "not_a_variable is not defined" in err
assertEqual(code, 0)
def test_set_timeout(self):
@@ -108,16 +108,25 @@ class Repl(object):
assertEqual(err, '')
assertEqual(code, 0)
+ def test_async_op(self):
+ out, err, code = self.input(
+ "fetch('http://localhost:4545/tests/001_hello.js')" +
+ ".then(res => res.text()).then(console.log)",
+ sleep=1)
+ assertEqual(out, 'Promise {}\nconsole.log("Hello World");\n\n')
+ assertEqual(err, '')
+ assertEqual(code, 0)
+
def test_syntax_error(self):
out, err, code = self.input("syntax error")
assertEqual(out, '')
- assertEqual(err, "SyntaxError: Unexpected identifier\n")
+ assert "Unexpected identifier" in err
assertEqual(code, 0)
def test_type_error(self):
out, err, code = self.input("console()")
assertEqual(out, '')
- assertEqual(err, 'TypeError: console is not a function\n')
+ assert "console is not a function" in err
assertEqual(code, 0)
def test_variable(self):
@@ -126,6 +135,12 @@ class Repl(object):
assertEqual(err, '')
assertEqual(code, 0)
+ def test_lexical_scoped_variable(self):
+ out, err, code = self.input("let a = 123;", "a")
+ assertEqual(out, 'undefined\n123\n')
+ assertEqual(err, '')
+ assertEqual(code, 0)
+
def assertEqual(left, right):
if left != right: