From 2f0e1817fd8ea876c69bd2b8fd3f7f0d17248b2b Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Sat, 24 Aug 2024 13:38:54 +0200 Subject: [PATCH] feat(repl): repl completion and colors are generated automatically --- CHANGELOG.md | 1 + include/CLI/REPL/Repl.hpp | 2 + include/CLI/REPL/Utils.hpp | 157 +---------------------------------- src/arkscript/REPL/Repl.cpp | 81 +++++++++++++----- src/arkscript/REPL/Utils.cpp | 12 +-- 5 files changed, 73 insertions(+), 180 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c10a4c26c..88dade39b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ - added a max macro evaluation depth - introduced `internal::listInstructions` with the different instructions, to be used by the compiler and name resolution pass - checking for forbidden variable/constant name in the name & scope resolution pass, to give errors to the user before compiling some weird code +- repl completion and colors are now generated automatically from the builtins, keywords & operators ### Removed - removed unused `NodeType::Closure` diff --git a/include/CLI/REPL/Repl.hpp b/include/CLI/REPL/Repl.hpp index 3edb4e207..8adf02ab3 100644 --- a/include/CLI/REPL/Repl.hpp +++ b/include/CLI/REPL/Repl.hpp @@ -49,6 +49,8 @@ namespace Ark State m_state; VM m_vm; bool m_has_init_vm; + std::vector m_keywords; + std::vector> m_words_colors; /** * @brief Configure replxx diff --git a/include/CLI/REPL/Utils.hpp b/include/CLI/REPL/Utils.hpp index 26166f361..461ef004f 100644 --- a/include/CLI/REPL/Utils.hpp +++ b/include/CLI/REPL/Utils.hpp @@ -2,7 +2,7 @@ * @file Utils.hpp * @author Alexandre Plateau (lexplt.dev@gmail.com) * @brief replxx utilities - * @version 1.0 + * @version 1.1 * @date 2020-10-27 * * @copyright Copyright (c) 2020-2024 @@ -19,155 +19,6 @@ 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 @@ -183,11 +34,11 @@ namespace Ark::internal */ void trimWhitespace(std::string& line); - replxx::Replxx::completions_t hookCompletion(const std::string& context, int& length); + replxx::Replxx::completions_t hookCompletion(const std::vector& words, const std::string& context, int& length); - void hookColor(const std::string& context, replxx::Replxx::colors_t& colors); + void hookColor(const std::vector>& words_colors, const std::string& context, replxx::Replxx::colors_t& colors); - replxx::Replxx::hints_t hookHint(const std::string& context, int& length, replxx::Replxx::Color& color); + replxx::Replxx::hints_t hookHint(const std::vector& words, const std::string& context, int& length, replxx::Replxx::Color& color); } #endif diff --git a/src/arkscript/REPL/Repl.cpp b/src/arkscript/REPL/Repl.cpp index fffc81946..8c0d0d2ed 100644 --- a/src/arkscript/REPL/Repl.cpp +++ b/src/arkscript/REPL/Repl.cpp @@ -2,6 +2,10 @@ #include #include #include +#include + +#include +#include #include #include @@ -9,12 +13,41 @@ namespace Ark { using namespace internal; + using namespace replxx; 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) - {} + { + m_keywords.reserve(keywords.size() + listInstructions.size() + operators.size() + Builtins::builtins.size()); + for (auto keyword : keywords) + m_keywords.emplace_back(keyword); + for (auto inst : listInstructions) + m_keywords.emplace_back(inst); + for (auto op : operators) + m_keywords.emplace_back(op); + for (const auto& builtin : std::ranges::views::keys(Builtins::builtins)) + m_keywords.push_back(builtin); + + m_words_colors.reserve(keywords.size() + listInstructions.size() + operators.size() + Builtins::builtins.size() + 2); + for (auto keyword : keywords) + m_words_colors.emplace_back(keyword, Replxx::Color::BRIGHTRED); + for (auto inst : listInstructions) + m_words_colors.emplace_back(inst, Replxx::Color::GREEN); + for (auto op : operators) + { + auto safe_op = std::string(op); + if (const auto it = safe_op.find_first_of(R"(-+=/*<>[]()?")"); it != std::string::npos) + safe_op.insert(it, "\\"); + m_words_colors.emplace_back(safe_op, Replxx::Color::BRIGHTBLUE); + } + for (const auto& builtin : std::ranges::views::keys(Builtins::builtins)) + m_words_colors.emplace_back(builtin, Replxx::Color::GREEN); + + m_words_colors.emplace_back("[\\-|+]?[0-9]+(\\.[0-9]+)?", Replxx::Color::YELLOW); + m_words_colors.emplace_back("\".*\"", Replxx::Color::MAGENTA); + } int Repl::run() { @@ -66,9 +99,15 @@ namespace Ark void Repl::cuiSetup() { - m_repl.set_completion_callback(hookCompletion); - m_repl.set_highlighter_callback(hookColor); - m_repl.set_hint_callback(hookHint); + m_repl.set_completion_callback([this](const std::string& ctx, int& len) { + return hookCompletion(m_keywords, ctx, len); + }); + m_repl.set_highlighter_callback([this](const std::string& ctx, Replxx::colors_t& colors) { + return hookColor(m_words_colors, ctx, colors); + }); + m_repl.set_hint_callback([this](const std::string& ctx, int& len, Replxx::Color& color) { + return hookHint(m_keywords, ctx, len, color); + }); m_repl.set_word_break_characters(" \t.,-%!;:=*~^'\"/?<>|[](){}"); m_repl.set_completion_count_cutoff(128); @@ -77,23 +116,23 @@ namespace Ark 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"); + m_repl.bind_key_internal(Replxx::KEY::HOME, "move_cursor_to_begining_of_line"); + m_repl.bind_key_internal(Replxx::KEY::END, "move_cursor_to_end_of_line"); + m_repl.bind_key_internal(Replxx::KEY::TAB, "complete_line"); + m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::LEFT), "move_cursor_one_word_left"); + m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::RIGHT), "move_cursor_one_word_right"); + m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::UP), "hint_previous"); + m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::DOWN), "hint_next"); + m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::ENTER), "commit_line"); + m_repl.bind_key_internal(Replxx::KEY::control('R'), "history_incremental_search"); + m_repl.bind_key_internal(Replxx::KEY::control('W'), "kill_to_begining_of_word"); + m_repl.bind_key_internal(Replxx::KEY::control('U'), "kill_to_begining_of_line"); + m_repl.bind_key_internal(Replxx::KEY::control('K'), "kill_to_end_of_line"); + m_repl.bind_key_internal(Replxx::KEY::control('Y'), "yank"); + m_repl.bind_key_internal(Replxx::KEY::control('L'), "clear_screen"); + m_repl.bind_key_internal(Replxx::KEY::control('D'), "send_eof"); + m_repl.bind_key_internal(Replxx::KEY::control('C'), "abort_line"); + m_repl.bind_key_internal(Replxx::KEY::control('T'), "transpose_characters"); } std::optional Repl::getLine(const bool continuation) diff --git a/src/arkscript/REPL/Utils.cpp b/src/arkscript/REPL/Utils.cpp index f6eaa266e..e6e5f806f 100644 --- a/src/arkscript/REPL/Utils.cpp +++ b/src/arkscript/REPL/Utils.cpp @@ -46,7 +46,7 @@ namespace Ark::internal return count; } - replxx::Replxx::completions_t hookCompletion(const std::string& context, int& length) + replxx::Replxx::completions_t hookCompletion(const std::vector& words, const std::string& context, int& length) { replxx::Replxx::completions_t completions; std::size_t utf8_context_len = contextLen(context); @@ -61,7 +61,7 @@ namespace Ark::internal length = static_cast(codepointLength(context.substr(prefix_len, utf8_context_len))); const std::string prefix = context.substr(prefix_len); - for (const auto& e : KeywordsDict) + for (const auto& e : words) { if (e.starts_with(prefix) == 0) completions.emplace_back(e.c_str()); @@ -70,10 +70,10 @@ namespace Ark::internal return completions; } - void hookColor(const std::string& context, replxx::Replxx::colors_t& colors) + void hookColor(const std::vector>& words_colors, const std::string& context, replxx::Replxx::colors_t& colors) { // highlight matching regex sequences - for (const auto& [regex, color] : ColorsRegexDict) + for (const auto& [regex, color] : words_colors) { std::size_t pos = 0; std::string str = context; @@ -95,7 +95,7 @@ namespace Ark::internal } } - replxx::Replxx::hints_t hookHint(const std::string& context, int& length, replxx::Replxx::Color& color) + replxx::Replxx::hints_t hookHint(const std::vector& words, 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 @@ -107,7 +107,7 @@ namespace Ark::internal if (prefix.size() >= 2 || (!prefix.empty() && prefix.at(0) == '.')) { - for (const auto& e : KeywordsDict) + for (const auto& e : words) { if (e.compare(0, prefix.size(), prefix) == 0) hints.emplace_back(e.c_str());