123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- /*
- * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
- * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
- * Copyright (c) 2020-2022, Ali Mohammad Pur <mpfard@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <LibCore/ArgsParser.h>
- #include <LibCore/ConfigFile.h>
- #include <LibCore/StandardPaths.h>
- #include <LibCore/System.h>
- #include <LibJS/AST.h>
- #include <LibJS/Bytecode/BasicBlock.h>
- #include <LibJS/Bytecode/Generator.h>
- #include <LibJS/Bytecode/Interpreter.h>
- #include <LibJS/Console.h>
- #include <LibJS/Interpreter.h>
- #include <LibJS/Parser.h>
- #include <LibJS/Print.h>
- #include <LibJS/Runtime/ConsoleObject.h>
- #include <LibJS/Runtime/JSONObject.h>
- #include <LibJS/Runtime/StringPrototype.h>
- #include <LibJS/SourceTextModule.h>
- #include <LibLine/Editor.h>
- #include <LibMain/Main.h>
- #include <LibTextCodec/Decoder.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #ifndef AK_OS_EMSCRIPTEN
- # error "This program is for Emscripten only"
- #endif
- #include <emscripten.h>
- class ReplConsoleClient;
- RefPtr<JS::VM> g_vm;
- OwnPtr<JS::Interpreter> g_interpreter;
- OwnPtr<ReplConsoleClient> g_console_client;
- JS::Handle<JS::Value> g_last_value = JS::make_handle(JS::js_undefined());
- EM_JS(void, user_display, (char const* string, u32 length), { globalDisplayToUser(UTF8ToString(string, length)); });
- template<typename... Args>
- void display(CheckedFormatString<Args...> format_string, Args const&... args)
- {
- auto string = DeprecatedString::formatted(format_string.view(), args...);
- user_display(string.characters(), string.length());
- }
- template<typename... Args>
- void displayln(CheckedFormatString<Args...> format_string, Args const&... args)
- {
- display(format_string.view(), args...);
- user_display("\n", 1);
- }
- void displayln() { user_display("\n", 1); }
- class UserDisplayStream final : public Stream {
- virtual ErrorOr<Bytes> read_some(Bytes) override { return Error::from_string_view("Not readable"sv); };
- virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
- {
- user_display(bit_cast<char const*>(bytes.data()), bytes.size());
- return bytes.size();
- }
- virtual bool is_eof() const override { return true; }
- virtual bool is_open() const override { return true; }
- virtual void close() override { }
- };
- ErrorOr<void> print(JS::Value value)
- {
- UserDisplayStream stream;
- JS::PrintContext print_context {
- .vm = *g_vm,
- .stream = stream,
- .strip_ansi = true,
- };
- return JS::print(value, print_context);
- }
- class ReplObject final : public JS::GlobalObject {
- JS_OBJECT(ReplObject, JS::GlobalObject);
- public:
- ReplObject(JS::Realm& realm)
- : GlobalObject(realm)
- {
- }
- virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
- virtual ~ReplObject() override = default;
- private:
- JS_DECLARE_NATIVE_FUNCTION(last_value_getter);
- JS_DECLARE_NATIVE_FUNCTION(print);
- };
- static bool s_dump_ast = false;
- static bool s_run_bytecode = false;
- static bool s_as_module = false;
- static bool s_print_last_result = false;
- static ErrorOr<bool> parse_and_run(JS::Interpreter& interpreter, StringView source, StringView source_name)
- {
- enum class ReturnEarly {
- No,
- Yes,
- };
- JS::ThrowCompletionOr<JS::Value> result { JS::js_undefined() };
- auto run_script_or_module = [&](auto& script_or_module) -> ErrorOr<ReturnEarly> {
- if (s_dump_ast)
- script_or_module->parse_node().dump(0);
- if (s_run_bytecode) {
- JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm());
- result = bytecode_interpreter.run(*script_or_module);
- } else {
- result = interpreter.run(*script_or_module);
- }
- return ReturnEarly::No;
- };
- if (!s_as_module) {
- auto script_or_error = JS::Script::parse(source, interpreter.realm(), source_name);
- if (script_or_error.is_error()) {
- auto error = script_or_error.error()[0];
- auto hint = error.source_location_hint(source);
- if (!hint.is_empty())
- displayln("{}", hint);
- auto error_string = TRY(error.to_string());
- displayln("{}", error_string);
- result = interpreter.vm().throw_completion<JS::SyntaxError>(move(error_string));
- } else {
- auto return_early = TRY(run_script_or_module(script_or_error.value()));
- if (return_early == ReturnEarly::Yes)
- return true;
- }
- } else {
- auto module_or_error = JS::SourceTextModule::parse(source, interpreter.realm(), source_name);
- if (module_or_error.is_error()) {
- auto error = module_or_error.error()[0];
- auto hint = error.source_location_hint(source);
- if (!hint.is_empty())
- displayln("{}", hint);
- auto error_string = TRY(error.to_string());
- displayln("{}", error_string);
- result = interpreter.vm().throw_completion<JS::SyntaxError>(move(error_string));
- } else {
- auto return_early = TRY(run_script_or_module(module_or_error.value()));
- if (return_early == ReturnEarly::Yes)
- return true;
- }
- }
- auto handle_exception = [&](JS::Value thrown_value) {
- display("Uncaught exception: ");
- (void)print(thrown_value);
- if (!thrown_value.is_object() || !is<JS::Error>(thrown_value.as_object()))
- return;
- auto& traceback = static_cast<JS::Error const&>(thrown_value.as_object()).traceback();
- if (traceback.size() > 1) {
- unsigned repetitions = 0;
- for (size_t i = 0; i < traceback.size(); ++i) {
- auto& traceback_frame = traceback[i];
- if (i + 1 < traceback.size()) {
- auto& next_traceback_frame = traceback[i + 1];
- if (next_traceback_frame.function_name == traceback_frame.function_name) {
- repetitions++;
- continue;
- }
- }
- if (repetitions > 4) {
- // If more than 5 (1 + >4) consecutive function calls with the same name, print
- // the name only once and show the number of repetitions instead. This prevents
- // printing ridiculously large call stacks of recursive functions.
- displayln(" -> {}", traceback_frame.function_name);
- displayln(" {} more calls", repetitions);
- } else {
- for (size_t j = 0; j < repetitions + 1; ++j)
- displayln(" -> {}", traceback_frame.function_name);
- }
- repetitions = 0;
- }
- }
- };
- if (!result.is_error())
- g_last_value = JS::make_handle(result.value());
- if (result.is_error()) {
- VERIFY(result.throw_completion().value().has_value());
- handle_exception(*result.release_error().value());
- return false;
- }
- if (s_print_last_result) {
- (void)print(result.value());
- display("\n");
- }
- return true;
- }
- JS::ThrowCompletionOr<void> ReplObject::initialize(JS::Realm& realm)
- {
- MUST_OR_THROW_OOM(Base::initialize(realm));
- define_direct_property("global", this, JS::Attribute::Enumerable);
- u8 attr = JS::Attribute::Configurable | JS::Attribute::Writable | JS::Attribute::Enumerable;
- define_native_function(realm, "print", print, 1, attr);
- define_native_accessor(
- realm,
- "_",
- [](JS::VM&) {
- return g_last_value.value();
- },
- [](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
- auto& global_object = vm.get_global_object();
- VERIFY(is<ReplObject>(global_object));
- displayln("Disable writing last value to '_'");
- // We must delete first otherwise this setter gets called recursively.
- TRY(global_object.internal_delete(JS::PropertyKey { "_" }));
- auto value = vm.argument(0);
- TRY(global_object.internal_set(JS::PropertyKey { "_" }, value, &global_object));
- return value;
- },
- attr);
- return {};
- }
- JS_DEFINE_NATIVE_FUNCTION(ReplObject::print)
- {
- auto result = ::print(vm.argument(0));
- if (result.is_error())
- return g_vm->throw_completion<JS::InternalError>(TRY_OR_THROW_OOM(*g_vm, String::formatted("Failed to print value: {}", result.error())));
- displayln();
- return JS::js_undefined();
- }
- class ReplConsoleClient final : public JS::ConsoleClient {
- public:
- ReplConsoleClient(JS::Console& console)
- : ConsoleClient(console)
- {
- }
- virtual void clear() override
- {
- display("FIXME: clear");
- m_group_stack_depth = 0;
- }
- virtual void end_group() override
- {
- if (m_group_stack_depth > 0)
- m_group_stack_depth--;
- }
- // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
- virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments arguments) override
- {
- DeprecatedString indent = DeprecatedString::repeated(" "sv, m_group_stack_depth);
- if (log_level == JS::Console::LogLevel::Trace) {
- auto trace = arguments.get<JS::Console::Trace>();
- StringBuilder builder;
- if (!trace.label.is_empty())
- builder.appendff("{}{}\n", indent, trace.label);
- for (auto& function_name : trace.stack)
- builder.appendff("{}-> {}\n", indent, function_name);
- displayln("{}", builder.string_view());
- return JS::js_undefined();
- }
- if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) {
- auto group = arguments.get<JS::Console::Group>();
- displayln("{}{}", indent, group.label);
- m_group_stack_depth++;
- return JS::js_undefined();
- }
- auto output = DeprecatedString::join(' ', arguments.get<JS::MarkedVector<JS::Value>>());
- switch (log_level) {
- case JS::Console::LogLevel::Debug:
- displayln("{}{}", indent, output);
- break;
- case JS::Console::LogLevel::Error:
- case JS::Console::LogLevel::Assert:
- displayln("{}{}", indent, output);
- break;
- case JS::Console::LogLevel::Info:
- displayln("{}(i) {}", indent, output);
- break;
- case JS::Console::LogLevel::Log:
- displayln("{}{}", indent, output);
- break;
- case JS::Console::LogLevel::Warn:
- case JS::Console::LogLevel::CountReset:
- displayln("{}{}", indent, output);
- break;
- default:
- displayln("{}{}", indent, output);
- break;
- }
- return JS::js_undefined();
- }
- private:
- int m_group_stack_depth { 0 };
- };
- extern "C" int initialize_repl(char const* time_zone)
- {
- if (time_zone)
- setenv("TZ", time_zone, 1);
- g_vm = MUST(JS::VM::create());
- g_vm->enable_default_host_import_module_dynamically_hook();
- // NOTE: These will print out both warnings when using something like Promise.reject().catch(...) -
- // which is, as far as I can tell, correct - a promise is created, rejected without handler, and a
- // handler then attached to it. The Node.js REPL doesn't warn in this case, so it's something we
- // might want to revisit at a later point and disable warnings for promises created this way.
- g_vm->on_promise_unhandled_rejection = [](auto& promise) {
- display("WARNING: A promise was rejected without any handlers");
- display(" (result: ");
- (void)print(promise.result());
- displayln(")");
- };
- g_vm->on_promise_rejection_handled = [](auto& promise) {
- display("WARNING: A handler was added to an already rejected promise");
- display(" (result: ");
- (void)print(promise.result());
- displayln(")");
- };
- OwnPtr<JS::Interpreter> interpreter;
- s_print_last_result = true;
- interpreter = JS::Interpreter::create<ReplObject>(*g_vm);
- auto console_object = interpreter->realm().intrinsics().console_object();
- g_console_client = make<ReplConsoleClient>(console_object->console());
- console_object->console().set_client(*g_console_client);
- g_interpreter = move(interpreter);
- return 0;
- }
- extern "C" bool execute(char const* source)
- {
- if (auto result = parse_and_run(*g_interpreter, { source, strlen(source) }, "REPL"sv); result.is_error()) {
- displayln("{}", result.error());
- return false;
- } else {
- return result.value();
- }
- }
|