ArkScript
A small, lisp-inspired, functional scripting language
Repl.cpp
Go to the documentation of this file.
1#include <fstream>
2#include <filesystem>
3#include <fmt/core.h>
4#include <fmt/color.h>
5#include <ranges>
6
9#include <Ark/TypeChecker.hpp>
10#include <Ark/Utils/Files.hpp>
11#include <Ark/Utils/Utils.hpp>
12
13#include <CLI/REPL/Repl.hpp>
14#include <CLI/REPL/Utils.hpp>
15
16namespace Ark
17{
18 using namespace internal;
19 using namespace replxx;
20
21 Repl::Repl(const std::vector<std::filesystem::path>& lib_env) :
22 m_line_count(1), m_running(true),
23 m_old_ip(0), m_lib_env(lib_env),
24 m_state(m_lib_env), m_vm(m_state), m_has_init_vm(false),
25 m_keywords(getAllKeywords()),
26 m_words_colors(getColorPerKeyword())
27 {}
28
30 {
31 fmt::println(R"(
32 █▓█▓ █▓█
33 █▓▓█ █▓█ █▓█▒
34 █▒█ █▒▒█ █▒█
35 █▒▒▒ █▒██▒█ ▓▓
36 ▓▒▓ █▒█ █▒█ █▓▓███████▓█ ██ █▓██ ▓▒█
37 █▒▓ █▒█ █▒█ ▓▒▓ ▓▒█ ▓▓ █▒█ ▓▒▓
38 ▓▒█ █▒█ █▒█ ▓▒▒▒▓▓▓▓▓▓▓▓█ ▓▓ █▓▒▓ ▓▒▓
39 █▓▓ █▒█ ████▒▒▒█ ▓▒▒▓▓▓▓▓▒▓ ▓▓ █▒▓▓▓ █▒█
40 █▓█ █▒▒▓██████████▒█ ▓▒▓ █▒█ ▓▒▓▓█ █▓█ █▓█
41 █▓██ █▓▓▓ █▓█ █▓█ █▓███ █▓█ █▓██ ███
42)");
43 fmt::println("ArkScript REPL -- Version {} [LICENSE: Mozilla Public License 2.0] -- Built on {}", ARK_FULL_VERSION, ARK_BUILD_DATE);
44 fmt::println(R"(Type "quit" to quit. Try "help" for more information)");
45 cuiSetup();
47
48 if (const char* arkrc = std::getenv("ARKSCRIPT_REPL_STARTUP"))
49 m_code = fmt::format("## Loaded via ARKSCRIPT_REPL_STARTUP environment variable: ##\n{}\n## END ##\n", Utils::readFile(arkrc));
50
51 while (m_running)
52 {
53 std::optional<std::string> maybe_block = getCodeBlock();
54
55 // save a valid ip if execution failed
57 if (maybe_block.has_value() && !maybe_block.value().empty())
58 {
59 std::string new_code = m_code + maybe_block.value();
61 {
62 // for only one vm init
63 if (!m_has_init_vm)
64 {
65 m_vm.init();
66 m_has_init_vm = true;
67 }
68 else
69 std::ignore = m_vm.forceReloadPlugins();
70
72 {
73 // save good code
74 m_code = new_code + m_temp_additional_code;
76 // place ip to end of bytecode instruction (HALT)
77 m_vm.m_execution_contexts[0]->ip -= 4;
78
79 const Value* maybe_value = m_vm.peekAndResolveAsPtr(*m_vm.getDefaultContext());
80 if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr)
81 fmt::println("{}", fmt::styled(maybe_value->toString(m_vm), fmt::fg(fmt::color::chocolate)));
82 }
83 else
84 {
85 // reset ip if execution failed
87 }
88
89 m_state.reset();
91 }
92 }
93 }
94
95 return 0;
96 }
97
99 {
100 m_repl.set_completion_callback([this](const std::string& ctx, int& len) {
101 return hookCompletion(m_keywords, ctx, len);
102 });
103 m_repl.set_highlighter_callback([this](const std::string& ctx, Replxx::colors_t& colors) {
104 return hookColor(m_words_colors, ctx, colors);
105 });
106 m_repl.set_hint_callback([this](const std::string& ctx, int& len, Replxx::Color& color) {
107 return hookHint(m_keywords, ctx, len, color);
108 });
109
110 m_repl.set_word_break_characters(" \t.,-%!;:=*~^'\"/?<>|[](){}");
111 m_repl.set_completion_count_cutoff(128);
112 m_repl.set_double_tab_completion(true);
113 m_repl.set_complete_on_empty(true);
114 m_repl.set_beep_on_ambiguous_completion(false);
115 m_repl.set_no_color(false);
116
117 m_repl.bind_key_internal(Replxx::KEY::HOME, "move_cursor_to_begining_of_line");
118 m_repl.bind_key_internal(Replxx::KEY::END, "move_cursor_to_end_of_line");
119 m_repl.bind_key_internal(Replxx::KEY::TAB, "complete_line");
120 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::LEFT), "move_cursor_one_word_left");
121 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::RIGHT), "move_cursor_one_word_right");
122 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::UP), "hint_previous");
123 m_repl.bind_key_internal(Replxx::KEY::control(Replxx::KEY::DOWN), "hint_next");
124 m_repl.bind_key_internal(Replxx::KEY::control('R'), "history_incremental_search");
125 m_repl.bind_key_internal(Replxx::KEY::control('W'), "kill_to_begining_of_word");
126 m_repl.bind_key_internal(Replxx::KEY::control('U'), "kill_to_begining_of_line");
127 m_repl.bind_key_internal(Replxx::KEY::control('K'), "kill_to_end_of_line");
128 m_repl.bind_key_internal(Replxx::KEY::control('L'), "clear_screen");
129 m_repl.bind_key_internal(Replxx::KEY::control('D'), "send_eof");
130 m_repl.bind_key_internal(Replxx::KEY::control('C'), "abort_line");
131 m_repl.bind_key_internal(Replxx::KEY::control('T'), "transpose_characters");
132 }
133
135 {
136 m_state.loadFunction("repl:history", [this]([[maybe_unused]] const std::vector<Value>&, [[maybe_unused]] VM*) {
137 return Value(m_code);
138 });
139
140 m_state.loadFunction("repl:save", [this](const std::vector<Value>& n, [[maybe_unused]] VM*) {
143 "repl:save",
144 { { types::Contract { { types::Typedef("filename", ValueType::String) } } } },
145 n);
146
147 std::ofstream history_file(n[0].string());
148 history_file << m_code;
149 return Nil;
150 });
151
152 m_state.loadFunction("repl:load", [this](const std::vector<Value>& n, [[maybe_unused]] VM*) {
155 "repl:load",
156 { { types::Contract { { types::Typedef("filename", ValueType::String) } } } },
157 n);
158
159 const std::string path = n[0].string();
160 if (!Utils::fileExists(path))
161 throw Error(fmt::format("`repl:load` expected a valid path to a file. {} doesn't exist, or can't be reached (try with an absolute path?)", path));
162
163 // we use += so that it can be called multiple times without overwriting previous code
164 m_temp_additional_code += fmt::format("## (repl:load \"{}\") ##\n{}\n## END ##\n", path, Utils::readFile(path));
165 return Nil;
166 });
167 }
168
169 std::optional<std::string> Repl::getLine(const bool continuation)
170 {
171 const std::string prompt = fmt::format("main:{:0>3}{} ", m_line_count, continuation ? ":" : ">");
172
173 const char* buf { nullptr };
174 do
175 {
176 buf = m_repl.input(prompt);
177 } while ((buf == nullptr) && (errno == EAGAIN));
178 std::string line = (buf != nullptr) ? std::string(buf) : "";
179
180 // line history
181 m_repl.history_add(line);
183
184 // specific commands handling
185 if (line == "quit" || buf == nullptr)
186 {
187 fmt::println("\nExiting REPL");
188 m_running = false;
189
190 return std::nullopt;
191 }
192 if (line == "help")
193 {
194 fmt::println("Available commands:");
195 fmt::println(" help -- display this message");
196 fmt::println(" quit -- quit the REPL");
197 fmt::println(" save -- save the history to disk");
198 fmt::println(" history -- print saved code");
199 fmt::println(" reset -- reset the VM state");
200 fmt::println("Available builtins:");
201 fmt::println(" (repl:history): returns the REPL history as a string");
202 fmt::println(" (repl:save filename): saves the REPL history to a file");
203 fmt::println(" (repl:load filename): loads code from a file in the REPL");
204
205 return std::nullopt;
206 }
207 if (line == "save")
208 {
209 std::ofstream history_file("arkscript_repl_history.ark");
210 m_repl.history_save(history_file);
211
212 fmt::println("Saved {} lines of history to arkscript_repl_history.ark", m_line_count);
213 return std::nullopt;
214 }
215 if (line == "history")
216 {
217 fmt::println("\n{}", m_code);
218 return std::nullopt;
219 }
220 if (line == "reset")
221 {
222 m_state.reset();
223 m_has_init_vm = false;
224 m_code.clear();
225
226 return std::nullopt;
227 }
228
229 return line;
230 }
231
232 std::optional<std::string> Repl::getCodeBlock()
233 {
234 std::string code_block;
235 long open_parentheses = 0;
236 long open_braces = 0;
237
238 while (m_running)
239 {
240 const bool unfinished_block = open_parentheses != 0 || open_braces != 0;
241
242 auto maybe_line = getLine(unfinished_block);
243 if (!maybe_line.has_value() && !unfinished_block)
244 return std::nullopt;
245
246 if (maybe_line.has_value() && !maybe_line.value().empty())
247 {
248 code_block += maybe_line.value() + "\n";
249 open_parentheses += Utils::countOpenEnclosures(maybe_line.value(), '(', ')');
250 open_braces += Utils::countOpenEnclosures(maybe_line.value(), '{', '}');
251
252 // lines number incrementation
253 ++m_line_count;
254 if (open_parentheses == 0 && open_braces == 0)
255 break;
256 }
257 }
258
259 return code_block;
260 }
261}
Lots of utilities about string, filesystem and more.
Host the declaration of all the ArkScript builtins.
replxx utilities
#define ARK_BUILD_DATE
Definition Constants.hpp:32
constexpr std::string_view ARK_FULL_VERSION
Definition Constants.hpp:28
Lots of utilities about the filesystem.
ArkScript REPL - Read Eval Print Loop.
bool m_has_init_vm
Definition Repl.hpp:51
std::vector< std::pair< std::string, replxx::Replxx::Color > > m_words_colors
Definition Repl.hpp:53
State m_state
Definition Repl.hpp:49
std::vector< std::string > m_keywords
Definition Repl.hpp:52
int run()
Start the REPL.
Definition Repl.cpp:29
std::string m_code
Definition Repl.hpp:43
void cuiSetup()
Configure replxx.
Definition Repl.cpp:98
bool m_running
Definition Repl.hpp:45
void registerBuiltins()
Definition Repl.cpp:134
replxx::Replxx m_repl
Definition Repl.hpp:41
std::optional< std::string > getCodeBlock()
Prompt the user to enter a complete code block and handle the prompt modifications until the code blo...
Definition Repl.cpp:232
std::size_t m_old_ip
Definition Repl.hpp:47
std::string m_temp_additional_code
Definition Repl.hpp:44
Repl(const std::vector< std::filesystem::path > &lib_env)
Construct a new Repl object.
Definition Repl.cpp:21
VM m_vm
Definition Repl.hpp:50
unsigned m_line_count
Definition Repl.hpp:42
std::optional< std::string > getLine(bool continuation)
Get a line via replxx and handle commands.
Definition Repl.cpp:169
bool doString(const std::string &code, uint16_t features=DefaultFeatures, std::ostream *stream=nullptr)
Compile a string (representing ArkScript code) and store resulting bytecode in m_bytecode.
Definition State.cpp:129
void reset() noexcept
Reset State (all member variables related to execution)
Definition State.cpp:217
void loadFunction(const std::string &name, Procedure::CallbackType &&function) noexcept
Register a function in the virtual machine.
Definition State.cpp:147
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:40
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition VM.hpp:174
ARK_ALWAYS_INLINE Value * peekAndResolveAsPtr(internal::ExecutionContext &context, std::size_t offset=0)
Return a pointer to the top of the stack without consuming it, and resolve it if possible.
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
Definition VM.cpp:353
int safeRun(internal::ExecutionContext &context, std::size_t untilFrameCount=0, bool fail_with_exception=false)
Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if...
Definition VM.cpp:401
void init() noexcept
Initialize the VM according to the parameters.
Definition VM.cpp:28
internal::ExecutionContext * getDefaultContext() const
Return a pointer to the first execution context, for the main thread of the app.
Definition VM.hpp:100
ValueType valueType() const noexcept
Definition Value.hpp:154
std::string toString(VM &vm, bool show_as_code=false) const noexcept
Definition Value.cpp:81
std::string readFile(const std::string &name)
Helper to read a file.
Definition Files.hpp:47
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
Definition Files.hpp:28
ARK_API long countOpenEnclosures(const std::string &line, char open, char close)
Count the open enclosure and its counterpart: (), {}, [].
Definition Utils.cpp:35
ARK_API void trimWhitespace(std::string &line)
Remove whitespaces at the start and end of a string.
Definition Utils.cpp:40
std::vector< std::string > getAllKeywords()
Compute a list of all the language keywords and builtins.
Definition Utils.cpp:13
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:135
constexpr std::array colors
Definition Logger.cpp:8
replxx::Replxx::completions_t hookCompletion(const std::vector< std::string > &words, const std::string &context, int &length)
Definition Utils.cpp:111
std::vector< std::pair< std::string, replxx::Replxx::Color > > getColorPerKeyword()
Compute a list of pairs (word -> color) to be used for coloration by the REPL.
Definition Utils.cpp:42
replxx::Replxx::hints_t hookHint(const std::vector< std::string > &words, const std::string &context, int &length, replxx::Replxx::Color &color)
Definition Utils.cpp:163
bool check(const std::vector< Value > &args, Ts... types)
Helper to see if a builtin has been given a wanted set of types.
constexpr uint16_t DefaultFeatures
Definition Constants.hpp:74
const auto Nil
ArkScript Nil value.
A contract is a list of typed arguments that a function can follow.
A type definition within a contract.