diff --git a/CHANGELOG.md b/CHANGELOG.md index b8725224e..ab066c155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - added boost-ext/ut to write unit tests in C++ - basic ArkScript code formatter, available through the CLI: `arkscript -f|--format` - comments are now tracked in the AST and attached to the nearest node below them +- `VM::forceReloadPlugins`, to be used by the REPL to force reload the plugins and be sure that their symbols are all define +- added `help`, `save` (save history to disk), `history` (print history), `reset` (reset vm and code) commands to the REPL +- REPL can now show when a code block isn't terminated (prompt changes from `>` to `:`) +- more controls available inside the REPL ### Changed - instructions are on 4 bytes: 1 byte for the instruction, 1 byte of padding, 2 bytes for an immediate argument diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index f1f1a2b34..a16356da3 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -146,6 +146,14 @@ namespace Ark */ void deleteFuture(internal::Future* f); + /** + * @brief Used by the REPL to force reload all the plugins and their bound methods + * + * @return true on success + * @return false if one or more plugins couldn't be reloaded + */ + bool forceReloadPlugins(); + friend class Value; friend class internal::Closure; friend class Repl; diff --git a/include/CLI/REPL/ConsoleStyle.hpp b/include/CLI/REPL/ConsoleStyle.hpp deleted file mode 100644 index a71c27895..000000000 --- a/include/CLI/REPL/ConsoleStyle.hpp +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @file ConsoleStyle.hpp - * @author Alexandre Plateau (lexplt.dev@gmail.com) - * @brief Colors per token used by replxx - * @version 0.2 - * @date 2020-10-27 - * - * @copyright Copyright (c) 2020-2022 - * - */ - -#ifndef ARK_REPL_CONSOLESTYLE_HPP -#define ARK_REPL_CONSOLESTYLE_HPP - -#include - -using Replxx = replxx::Replxx; - -namespace Ark -{ - const std::vector KeywordsDict { - // Keywords - "if", "let", "mut", "set", - "fun", "while", "begin", "import", - "quote", "del", - // Operators - "len", "empty?", "tail", "head", - "nil?", "assert", "toNumber", - "toString", "and", "or", "mod", - "type", "hasField", "not", - // Constants - "true", "false", "nil", "math:pi", - "math:e", "math:tau", "math:Inf", "math:NaN", - // Functions - // List - "append", "concat", "list", "list:reverse", - "list:find", "list:slice", "list:sort", - "list:fill", "list:setAt", - // IO - "print", "puts", "input", "io:writeFile", - "io:readFile", "io:fileExists?", "io:listFiles", "io:dir?", - "io:makeDir", "io:removeFiles", - // Times - "time", - // System - "sys:exec", "sys:sleep", - // String - "str:format", "str:find", "str:removeAt", - // Mathematics - "math:exp", "math:ln", "math:ceil", "math:floor", - "math:round", "math:NaN?", "Inf?", "math:cos", - "math:sin", "math:tan", "math:arccos", "math:arcsin", - "math:arctan", - // Commands - "quit" - }; - - const std::vector> ColorsRegexDict { - // Keywords - { "if", Replxx::Color::BRIGHTRED }, - { "let", Replxx::Color::BRIGHTRED }, - { "mut", Replxx::Color::BRIGHTRED }, - { "set", Replxx::Color::BRIGHTRED }, - { "fun", Replxx::Color::BRIGHTRED }, - { "while", Replxx::Color::BRIGHTRED }, - { "begin", Replxx::Color::BRIGHTRED }, - { "import", Replxx::Color::BRIGHTRED }, - { "quote", Replxx::Color::BRIGHTRED }, - { "del", Replxx::Color::BRIGHTRED }, - // Operators - { "\\\"", Replxx::Color::BRIGHTBLUE }, - { "\\-", Replxx::Color::BRIGHTBLUE }, - { "\\+", Replxx::Color::BRIGHTBLUE }, - { "\\=", Replxx::Color::BRIGHTBLUE }, - { "\\/", Replxx::Color::BRIGHTBLUE }, - { "\\*", Replxx::Color::BRIGHTBLUE }, - { "\\<", Replxx::Color::BRIGHTBLUE }, - { "\\>", Replxx::Color::BRIGHTBLUE }, - { "\\!", Replxx::Color::BRIGHTBLUE }, - { "\\[", Replxx::Color::BRIGHTBLUE }, - { "\\]", Replxx::Color::BRIGHTBLUE }, - { "@", Replxx::Color::BRIGHTBLUE }, - { "len", Replxx::Color::BRIGHTBLUE }, - { "empty?", Replxx::Color::BRIGHTBLUE }, - { "tail", Replxx::Color::BRIGHTBLUE }, - { "head", Replxx::Color::BRIGHTBLUE }, - { "nil?", Replxx::Color::BRIGHTBLUE }, - { "assert", Replxx::Color::BRIGHTBLUE }, - { "toNumber", Replxx::Color::BRIGHTBLUE }, - { "toString", Replxx::Color::BRIGHTBLUE }, - { "and", Replxx::Color::BRIGHTBLUE }, - { "or ", Replxx::Color::BRIGHTBLUE }, - { "mod", Replxx::Color::BRIGHTBLUE }, - { "type", Replxx::Color::BRIGHTBLUE }, - { "hasField", Replxx::Color::BRIGHTBLUE }, - { "not", Replxx::Color::BRIGHTBLUE }, - // Constants - { "true", Replxx::Color::RED }, - { "false", Replxx::Color::RED }, - { "nil", Replxx::Color::RED }, - { "math:pi", Replxx::Color::BLUE }, - { "math:e", Replxx::Color::BLUE }, - { "math:tau", Replxx::Color::BLUE }, - { "math:Inf", Replxx::Color::BLUE }, - { "math:NaN", Replxx::Color::BLUE }, - // Functions - // List - { "append", Replxx::Color::BRIGHTGREEN }, - { "concat", Replxx::Color::BRIGHTGREEN }, - { "pop", Replxx::Color::BRIGHTGREEN }, - { "append!", Replxx::Color::BRIGHTGREEN }, - { "concat!", Replxx::Color::BRIGHTGREEN }, - { "pop!", Replxx::Color::BRIGHTGREEN }, - { "list", Replxx::Color::BRIGHTGREEN }, - { "list:reverse", Replxx::Color::BRIGHTGREEN }, - { "list:find", Replxx::Color::BRIGHTGREEN }, - { "list:slice", Replxx::Color::BRIGHTGREEN }, - { "list:sort", Replxx::Color::BRIGHTGREEN }, - { "list:fill", Replxx::Color::BRIGHTGREEN }, - { "list:setAt", Replxx::Color::BRIGHTGREEN }, - // IO - { "print", Replxx::Color::GREEN }, - { "puts", Replxx::Color::GREEN }, - { "input", Replxx::Color::GREEN }, - { "io:writeFile", Replxx::Color::GREEN }, - { "io:readFile", Replxx::Color::GREEN }, - { "io:fileExists?", Replxx::Color::GREEN }, - { "io:listFiles", Replxx::Color::GREEN }, - { "io:dir?", Replxx::Color::GREEN }, - { "io:makeDir", Replxx::Color::GREEN }, - { "io:removeFiles", Replxx::Color::GREEN }, - // Times - { "time", Replxx::Color::GREEN }, - // System - { "sys:exec", Replxx::Color::GREEN }, - { "sys:sleep", Replxx::Color::GREEN }, - { "sys:exit", Replxx::Color::GREEN }, - // String - { "str:format", Replxx::Color::BRIGHTGREEN }, - { "str:find", Replxx::Color::BRIGHTGREEN }, - { "str:removeAt", Replxx::Color::BRIGHTGREEN }, - { "str:ord", Replxx::Color::BRIGHTGREEN }, - { "str:chr", Replxx::Color::BRIGHTGREEN }, - // Mathematics - { "math:exp", Replxx::Color::BRIGHTCYAN }, - { "math:ln", Replxx::Color::BRIGHTCYAN }, - { "math:ceil", Replxx::Color::BRIGHTCYAN }, - { "math:floor", Replxx::Color::BRIGHTCYAN }, - { "math:round", Replxx::Color::BRIGHTCYAN }, - { "math:NaN?", Replxx::Color::BRIGHTCYAN }, - { "math:Inf?", Replxx::Color::BRIGHTCYAN }, - { "math:cos", Replxx::Color::BRIGHTCYAN }, - { "math:sin", Replxx::Color::BRIGHTCYAN }, - { "math:tan", Replxx::Color::BRIGHTCYAN }, - { "math:arccos", Replxx::Color::BRIGHTCYAN }, - { "math:arcsin", Replxx::Color::BRIGHTCYAN }, - { "math:arctan", Replxx::Color::BRIGHTCYAN }, - { "math:cosh", Replxx::Color::BRIGHTCYAN }, - { "math:sinh", Replxx::Color::BRIGHTCYAN }, - { "math:tanh", Replxx::Color::BRIGHTCYAN }, - { "math:acosh", Replxx::Color::BRIGHTCYAN }, - { "math:asinh", Replxx::Color::BRIGHTCYAN }, - { "math:atanh", Replxx::Color::BRIGHTCYAN }, - // Numbers - { "[\\-|+]{0,1}[0-9]+(\\.[0-9]+)?", Replxx::Color::YELLOW }, - // Strings - { "\".*?\"", Replxx::Color::BRIGHTGREEN }, - // Commands - { "quit", Replxx::Color::BRIGHTMAGENTA } - }; -} - -#endif diff --git a/include/CLI/REPL/Repl.hpp b/include/CLI/REPL/Repl.hpp index 80072876f..f8d58c969 100644 --- a/include/CLI/REPL/Repl.hpp +++ b/include/CLI/REPL/Repl.hpp @@ -2,24 +2,25 @@ * @file Repl.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief ArkScript REPL - Read Eval Print Loop - * @version 0.2 + * @version 1.0 * @date 2020-10-27 * - * @copyright Copyright (c) 2020-2021 + * @copyright Copyright (c) 2020-2024 * */ #ifndef ARK_REPL_REPL_HPP #define ARK_REPL_REPL_HPP -#include -#include +#include +#include #include #include #include #include -#include + +#include namespace Ark { @@ -29,9 +30,9 @@ namespace Ark /** * @brief Construct a new Repl object * - * @param libenv search path for the std library + * @param lib_env search path for the std library */ - explicit Repl(const std::vector& libenv); + explicit Repl(const std::vector& lib_env); /** * @brief Start the REPL @@ -40,16 +41,29 @@ namespace Ark int run(); private: - Replxx m_repl; - unsigned m_lines; + replxx::Replxx m_repl; + unsigned m_line_count; + std::string m_code; + bool m_running; + int m_old_ip; - std::vector m_libenv; + std::vector m_lib_env; + State m_state; + VM m_vm; + bool m_has_init_vm; - static inline void print_repl_header(); - static int count_open_parentheses(const std::string& line); - static int count_open_braces(const std::string& line); - static void trim_whitespace(std::string& line); - void cui_setup(); + /** + * @brief Configure replxx + */ + void cuiSetup(); + + /** + * @brief Get a line via replxx and handle commands + * @param continuation if the prompt needs to be modified because a code block isn't entirely closed, set to true + * @return + */ + std::optional getLine(bool continuation); + std::optional getCodeBlock(); }; } diff --git a/include/CLI/REPL/Utils.hpp b/include/CLI/REPL/Utils.hpp new file mode 100644 index 000000000..fbc356ada --- /dev/null +++ b/include/CLI/REPL/Utils.hpp @@ -0,0 +1,194 @@ +/** + * @file Utils.hpp + * @author Alexandre Plateau (lexplt.dev@gmail.com) + * @brief replxx utilities + * @version 1.0 + * @date 2020-10-27 + * + * @copyright Copyright (c) 2020-2024 + * + */ + +#ifndef REPL_REPLXX_UTIL_HPP +#define REPL_REPLXX_UTIL_HPP + +#include +#include +#include + +#include + +namespace Ark::internal +{ + const std::vector KeywordsDict { + // Keywords + "if", "let", "mut", "set", "fun", "while", "begin", "import", "del", + // Operators + "len", "empty?", "tail", "head", + "nil?", "assert", "toNumber", + "toString", "and", "or", "mod", + "type", "hasField", "not", + // Constants + "true", "false", "nil", "math:pi", + "math:e", "math:tau", "math:Inf", "math:NaN", + // Functions + // List + "append", "concat", "list", "append!", "concat!", "pop", "pop!", + "list:reverse", "list:find", "list:slice", "list:sort", "list:fill", "list:setAt", + // IO + "print", "puts", "input", "io:writeFile", + "io:readFile", "io:fileExists?", "io:listFiles", "io:dir?", + "io:makeDir", "io:removeFiles", + // Times + "time", + // System + "sys:exec", "sys:sleep", + // String + "str:format", "str:find", "str:removeAt", + // Mathematics + "math:exp", "math:ln", "math:ceil", "math:floor", + "math:round", "math:NaN?", "math:Inf?", "math:cos", + "math:sin", "math:tan", "math:arccos", "math:arcsin", + "math:arctan", + // Commands + "quit" + }; + + const std::vector> ColorsRegexDict { + // Keywords + { "if", replxx::Replxx::Color::BRIGHTRED }, + { "let", replxx::Replxx::Color::BRIGHTRED }, + { "mut", replxx::Replxx::Color::BRIGHTRED }, + { "set", replxx::Replxx::Color::BRIGHTRED }, + { "fun", replxx::Replxx::Color::BRIGHTRED }, + { "while", replxx::Replxx::Color::BRIGHTRED }, + { "begin", replxx::Replxx::Color::BRIGHTRED }, + { "import", replxx::Replxx::Color::BRIGHTRED }, + { "quote", replxx::Replxx::Color::BRIGHTRED }, + { "del", replxx::Replxx::Color::BRIGHTRED }, + // Operators + { "\\\"", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\-", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\+", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\=", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\/", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\*", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\<", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\>", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\!", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\[", replxx::Replxx::Color::BRIGHTBLUE }, + { "\\]", replxx::Replxx::Color::BRIGHTBLUE }, + { "@", replxx::Replxx::Color::BRIGHTBLUE }, + { "len", replxx::Replxx::Color::BRIGHTBLUE }, + { "empty\\?", replxx::Replxx::Color::BRIGHTBLUE }, + { "tail", replxx::Replxx::Color::BRIGHTBLUE }, + { "head", replxx::Replxx::Color::BRIGHTBLUE }, + { "nil\\?", replxx::Replxx::Color::BRIGHTBLUE }, + { "assert", replxx::Replxx::Color::BRIGHTBLUE }, + { "toNumber", replxx::Replxx::Color::BRIGHTBLUE }, + { "toString", replxx::Replxx::Color::BRIGHTBLUE }, + { "and", replxx::Replxx::Color::BRIGHTBLUE }, + { "or ", replxx::Replxx::Color::BRIGHTBLUE }, + { "mod", replxx::Replxx::Color::BRIGHTBLUE }, + { "type", replxx::Replxx::Color::BRIGHTBLUE }, + { "hasField", replxx::Replxx::Color::BRIGHTBLUE }, + { "not", replxx::Replxx::Color::BRIGHTBLUE }, + // Constants + { "true", replxx::Replxx::Color::RED }, + { "false", replxx::Replxx::Color::RED }, + { "nil", replxx::Replxx::Color::RED }, + { "math:pi", replxx::Replxx::Color::BLUE }, + { "math:e", replxx::Replxx::Color::BLUE }, + { "math:tau", replxx::Replxx::Color::BLUE }, + { "math:Inf", replxx::Replxx::Color::BLUE }, + { "math:NaN", replxx::Replxx::Color::BLUE }, + // Functions + // List + { "append", replxx::Replxx::Color::BRIGHTGREEN }, + { "concat", replxx::Replxx::Color::BRIGHTGREEN }, + { "pop", replxx::Replxx::Color::BRIGHTGREEN }, + { "append!", replxx::Replxx::Color::BRIGHTGREEN }, + { "concat!", replxx::Replxx::Color::BRIGHTGREEN }, + { "pop!", replxx::Replxx::Color::BRIGHTGREEN }, + { "list", replxx::Replxx::Color::BRIGHTGREEN }, + { "list:reverse", replxx::Replxx::Color::BRIGHTGREEN }, + { "list:find", replxx::Replxx::Color::BRIGHTGREEN }, + { "list:slice", replxx::Replxx::Color::BRIGHTGREEN }, + { "list:sort", replxx::Replxx::Color::BRIGHTGREEN }, + { "list:fill", replxx::Replxx::Color::BRIGHTGREEN }, + { "list:setAt", replxx::Replxx::Color::BRIGHTGREEN }, + // IO + { "print", replxx::Replxx::Color::GREEN }, + { "puts", replxx::Replxx::Color::GREEN }, + { "input", replxx::Replxx::Color::GREEN }, + { "io:writeFile", replxx::Replxx::Color::GREEN }, + { "io:readFile", replxx::Replxx::Color::GREEN }, + { "io:fileExists\\?", replxx::Replxx::Color::GREEN }, + { "io:listFiles", replxx::Replxx::Color::GREEN }, + { "io:dir\\?", replxx::Replxx::Color::GREEN }, + { "io:makeDir", replxx::Replxx::Color::GREEN }, + { "io:removeFiles", replxx::Replxx::Color::GREEN }, + // Times + { "time", replxx::Replxx::Color::GREEN }, + // System + { "sys:exec", replxx::Replxx::Color::GREEN }, + { "sys:sleep", replxx::Replxx::Color::GREEN }, + { "sys:exit", replxx::Replxx::Color::GREEN }, + // String + { "str:format", replxx::Replxx::Color::BRIGHTGREEN }, + { "str:find", replxx::Replxx::Color::BRIGHTGREEN }, + { "str:removeAt", replxx::Replxx::Color::BRIGHTGREEN }, + { "str:ord", replxx::Replxx::Color::BRIGHTGREEN }, + { "str:chr", replxx::Replxx::Color::BRIGHTGREEN }, + // Mathematics + { "math:exp", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:ln", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:ceil", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:floor", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:round", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:NaN\\?", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:Inf\\?", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:cos", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:sin", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:tan", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:arccos", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:arcsin", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:arctan", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:cosh", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:sinh", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:tanh", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:acosh", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:asinh", replxx::Replxx::Color::BRIGHTCYAN }, + { "math:atanh", replxx::Replxx::Color::BRIGHTCYAN }, + // Numbers + { "[\\-|+]{0,1}[0-9]+(\\.[0-9]+)?", replxx::Replxx::Color::YELLOW }, + // Strings + { "\".*?\"", replxx::Replxx::Color::BRIGHTGREEN }, + // Commands + { "quit", replxx::Replxx::Color::BRIGHTMAGENTA } + }; + + + /** + * @brief Count the open enclosure and its counterpart: (), {}, [] + * @param line data to operate on + * @param open the open char: (, { or [ + * @param close the closing char: ), } or ] + * @return positive if there are more open enclosures than closed. 0 when both are equal, negative otherwise + */ + long countOpenEnclosures(const std::string& line, char open, char close); + + /** + * @brief Remove whitespaces at the start and end of a string + * @param line string modified in place + */ + void trimWhitespace(std::string& line); + + replxx::Replxx::completions_t hookCompletion(const std::string& context, int& length); + + void hookColor(const std::string& str, replxx::Replxx::colors_t& colors); + + replxx::Replxx::hints_t hookHint(const std::string& context, int& length, replxx::Replxx::Color& color); +} + +#endif diff --git a/include/CLI/REPL/replxx/Util.hpp b/include/CLI/REPL/replxx/Util.hpp deleted file mode 100644 index 6f9de31f9..000000000 --- a/include/CLI/REPL/replxx/Util.hpp +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @file Util.hpp - * @author Alexandre Plateau (lexplt.dev@gmail.com) - * @brief replxx utilities - * @version 0.1 - * @date 2020-10-27 - * - * @copyright Copyright (c) 2020-2021 - * - */ - -#ifndef REPL_REPLXX_UTIL_HPP -#define REPL_REPLXX_UTIL_HPP - -#include -#include -#include - -#include - -using Replxx = replxx::Replxx; - -int utf8str_codepoint_len(char const* s, int utf8len); -int context_len(char const* prefix); -Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& user_data); -void hook_color(std::string const& str, Replxx::colors_t& colors, std::vector> const& user_data); -Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& examples); - -#endif diff --git a/include/utf8.hpp b/include/utf8.hpp index faa644a64..1a1b4b913 100644 --- a/include/utf8.hpp +++ b/include/utf8.hpp @@ -194,9 +194,6 @@ namespace utf8 if (isValid(str)) { - if (str == nullptr) - return -1; - while (*s != 0) { if (0xf0 == (0xf8 & *s)) diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 92827af66..8ca9614fe 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -220,6 +220,35 @@ namespace Ark m_futures.erase(it); } + bool VM::forceReloadPlugins() + { + // load the mapping from the dynamic library + try + { + for (auto& shared_lib : m_shared_lib_objects) + { + mapping* map = shared_lib->template get("getFunctionsMapping")(); + // load the mapping data + std::size_t i = 0; + while (map[i].name != nullptr) + { + // put it in the global frame, aka the first one + auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), std::string(map[i].name)); + if (it != m_state.m_symbols.end()) + (m_execution_contexts[0]->locals[0]).push_back(static_cast(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value)); + + ++i; + } + } + + return true; + } + catch (const std::system_error& e) + { + return false; + } + } + int VM::run() noexcept { init(); diff --git a/src/arkscript/REPL/Repl.cpp b/src/arkscript/REPL/Repl.cpp index acc091108..509bdf857 100644 --- a/src/arkscript/REPL/Repl.cpp +++ b/src/arkscript/REPL/Repl.cpp @@ -1,177 +1,190 @@ -#include -#include -#include +#include +#include +#include +#include #include -#include +#include namespace Ark { - Repl::Repl(const std::vector& libenv) : - m_lines(1), m_old_ip(0), m_libenv(libenv) + using namespace internal; + + Repl::Repl(const std::vector& lib_env) : + m_line_count(1), m_running(true), + m_old_ip(0), m_lib_env(lib_env), + m_state(m_lib_env), m_vm(m_state), m_has_init_vm(false) {} int Repl::run() { - Ark::State state(m_libenv); - Ark::VM vm(state); - state.setDebug(0); - std::string code; - bool init = false; - - print_repl_header(); - cui_setup(); + fmt::print("ArkScript REPL -- Version {} [LICENSE: Mozilla Public License 2.0]\nType \"quit\" to quit. Try \"help\" for more information\n", ARK_FULL_VERSION); + cuiSetup(); - while (true) + while (m_running) { - std::stringstream tmp_code; - unsigned open_parentheses = 0; - unsigned open_braces = 0; - - tmp_code << code; - while (true) - { - std::string str_lines = "000"; - if (std::to_string(m_lines).size() < 3) - { - std::size_t size = std::to_string(m_lines).size(); - str_lines.replace((str_lines.size() - size), size, std::to_string(m_lines)); - } - else - str_lines = std::to_string(m_lines); - - std::string prompt = "main:" + str_lines + "> "; - char const* buf { nullptr }; - do - { - buf = m_repl.input(prompt); - } while ((buf == nullptr) && (errno == EAGAIN)); - std::string line = (buf != nullptr) ? std::string(buf) : ""; - - // line history - m_repl.history_add(line); - trim_whitespace(line); - - // specific commands handling - if (line == "(quit)" || buf == nullptr) - { - std::cout << "\nExiting REPL\n"; - return 1; - } - - if (!line.empty()) - tmp_code << line << "\n"; - open_parentheses += count_open_parentheses(line); - open_braces += count_open_braces(line); - - // lines number incrementation - ++m_lines; - if (open_parentheses == 0 && open_braces == 0) - break; - } + auto maybe_block = getCodeBlock(); // save a valid ip if execution failed - m_old_ip = vm.m_execution_contexts[0]->ip; - if (!tmp_code.str().empty()) + m_old_ip = m_vm.m_execution_contexts[0]->ip; + if (maybe_block.has_value() && !maybe_block.value().empty()) { - if (state.doString(tmp_code.str())) + std::string new_code = m_code + maybe_block.value(); + if (m_state.doString(new_code)) { // for only one vm init - if (!init) + if (!m_has_init_vm) { - vm.init(); - init = true; + m_vm.init(); + m_has_init_vm = true; } - if (vm.safeRun(*vm.m_execution_contexts[0]) == 0) + else + m_vm.forceReloadPlugins(); + + if (m_vm.safeRun(*m_vm.m_execution_contexts[0]) == 0) { // save good code - code = tmp_code.str(); + m_code = new_code; // place ip to end of bytecode instruction (HALT) - vm.m_execution_contexts[0]->ip -= 4; + m_vm.m_execution_contexts[0]->ip -= 4; } else { // reset ip if execution failed - vm.m_execution_contexts[0]->ip = m_old_ip; + m_vm.m_execution_contexts[0]->ip = m_old_ip; } - state.reset(); + m_state.reset(); } else - std::cout << "Ark::State::doString failed\n"; + std::cout << "\nCouldn't run code\n"; } } return 0; } - inline void Repl::print_repl_header() + void Repl::cuiSetup() { - std::printf( - "ArkScript REPL -- Version %i.%i.%i [LICENSE: Mozilla Public License 2.0]\n" - "Type \"(quit)\" to quit.\n", - ARK_VERSION_MAJOR, - ARK_VERSION_MINOR, - ARK_VERSION_PATCH); + m_repl.set_completion_callback(hookCompletion); + m_repl.set_highlighter_callback(hookColor); + m_repl.set_hint_callback(hookHint); + + m_repl.set_word_break_characters(" \t.,-%!;:=*~^'\"/?<>|[](){}"); + m_repl.set_completion_count_cutoff(128); + m_repl.set_double_tab_completion(true); + m_repl.set_complete_on_empty(true); + m_repl.set_beep_on_ambiguous_completion(false); + m_repl.set_no_color(false); + + m_repl.bind_key_internal(replxx::Replxx::KEY::HOME, "move_cursor_to_begining_of_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::END, "move_cursor_to_end_of_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::TAB, "complete_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::LEFT), "move_cursor_one_word_left"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::RIGHT), "move_cursor_one_word_right"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::UP), "hint_previous"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::DOWN), "hint_next"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::ENTER), "commit_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('R'), "history_incremental_search"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('W'), "kill_to_begining_of_word"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('U'), "kill_to_begining_of_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('K'), "kill_to_end_of_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('Y'), "yank"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('L'), "clear_screen"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('D'), "send_eof"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('C'), "abort_line"); + m_repl.bind_key_internal(replxx::Replxx::KEY::control('T'), "transpose_characters"); } - int Repl::count_open_parentheses(const std::string& line) + std::optional Repl::getLine(bool continuation) { - int open_parentheses = 0; + std::string prompt = fmt::format("main:{:0>3}{} ", m_line_count, continuation ? ":" : ">"); - for (const char& c : line) + const char* buf { nullptr }; + do { - switch (c) - { - case '(': ++open_parentheses; break; - case ')': --open_parentheses; break; - default: break; - } - } + buf = m_repl.input(prompt); + } while ((buf == nullptr) && (errno == EAGAIN)); + std::string line = (buf != nullptr) ? std::string(buf) : ""; - return open_parentheses; - } + // line history + m_repl.history_add(line); + trimWhitespace(line); - int Repl::count_open_braces(const std::string& line) - { - int open_braces = 0; + // specific commands handling + if (line == "quit" || buf == nullptr) + { + std::cout << "\nExiting REPL\n"; + m_running = false; - for (const char& c : line) + return std::nullopt; + } + else if (line == "help") { - switch (c) - { - case '{': ++open_braces; break; - case '}': --open_braces; break; - default: break; - } + std::cout << "Available commands:\n"; + std::cout << " help -- display this message\n"; + std::cout << " quit -- quit the REPL\n"; + std::cout << " save -- save the history to disk\n"; + std::cout << " history -- print saved code\n"; + std::cout << " reset -- reset the VM state\n"; + + return std::nullopt; } + else if (line == "save") + { + std::ofstream history_file("arkscript_repl_history.ark"); + m_repl.history_save(history_file); - return open_braces; - } + fmt::print("Saved {} lines of history to arkscript_repl_history.ark\n", m_line_count); - void Repl::trim_whitespace(std::string& line) - { - size_t string_begin = line.find_first_not_of(" \t"); - if (std::string::npos != string_begin) + return std::nullopt; + } + else if (line == "history") { - size_t string_end = line.find_last_not_of(" \t"); - line = line.substr(string_begin, string_end - string_begin + 1); + std::cout << "\n" + << m_code << "\n"; + + return std::nullopt; } + else if (line == "reset") + { + m_state.reset(); + m_has_init_vm = false; + m_code.clear(); + + return std::nullopt; + } + + return line; } - void Repl::cui_setup() + std::optional Repl::getCodeBlock() { - using namespace std::placeholders; + std::string code_block; + long open_parentheses = 0; + long open_braces = 0; - m_repl.set_completion_callback(std::bind(&hook_completion, _1, _2, std::cref(KeywordsDict))); - m_repl.set_highlighter_callback(std::bind(&hook_color, _1, _2, std::cref(ColorsRegexDict))); - m_repl.set_hint_callback(std::bind(&hook_hint, _1, _2, _3, std::cref(KeywordsDict))); + while (m_running) + { + bool unfinished_block = open_parentheses != 0 || open_braces != 0; - m_repl.set_word_break_characters(" \t.,-%!;:=*~^'\"/?<>|[](){}"); - m_repl.set_completion_count_cutoff(128); - m_repl.set_double_tab_completion(false); - m_repl.set_complete_on_empty(true); - m_repl.set_beep_on_ambiguous_completion(false); - m_repl.set_no_color(false); + auto maybe_line = getLine(unfinished_block); + if (!maybe_line.has_value() && !unfinished_block) + return std::nullopt; + + if (maybe_line.has_value() && !maybe_line.value().empty()) + { + code_block += maybe_line.value() + "\n"; + open_parentheses += countOpenEnclosures(maybe_line.value(), '(', ')'); + open_braces += countOpenEnclosures(maybe_line.value(), '{', '}'); + + // lines number incrementation + ++m_line_count; + if (open_parentheses == 0 && open_braces == 0) + break; + } + } + + return code_block; } } diff --git a/src/arkscript/REPL/Utils.cpp b/src/arkscript/REPL/Utils.cpp index 25f97a6c2..732e36f85 100644 --- a/src/arkscript/REPL/Utils.cpp +++ b/src/arkscript/REPL/Utils.cpp @@ -1,118 +1,121 @@ -#include +#include -#include #include -int utf8str_codepoint_len(char const* s, int utf8len) +namespace Ark::internal { - int codepointLen = 0; - unsigned char m4 = 128 + 64 + 32 + 16; - unsigned char m3 = 128 + 64 + 32; - unsigned char m2 = 128 + 64; - - for (int i = 0; i < utf8len; ++i, ++codepointLen) + long countOpenEnclosures(const std::string& line, char open, char close) { - char c = s[i]; - - if ((c & m4) == m4) - i += 3; - else if ((c & m3) == m3) - i += 2; - else if ((c & m2) == m2) - i += 1; + return static_cast(std::count(line.begin(), line.end(), open)) - static_cast(std::count(line.begin(), line.end(), close)); } - return codepointLen; -} - -int context_len(char const* prefix) -{ - char const wb[] = " \t\n\r\v\f-=+*&^%$#@!,./?<>;:`~'\"[]{}()\\|"; - int i = std::strlen(prefix) - 1; - int cl = 0; - - while (i >= 0) + void trimWhitespace(std::string& line) { - if (std::strchr(wb, prefix[i]) != nullptr) - break; - - ++cl; - --i; + size_t string_begin = line.find_first_not_of(" \t"); + if (std::string::npos != string_begin) + { + size_t string_end = line.find_last_not_of(" \t"); + line = line.substr(string_begin, string_end - string_begin + 1); + } } - return cl; -} - -Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& examples) -{ - Replxx::completions_t completions; - int utf8ContextLen = context_len(context.c_str()); - int prefixLen = context.length() - utf8ContextLen; - - if ((prefixLen > 0) && (context[prefixLen - 1] == '\\')) + std::size_t codepointLength(const std::string& str) { - --prefixLen; - ++utf8ContextLen; + std::size_t len = 0; + for (auto c : str) + len += (c & 0xc0) != 0x80; + return len; } - contextLen = utf8str_codepoint_len(context.c_str() + prefixLen, utf8ContextLen); - - std::string prefix = context.substr(prefixLen); - for (auto const& e : examples) + std::size_t contextLen(const std::string& prefix) { - if (e.compare(0, prefix.size(), prefix) == 0) - completions.emplace_back(e.c_str()); - } + const std::string word_break = " \t\n\r\v\f=+*&^%$#@!,./?<>;`~'\"[]{}()\\|"; + int i = prefix.size() - 1; + std::size_t count = 0; - return completions; -} + while (i >= 0) + { + if (word_break.find(prefix[i]) != std::string::npos) + break; -void hook_color(std::string const& context, Replxx::colors_t& colors, std::vector> const& regex_color) -{ - // highlight matching regex sequences - for (auto const& e : regex_color) + ++count; + --i; + } + + return count; + } + + replxx::Replxx::completions_t hookCompletion(const std::string& context, int& length) { - std::size_t pos = 0; - std::string str = context; - std::smatch match; + replxx::Replxx::completions_t completions; + std::size_t utf8_context_len = contextLen(context); + std::size_t prefix_len = context.size() - utf8_context_len; - while (std::regex_search(str, match, std::regex(e.first))) + if (prefix_len > 0 && context[prefix_len - 1] == '\\') { - std::string c = match[0]; - std::string prefix = match.prefix().str(); - pos += utf8str_codepoint_len(prefix.c_str(), static_cast(prefix.length())); - int len = utf8str_codepoint_len(c.c_str(), static_cast(c.length())); + --prefix_len; + ++utf8_context_len; + } - for (int i = 0; i < len; ++i) - colors.at(pos + i) = e.second; + length = static_cast(codepointLength(context.substr(prefix_len, utf8_context_len))); - pos += len; - str = match.suffix(); + std::string prefix = context.substr(prefix_len); + for (const auto& e : KeywordsDict) + { + if (e.starts_with(prefix) == 0) + completions.emplace_back(e.c_str()); } + + return completions; } -} -Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& examples) -{ - Replxx::hints_t hints; - // only show hint if prefix is at least 'n' chars long - // or if prefix begins with a specific character - int utf8ContextLen = context_len(context.c_str()); - int prefixLen = context.length() - utf8ContextLen; - contextLen = utf8str_codepoint_len(context.c_str() + prefixLen, utf8ContextLen); - std::string prefix = context.substr(prefixLen); - - if (prefix.size() >= 2 || (!prefix.empty() && prefix.at(0) == '.')) + void hookColor(const std::string& context, replxx::Replxx::colors_t& colors) { - for (auto const& e : examples) + // highlight matching regex sequences + for (const auto& e : ColorsRegexDict) { - if (e.compare(0, prefix.size(), prefix) == 0) - hints.emplace_back(e.c_str()); + std::size_t pos = 0; + std::string str = context; + std::smatch match; + + while (std::regex_search(str, match, std::regex(e.first))) + { + std::string c = match[0]; + std::string prefix = match.prefix().str(); + std::size_t len = codepointLength(c); + + pos += codepointLength(prefix); + for (std::size_t i = 0; i < len; ++i) + colors.at(pos + i) = e.second; + + pos += len; + str = match.suffix(); + } } } - if (hints.size() == 1) - color = Replxx::Color::GREEN; + replxx::Replxx::hints_t hookHint(const std::string& context, int& length, replxx::Replxx::Color& color) + { + replxx::Replxx::hints_t hints; + // only show hint if prefix is at least 'n' chars long + // or if prefix begins with a specific character + std::size_t utf8_context_len = contextLen(context); + std::size_t prefix_len = context.size() - utf8_context_len; + length = static_cast(codepointLength(context.substr(prefix_len, utf8_context_len))); + std::string prefix = context.substr(prefix_len); + + if (prefix.size() >= 2 || (!prefix.empty() && prefix.at(0) == '.')) + { + for (const auto& e : KeywordsDict) + { + if (e.compare(0, prefix.size(), prefix) == 0) + hints.emplace_back(e.c_str()); + } + } + + if (hints.size() == 1) + color = replxx::Replxx::Color::GREEN; - return hints; + return hints; + } }