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 <iostream>
3#include <filesystem>
4#include <fmt/core.h>
5
6#include <CLI/REPL/Repl.hpp>
7#include <CLI/REPL/Utils.hpp>
8
9namespace Ark
10{
11 using namespace internal;
12
13 Repl::Repl(const std::vector<std::filesystem::path>& lib_env) :
14 m_line_count(1), m_running(true),
15 m_old_ip(0), m_lib_env(lib_env),
16 m_state(m_lib_env), m_vm(m_state), m_has_init_vm(false)
17 {}
18
20 {
21 fmt::print("ArkScript REPL -- Version {} [LICENSE: Mozilla Public License 2.0]\nType \"quit\" to quit. Try \"help\" for more information\n", ARK_FULL_VERSION);
22 cuiSetup();
23
24 while (m_running)
25 {
26 auto maybe_block = getCodeBlock();
27
28 // save a valid ip if execution failed
30 if (maybe_block.has_value() && !maybe_block.value().empty())
31 {
32 std::string new_code = m_code + maybe_block.value();
33 if (m_state.doString(new_code))
34 {
35 // for only one vm init
36 if (!m_has_init_vm)
37 {
38 m_vm.init();
39 m_has_init_vm = true;
40 }
41 else
42 std::ignore = m_vm.forceReloadPlugins();
43
45 {
46 // save good code
47 m_code = new_code;
48 // place ip to end of bytecode instruction (HALT)
49 m_vm.m_execution_contexts[0]->ip -= 4;
50 }
51 else
52 {
53 // reset ip if execution failed
55 }
56
57 m_state.reset();
58 }
59 else
60 std::cout << "\nCouldn't run code\n";
61 }
62 }
63
64 return 0;
65 }
66
68 {
69 m_repl.set_completion_callback(hookCompletion);
70 m_repl.set_highlighter_callback(hookColor);
71 m_repl.set_hint_callback(hookHint);
72
73 m_repl.set_word_break_characters(" \t.,-%!;:=*~^'\"/?<>|[](){}");
74 m_repl.set_completion_count_cutoff(128);
75 m_repl.set_double_tab_completion(true);
76 m_repl.set_complete_on_empty(true);
77 m_repl.set_beep_on_ambiguous_completion(false);
78 m_repl.set_no_color(false);
79
80 m_repl.bind_key_internal(replxx::Replxx::KEY::HOME, "move_cursor_to_begining_of_line");
81 m_repl.bind_key_internal(replxx::Replxx::KEY::END, "move_cursor_to_end_of_line");
82 m_repl.bind_key_internal(replxx::Replxx::KEY::TAB, "complete_line");
83 m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::LEFT), "move_cursor_one_word_left");
84 m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::RIGHT), "move_cursor_one_word_right");
85 m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::UP), "hint_previous");
86 m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::DOWN), "hint_next");
87 m_repl.bind_key_internal(replxx::Replxx::KEY::control(replxx::Replxx::KEY::ENTER), "commit_line");
88 m_repl.bind_key_internal(replxx::Replxx::KEY::control('R'), "history_incremental_search");
89 m_repl.bind_key_internal(replxx::Replxx::KEY::control('W'), "kill_to_begining_of_word");
90 m_repl.bind_key_internal(replxx::Replxx::KEY::control('U'), "kill_to_begining_of_line");
91 m_repl.bind_key_internal(replxx::Replxx::KEY::control('K'), "kill_to_end_of_line");
92 m_repl.bind_key_internal(replxx::Replxx::KEY::control('Y'), "yank");
93 m_repl.bind_key_internal(replxx::Replxx::KEY::control('L'), "clear_screen");
94 m_repl.bind_key_internal(replxx::Replxx::KEY::control('D'), "send_eof");
95 m_repl.bind_key_internal(replxx::Replxx::KEY::control('C'), "abort_line");
96 m_repl.bind_key_internal(replxx::Replxx::KEY::control('T'), "transpose_characters");
97 }
98
99 std::optional<std::string> Repl::getLine(const bool continuation)
100 {
101 const std::string prompt = fmt::format("main:{:0>3}{} ", m_line_count, continuation ? ":" : ">");
102
103 const char* buf { nullptr };
104 do
105 {
106 buf = m_repl.input(prompt);
107 } while ((buf == nullptr) && (errno == EAGAIN));
108 std::string line = (buf != nullptr) ? std::string(buf) : "";
109
110 // line history
111 m_repl.history_add(line);
112 trimWhitespace(line);
113
114 // specific commands handling
115 if (line == "quit" || buf == nullptr)
116 {
117 std::cout << "\nExiting REPL\n";
118 m_running = false;
119
120 return std::nullopt;
121 }
122 if (line == "help")
123 {
124 std::cout << "Available commands:\n";
125 std::cout << " help -- display this message\n";
126 std::cout << " quit -- quit the REPL\n";
127 std::cout << " save -- save the history to disk\n";
128 std::cout << " history -- print saved code\n";
129 std::cout << " reset -- reset the VM state\n";
130
131 return std::nullopt;
132 }
133 if (line == "save")
134 {
135 std::ofstream history_file("arkscript_repl_history.ark");
136 m_repl.history_save(history_file);
137
138 fmt::print("Saved {} lines of history to arkscript_repl_history.ark\n", m_line_count);
139
140 return std::nullopt;
141 }
142 if (line == "history")
143 {
144 std::cout << "\n"
145 << m_code << "\n";
146
147 return std::nullopt;
148 }
149 if (line == "reset")
150 {
151 m_state.reset();
152 m_has_init_vm = false;
153 m_code.clear();
154
155 return std::nullopt;
156 }
157
158 return line;
159 }
160
161 std::optional<std::string> Repl::getCodeBlock()
162 {
163 std::string code_block;
164 long open_parentheses = 0;
165 long open_braces = 0;
166
167 while (m_running)
168 {
169 const bool unfinished_block = open_parentheses != 0 || open_braces != 0;
170
171 auto maybe_line = getLine(unfinished_block);
172 if (!maybe_line.has_value() && !unfinished_block)
173 return std::nullopt;
174
175 if (maybe_line.has_value() && !maybe_line.value().empty())
176 {
177 code_block += maybe_line.value() + "\n";
178 open_parentheses += countOpenEnclosures(maybe_line.value(), '(', ')');
179 open_braces += countOpenEnclosures(maybe_line.value(), '{', '}');
180
181 // lines number incrementation
182 ++m_line_count;
183 if (open_parentheses == 0 && open_braces == 0)
184 break;
185 }
186 }
187
188 return code_block;
189 }
190}
replxx utilities
constexpr std::string_view ARK_FULL_VERSION
Definition: Constants.hpp:23
ArkScript REPL - Read Eval Print Loop.
bool m_has_init_vm
Definition: Repl.hpp:51
State m_state
Definition: Repl.hpp:49
int run()
Start the REPL.
Definition: Repl.cpp:19
std::string m_code
Definition: Repl.hpp:44
void cuiSetup()
Configure replxx.
Definition: Repl.cpp:67
bool m_running
Definition: Repl.hpp:45
replxx::Replxx m_repl
Definition: Repl.hpp:42
std::optional< std::string > getCodeBlock()
Definition: Repl.cpp:161
Repl(const std::vector< std::filesystem::path > &lib_env)
Construct a new Repl object.
Definition: Repl.cpp:13
int m_old_ip
Definition: Repl.hpp:47
VM m_vm
Definition: Repl.hpp:50
unsigned m_line_count
Definition: Repl.hpp:43
std::optional< std::string > getLine(bool continuation)
Get a line via replxx and handle commands.
Definition: Repl.cpp:99
void reset() noexcept
Reset State (all member variables related to execution)
Definition: State.cpp:289
bool doString(const std::string &code)
Compile a string (representing ArkScript code) and store resulting bytecode in m_bytecode.
Definition: State.cpp:101
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition: VM.hpp:154
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
Definition: VM.cpp:222
int safeRun(internal::ExecutionContext &context, std::size_t untilFrameCount=0)
Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if...
Definition: VM.cpp:258
void init() noexcept
Initialize the VM according to the parameters.
Definition: VM.cpp:34
long countOpenEnclosures(const std::string &line, char open, char close)
Count the open enclosure and its counterpart: (), {}, [].
Definition: Utils.cpp:7
replxx::Replxx::hints_t hookHint(const std::string &context, int &length, replxx::Replxx::Color &color)
Definition: Utils.cpp:97
void hookColor(const std::string &context, replxx::Replxx::colors_t &colors)
Definition: Utils.cpp:72
replxx::Replxx::completions_t hookCompletion(const std::string &context, int &length)
Definition: Utils.cpp:48
void trimWhitespace(std::string &line)
Remove whitespaces at the start and end of a string.
Definition: Utils.cpp:12
Definition: Builtins.hpp:21