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