Serenity Operating System
at master 906 lines 35 kB view raw
1/* 2 * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org> 4 * Copyright (c) 2020-2022, Ali Mohammad Pur <mpfard@serenityos.org> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <LibCore/ArgsParser.h> 10#include <LibCore/ConfigFile.h> 11#include <LibCore/StandardPaths.h> 12#include <LibCore/System.h> 13#include <LibJS/Bytecode/BasicBlock.h> 14#include <LibJS/Bytecode/Generator.h> 15#include <LibJS/Bytecode/Interpreter.h> 16#include <LibJS/Console.h> 17#include <LibJS/Interpreter.h> 18#include <LibJS/Parser.h> 19#include <LibJS/Print.h> 20#include <LibJS/Runtime/ConsoleObject.h> 21#include <LibJS/Runtime/JSONObject.h> 22#include <LibJS/Runtime/StringPrototype.h> 23#include <LibJS/Runtime/ThrowableStringBuilder.h> 24#include <LibJS/SourceTextModule.h> 25#include <LibLine/Editor.h> 26#include <LibMain/Main.h> 27#include <LibTextCodec/Decoder.h> 28#include <signal.h> 29 30RefPtr<JS::VM> g_vm; 31Vector<String> g_repl_statements; 32JS::Handle<JS::Value> g_last_value = JS::make_handle(JS::js_undefined()); 33 34class ReplObject final : public JS::GlobalObject { 35 JS_OBJECT(ReplObject, JS::GlobalObject); 36 37public: 38 ReplObject(JS::Realm& realm) 39 : GlobalObject(realm) 40 { 41 } 42 virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; 43 virtual ~ReplObject() override = default; 44 45private: 46 JS_DECLARE_NATIVE_FUNCTION(exit_interpreter); 47 JS_DECLARE_NATIVE_FUNCTION(repl_help); 48 JS_DECLARE_NATIVE_FUNCTION(save_to_file); 49 JS_DECLARE_NATIVE_FUNCTION(load_ini); 50 JS_DECLARE_NATIVE_FUNCTION(load_json); 51 JS_DECLARE_NATIVE_FUNCTION(last_value_getter); 52 JS_DECLARE_NATIVE_FUNCTION(print); 53}; 54 55class ScriptObject final : public JS::GlobalObject { 56 JS_OBJECT(ScriptObject, JS::GlobalObject); 57 58public: 59 ScriptObject(JS::Realm& realm) 60 : JS::GlobalObject(realm) 61 { 62 } 63 virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; 64 virtual ~ScriptObject() override = default; 65 66private: 67 JS_DECLARE_NATIVE_FUNCTION(load_ini); 68 JS_DECLARE_NATIVE_FUNCTION(load_json); 69 JS_DECLARE_NATIVE_FUNCTION(print); 70}; 71 72static bool s_dump_ast = false; 73static bool s_run_bytecode = false; 74static bool s_opt_bytecode = false; 75static bool s_as_module = false; 76static bool s_print_last_result = false; 77static bool s_strip_ansi = false; 78static bool s_disable_source_location_hints = false; 79static RefPtr<Line::Editor> s_editor; 80static String s_history_path = String {}; 81static int s_repl_line_level = 0; 82static bool s_fail_repl = false; 83 84static ErrorOr<void> print(JS::Value value, Stream& stream) 85{ 86 JS::PrintContext print_context { .vm = *g_vm, .stream = stream, .strip_ansi = s_strip_ansi }; 87 return JS::print(value, print_context); 88} 89 90enum class PrintTarget { 91 StandardError, 92 StandardOutput, 93}; 94 95static ErrorOr<void> print(JS::Value value, PrintTarget target = PrintTarget::StandardOutput) 96{ 97 auto stream = TRY(target == PrintTarget::StandardError ? Core::File::standard_error() : Core::File::standard_output()); 98 return print(value, *stream); 99} 100 101static ErrorOr<String> prompt_for_level(int level) 102{ 103 static StringBuilder prompt_builder; 104 prompt_builder.clear(); 105 prompt_builder.append("> "sv); 106 107 for (auto i = 0; i < level; ++i) 108 prompt_builder.append(" "sv); 109 110 return prompt_builder.to_string(); 111} 112 113static ErrorOr<String> read_next_piece() 114{ 115 StringBuilder piece; 116 117 auto line_level_delta_for_next_line { 0 }; 118 119 do { 120 auto line_result = s_editor->get_line(TRY(prompt_for_level(s_repl_line_level)).to_deprecated_string()); 121 122 line_level_delta_for_next_line = 0; 123 124 if (line_result.is_error()) { 125 s_fail_repl = true; 126 return String {}; 127 } 128 129 auto& line = line_result.value(); 130 s_editor->add_to_history(line); 131 132 piece.append(line); 133 piece.append('\n'); 134 auto lexer = JS::Lexer(line); 135 136 enum { 137 NotInLabelOrObjectKey, 138 InLabelOrObjectKeyIdentifier, 139 InLabelOrObjectKey 140 } label_state { NotInLabelOrObjectKey }; 141 142 for (JS::Token token = lexer.next(); token.type() != JS::TokenType::Eof; token = lexer.next()) { 143 switch (token.type()) { 144 case JS::TokenType::BracketOpen: 145 case JS::TokenType::CurlyOpen: 146 case JS::TokenType::ParenOpen: 147 label_state = NotInLabelOrObjectKey; 148 s_repl_line_level++; 149 break; 150 case JS::TokenType::BracketClose: 151 case JS::TokenType::CurlyClose: 152 case JS::TokenType::ParenClose: 153 label_state = NotInLabelOrObjectKey; 154 s_repl_line_level--; 155 break; 156 157 case JS::TokenType::Identifier: 158 case JS::TokenType::StringLiteral: 159 if (label_state == NotInLabelOrObjectKey) 160 label_state = InLabelOrObjectKeyIdentifier; 161 else 162 label_state = NotInLabelOrObjectKey; 163 break; 164 case JS::TokenType::Colon: 165 if (label_state == InLabelOrObjectKeyIdentifier) 166 label_state = InLabelOrObjectKey; 167 else 168 label_state = NotInLabelOrObjectKey; 169 break; 170 default: 171 break; 172 } 173 } 174 175 if (label_state == InLabelOrObjectKey) { 176 // If there's a label or object literal key at the end of this line, 177 // prompt for more lines but do not change the line level. 178 line_level_delta_for_next_line += 1; 179 } 180 } while (s_repl_line_level + line_level_delta_for_next_line > 0); 181 182 return piece.to_string(); 183} 184 185static ErrorOr<void> write_to_file(String const& path) 186{ 187 auto file = TRY(Core::File::open(path, Core::File::OpenMode::Write, 0666)); 188 for (size_t i = 0; i < g_repl_statements.size(); i++) { 189 auto line = g_repl_statements[i].bytes(); 190 if (line.size() > 0 && i != g_repl_statements.size() - 1) { 191 TRY(file->write_until_depleted(line)); 192 } 193 if (i != g_repl_statements.size() - 1) { 194 TRY(file->write_value('\n')); 195 } 196 } 197 file->close(); 198 return {}; 199} 200 201static ErrorOr<bool> parse_and_run(JS::Interpreter& interpreter, StringView source, StringView source_name) 202{ 203 enum class ReturnEarly { 204 No, 205 Yes, 206 }; 207 208 JS::ThrowCompletionOr<JS::Value> result { JS::js_undefined() }; 209 210 auto run_script_or_module = [&](auto& script_or_module) -> ErrorOr<ReturnEarly> { 211 if (s_dump_ast) 212 script_or_module->parse_node().dump(0); 213 214 if (JS::Bytecode::g_dump_bytecode || s_run_bytecode) { 215 auto executable_result = JS::Bytecode::Generator::generate(script_or_module->parse_node()); 216 if (executable_result.is_error()) { 217 result = g_vm->throw_completion<JS::InternalError>(TRY(executable_result.error().to_string())); 218 return ReturnEarly::No; 219 } 220 221 auto executable = executable_result.release_value(); 222 executable->name = source_name; 223 if (s_opt_bytecode) { 224 auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(JS::Bytecode::Interpreter::OptimizationLevel::Optimize); 225 passes.perform(*executable); 226 dbgln("Optimisation passes took {}us", passes.elapsed()); 227 } 228 229 if (JS::Bytecode::g_dump_bytecode) 230 executable->dump(); 231 232 if (s_run_bytecode) { 233 JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); 234 auto result_or_error = bytecode_interpreter.run_and_return_frame(*executable, nullptr); 235 if (result_or_error.value.is_error()) 236 result = result_or_error.value.release_error(); 237 else 238 result = result_or_error.frame->registers[0]; 239 } else { 240 return ReturnEarly::Yes; 241 } 242 } else { 243 result = interpreter.run(*script_or_module); 244 } 245 246 return ReturnEarly::No; 247 }; 248 249 if (!s_as_module) { 250 auto script_or_error = JS::Script::parse(source, interpreter.realm(), source_name); 251 if (script_or_error.is_error()) { 252 auto error = script_or_error.error()[0]; 253 auto hint = error.source_location_hint(source); 254 if (!hint.is_empty()) 255 outln("{}", hint); 256 257 auto error_string = TRY(error.to_string()); 258 outln("{}", error_string); 259 result = interpreter.vm().throw_completion<JS::SyntaxError>(move(error_string)); 260 } else { 261 auto return_early = TRY(run_script_or_module(script_or_error.value())); 262 if (return_early == ReturnEarly::Yes) 263 return true; 264 } 265 } else { 266 auto module_or_error = JS::SourceTextModule::parse(source, interpreter.realm(), source_name); 267 if (module_or_error.is_error()) { 268 auto error = module_or_error.error()[0]; 269 auto hint = error.source_location_hint(source); 270 if (!hint.is_empty()) 271 outln("{}", hint); 272 273 auto error_string = TRY(error.to_string()); 274 outln("{}", error_string); 275 result = interpreter.vm().throw_completion<JS::SyntaxError>(move(error_string)); 276 } else { 277 auto return_early = TRY(run_script_or_module(module_or_error.value())); 278 if (return_early == ReturnEarly::Yes) 279 return true; 280 } 281 } 282 283 auto handle_exception = [&](JS::Value thrown_value) -> ErrorOr<void> { 284 warnln("Uncaught exception: "); 285 TRY(print(thrown_value, PrintTarget::StandardError)); 286 warnln(); 287 288 if (!thrown_value.is_object() || !is<JS::Error>(thrown_value.as_object())) 289 return {}; 290 auto const& traceback = static_cast<JS::Error const&>(thrown_value.as_object()).traceback(); 291 if (traceback.size() > 1) { 292 unsigned repetitions = 0; 293 for (size_t i = 0; i < traceback.size(); ++i) { 294 auto const& traceback_frame = traceback[i]; 295 if (i + 1 < traceback.size()) { 296 auto const& next_traceback_frame = traceback[i + 1]; 297 if (next_traceback_frame.function_name == traceback_frame.function_name) { 298 repetitions++; 299 continue; 300 } 301 } 302 if (repetitions > 4) { 303 // If more than 5 (1 + >4) consecutive function calls with the same name, print 304 // the name only once and show the number of repetitions instead. This prevents 305 // printing ridiculously large call stacks of recursive functions. 306 warnln(" -> {}", traceback_frame.function_name); 307 warnln(" {} more calls", repetitions); 308 } else { 309 for (size_t j = 0; j < repetitions + 1; ++j) 310 warnln(" -> {}", traceback_frame.function_name); 311 } 312 repetitions = 0; 313 } 314 } 315 return {}; 316 }; 317 318 if (!result.is_error()) 319 g_last_value = JS::make_handle(result.value()); 320 321 if (result.is_error()) { 322 VERIFY(result.throw_completion().value().has_value()); 323 TRY(handle_exception(*result.release_error().value())); 324 return false; 325 } 326 327 if (s_print_last_result) { 328 TRY(print(result.value())); 329 warnln(); 330 } 331 332 return true; 333} 334 335static JS::ThrowCompletionOr<JS::Value> load_ini_impl(JS::VM& vm) 336{ 337 auto& realm = *vm.current_realm(); 338 339 auto filename = TRY(vm.argument(0).to_deprecated_string(vm)); 340 auto file_or_error = Core::File::open(filename, Core::File::OpenMode::Read); 341 if (file_or_error.is_error()) 342 return vm.throw_completion<JS::Error>(TRY_OR_THROW_OOM(vm, String::formatted("Failed to open '{}': {}", filename, file_or_error.error()))); 343 344 auto config_file = MUST(Core::ConfigFile::open(filename, file_or_error.release_value())); 345 auto object = JS::Object::create(realm, realm.intrinsics().object_prototype()); 346 for (auto const& group : config_file->groups()) { 347 auto group_object = JS::Object::create(realm, realm.intrinsics().object_prototype()); 348 for (auto const& key : config_file->keys(group)) { 349 auto entry = config_file->read_entry(group, key); 350 group_object->define_direct_property(key, JS::PrimitiveString::create(vm, move(entry)), JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable); 351 } 352 object->define_direct_property(group, group_object, JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable); 353 } 354 return object; 355} 356 357static JS::ThrowCompletionOr<JS::Value> load_json_impl(JS::VM& vm) 358{ 359 auto filename = TRY(vm.argument(0).to_string(vm)); 360 auto file_or_error = Core::File::open(filename, Core::File::OpenMode::Read); 361 if (file_or_error.is_error()) 362 return vm.throw_completion<JS::Error>(TRY_OR_THROW_OOM(vm, String::formatted("Failed to open '{}': {}", filename, file_or_error.error()))); 363 364 auto file_contents_or_error = file_or_error.value()->read_until_eof(); 365 if (file_contents_or_error.is_error()) 366 return vm.throw_completion<JS::Error>(TRY_OR_THROW_OOM(vm, String::formatted("Failed to read '{}': {}", filename, file_contents_or_error.error()))); 367 368 auto json = JsonValue::from_string(file_contents_or_error.value()); 369 if (json.is_error()) 370 return vm.throw_completion<JS::SyntaxError>(JS::ErrorType::JsonMalformed); 371 372 return JS::JSONObject::parse_json_value(vm, json.value()); 373} 374 375JS::ThrowCompletionOr<void> ReplObject::initialize(JS::Realm& realm) 376{ 377 MUST_OR_THROW_OOM(Base::initialize(realm)); 378 379 define_direct_property("global", this, JS::Attribute::Enumerable); 380 u8 attr = JS::Attribute::Configurable | JS::Attribute::Writable | JS::Attribute::Enumerable; 381 define_native_function(realm, "exit", exit_interpreter, 0, attr); 382 define_native_function(realm, "help", repl_help, 0, attr); 383 define_native_function(realm, "save", save_to_file, 1, attr); 384 define_native_function(realm, "loadINI", load_ini, 1, attr); 385 define_native_function(realm, "loadJSON", load_json, 1, attr); 386 define_native_function(realm, "print", print, 1, attr); 387 388 define_native_accessor( 389 realm, 390 "_", 391 [](JS::VM&) { 392 return g_last_value.value(); 393 }, 394 [](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> { 395 auto& global_object = vm.get_global_object(); 396 VERIFY(is<ReplObject>(global_object)); 397 outln("Disable writing last value to '_'"); 398 399 // We must delete first otherwise this setter gets called recursively. 400 TRY(global_object.internal_delete(JS::PropertyKey { "_" })); 401 402 auto value = vm.argument(0); 403 TRY(global_object.internal_set(JS::PropertyKey { "_" }, value, &global_object)); 404 return value; 405 }, 406 attr); 407 408 return {}; 409} 410 411JS_DEFINE_NATIVE_FUNCTION(ReplObject::save_to_file) 412{ 413 if (!vm.argument_count()) 414 return JS::Value(false); 415 auto const save_path = TRY(vm.argument(0).to_string(vm)); 416 if (!write_to_file(save_path).is_error()) { 417 return JS::Value(true); 418 } 419 return JS::Value(false); 420} 421 422JS_DEFINE_NATIVE_FUNCTION(ReplObject::exit_interpreter) 423{ 424 s_editor->save_history(s_history_path.to_deprecated_string()); 425 if (!vm.argument_count()) 426 exit(0); 427 exit(TRY(vm.argument(0).to_number(vm)).as_double()); 428} 429 430JS_DEFINE_NATIVE_FUNCTION(ReplObject::repl_help) 431{ 432 warnln("REPL commands:"); 433 warnln(" exit(code): exit the REPL with specified code. Defaults to 0."); 434 warnln(" help(): display this menu"); 435 warnln(" loadINI(file): load the given file as INI."); 436 warnln(" loadJSON(file): load the given file as JSON."); 437 warnln(" print(value): pretty-print the given JS value."); 438 warnln(" save(file): write REPL input history to the given file. For example: save(\"foo.txt\")"); 439 return JS::js_undefined(); 440} 441 442JS_DEFINE_NATIVE_FUNCTION(ReplObject::load_ini) 443{ 444 return load_ini_impl(vm); 445} 446 447JS_DEFINE_NATIVE_FUNCTION(ReplObject::load_json) 448{ 449 return load_json_impl(vm); 450} 451 452JS_DEFINE_NATIVE_FUNCTION(ReplObject::print) 453{ 454 auto result = ::print(vm.argument(0)); 455 if (result.is_error()) 456 return g_vm->throw_completion<JS::InternalError>(TRY_OR_THROW_OOM(*g_vm, String::formatted("Failed to print value: {}", result.error()))); 457 458 outln(); 459 460 return JS::js_undefined(); 461} 462 463JS::ThrowCompletionOr<void> ScriptObject::initialize(JS::Realm& realm) 464{ 465 MUST_OR_THROW_OOM(Base::initialize(realm)); 466 467 define_direct_property("global", this, JS::Attribute::Enumerable); 468 u8 attr = JS::Attribute::Configurable | JS::Attribute::Writable | JS::Attribute::Enumerable; 469 define_native_function(realm, "loadINI", load_ini, 1, attr); 470 define_native_function(realm, "loadJSON", load_json, 1, attr); 471 define_native_function(realm, "print", print, 1, attr); 472 473 return {}; 474} 475 476JS_DEFINE_NATIVE_FUNCTION(ScriptObject::load_ini) 477{ 478 return load_ini_impl(vm); 479} 480 481JS_DEFINE_NATIVE_FUNCTION(ScriptObject::load_json) 482{ 483 return load_json_impl(vm); 484} 485 486JS_DEFINE_NATIVE_FUNCTION(ScriptObject::print) 487{ 488 auto result = ::print(vm.argument(0)); 489 if (result.is_error()) 490 return g_vm->throw_completion<JS::InternalError>(TRY_OR_THROW_OOM(*g_vm, String::formatted("Failed to print value: {}", result.error()))); 491 492 outln(); 493 494 return JS::js_undefined(); 495} 496 497static ErrorOr<void> repl(JS::Interpreter& interpreter) 498{ 499 while (!s_fail_repl) { 500 auto const piece = TRY(read_next_piece()); 501 if (Utf8View { piece }.trim(JS::whitespace_characters).is_empty()) 502 continue; 503 504 g_repl_statements.append(piece); 505 TRY(parse_and_run(interpreter, piece, "REPL"sv)); 506 } 507 return {}; 508} 509 510static Function<void()> interrupt_interpreter; 511static void sigint_handler() 512{ 513 interrupt_interpreter(); 514} 515 516class ReplConsoleClient final : public JS::ConsoleClient { 517public: 518 ReplConsoleClient(JS::Console& console) 519 : ConsoleClient(console) 520 { 521 } 522 523 virtual void clear() override 524 { 525 out("\033[3J\033[H\033[2J"); 526 m_group_stack_depth = 0; 527 fflush(stdout); 528 } 529 530 virtual void end_group() override 531 { 532 if (m_group_stack_depth > 0) 533 m_group_stack_depth--; 534 } 535 536 // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer 537 virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments arguments) override 538 { 539 auto indent = TRY_OR_THROW_OOM(*g_vm, String::repeated(' ', m_group_stack_depth * 2)); 540 541 if (log_level == JS::Console::LogLevel::Trace) { 542 auto trace = arguments.get<JS::Console::Trace>(); 543 JS::ThrowableStringBuilder builder(*g_vm); 544 if (!trace.label.is_empty()) 545 MUST_OR_THROW_OOM(builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label)); 546 547 for (auto& function_name : trace.stack) 548 MUST_OR_THROW_OOM(builder.appendff("{}-> {}\n", indent, function_name)); 549 550 outln("{}", builder.string_view()); 551 return JS::js_undefined(); 552 } 553 554 if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) { 555 auto group = arguments.get<JS::Console::Group>(); 556 outln("{}\033[36;1m{}\033[0m", indent, group.label); 557 m_group_stack_depth++; 558 return JS::js_undefined(); 559 } 560 561 auto output = TRY(generically_format_values(arguments.get<JS::MarkedVector<JS::Value>>())); 562#ifdef AK_OS_SERENITY 563 m_console.output_debug_message(log_level, output); 564#endif 565 566 switch (log_level) { 567 case JS::Console::LogLevel::Debug: 568 outln("{}\033[36;1m{}\033[0m", indent, output); 569 break; 570 case JS::Console::LogLevel::Error: 571 case JS::Console::LogLevel::Assert: 572 outln("{}\033[31;1m{}\033[0m", indent, output); 573 break; 574 case JS::Console::LogLevel::Info: 575 outln("{}(i) {}", indent, output); 576 break; 577 case JS::Console::LogLevel::Log: 578 outln("{}{}", indent, output); 579 break; 580 case JS::Console::LogLevel::Warn: 581 case JS::Console::LogLevel::CountReset: 582 outln("{}\033[33;1m{}\033[0m", indent, output); 583 break; 584 default: 585 outln("{}{}", indent, output); 586 break; 587 } 588 return JS::js_undefined(); 589 } 590 591private: 592 int m_group_stack_depth { 0 }; 593}; 594 595ErrorOr<int> serenity_main(Main::Arguments arguments) 596{ 597 TRY(Core::System::pledge("stdio rpath wpath cpath tty sigaction")); 598 599 bool gc_on_every_allocation = false; 600 bool disable_syntax_highlight = false; 601 StringView evaluate_script; 602 Vector<StringView> script_paths; 603 604 Core::ArgsParser args_parser; 605 args_parser.set_general_help("This is a JavaScript interpreter."); 606 args_parser.add_option(s_dump_ast, "Dump the AST", "dump-ast", 'A'); 607 args_parser.add_option(JS::Bytecode::g_dump_bytecode, "Dump the bytecode", "dump-bytecode", 'd'); 608 args_parser.add_option(s_run_bytecode, "Run the bytecode", "run-bytecode", 'b'); 609 args_parser.add_option(s_opt_bytecode, "Optimize the bytecode", "optimize-bytecode", 'p'); 610 args_parser.add_option(s_as_module, "Treat as module", "as-module", 'm'); 611 args_parser.add_option(s_print_last_result, "Print last result", "print-last-result", 'l'); 612 args_parser.add_option(s_strip_ansi, "Disable ANSI colors", "disable-ansi-colors", 'i'); 613 args_parser.add_option(s_disable_source_location_hints, "Disable source location hints", "disable-source-location-hints", 'h'); 614 args_parser.add_option(gc_on_every_allocation, "GC on every allocation", "gc-on-every-allocation", 'g'); 615 args_parser.add_option(disable_syntax_highlight, "Disable live syntax highlighting", "no-syntax-highlight", 's'); 616 args_parser.add_option(evaluate_script, "Evaluate argument as a script", "evaluate", 'c', "script"); 617 args_parser.add_positional_argument(script_paths, "Path to script files", "scripts", Core::ArgsParser::Required::No); 618 args_parser.parse(arguments); 619 620 bool syntax_highlight = !disable_syntax_highlight; 621 622 s_history_path = TRY(String::formatted("{}/.js-history", Core::StandardPaths::home_directory())); 623 624 g_vm = JS::VM::create(); 625 g_vm->enable_default_host_import_module_dynamically_hook(); 626 627 // NOTE: These will print out both warnings when using something like Promise.reject().catch(...) - 628 // which is, as far as I can tell, correct - a promise is created, rejected without handler, and a 629 // handler then attached to it. The Node.js REPL doesn't warn in this case, so it's something we 630 // might want to revisit at a later point and disable warnings for promises created this way. 631 g_vm->on_promise_unhandled_rejection = [](auto& promise) { 632 warn("WARNING: A promise was rejected without any handlers"); 633 warn(" (result: "); 634 (void)print(promise.result(), PrintTarget::StandardError); 635 warnln(")"); 636 }; 637 g_vm->on_promise_rejection_handled = [](auto& promise) { 638 warn("WARNING: A handler was added to an already rejected promise"); 639 warn(" (result: "); 640 (void)print(promise.result(), PrintTarget::StandardError); 641 warnln(")"); 642 }; 643 OwnPtr<JS::Interpreter> interpreter; 644 645 // FIXME: Figure out some way to interrupt the interpreter now that vm.exception() is gone. 646 647 if (evaluate_script.is_empty() && script_paths.is_empty()) { 648 s_print_last_result = true; 649 interpreter = JS::Interpreter::create<ReplObject>(*g_vm); 650 auto& console_object = *interpreter->realm().intrinsics().console_object(); 651 ReplConsoleClient console_client(console_object.console()); 652 console_object.console().set_client(console_client); 653 interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation); 654 655 auto& global_environment = interpreter->realm().global_environment(); 656 657 s_editor = Line::Editor::construct(); 658 s_editor->load_history(s_history_path.to_deprecated_string()); 659 660 signal(SIGINT, [](int) { 661 if (!s_editor->is_editing()) 662 sigint_handler(); 663 s_editor->save_history(s_history_path.to_deprecated_string()); 664 }); 665 666 s_editor->on_display_refresh = [syntax_highlight](Line::Editor& editor) { 667 auto stylize = [&](Line::Span span, Line::Style styles) { 668 if (syntax_highlight) 669 editor.stylize(span, styles); 670 }; 671 editor.strip_styles(); 672 673 size_t open_indents = s_repl_line_level; 674 675 auto line = editor.line(); 676 JS::Lexer lexer(line); 677 bool indenters_starting_line = true; 678 for (JS::Token token = lexer.next(); token.type() != JS::TokenType::Eof; token = lexer.next()) { 679 auto length = Utf8View { token.value() }.length(); 680 auto start = token.offset(); 681 auto end = start + length; 682 if (indenters_starting_line) { 683 if (token.type() != JS::TokenType::ParenClose && token.type() != JS::TokenType::BracketClose && token.type() != JS::TokenType::CurlyClose) { 684 indenters_starting_line = false; 685 } else { 686 --open_indents; 687 } 688 } 689 690 switch (token.category()) { 691 case JS::TokenCategory::Invalid: 692 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::Red), Line::Style::Underline }); 693 break; 694 case JS::TokenCategory::Number: 695 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::Magenta) }); 696 break; 697 case JS::TokenCategory::String: 698 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::Green), Line::Style::Bold }); 699 break; 700 case JS::TokenCategory::Punctuation: 701 break; 702 case JS::TokenCategory::Operator: 703 break; 704 case JS::TokenCategory::Keyword: 705 switch (token.type()) { 706 case JS::TokenType::BoolLiteral: 707 case JS::TokenType::NullLiteral: 708 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow), Line::Style::Bold }); 709 break; 710 default: 711 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Bold }); 712 break; 713 } 714 break; 715 case JS::TokenCategory::ControlKeyword: 716 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan), Line::Style::Italic }); 717 break; 718 case JS::TokenCategory::Identifier: 719 stylize({ start, end, Line::Span::CodepointOriented }, { Line::Style::Foreground(Line::Style::XtermColor::White), Line::Style::Bold }); 720 break; 721 default: 722 break; 723 } 724 } 725 726 editor.set_prompt(prompt_for_level(open_indents).release_value_but_fixme_should_propagate_errors().to_deprecated_string()); 727 }; 728 729 auto complete = [&interpreter, &global_environment](Line::Editor const& editor) -> Vector<Line::CompletionSuggestion> { 730 auto line = editor.line(editor.cursor()); 731 732 JS::Lexer lexer { line }; 733 enum { 734 Initial, 735 CompleteVariable, 736 CompleteNullProperty, 737 CompleteProperty, 738 } mode { Initial }; 739 740 StringView variable_name; 741 StringView property_name; 742 743 // we're only going to complete either 744 // - <N> 745 // where N is part of the name of a variable 746 // - <N>.<P> 747 // where N is the complete name of a variable and 748 // P is part of the name of one of its properties 749 auto js_token = lexer.next(); 750 for (; js_token.type() != JS::TokenType::Eof; js_token = lexer.next()) { 751 switch (mode) { 752 case CompleteVariable: 753 switch (js_token.type()) { 754 case JS::TokenType::Period: 755 // ...<name> <dot> 756 mode = CompleteNullProperty; 757 break; 758 default: 759 // not a dot, reset back to initial 760 mode = Initial; 761 break; 762 } 763 break; 764 case CompleteNullProperty: 765 if (js_token.is_identifier_name()) { 766 // ...<name> <dot> <name> 767 mode = CompleteProperty; 768 property_name = js_token.value(); 769 } else { 770 mode = Initial; 771 } 772 break; 773 case CompleteProperty: 774 // something came after the property access, reset to initial 775 case Initial: 776 if (js_token.type() == JS::TokenType::Identifier) { 777 // ...<name>... 778 mode = CompleteVariable; 779 variable_name = js_token.value(); 780 } else { 781 mode = Initial; 782 } 783 break; 784 } 785 } 786 787 bool last_token_has_trivia = js_token.trivia().length() > 0; 788 789 if (mode == CompleteNullProperty) { 790 mode = CompleteProperty; 791 property_name = ""sv; 792 last_token_has_trivia = false; // <name> <dot> [tab] is sensible to complete. 793 } 794 795 if (mode == Initial || last_token_has_trivia) 796 return {}; // we do not know how to complete this 797 798 Vector<Line::CompletionSuggestion> results; 799 800 Function<void(JS::Shape const&, StringView)> list_all_properties = [&results, &list_all_properties](JS::Shape const& shape, auto property_pattern) { 801 for (auto const& descriptor : shape.property_table()) { 802 if (!descriptor.key.is_string()) 803 continue; 804 auto key = descriptor.key.as_string(); 805 if (key.view().starts_with(property_pattern)) { 806 Line::CompletionSuggestion completion { key, Line::CompletionSuggestion::ForSearch }; 807 if (!results.contains_slow(completion)) { // hide duplicates 808 results.append(DeprecatedString(key)); 809 results.last().invariant_offset = property_pattern.length(); 810 } 811 } 812 } 813 if (auto const* prototype = shape.prototype()) { 814 list_all_properties(prototype->shape(), property_pattern); 815 } 816 }; 817 818 switch (mode) { 819 case CompleteProperty: { 820 auto reference_or_error = g_vm->resolve_binding(variable_name, &global_environment); 821 if (reference_or_error.is_error()) 822 return {}; 823 auto value_or_error = reference_or_error.value().get_value(*g_vm); 824 if (value_or_error.is_error()) 825 return {}; 826 auto variable = value_or_error.value(); 827 VERIFY(!variable.is_empty()); 828 829 if (!variable.is_object()) 830 break; 831 832 auto const* object = MUST(variable.to_object(*g_vm)); 833 auto const& shape = object->shape(); 834 list_all_properties(shape, property_name); 835 break; 836 } 837 case CompleteVariable: { 838 auto const& variable = interpreter->realm().global_object(); 839 list_all_properties(variable.shape(), variable_name); 840 841 for (auto const& name : global_environment.declarative_record().bindings()) { 842 if (name.starts_with(variable_name)) { 843 results.empend(name); 844 results.last().invariant_offset = variable_name.length(); 845 } 846 } 847 848 break; 849 } 850 default: 851 VERIFY_NOT_REACHED(); 852 } 853 854 return results; 855 }; 856 s_editor->on_tab_complete = move(complete); 857 TRY(repl(*interpreter)); 858 s_editor->save_history(s_history_path.to_deprecated_string()); 859 } else { 860 interpreter = JS::Interpreter::create<ScriptObject>(*g_vm); 861 auto& console_object = *interpreter->realm().intrinsics().console_object(); 862 ReplConsoleClient console_client(console_object.console()); 863 console_object.console().set_client(console_client); 864 interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation); 865 866 signal(SIGINT, [](int) { 867 sigint_handler(); 868 }); 869 870 StringBuilder builder; 871 StringView source_name; 872 873 if (evaluate_script.is_empty()) { 874 if (script_paths.size() > 1) 875 warnln("Warning: Multiple files supplied, this will concatenate the sources and resolve modules as if it was the first file"); 876 877 for (auto& path : script_paths) { 878 auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read)); 879 auto file_contents = TRY(file->read_until_eof()); 880 auto source = StringView { file_contents }; 881 882 if (Utf8View { file_contents }.validate()) { 883 builder.append(source); 884 } else { 885 auto decoder = TextCodec::decoder_for("windows-1252"sv); 886 VERIFY(decoder.has_value()); 887 888 auto utf8_source = TRY(TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, source)); 889 builder.append(utf8_source); 890 } 891 } 892 893 source_name = script_paths[0]; 894 } else { 895 builder.append(evaluate_script); 896 source_name = "eval"sv; 897 } 898 899 // We resolve modules as if it is the first file 900 901 if (!TRY(parse_and_run(*interpreter, builder.string_view(), source_name))) 902 return 1; 903 } 904 905 return 0; 906}