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