ArkScript
A small, fast, functional and scripting language for video games
Repl.cpp
Go to the documentation of this file.
1#include <fstream>
2#include <filesystem>
3#include <fmt/core.h>
4#include <ranges>
5
8
9#include <CLI/REPL/Repl.hpp>
10#include <CLI/REPL/Utils.hpp>
11
12namespace Ark
13{
14 using namespace internal;
15 using namespace replxx;
16
17 Repl::Repl(const std::vector<std::filesystem::path>& lib_env) :
18 m_line_count(1), m_running(true),
19 m_old_ip(0), m_lib_env(lib_env),
20 m_state(m_lib_env), m_vm(m_state), m_has_init_vm(false)
21 {
22 m_keywords.reserve(keywords.size() + Language::listInstructions.size() + Language::operators.size() + Builtins::builtins.size() + 2);
23 for (auto keyword : keywords)
24 m_keywords.emplace_back(keyword);
25 for (auto inst : Language::listInstructions)
26 m_keywords.emplace_back(inst);
27 for (auto op : Language::operators)
28 m_keywords.emplace_back(op);
29 for (const auto& builtin : std::ranges::views::keys(Builtins::builtins))
30 m_keywords.push_back(builtin);
31 m_keywords.emplace_back("and");
32 m_keywords.emplace_back("or");
33
34 m_words_colors.reserve(keywords.size() + Language::listInstructions.size() + Language::operators.size() + Builtins::builtins.size() + 4);
35 for (auto keyword : keywords)
36 m_words_colors.emplace_back(keyword, Replxx::Color::BRIGHTRED);
37 for (auto inst : Language::listInstructions)
38 m_words_colors.emplace_back(inst, Replxx::Color::GREEN);
39 for (auto op : Language::operators)
40 {
41 auto safe_op = std::string(op);
42 if (const auto it = safe_op.find_first_of(R"(-+=/*<>[]()?")"); it != std::string::npos)
43 safe_op.insert(it, "\\");
44 m_words_colors.emplace_back(safe_op, Replxx::Color::BRIGHTBLUE);
45 }
46 for (const auto& builtin : std::ranges::views::keys(Builtins::builtins))
47 m_words_colors.emplace_back(builtin, Replxx::Color::GREEN);
48
49 m_words_colors.emplace_back("and", Replxx::Color::BRIGHTBLUE);
50 m_words_colors.emplace_back("or", Replxx::Color::BRIGHTBLUE);
51 m_words_colors.emplace_back("[\\-|+]?[0-9]+(\\.[0-9]+)?", Replxx::Color::YELLOW);
52 m_words_colors.emplace_back("\".*\"", Replxx::Color::MAGENTA);
53 }
54
55 int Repl::run()
56 {
57 fmt::print("ArkScript REPL -- Version {} [LICENSE: Mozilla Public License 2.0]\nType \"quit\" to quit. Try \"help\" for more information\n", ARK_FULL_VERSION);
58 cuiSetup();
59
60 while (m_running)
61 {
62 auto maybe_block = getCodeBlock();
63
64 // save a valid ip if execution failed
65 m_old_ip = m_vm.m_execution_contexts[0]->ip;
66 if (maybe_block.has_value() && !maybe_block.value().empty())
67 {
68 std::string new_code = m_code + maybe_block.value();
69 if (m_state.doString(new_code))
70 {
71 // for only one vm init
72 if (!m_has_init_vm)
73 {
74 m_vm.init();
75 m_has_init_vm = true;
76 }
77 else
78 std::ignore = m_vm.forceReloadPlugins();
79
80 if (m_vm.safeRun(*m_vm.m_execution_contexts[0]) == 0)
81 {
82 // save good code
83 m_code = new_code;
84 // place ip to end of bytecode instruction (HALT)
85 m_vm.m_execution_contexts[0]->ip -= 4;
86 }
87 else
88 {
89 // reset ip if execution failed
90 m_vm.m_execution_contexts[0]->ip = m_old_ip;
91 }
92
93 m_state.reset();
94 }
95 else
96 fmt::println("\nCouldn't run code");
97 }
98 }
99
100 return 0;
101 }
102
103 void Repl::cuiSetup()
104 {
105 m_repl.set_completion_callback([this](const std::string& ctx, int& len) {
106 return hookCompletion(m_keywords, ctx, len);
107 });
108 m_repl.set_highlighter_callback([this](const std::string& ctx, Replxx::colors_t& colors) {
109 return hookColor(m_words_colors, ctx, colors);
110 });
111 m_repl.set_hint_callback([this](const std::string& ctx, int& len, Replxx::Color& color) {
112 return hookHint(m_keywords, ctx, len, color);
113 });
114
115 m_repl.set_word_break_characters(" \t.,-%!;:=*~^'\"/?<>|[](){}");
116 m_repl.set_completion_count_cutoff(128);
117 m_repl.set_double_tab_completion(true);
118 m_repl.set_complete_on_empty(true);
119 m_repl.set_beep_on_ambiguous_completion(false);
120 m_repl.set_no_color(false);
121
122 m_repl.bind_key_internal(Replxx::KEY::HOME, "move_cursor_to_begining_of_line");
123 m_repl.bind_key_internal(Replxx::KEY::END, "move_cursor_to_end_of_line");
124 m_repl.bind_key_internal(Replxx::KEY::TAB, "complete_line");
125 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::LEFT), "move_cursor_one_word_left");
126 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::RIGHT), "move_cursor_one_word_right");
127 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::UP), "hint_previous");
128 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::DOWN), "hint_next");
129 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::ENTER), "commit_line");
130 m_repl.bind_key_internal(Replxx::KEY::control('R'), "history_incremental_search");
131 m_repl.bind_key_internal(Replxx::KEY::control('W'), "kill_to_begining_of_word");
132 m_repl.bind_key_internal(Replxx::KEY::control('U'), "kill_to_begining_of_line");
133 m_repl.bind_key_internal(Replxx::KEY::control('K'), "kill_to_end_of_line");
134 m_repl.bind_key_internal(Replxx::KEY::control('Y'), "yank");
135 m_repl.bind_key_internal(Replxx::KEY::control('L'), "clear_screen");
136 m_repl.bind_key_internal(Replxx::KEY::control('D'), "send_eof");
137 m_repl.bind_key_internal(Replxx::KEY::control('C'), "abort_line");
138 m_repl.bind_key_internal(Replxx::KEY::control('T'), "transpose_characters");
139 }
140
141 std::optional<std::string> Repl::getLine(const bool continuation)
142 {
143 const std::string prompt = fmt::format("main:{:0>3}{} ", m_line_count, continuation ? ":" : ">");
144
145 const char* buf { nullptr };
146 do
147 {
148 buf = m_repl.input(prompt);
149 } while ((buf == nullptr) && (errno == EAGAIN));
150 std::string line = (buf != nullptr) ? std::string(buf) : "";
151
152 // line history
153 m_repl.history_add(line);
154 trimWhitespace(line);
155
156 // specific commands handling
157 if (line == "quit" || buf == nullptr)
158 {
159 fmt::println("\nExiting REPL");
160 m_running = false;
161
162 return std::nullopt;
163 }
164 if (line == "help")
165 {
166 fmt::println("Available commands:");
167 fmt::println(" help -- display this message");
168 fmt::println(" quit -- quit the REPL");
169 fmt::println(" save -- save the history to disk");
170 fmt::println(" history -- print saved code");
171 fmt::println(" reset -- reset the VM state");
172
173 return std::nullopt;
174 }
175 if (line == "save")
176 {
177 std::ofstream history_file("arkscript_repl_history.ark");
178 m_repl.history_save(history_file);
179
180 fmt::println("Saved {} lines of history to arkscript_repl_history.ark", m_line_count);
181 return std::nullopt;
182 }
183 if (line == "history")
184 {
185 fmt::println("\n{}", m_code);
186 return std::nullopt;
187 }
188 if (line == "reset")
189 {
190 m_state.reset();
191 m_has_init_vm = false;
192 m_code.clear();
193
194 return std::nullopt;
195 }
196
197 return line;
198 }
199
200 std::optional<std::string> Repl::getCodeBlock()
201 {
202 std::string code_block;
203 long open_parentheses = 0;
204 long open_braces = 0;
205
206 while (m_running)
207 {
208 const bool unfinished_block = open_parentheses != 0 || open_braces != 0;
209
210 auto maybe_line = getLine(unfinished_block);
211 if (!maybe_line.has_value() && !unfinished_block)
212 return std::nullopt;
213
214 if (maybe_line.has_value() && !maybe_line.value().empty())
215 {
216 code_block += maybe_line.value() + "\n";
217 open_parentheses += countOpenEnclosures(maybe_line.value(), '(', ')');
218 open_braces += countOpenEnclosures(maybe_line.value(), '{', '}');
219
220 // lines number incrementation
221 ++m_line_count;
222 if (open_parentheses == 0 && open_braces == 0)
223 break;
224 }
225 }
226
227 return code_block;
228 }
229}
Host the declaration of all the ArkScript builtins.
replxx utilities
Common code for the compiler.
constexpr std::string_view ARK_FULL_VERSION
Definition Constants.hpp:23
ArkScript REPL - Read Eval Print Loop.
Repl(const std::vector< std::filesystem::path > &lib_env)
Construct a new Repl object.
long countOpenEnclosures(const std::string &line, char open, char close)
Count the open enclosure and its counterpart: (), {}, [].
Definition Utils.cpp:8
void hookColor(const std::vector< std::pair< std::string, replxx::Replxx::Color > > &words_colors, const std::string &context, replxx::Replxx::colors_t &colors)
Definition Utils.cpp:73
replxx::Replxx::completions_t hookCompletion(const std::vector< std::string > &words, const std::string &context, int &length)
Definition Utils.cpp:49
replxx::Replxx::hints_t hookHint(const std::vector< std::string > &words, const std::string &context, int &length, replxx::Replxx::Color &color)
Definition Utils.cpp:98
void trimWhitespace(std::string &line)
Remove whitespaces at the start and end of a string.
Definition Utils.cpp:13
constexpr std::array< std::string_view, 9 > keywords
List of available keywords in ArkScript.
Definition Common.hpp:73