diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json index 15691e1583..0b5a8576a0 100644 --- a/.vs/launch.vs.json +++ b/.vs/launch.vs.json @@ -141,13 +141,6 @@ "name": "REPL - Jak 2", "args": ["--user-auto", "--game", "jak2"] }, - { - "type": "default", - "project": "CMakeLists.txt", - "projectTarget": "goalc.exe (bin\\goalc.exe)", - "name": "REPL - Auto Listen", - "args": ["--user-auto", "--auto-lt"] - }, { "type": "default", "project": "CMakeLists.txt", diff --git a/Taskfile.yml b/Taskfile.yml index 08844a2bd1..17fd4dcb8c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -68,9 +68,6 @@ tasks: msg: "Couldn't locate compiler executable in '{{.GOALC_BIN_RELEASE_DIR}}/goalc'" cmds: - "{{.GOALC_BIN_RELEASE_DIR}}/goalc --user-auto --game {{.GAME}}" - repl-lt: - cmds: - - "{{.GOALC_BIN_RELEASE_DIR}}/goalc --auto-lt --user-auto --game {{.GAME}}" format: desc: "Format code" cmds: diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index adf0c5e3ee..24b25a6324 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -19,11 +19,12 @@ add_library(common goos/PrettyPrinter2.cpp goos/Reader.cpp goos/TextDB.cpp - goos/ReplUtils.cpp + repl/config.cpp + repl/util.cpp log/log.cpp math/geometry.cpp - nrepl/ReplClient.cpp - nrepl/ReplServer.cpp + repl/nrepl/ReplClient.cpp + repl/nrepl/ReplServer.cpp type_system/defenum.cpp type_system/deftype.cpp type_system/state.cpp diff --git a/common/cross_sockets/XSocketClient.cpp b/common/cross_sockets/XSocketClient.cpp index 025b445a7e..25aff3e657 100644 --- a/common/cross_sockets/XSocketClient.cpp +++ b/common/cross_sockets/XSocketClient.cpp @@ -12,7 +12,7 @@ #include #include #endif -#include "common/nrepl/ReplServer.h" +#include "common/repl/nrepl/ReplServer.h" #include "third-party/fmt/core.h" // clang-format on diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index bc1b927347..222635cf58 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -160,7 +160,7 @@ HeapObject* Interpreter::intern_ptr(const std::string& name) { /*! * Display the REPL, which will run until the user executes exit. */ -void Interpreter::execute_repl(ReplWrapper& repl) { +void Interpreter::execute_repl(REPL::Wrapper& repl) { want_exit = false; while (!want_exit) { try { diff --git a/common/goos/Interpreter.h b/common/goos/Interpreter.h index cb78fc30d5..a58dbdb61c 100644 --- a/common/goos/Interpreter.h +++ b/common/goos/Interpreter.h @@ -16,7 +16,7 @@ class Interpreter { public: Interpreter(const std::string& user_profile = "#f"); ~Interpreter(); - void execute_repl(ReplWrapper& repl); + void execute_repl(REPL::Wrapper& repl); void throw_eval_error(const Object& o, const std::string& err); Object eval_with_rewind(const Object& obj, const std::shared_ptr& env); bool get_global_variable_by_name(const std::string& name, Object* dest); diff --git a/common/goos/Reader.cpp b/common/goos/Reader.cpp index ce86dd29bf..7c77d4dae7 100644 --- a/common/goos/Reader.cpp +++ b/common/goos/Reader.cpp @@ -11,9 +11,8 @@ #include "Reader.h" -#include "ReplUtils.h" - #include "common/log/log.h" +#include "common/repl/util.h" #include "common/util/FileUtil.h" #include "common/util/FontUtils.h" @@ -195,7 +194,7 @@ bool Reader::is_valid_source_char(char c) const { /*! * Prompt the user and read the result. */ -std::optional Reader::read_from_stdin(const std::string& prompt, ReplWrapper& repl) { +std::optional Reader::read_from_stdin(const std::string& prompt, REPL::Wrapper& repl) { // escape code will make sure that we remove any color std::string prompt_full = "\033[0m" + prompt; diff --git a/common/goos/Reader.h b/common/goos/Reader.h index 9dd725ab97..32b51f075b 100644 --- a/common/goos/Reader.h +++ b/common/goos/Reader.h @@ -16,10 +16,9 @@ #include #include -#include "ReplUtils.h" - #include "common/goos/Object.h" #include "common/goos/TextDB.h" +#include "common/repl/util.h" #include "common/util/Assert.h" namespace goos { @@ -74,7 +73,7 @@ class Reader { Object read_from_string(const std::string& str, bool add_top_level = true, const std::optional& string_name = {}); - std::optional read_from_stdin(const std::string& prompt, ReplWrapper& repl); + std::optional read_from_stdin(const std::string& prompt, REPL::Wrapper& repl); Object read_from_file(const std::vector& file_path, bool check_encoding = false); bool check_string_is_valid(const std::string& str) const; diff --git a/common/goos/ReplUtils.h b/common/goos/ReplUtils.h deleted file mode 100644 index 199bd0444e..0000000000 --- a/common/goos/ReplUtils.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "third-party/replxx/include/replxx.hxx" - -using Replxx = replxx::Replxx; - -class ReplWrapper { - Replxx repl; - - public: - ReplWrapper() {} - Replxx& get_repl() { return repl; } - void init_default_settings(); - - // Functionality / Commands - void clear_screen(); - void print_to_repl(const std::string_view& str); - void print_welcome_message(); - void set_history_max_size(size_t len); - const char* readline(const std::string& prompt); - void add_to_history(const std::string& line); - void save_history(); - void load_history(); - void print_help_message(); - std::pair get_current_repl_token(std::string const& context); - - std::vector examples{}; - using cl = Replxx::Color; - std::vector> regex_colors{}; - - private: - replxx::Replxx::key_press_handler_t commit_text_action(std::string text_to_commit); -}; diff --git a/common/repl/config.cpp b/common/repl/config.cpp new file mode 100644 index 0000000000..ea106405e6 --- /dev/null +++ b/common/repl/config.cpp @@ -0,0 +1,95 @@ +#include "config.h" + +#include "common/versions.h" + +#include "third-party/fmt/core.h" + +namespace REPL { +void to_json(json& j, const Config& obj) { + j = json{ + {"numConnectToTargetAttempts", obj.target_connect_attempts}, + {"asmFileSearchDirs", obj.asm_file_search_dirs}, + {"keybinds", obj.keybinds}, + }; +} + +void from_json(const json& j, Config& obj) { + if (j.contains("numConnectToTargetAttempts")) { + j.at("numConnectToTargetAttempts").get_to(obj.target_connect_attempts); + } + if (j.contains("asmFileSearchDirs")) { + j.at("asmFileSearchDirs").get_to(obj.asm_file_search_dirs); + } + if (j.contains("appendKeybinds")) { + j.at("appendKeybinds").get_to(obj.append_keybinds); + } + if (j.contains("keybinds")) { + std::vector keybinds = j.at("keybinds"); + if (!obj.append_keybinds) { + obj.keybinds = keybinds; + } else { + // append the keybinds + // - start with the provided ones + // - skip ones from the default set if they have the same key + modifier combination + for (const auto& default_bind : obj.keybinds) { + // check if it's a duplicate bind + bool duplicate = false; + for (const auto& new_bind : keybinds) { + if (new_bind.key == default_bind.key && new_bind.modifier == default_bind.modifier) { + duplicate = true; + break; + } + } + if (!duplicate) { + keybinds.push_back(default_bind); + } + } + obj.keybinds = keybinds; + } + } + // if there is game specific configuration, override any values we just set + if (j.contains(version_to_game_name(obj.game_version))) { + from_json(j.at(version_to_game_name(obj.game_version)), obj); + } +} + +std::string KeyBind::string() const { + switch (modifier) { + case KeyBind::Modifier::CTRL: + return fmt::format("CTRL-{}", key); + case KeyBind::Modifier::SHIFT: + return fmt::format("SHIFT-{}", key); + case KeyBind::Modifier::META: + return fmt::format("META-{}", key); + } +} + +void to_json(json& j, const KeyBind& obj) { + j = json{{"description", obj.description}, {"command", obj.command}, {"key", obj.key}}; + switch (obj.modifier) { + case KeyBind::Modifier::CTRL: + j["modifier"] = "ctrl"; + break; + case KeyBind::Modifier::SHIFT: + j["modifier"] = "shift"; + break; + case KeyBind::Modifier::META: + j["modifier"] = "meta"; + break; + } +} + +void from_json(const json& j, KeyBind& obj) { + j.at("description").get_to(obj.description); + j.at("command").get_to(obj.command); + j.at("key").get_to(obj.key); + auto modString = j.at("modifier").get(); + if (modString == "ctrl") { + obj.modifier = KeyBind::Modifier::CTRL; + } else if (modString == "shift") { + obj.modifier = KeyBind::Modifier::SHIFT; + } else if (modString == "meta") { + obj.modifier = KeyBind::Modifier::META; + } +} +} // namespace REPL diff --git a/common/repl/config.h b/common/repl/config.h new file mode 100644 index 0000000000..c051ea5153 --- /dev/null +++ b/common/repl/config.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#include "common/versions.h" + +#include "third-party/json.hpp" + +using json = nlohmann::json; + +namespace REPL { +struct KeyBind { + // NOTE - in my experience, meta doesn't work on windows and shift is probably a bad idea when + // typing text! but I leave it up to the user. + enum class Modifier { CTRL, SHIFT, META }; + Modifier modifier; + std::string key; + std::string description; + std::string command; + + std::string string() const; +}; +void to_json(json& j, const KeyBind& obj); +void from_json(const json& j, KeyBind& obj); + +struct Config { + GameVersion game_version; + Config(GameVersion _game_version) : game_version(_game_version){}; + + // this is the default REPL configuration + int target_connect_attempts = 30; + std::vector asm_file_search_dirs = {}; + bool append_keybinds = true; + std::vector keybinds = { + {KeyBind::Modifier::CTRL, "T", "Starts up the game runtime", "(test-play)"}, + {KeyBind::Modifier::CTRL, "Q", "Exit the REPL", "(e)"}, + {KeyBind::Modifier::CTRL, "L", "Listen to an available game process", "(lt)"}, + {KeyBind::Modifier::CTRL, "W", + "Halt the attached process so you can re-launch a crashed game", "(:stop)"}, + {KeyBind::Modifier::CTRL, "G", "Attach the debugger to the process", "(dbgc)"}, + {KeyBind::Modifier::CTRL, "B", "Displays the most recently caught backtrace", "(:di)"}, + {KeyBind::Modifier::CTRL, "N", "Full build of the game", "(mi)"}}; +}; +void to_json(json& j, const Config& obj); +void from_json(const json& j, Config& obj); + +} // namespace REPL diff --git a/common/nrepl/ReplClient.cpp b/common/repl/nrepl/ReplClient.cpp similarity index 100% rename from common/nrepl/ReplClient.cpp rename to common/repl/nrepl/ReplClient.cpp diff --git a/common/nrepl/ReplClient.h b/common/repl/nrepl/ReplClient.h similarity index 100% rename from common/nrepl/ReplClient.h rename to common/repl/nrepl/ReplClient.h diff --git a/common/nrepl/ReplServer.cpp b/common/repl/nrepl/ReplServer.cpp similarity index 97% rename from common/nrepl/ReplServer.cpp rename to common/repl/nrepl/ReplServer.cpp index c274bf694a..2286a8b8fd 100644 --- a/common/nrepl/ReplServer.cpp +++ b/common/repl/nrepl/ReplServer.cpp @@ -107,8 +107,8 @@ std::optional ReplServer::get_msg() { auto req_bytes = read_from_socket(sock, header_buffer.data(), header_buffer.size()); if (req_bytes == 0) { // Socket disconnected - // TODO - add a queue of messages in the ReplWrapper so we can print _BEFORE_ the prompt is - // output + // TODO - add a queue of messages in the REPL::Wrapper so we can print _BEFORE_ the prompt + // is output fmt::print("[nREPL:{}] Client Disconnected: {}\n", tcp_port, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), sock); diff --git a/common/nrepl/ReplServer.h b/common/repl/nrepl/ReplServer.h similarity index 100% rename from common/nrepl/ReplServer.h rename to common/repl/nrepl/ReplServer.h diff --git a/common/goos/ReplUtils.cpp b/common/repl/util.cpp similarity index 52% rename from common/goos/ReplUtils.cpp rename to common/repl/util.cpp index 62cedf0978..9fc926bba4 100644 --- a/common/goos/ReplUtils.cpp +++ b/common/repl/util.cpp @@ -1,22 +1,22 @@ -#include "ReplUtils.h" +#include "util.h" #include "common/util/FileUtil.h" +#include "common/util/json_util.h" +#include "common/util/string_util.h" #include "common/versions.h" #include "third-party/fmt/color.h" #include "third-party/fmt/core.h" #include "third-party/replxx/include/replxx.hxx" - // TODO - expand a list of hints (ie. a hint for defun to show at a glance how to write a function, // or perhaps, show the docstring for the current function being used?) -using Replxx = replxx::Replxx; - -void ReplWrapper::clear_screen() { +namespace REPL { +void Wrapper::clear_screen() { repl.clear_screen(); } -void ReplWrapper::print_welcome_message() { +void Wrapper::print_welcome_message() { // TODO - dont print on std-out // Welcome message / brief intro for documentation std::string ascii; @@ -29,9 +29,9 @@ void ReplWrapper::print_welcome_message() { fmt::print("Welcome to OpenGOAL {}.{}!\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); - fmt::print("Run "); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(repl-help)"); - fmt::print(" for help with common commands and REPL usage.\n"); + fmt::print("Run {} or {} for help with common commands and REPL usage.\n", + fmt::styled("(repl-help)", fmt::emphasis::bold | fg(fmt::color::cyan)), + fmt::styled("(repl-keybinds)", fmt::emphasis::bold | fg(fmt::color::cyan))); fmt::print("Run "); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)"); fmt::print(" to connect to the local target.\n"); @@ -40,29 +40,29 @@ void ReplWrapper::print_welcome_message() { fmt::print(" to rebuild the entire game.\n\n"); } -void ReplWrapper::print_to_repl(const std::string_view& str) { +void Wrapper::print_to_repl(const std::string& str) { repl.print(str.data()); } -void ReplWrapper::set_history_max_size(size_t len) { +void Wrapper::set_history_max_size(size_t len) { repl.set_max_history_size(len); } -const char* ReplWrapper::readline(const std::string& prompt) { +const char* Wrapper::readline(const std::string& prompt) { return repl.input(prompt); } -void ReplWrapper::add_to_history(const std::string& line) { +void Wrapper::add_to_history(const std::string& line) { repl.history_add(line); } -void ReplWrapper::save_history() { +void Wrapper::save_history() { fs::path path = file_util::get_user_config_dir() / ".opengoal.repl.history"; file_util::create_dir_if_needed_for_file(path.string()); repl.history_save(path.string()); } -void ReplWrapper::load_history() { +void Wrapper::load_history() { fs::path path = file_util::get_user_config_dir() / ".opengoal.repl.history"; if (fs::exists(path)) { repl.history_load(path.string()); @@ -71,7 +71,7 @@ void ReplWrapper::load_history() { } } -std::pair ReplWrapper::get_current_repl_token(std::string const& context) { +std::pair Wrapper::get_current_repl_token(std::string const& context) { // Find the current token std::string token = ""; for (auto c = context.crbegin(); c != context.crend(); c++) { @@ -90,7 +90,7 @@ std::pair ReplWrapper::get_current_repl_token(std::string con return {token, false}; } -void ReplWrapper::print_help_message() { +void Wrapper::print_help_message() { fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(:clear)\n"); fmt::print(" - Clear the current screen\n"); @@ -130,31 +130,110 @@ void ReplWrapper::print_help_message() { fmt::print(" - Enter a GOOS REPL\n"); } -replxx::Replxx::key_press_handler_t ReplWrapper::commit_text_action(std::string text_to_commit) { +void Wrapper::print_keybind_help() { + fmt::print(fmt::emphasis::bold, "\nREPL KeyBinds:\n"); + for (const auto& bind : repl_config.keybinds) { + fmt::print("{}\n", fmt::styled(bind.string(), fmt::fg(fmt::color::cyan))); + fmt::print("{}\n", fmt::styled(bind.description, fmt::fg(fmt::color::gray))); + } +} + +replxx::Replxx::key_press_handler_t Wrapper::commit_text_action(std::string text_to_commit) { return [this, text_to_commit](char32_t code) { repl.set_state( replxx::Replxx::State(text_to_commit.c_str(), static_cast(text_to_commit.size()))); - return repl.invoke(Replxx::ACTION::COMMIT_LINE, code); + return repl.invoke(replxx::Replxx::ACTION::COMMIT_LINE, code); }; } -void ReplWrapper::init_default_settings() { +void Wrapper::init_settings() { // NOTE - a nice popular project that uses replxx // - https://github.com/ClickHouse/ClickHouse/blob/master/base/base/ReplxxLineReader.cpp#L366 repl.set_word_break_characters(" \t"); // Setup default keybinds - // (test-play) : Ctrl-T - repl.bind_key(Replxx::KEY::control('T'), commit_text_action("(test-play)")); - // (e) : Ctrl-Q - repl.bind_key(Replxx::KEY::control('Q'), commit_text_action("(e)")); - // (lt) : Ctrl-L - repl.bind_key(Replxx::KEY::control('L'), commit_text_action("(lt)")); - /// (:stop) : Ctrl-W - repl.bind_key(Replxx::KEY::control('W'), commit_text_action("(:stop)")); - // (dbgc) : Ctrl-G - repl.bind_key(Replxx::KEY::control('G'), commit_text_action("(dbgc)")); - // (:di) : Ctrl-B - repl.bind_key(Replxx::KEY::control('B'), commit_text_action("(:di)")); - // (mi) : Ctrl-N - repl.bind_key(Replxx::KEY::control('N'), commit_text_action("(mi)")); + for (const auto& bind : repl_config.keybinds) { + char32_t code; + switch (bind.modifier) { + case KeyBind::Modifier::CTRL: + code = replxx::Replxx::KEY::control(bind.key.at(0)); + break; + case KeyBind::Modifier::SHIFT: + code = replxx::Replxx::KEY::shift(bind.key.at(0)); + break; + case KeyBind::Modifier::META: + code = replxx::Replxx::KEY::meta(bind.key.at(0)); + break; + } + repl.bind_key(code, commit_text_action(bind.command)); + } } + +// TODO - command to print out keybinds + +void Wrapper::reload_startup_file() { + startup_file = load_user_startup_file(username); +} + +std::string find_repl_username() { + // Two options - either: + // 1. look for the `user.txt` file, which should only contain the username + // 2. if this is absent AND there is a single folder inside the "user" folder, use that as the + // username + auto user_dir = file_util::get_jak_project_dir() / "goal_src" / "user"; + auto dirs = file_util::find_directories_in_dir(user_dir); + if (dirs.size() == 1) { + return dirs.at(0).filename().string(); + } + + std::regex allowed_chars("(^[0-9a-zA-Z\\-\\.\\!\\?<>]*$)"); + if (file_util::file_exists((user_dir / "user.txt").string())) { + auto text = file_util::read_text_file(user_dir / "user.txt"); + text = str_util::trim(text); + if (!text.empty() && std::regex_match(text, allowed_chars)) { + return text; + } + } + + return "#f"; +} + +StartupFile load_user_startup_file(const std::string& username) { + // Check for a `startup.gc` file, each line will be executed on the REPL on startup + auto startup_file_path = + file_util::get_jak_project_dir() / "goal_src" / "user" / username / "startup.gc"; + StartupFile startup_file; + if (file_util::file_exists(startup_file_path.string())) { + auto data = file_util::read_text_file(startup_file_path); + auto startup_cmds = str_util::split(data); + bool found_run_on_listen_line = false; + for (const auto& cmd : startup_cmds) { + if (found_run_on_listen_line) { + startup_file.run_after_listen.push_back(cmd); + } else { + startup_file.run_before_listen.push_back(cmd); + } + if (str_util::contains(cmd, "og:run-below-on-listen")) { + found_run_on_listen_line = true; + } + } + } + return startup_file; +} + +REPL::Config load_repl_config(const std::string& username, const GameVersion game_version) { + auto repl_config_path = + file_util::get_jak_project_dir() / "goal_src" / "user" / username / "repl-config.json"; + if (file_util::file_exists(repl_config_path.string())) { + try { + REPL::Config config(game_version); + auto repl_config_data = + parse_commented_json(file_util::read_text_file(repl_config_path), "repl-config.json"); + from_json(repl_config_data, config); + return config; + } catch (std::exception& e) { + REPL::Config config(game_version); + } + } + return REPL::Config(game_version); +} +} // namespace REPL diff --git a/common/repl/util.h b/common/repl/util.h new file mode 100644 index 0000000000..d12b0c573f --- /dev/null +++ b/common/repl/util.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include + +#include "config.h" + +#include "third-party/replxx/include/replxx.hxx" + +namespace REPL { + +struct StartupFile { + std::vector run_before_listen = {}; + std::vector run_after_listen = {}; +}; + +class Wrapper { + replxx::Replxx repl; + + public: + std::string username; + Config repl_config; + StartupFile startup_file; + std::vector examples{}; + std::vector> regex_colors{}; + + Wrapper(const std::string& _username, const Config& config, const StartupFile& startup) + : username(_username), repl_config(config), startup_file(startup) {} + replxx::Replxx& get_repl() { return repl; } + void init_settings(); + void reload_startup_file(); + + // Functionality / Commands + void clear_screen(); + void print_to_repl(const std::string& str); + void print_welcome_message(); + void set_history_max_size(size_t len); + const char* readline(const std::string& prompt); + void add_to_history(const std::string& line); + void save_history(); + void load_history(); + void print_help_message(); + void print_keybind_help(); + std::pair get_current_repl_token(std::string const& context); + + private: + replxx::Replxx::key_press_handler_t commit_text_action(std::string text_to_commit); + std::vector keybindings = {}; +}; + +std::string find_repl_username(); +StartupFile load_user_startup_file(const std::string& username); +REPL::Config load_repl_config(const std::string& username, const GameVersion game_version); +} // namespace REPL diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp index eb1dabbfb6..8e9de81640 100644 --- a/common/util/FileUtil.cpp +++ b/common/util/FileUtil.cpp @@ -550,4 +550,14 @@ std::vector find_files_recursively(const fs::path& base_dir, const std return files; } +std::vector find_directories_in_dir(const fs::path& base_dir) { + std::vector dirs = {}; + for (auto& p : fs::recursive_directory_iterator(base_dir)) { + if (p.is_directory()) { + dirs.push_back(p.path()); + } + } + return dirs; +} + } // namespace file_util diff --git a/common/util/FileUtil.h b/common/util/FileUtil.h index 10c6869f42..49de36971b 100644 --- a/common/util/FileUtil.h +++ b/common/util/FileUtil.h @@ -60,4 +60,5 @@ bool dgo_header_is_compressed(const std::vector& data); std::vector decompress_dgo(const std::vector& data_in); FILE* open_file(const fs::path& path, const std::string& mode); std::vector find_files_recursively(const fs::path& base_dir, const std::regex& pattern); +std::vector find_directories_in_dir(const fs::path& base_dir); } // namespace file_util diff --git a/common/util/diff.cpp b/common/util/diff.cpp index 7b6890e80c..0e61b8eb78 100644 --- a/common/util/diff.cpp +++ b/common/util/diff.cpp @@ -296,6 +296,8 @@ std::vector SplitString(const ::std::string& str, char delimiter = } } // namespace + +namespace google_diff { std::string diff_strings(const std::string& lhs, const std::string& rhs) { if (!lhs.empty() && !rhs.empty()) { const std::vector lhs_lines = SplitString(lhs); @@ -310,3 +312,4 @@ std::string diff_strings(const std::string& lhs, const std::string& rhs) { std::vector split_string(const ::std::string& str, char delimiter) { return SplitString(str, delimiter); } +} // namespace google_diff diff --git a/common/util/diff.h b/common/util/diff.h index 8dac5a4875..3eab1d5374 100644 --- a/common/util/diff.h +++ b/common/util/diff.h @@ -6,6 +6,7 @@ /*! * Diff two strings. This uses the code from gtest's diff implementation. */ +namespace google_diff { std::string diff_strings(const std::string& lhs, const std::string& rhs); - std::vector split_string(const ::std::string& str, char delimiter = '\n'); +} // namespace google_diff diff --git a/common/util/string_util.cpp b/common/util/string_util.cpp index cefb956643..44b89a4cce 100644 --- a/common/util/string_util.cpp +++ b/common/util/string_util.cpp @@ -2,6 +2,8 @@ #include +#include "common/util/diff.h" + namespace str_util { const std::string WHITESPACE = " \n\r\t\f\v"; @@ -47,4 +49,12 @@ bool valid_regex(const std::string& regex) { } return true; } + +std::string diff(const std::string& lhs, const std::string& rhs) { + return google_diff::diff_strings(lhs, rhs); +} +/// Default splits on \n characters +std::vector split(const ::std::string& str, char delimiter) { + return google_diff::split_string(str, delimiter); +} } // namespace str_util diff --git a/common/util/string_util.h b/common/util/string_util.h index 3e6b8f9209..e93054d897 100644 --- a/common/util/string_util.h +++ b/common/util/string_util.h @@ -1,4 +1,7 @@ +#pragma once + #include +#include namespace str_util { bool contains(const std::string& s, const std::string& substr); @@ -8,4 +11,7 @@ std::string rtrim(const std::string& s); std::string trim(const std::string& s); int line_count(const std::string& str); bool valid_regex(const std::string& regex); +std::string diff(const std::string& lhs, const std::string& rhs); +/// Default splits on \n characters +std::vector split(const ::std::string& str, char delimiter = '\n'); } // namespace str_util diff --git a/common/versions.cpp b/common/versions.cpp index 945ce15453..72932c04d7 100644 --- a/common/versions.cpp +++ b/common/versions.cpp @@ -29,3 +29,7 @@ std::string version_to_game_name(GameVersion v) { ASSERT_MSG(false, fmt::format("no game_name for version: {} found", fmt::underlying(v))); } } + +std::vector valid_game_version_names() { + return {game_version_names[GameVersion::Jak1], game_version_names[GameVersion::Jak2]}; +} diff --git a/common/versions.h b/common/versions.h index f3d41089f3..e5904f6971 100644 --- a/common/versions.h +++ b/common/versions.h @@ -6,6 +6,7 @@ */ #include +#include #include "common/common_types.h" @@ -55,3 +56,4 @@ constexpr PerGameVersion game_version_names = {"jak1", "jak2"}; GameVersion game_name_to_version(const std::string& name); bool valid_game_version(const std::string& name); std::string version_to_game_name(GameVersion v); +std::vector valid_game_version_names(); diff --git a/game/tools/subtitles/subtitle_editor.h b/game/tools/subtitles/subtitle_editor.h index f07a826ea3..655e16355d 100644 --- a/game/tools/subtitles/subtitle_editor.h +++ b/game/tools/subtitles/subtitle_editor.h @@ -3,7 +3,7 @@ #include #include -#include "common/nrepl/ReplClient.h" +#include "common/repl/nrepl/ReplClient.h" #include "common/serialization/subtitles/subtitles_ser.h" #include "third-party/imgui/imgui.h" diff --git a/goal_src/user/readme.md b/goal_src/user/readme.md index cbd2c419d0..d4f5d66f53 100644 --- a/goal_src/user/readme.md +++ b/goal_src/user/readme.md @@ -5,6 +5,8 @@ e.g. for username `mark` make a directory called `mark` Inside that directory, create `user.gs` and `user.gc` files. These are your own user scripts, loaded after the GOOS library and GOAL library respectively. +> Alternatively, if you only have a single folder in `goal_src/user/` it will be assumed to be your user folder + The rest of the directory can be used however you please! To automatically log in as a specific user, create a `user.txt` file in this directory @@ -13,7 +15,60 @@ modify multiple scripts when you want to change users. If you want to make your profile public, edit the .gitignore in this directory. -Additionally, you can provide a `repl-config.json` to set various REPL settings: -- `numConnectToTargetAttempts` - the number of times the REPL will attempt to connect to the target on an `(lt)` +Additionally, you can provide a `repl-config.json` to set various REPL settings, an example configuration: +```json +{ + "numConnectToTargetAttempts": 1, + "jak1": { + "asmFileSearchDirs": [ + "goal_src/jak1" + ] + }, + "jak2": { + "asmFileSearchDirs": [ + "goal_src/jak2" + ] + }, + "appendKeybinds": true, + "keybinds": [ + { + "modifier": "ctrl", + "key": "S", + "description": "Test Bind", + "command": "(format 0 \"hello world\")" + } + ] +} +``` And a `startup.gc` where each line will be executed upon startup + +## Re-running certain commands upon listening to the target + +A common workflow that you might want in your `startup.gc` is something like the following: + +```clj +(mi) +(lt) +(dbgc) +(test-play) +``` + +This builds the game, connects to the game, attaches the debugger, and runs it. + +However, when you crash you ideally want to just be able to: +- stop the game via `(:stop)` or the respective keybind +- fix the code, rebuild just that file +- re-launch the game and re-connect + +Upon which you'd probably want to run all or some of the above startup again. But how can you accomplish this without re-launching the REPL completely? Like so: + +```clj +(mi) +(lt) +;; og:run-below-on-listen +(dbgc) +(test-play) +``` + +As the comment suggests, upon a succesful `(lt)` it will run any lines below it again. diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 46088fc995..7a6e29263b 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -20,7 +20,7 @@ using namespace goos; Compiler::Compiler(GameVersion version, const std::string& user_profile, - std::unique_ptr repl) + std::unique_ptr repl) : m_version(version), m_goos(user_profile), m_debugger(&m_listener, &m_goos.reader, version), @@ -63,9 +63,9 @@ Compiler::Compiler(GameVersion version, m_repl->load_history(); // init repl m_repl->print_welcome_message(); - auto examples = m_repl->examples; - auto regex_colors = m_repl->regex_colors; - m_repl->init_default_settings(); + auto& examples = m_repl->examples; + auto& regex_colors = m_repl->regex_colors; + m_repl->init_settings(); using namespace std::placeholders; m_repl->get_repl().set_completion_callback(std::bind( &Compiler::find_symbols_or_object_file_by_prefix, this, _1, _2, std::cref(examples))); @@ -397,7 +397,7 @@ void Compiler::asm_file(const CompilationOptions& options) { if (file_path.empty()) { lg::print("ERROR - can't load a file without a providing a path\n"); return; - } else if (m_asm_file_search_dirs.empty()) { + } else if (m_repl && m_repl->repl_config.asm_file_search_dirs.empty()) { lg::print( "ERROR - can't load a file that doesn't exist - '{}' and no search dirs are defined\n", file_path); @@ -406,14 +406,17 @@ void Compiler::asm_file(const CompilationOptions& options) { std::string base_name = file_util::base_name_no_ext(file_path); // Attempt the find the full path of the file (ignore extension) std::vector candidate_paths = {}; - for (const auto& dir : m_asm_file_search_dirs) { - std::string base_dir = file_util::get_file_path({dir}); - const auto& results = file_util::find_files_recursively( - base_dir, std::regex(fmt::format("^{}(\\..*)?$", base_name))); - for (const auto& result : results) { - candidate_paths.push_back(result); + if (m_repl) { + for (const auto& dir : m_repl->repl_config.asm_file_search_dirs) { + std::string base_dir = file_util::get_file_path({dir}); + const auto& results = file_util::find_files_recursively( + base_dir, std::regex(fmt::format("^{}(\\..*)?$", base_name))); + for (const auto& result : results) { + candidate_paths.push_back(result); + } } } + if (candidate_paths.empty()) { lg::print("ERROR - attempt to find object file automatically, but found nothing\n"); return; diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index bd41b63364..351b62818f 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -5,7 +5,7 @@ #include #include "common/goos/Interpreter.h" -#include "common/goos/ReplUtils.h" +#include "common/repl/util.h" #include "common/type_system/TypeSystem.h" #include "goalc/compiler/CompilerException.h" @@ -41,12 +41,12 @@ class Compiler { public: Compiler(GameVersion version, const std::string& user_profile = "#f", - std::unique_ptr repl = nullptr); + std::unique_ptr repl = nullptr); ~Compiler(); void asm_file(const CompilationOptions& options); void save_repl_history(); - void print_to_repl(const std::string_view& str); + void print_to_repl(const std::string& str); std::string get_prompt(); std::string get_repl_input(); ReplStatus handle_repl_string(const std::string& input); @@ -81,21 +81,19 @@ class Compiler { listener::Listener& listener() { return m_listener; } void poke_target() { m_listener.send_poke(); } bool connect_to_target(); - Replxx::completions_t find_symbols_or_object_file_by_prefix( + replxx::Replxx::completions_t find_symbols_or_object_file_by_prefix( std::string const& context, int& contextLen, std::vector const& user_data); - Replxx::hints_t find_hints_by_prefix(std::string const& context, - int& contextLen, - Replxx::Color& color, - std::vector const& user_data); + replxx::Replxx::hints_t find_hints_by_prefix(std::string const& context, + int& contextLen, + replxx::Replxx::Color& color, + std::vector const& user_data); void repl_coloring(std::string const& str, - Replxx::colors_t& colors, - std::vector> const& user_data); + replxx::Replxx::colors_t& colors, + std::vector> const& user_data); bool knows_object_file(const std::string& name); MakeSystem& make_system() { return m_make; } - void update_via_config_file(const std::string& json, - const std::optional game_name = {}); private: GameVersion m_version; @@ -114,13 +112,9 @@ class Compiler { bool m_throw_on_define_extern_redefinition = false; std::unordered_set m_allow_inconsistent_definition_symbols; SymbolInfoMap m_symbol_info; - std::unique_ptr m_repl; + std::unique_ptr m_repl; MakeSystem m_make; - // Configurable fields - int m_target_connect_attempts = 30; - std::vector m_asm_file_search_dirs = {}; - struct DebugStats { int num_spills = 0; int num_spills_v1 = 0; @@ -602,6 +596,7 @@ class Compiler { Val* compile_asm_data_file(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_asm_text_file(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_repl_help(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_repl_keybinds(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_listen_to_target(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_poke(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index c0cb4536de..a1a4e12d09 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -9,7 +9,7 @@ void Compiler::save_repl_history() { m_repl->save_history(); } -void Compiler::print_to_repl(const std::string_view& str) { +void Compiler::print_to_repl(const std::string& str) { m_repl->print_to_repl(str); } @@ -154,22 +154,6 @@ bool Compiler::knows_object_file(const std::string& name) { return m_debugger.knows_object(name); } -void Compiler::update_via_config_file(const std::string& json, - const std::optional game_name) { - auto cfg = parse_commented_json(json, "repl-config.json"); - if (cfg.contains("numConnectToTargetAttempts")) { - m_target_connect_attempts = cfg.at("numConnectToTargetAttempts").get(); - } - if (cfg.contains("asmFileSearchDirs")) { - m_asm_file_search_dirs = cfg.at("asmFileSearchDirs").get>(); - } - // If there are any game specific config entries, set or override with them - if (game_name && cfg.contains(game_name.value())) { - auto game_cfg = cfg.at(game_name.value()).get(); - update_via_config_file(game_cfg.dump(), {}); - } -} - /*! * Parse arguments into a goos::Arguments format. */ diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index c928f883d3..2ff6bd9a71 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -140,6 +140,7 @@ const std::unordered_map< // COMPILER CONTROL {"repl-help", &Compiler::compile_repl_help}, + {"repl-keybinds", &Compiler::compile_repl_keybinds}, {":clear", &Compiler::compile_repl_clear_screen}, {"gs", &Compiler::compile_gs}, {":exit", &Compiler::compile_exit}, diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index 6e853ea843..8b50bed433 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -6,7 +6,7 @@ #include #include -#include "common/goos/ReplUtils.h" +#include "common/repl/util.h" #include "common/util/DgoWriter.h" #include "common/util/FileUtil.h" #include "common/util/Timer.h" @@ -177,6 +177,14 @@ Val* Compiler::compile_repl_help(const goos::Object&, const goos::Object&, Env*) return get_none(); } +/*! + * Print out all set keybinds for the REPL (by our tooling) + */ +Val* Compiler::compile_repl_keybinds(const goos::Object&, const goos::Object&, Env*) { + m_repl.get()->print_keybind_help(); + return get_none(); +} + /*! * Connect the compiler to a target. Takes an optional IP address / port, defaults to * 127.0.0.1 and 8112, which is the local computer and the default port for the DECI2 over IP @@ -208,7 +216,17 @@ Val* Compiler::compile_listen_to_target(const goos::Object& form, } }); - m_listener.connect_to_target(m_target_connect_attempts, ip, port); + int retries = 30; + if (m_repl) { + retries = m_repl->repl_config.target_connect_attempts; + } + auto connected = m_listener.connect_to_target(retries, ip, port); + if (connected && m_repl) { + m_repl->reload_startup_file(); + for (const auto& line : m_repl->startup_file.run_after_listen) { + handle_repl_string(line); + } + } return get_none(); } @@ -379,13 +397,13 @@ Val* Compiler::compile_get_info(const goos::Object& form, const goos::Object& re return get_none(); } -Replxx::completions_t Compiler::find_symbols_or_object_file_by_prefix( +replxx::Replxx::completions_t Compiler::find_symbols_or_object_file_by_prefix( std::string const& context, int& contextLen, std::vector const& user_data) { (void)contextLen; (void)user_data; - Replxx::completions_t completions; + replxx::Replxx::completions_t completions; // If we are trying to execute a `(ml ...)` we can automatically get the object file // insert quotes if needed as well. @@ -418,16 +436,16 @@ Replxx::completions_t Compiler::find_symbols_or_object_file_by_prefix( return completions; } -Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context, - int& contextLen, - Replxx::Color& color, - std::vector const& user_data) { +replxx::Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context, + int& contextLen, + replxx::Replxx::Color& color, + std::vector const& user_data) { (void)contextLen; (void)user_data; auto token = m_repl->get_current_repl_token(context); auto possible_forms = lookup_symbol_infos_starting_with(token.first); - Replxx::hints_t hints; + replxx::Replxx::hints_t hints; // TODO - hints for `(ml ...` as well @@ -440,7 +458,7 @@ Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context, // set hint color to green if single match found if (hints.size() == 1) { - color = Replxx::Color::GREEN; + color = replxx::Replxx::Color::GREEN; } return hints; @@ -448,10 +466,10 @@ Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context, void Compiler::repl_coloring( std::string const& context, - Replxx::colors_t& colors, - std::vector> const& regex_color) { + replxx::Replxx::colors_t& colors, + std::vector> const& regex_color) { (void)regex_color; - using cl = Replxx::Color; + using cl = replxx::Replxx::Color; // TODO - a proper circular queue would be cleaner to use std::deque paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA}; std::stack> expression_stack; diff --git a/goalc/main.cpp b/goalc/main.cpp index 72c67e5792..91996157ad 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -1,11 +1,12 @@ #include #include -#include "common/goos/ReplUtils.h" #include "common/log/log.h" -#include "common/nrepl/ReplServer.h" +#include "common/repl/nrepl/ReplServer.h" +#include "common/repl/util.h" #include "common/util/FileUtil.h" #include "common/util/diff.h" +#include "common/util/string_util.h" #include "common/versions.h" #include "goalc/compiler/Compiler.h" @@ -27,7 +28,6 @@ int main(int argc, char** argv) { bool auto_debug = false; bool auto_find_user = false; std::string cmd = ""; - std::string startup_cmd = ""; std::string username = "#f"; std::string game = "jak1"; int nrepl_port = 8181; @@ -36,9 +36,7 @@ int main(int argc, char** argv) { // TODO - a lot of these flags could be deprecated and moved into `repl-config.json` // TODO - auto-find the user if there is only one folder within `user/` CLI::App app{"OpenGOAL Compiler / REPL"}; - app.add_option("-c,--cmd", cmd, "Specify a command to run"); - app.add_option("--startup-cmd", startup_cmd, - "Specify a command to run and keep the REPL open afterwards"); + app.add_option("-c,--cmd", cmd, "Specify a command to run, no REPL is launched in this mode"); app.add_option("-u,--user", username, "Specify the username to use for your user profile in 'goal_src/user/'"); app.add_option("-p,--port", nrepl_port, "Specify the nREPL port. Defaults to 8181"); @@ -47,13 +45,25 @@ int main(int argc, char** argv) { app.add_flag("--auto-dbg", auto_debug, "Attempt to automatically connect to the debugger on startup"); app.add_flag("--user-auto", auto_find_user, - "Attempt to automatically deduce the user, overrides '-user'"); + "Attempt to automatically deduce the user, overrides '--user'"); app.add_option("-g,--game", game, "The game name: 'jak1' or 'jak2'"); app.add_option("--proj-path", project_path_override, "Specify the location of the 'data/' folder"); app.validate_positionals(); CLI11_PARSE(app, argc, argv); + // Yell about deprecations + if (auto_listen) { + lg::warn( + "--auto-lt will be deprecated, migrate to a 'startup.gc' file in your goal_src/user " + "folder"); + } + if (auto_debug) { + lg::warn( + "--auto-dbg will be deprecated, migrate to a 'startup.gc' file in your goal_src/user " + "folder"); + } + GameVersion game_version = game_name_to_version(game); if (!project_path_override.empty()) { @@ -69,74 +79,60 @@ int main(int argc, char** argv) { return 1; } - std::vector user_startup_commands = {}; - std::optional repl_config = {}; - - if (auto_find_user) { - username = "#f"; - std::regex allowed_chars("[0-9a-zA-Z\\-\\.\\!\\?<>]"); - try { - auto text = std::make_shared( - file_util::get_file_path({"goal_src", "user", "user.txt"}), "goal_src/user/user.txt"); - goos::TextStream ts(text); - ts.seek_past_whitespace_and_comments(); - std::string found_username; - while (ts.text_remains()) { - auto character = ts.read(); - if (!std::regex_match(std::string(1, character), allowed_chars)) { - break; - } - found_username.push_back(character); - } - if (!found_username.empty()) { - username = found_username; - // Check for a `startup.gc` file, each line will be executed on the REPL on startup - auto startup_file_path = - file_util::get_file_path({"goal_src", "user", username, "startup.gc"}); - if (file_util::file_exists(startup_file_path)) { - auto data = file_util::read_text_file(startup_file_path); - auto startup_cmds = split_string(data); - for (const auto& cmd : startup_cmds) { - user_startup_commands.push_back(cmd); - } - } - // Check for a `repl-config.json` file, so things can be configured without tons of flags - auto repl_config_path = - file_util::get_file_path({"goal_src", "user", username, "repl-config.json"}); - if (file_util::file_exists(repl_config_path)) { - repl_config = file_util::read_text_file(repl_config_path); - } - } - } catch (std::exception& e) { - printf("error opening user desc file: %s\n", e.what()); - } - } - setup_logging(); - lg::info("OpenGOAL Compiler {}.{}", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); - // Init REPL + // Figure out the username + if (auto_find_user) { + username = REPL::find_repl_username(); + } + // Load the user's startup file + auto startup_file = REPL::load_user_startup_file(username); + // TODO - deprecate these two flags + if (startup_file.run_before_listen.empty() && (auto_debug || auto_listen)) { + startup_file.run_before_listen.push_back("(lt)"); + } + if (startup_file.run_after_listen.empty() && (auto_debug || auto_listen)) { + startup_file.run_before_listen.push_back("(dbgc)"); + } + // Load the user's REPL config + auto repl_config = REPL::load_repl_config(username, game_version); + + // Init Compiler + std::unique_ptr compiler; + std::mutex compiler_mutex; + // if a command is provided on the command line, no REPL just run the compiler on it + try { + if (!cmd.empty()) { + compiler = std::make_unique(game_version); + compiler->run_front_end_on_string(cmd); + return 0; + } + } catch (std::exception& e) { + lg::error("Compiler Fatal Error: {}", e.what()); + } + + // Otherwise, start the REPL normally ReplStatus status = ReplStatus::OK; + std::function repl_startup_func = [&]() { + // Run automatic forms if applicable + std::lock_guard lock(compiler_mutex); + for (const auto& cmd : startup_file.run_before_listen) { + status = compiler->handle_repl_string(cmd); + } + }; + + // Initialize nREPL server socket std::function shutdown_callback = [&]() { return status == ReplStatus::WANT_EXIT; }; ReplServer repl_server(shutdown_callback, nrepl_port); bool repl_server_ok = repl_server.init_server(); std::thread nrepl_thread; // the compiler may throw an exception if it fails to load its standard library. try { - std::unique_ptr compiler; - std::mutex compiler_mutex; - // if a command is provided on the command line, no REPL just run the compiler on it - if (!cmd.empty()) { - compiler = std::make_unique(game_version); - compiler->run_front_end_on_string(cmd); - return 0; - } - compiler = std::make_unique(game_version, username, std::make_unique()); - if (repl_config) { - compiler->update_via_config_file(repl_config.value(), game); - } - // Start nREPL Server + compiler = std::make_unique( + game_version, username, + std::make_unique(username, repl_config, startup_file)); + // Start nREPL Server if it spun up successfully if (repl_server_ok) { nrepl_thread = std::thread([&]() { while (!shutdown_callback()) { @@ -151,23 +147,7 @@ int main(int argc, char** argv) { } }); } - // Run automatic forms if applicable - // - this should probably be deprecated in favor of the `startup.gc` file - if (user_startup_commands.empty() && (auto_debug || auto_listen)) { - user_startup_commands.push_back("(lt)"); - } - if (user_startup_commands.empty() && auto_debug) { - std::lock_guard lock(compiler_mutex); - status = compiler->handle_repl_string("(dbgc)"); - user_startup_commands.push_back("(dbgc)"); - } - - if (!user_startup_commands.empty()) { - std::lock_guard lock(compiler_mutex); - for (const auto& cmd : user_startup_commands) { - status = compiler->handle_repl_string(cmd); - } - } + repl_startup_func(); // Poll Terminal while (status != ReplStatus::WANT_EXIT) { @@ -177,18 +157,13 @@ int main(int argc, char** argv) { if (compiler) { compiler->save_repl_history(); } - compiler = - std::make_unique(game_version, username, std::make_unique()); - if (repl_config) { - compiler->update_via_config_file(repl_config.value(), game); - } - if (!startup_cmd.empty()) { - compiler->handle_repl_string(startup_cmd); - // reset to prevent re-executing on manual reload - startup_cmd = ""; - } + compiler = std::make_unique( + game_version, username, + std::make_unique(username, repl_config, startup_file)); status = ReplStatus::OK; + repl_startup_func(); } + // process user input std::string input_from_stdin = compiler->get_repl_input(); if (!input_from_stdin.empty()) { // lock, while we compile @@ -201,6 +176,8 @@ int main(int argc, char** argv) { status = ReplStatus::WANT_EXIT; } + // TODO - investigate why there is such a delay when exitting + // Cleanup if (repl_server_ok) { repl_server.shutdown_server(); diff --git a/test/offline/framework/execution.cpp b/test/offline/framework/execution.cpp index 23e49a6911..65cfda2aba 100644 --- a/test/offline/framework/execution.cpp +++ b/test/offline/framework/execution.cpp @@ -1,6 +1,5 @@ #include "execution.h" -#include "common/util/diff.h" #include "common/util/string_util.h" #include "goalc/compiler/Compiler.h" @@ -36,7 +35,7 @@ void decompile(OfflineTestDecompiler& dc, std::string clean_decompilation_code(const std::string& in, const bool leave_comments = false) { std::string out = in; if (!leave_comments) { - std::vector lines = split_string(in); + std::vector lines = str_util::split(in); // Remove all lines that are comments // comments are added only by us, meaning this _should_ be consistent std::vector::iterator line_itr = lines.begin(); @@ -82,7 +81,7 @@ OfflineTestCompareResult compare(OfflineTestDecompiler& dc, compare_result.total_files++; compare_result.total_lines += str_util::line_count(result); if (result != ref) { - compare_result.failing_files.push_back({file.unique_name, diff_strings(ref, result)}); + compare_result.failing_files.push_back({file.unique_name, str_util::diff(ref, result)}); compare_result.total_pass = false; if (config.dump_mode) { auto failure_dir = file_util::get_jak_project_dir() / "failures"; diff --git a/test/offline/framework/orchestration.cpp b/test/offline/framework/orchestration.cpp index eeeaae86cb..68d95a219b 100644 --- a/test/offline/framework/orchestration.cpp +++ b/test/offline/framework/orchestration.cpp @@ -311,11 +311,11 @@ void OfflineTestThreadManager::print_current_test_status(const OfflineTestConfig fmt::print("\x1b[{}A", lines_to_clear); // move n lines up fmt::print("\e[?25l"); // hide the cursor int threads_shown = 0; - for (int i = 0; i < statuses.size() && threads_shown < threads_to_display; i++) { + for (int i = 0; i < (int)statuses.size() && threads_shown < threads_to_display; i++) { const auto& status = statuses.at(i); // Skip completed threads if there are potential in-progress ones to show if (threads_hidden != 0 && !status->in_progress() && - (statuses.size() - i) > threads_to_display) { + ((int)statuses.size() - i) > threads_to_display) { continue; }