mirror of
https://github.com/open-goal/jak-project
synced 2026-08-18 13:53:01 -04:00
goalc/repl: cleanup of goalc/REPL code and some QoL improvements (#2104)
- lets you split up your `startup.gc` file into two sections - one that runs on initial startup / reloads - the other that runs when you listen to a target - allows for customization of the keybinds added a month or so ago - removes a useless flag (--startup-cmd) and marks others for deprecation. - added another help prompt that lists all the keybinds and what they do Co-authored-by: water <awaterford111445@gmail.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <WinSock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#endif
|
||||
#include "common/nrepl/ReplServer.h"
|
||||
#include "common/repl/nrepl/ReplServer.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
// clang-format on
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<EnvironmentObject>& env);
|
||||
bool get_global_variable_by_name(const std::string& name, Object* dest);
|
||||
|
||||
@@ -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<Object> Reader::read_from_stdin(const std::string& prompt, ReplWrapper& repl) {
|
||||
std::optional<Object> 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;
|
||||
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#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<std::string>& string_name = {});
|
||||
std::optional<Object> read_from_stdin(const std::string& prompt, ReplWrapper& repl);
|
||||
std::optional<Object> read_from_stdin(const std::string& prompt, REPL::Wrapper& repl);
|
||||
Object read_from_file(const std::vector<std::string>& file_path, bool check_encoding = false);
|
||||
bool check_string_is_valid(const std::string& str) const;
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string, bool> get_current_repl_token(std::string const& context);
|
||||
|
||||
std::vector<std::string> examples{};
|
||||
using cl = Replxx::Color;
|
||||
std::vector<std::pair<std::string, cl>> regex_colors{};
|
||||
|
||||
private:
|
||||
replxx::Replxx::key_press_handler_t commit_text_action(std::string text_to_commit);
|
||||
};
|
||||
@@ -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<KeyBind> 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<std::string>();
|
||||
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
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string> asm_file_search_dirs = {};
|
||||
bool append_keybinds = true;
|
||||
std::vector<KeyBind> 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
|
||||
@@ -107,8 +107,8 @@ std::optional<std::string> 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);
|
||||
|
||||
@@ -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<std::string, bool> ReplWrapper::get_current_repl_token(std::string const& context) {
|
||||
std::pair<std::string, bool> 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<std::string, bool> 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<int>(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
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "third-party/replxx/include/replxx.hxx"
|
||||
|
||||
namespace REPL {
|
||||
|
||||
struct StartupFile {
|
||||
std::vector<std::string> run_before_listen = {};
|
||||
std::vector<std::string> run_after_listen = {};
|
||||
};
|
||||
|
||||
class Wrapper {
|
||||
replxx::Replxx repl;
|
||||
|
||||
public:
|
||||
std::string username;
|
||||
Config repl_config;
|
||||
StartupFile startup_file;
|
||||
std::vector<std::string> examples{};
|
||||
std::vector<std::pair<std::string, replxx::Replxx::Color>> 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<std::string, bool> 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<REPL::KeyBind> 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
|
||||
@@ -550,4 +550,14 @@ std::vector<fs::path> find_files_recursively(const fs::path& base_dir, const std
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<fs::path> find_directories_in_dir(const fs::path& base_dir) {
|
||||
std::vector<fs::path> dirs = {};
|
||||
for (auto& p : fs::recursive_directory_iterator(base_dir)) {
|
||||
if (p.is_directory()) {
|
||||
dirs.push_back(p.path());
|
||||
}
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
} // namespace file_util
|
||||
|
||||
@@ -60,4 +60,5 @@ bool dgo_header_is_compressed(const std::vector<u8>& data);
|
||||
std::vector<u8> decompress_dgo(const std::vector<u8>& data_in);
|
||||
FILE* open_file(const fs::path& path, const std::string& mode);
|
||||
std::vector<fs::path> find_files_recursively(const fs::path& base_dir, const std::regex& pattern);
|
||||
std::vector<fs::path> find_directories_in_dir(const fs::path& base_dir);
|
||||
} // namespace file_util
|
||||
|
||||
@@ -296,6 +296,8 @@ std::vector<std::string> 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<std::string> lhs_lines = SplitString(lhs);
|
||||
@@ -310,3 +312,4 @@ std::string diff_strings(const std::string& lhs, const std::string& rhs) {
|
||||
std::vector<std::string> split_string(const ::std::string& str, char delimiter) {
|
||||
return SplitString(str, delimiter);
|
||||
}
|
||||
} // namespace google_diff
|
||||
|
||||
+2
-1
@@ -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<std::string> split_string(const ::std::string& str, char delimiter = '\n');
|
||||
} // namespace google_diff
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <regex>
|
||||
|
||||
#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<std::string> split(const ::std::string& str, char delimiter) {
|
||||
return google_diff::split_string(str, delimiter);
|
||||
}
|
||||
} // namespace str_util
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> split(const ::std::string& str, char delimiter = '\n');
|
||||
} // namespace str_util
|
||||
|
||||
@@ -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<std::string> valid_game_version_names() {
|
||||
return {game_version_names[GameVersion::Jak1], game_version_names[GameVersion::Jak2]};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -55,3 +56,4 @@ constexpr PerGameVersion<const char*> 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<std::string> valid_game_version_names();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/nrepl/ReplClient.h"
|
||||
#include "common/repl/nrepl/ReplClient.h"
|
||||
#include "common/serialization/subtitles/subtitles_ser.h"
|
||||
|
||||
#include "third-party/imgui/imgui.h"
|
||||
|
||||
+57
-2
@@ -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.
|
||||
|
||||
+14
-11
@@ -20,7 +20,7 @@ using namespace goos;
|
||||
|
||||
Compiler::Compiler(GameVersion version,
|
||||
const std::string& user_profile,
|
||||
std::unique_ptr<ReplWrapper> repl)
|
||||
std::unique_ptr<REPL::Wrapper> 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<fs::path> 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;
|
||||
|
||||
+12
-17
@@ -5,7 +5,7 @@
|
||||
#include <optional>
|
||||
|
||||
#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<ReplWrapper> repl = nullptr);
|
||||
std::unique_ptr<REPL::Wrapper> 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<std::string> const& user_data);
|
||||
Replxx::hints_t find_hints_by_prefix(std::string const& context,
|
||||
int& contextLen,
|
||||
Replxx::Color& color,
|
||||
std::vector<std::string> const& user_data);
|
||||
replxx::Replxx::hints_t find_hints_by_prefix(std::string const& context,
|
||||
int& contextLen,
|
||||
replxx::Replxx::Color& color,
|
||||
std::vector<std::string> const& user_data);
|
||||
void repl_coloring(std::string const& str,
|
||||
Replxx::colors_t& colors,
|
||||
std::vector<std::pair<std::string, Replxx::Color>> const& user_data);
|
||||
replxx::Replxx::colors_t& colors,
|
||||
std::vector<std::pair<std::string, replxx::Replxx::Color>> 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<std::string> game_name = {});
|
||||
|
||||
private:
|
||||
GameVersion m_version;
|
||||
@@ -114,13 +112,9 @@ class Compiler {
|
||||
bool m_throw_on_define_extern_redefinition = false;
|
||||
std::unordered_set<std::string> m_allow_inconsistent_definition_symbols;
|
||||
SymbolInfoMap m_symbol_info;
|
||||
std::unique_ptr<ReplWrapper> m_repl;
|
||||
std::unique_ptr<REPL::Wrapper> m_repl;
|
||||
MakeSystem m_make;
|
||||
|
||||
// Configurable fields
|
||||
int m_target_connect_attempts = 30;
|
||||
std::vector<std::string> 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);
|
||||
|
||||
+1
-17
@@ -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<std::string> game_name) {
|
||||
auto cfg = parse_commented_json(json, "repl-config.json");
|
||||
if (cfg.contains("numConnectToTargetAttempts")) {
|
||||
m_target_connect_attempts = cfg.at("numConnectToTargetAttempts").get<int>();
|
||||
}
|
||||
if (cfg.contains("asmFileSearchDirs")) {
|
||||
m_asm_file_search_dirs = cfg.at("asmFileSearchDirs").get<std::vector<std::string>>();
|
||||
}
|
||||
// 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<nlohmann::json>();
|
||||
update_via_config_file(game_cfg.dump(), {});
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Parse arguments into a goos::Arguments format.
|
||||
*/
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <regex>
|
||||
#include <stack>
|
||||
|
||||
#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<std::string> 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<std::string> const& user_data) {
|
||||
replxx::Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context,
|
||||
int& contextLen,
|
||||
replxx::Replxx::Color& color,
|
||||
std::vector<std::string> 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<std::pair<std::string, Replxx::Color>> const& regex_color) {
|
||||
replxx::Replxx::colors_t& colors,
|
||||
std::vector<std::pair<std::string, replxx::Replxx::Color>> 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<cl> paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA};
|
||||
std::stack<std::pair<char, cl>> expression_stack;
|
||||
|
||||
+69
-92
@@ -1,11 +1,12 @@
|
||||
#include <cstdio>
|
||||
#include <regex>
|
||||
|
||||
#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<std::string> user_startup_commands = {};
|
||||
std::optional<std::string> repl_config = {};
|
||||
|
||||
if (auto_find_user) {
|
||||
username = "#f";
|
||||
std::regex allowed_chars("[0-9a-zA-Z\\-\\.\\!\\?<>]");
|
||||
try {
|
||||
auto text = std::make_shared<goos::FileText>(
|
||||
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> 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<Compiler>(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<void()> repl_startup_func = [&]() {
|
||||
// Run automatic forms if applicable
|
||||
std::lock_guard<std::mutex> lock(compiler_mutex);
|
||||
for (const auto& cmd : startup_file.run_before_listen) {
|
||||
status = compiler->handle_repl_string(cmd);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize nREPL server socket
|
||||
std::function<bool()> 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> 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<Compiler>(game_version);
|
||||
compiler->run_front_end_on_string(cmd);
|
||||
return 0;
|
||||
}
|
||||
compiler = std::make_unique<Compiler>(game_version, username, std::make_unique<ReplWrapper>());
|
||||
if (repl_config) {
|
||||
compiler->update_via_config_file(repl_config.value(), game);
|
||||
}
|
||||
// Start nREPL Server
|
||||
compiler = std::make_unique<Compiler>(
|
||||
game_version, username,
|
||||
std::make_unique<REPL::Wrapper>(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<std::mutex> lock(compiler_mutex);
|
||||
status = compiler->handle_repl_string("(dbgc)");
|
||||
user_startup_commands.push_back("(dbgc)");
|
||||
}
|
||||
|
||||
if (!user_startup_commands.empty()) {
|
||||
std::lock_guard<std::mutex> 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<Compiler>(game_version, username, std::make_unique<ReplWrapper>());
|
||||
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<Compiler>(
|
||||
game_version, username,
|
||||
std::make_unique<REPL::Wrapper>(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();
|
||||
|
||||
@@ -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<std::string> lines = split_string(in);
|
||||
std::vector<std::string> lines = str_util::split(in);
|
||||
// Remove all lines that are comments
|
||||
// comments are added only by us, meaning this _should_ be consistent
|
||||
std::vector<std::string>::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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user