ArkScript
A small, fast, functional and scripting language for video games
State.cpp
Go to the documentation of this file.
1#include <Ark/VM/State.hpp>
2
3#include <Ark/Constants.hpp>
4#include <Ark/Files.hpp>
5#include <Ark/Utils.hpp>
6
7#ifdef _MSC_VER
8# pragma warning(push)
9# pragma warning(disable : 4996)
10#endif
11
12#include <stdlib.h>
13#include <picosha2.h>
14#include <termcolor/proxy.hpp>
15
16namespace Ark
17{
18 State::State(uint16_t options, const std::vector<std::string>& libenv) noexcept :
19 m_debug_level(0),
20 m_filename(ARK_NO_NAME_FILE),
21 m_options(options)
22 {
23 if (libenv.size() > 0)
24 {
25 m_libenv = libenv;
26 }
27 else
28 {
29 const char* arkpath = getenv("ARKSCRIPT_PATH");
30 if (arkpath)
31 m_libenv = Utils::splitString(arkpath, ';');
32 else if (Utils::fileExists("./lib"))
33 m_libenv.push_back(Utils::canonicalRelPath("./lib"));
34 else
35 {
36 if (m_debug_level >= 1)
37 std::cout << termcolor::yellow << "Warning" << termcolor::reset << " no std library was found and ARKSCRIPT_PATH was not supplied" << std::endl;
38 }
39 }
40 }
41
42 bool State::feed(const std::string& bytecode_filename)
43 {
44 bool result = true;
45 try
46 {
48 bcr.feed(bytecode_filename);
49 m_bytecode = bcr.bytecode();
50
51 configure();
52 }
53 catch (const std::exception& e)
54 {
55 result = false;
56 std::printf("%s\n", e.what());
57 }
58
59 return result;
60 }
61
62 bool State::feed(const bytecode_t& bytecode)
63 {
64 bool result = true;
65 try
66 {
67 m_bytecode = bytecode;
68 configure();
69 }
70 catch (const std::exception& e)
71 {
72 result = false;
73 std::printf("%s\n", e.what());
74 }
75
76 return result;
77 }
78
79 bool State::compile(const std::string& file, const std::string& output)
80 {
82
83 try
84 {
85 compiler.feed(Utils::readFile(file), file);
86 for (auto& p : m_binded)
87 compiler.m_defined_symbols.push_back(p.first);
88 compiler.compile();
89
90 if (output != "")
91 compiler.saveTo(output);
92 else
93 compiler.saveTo(file.substr(0, file.find_last_of('.')) + ".arkc");
94 }
95 catch (const std::exception& e)
96 {
97 std::printf("%s\n", e.what());
98 return false;
99 }
100 catch (...)
101 {
102 std::printf("Unknown lexer-parser-or-compiler error (%s)\n", file.c_str());
103 return false;
104 }
105
106 return true;
107 }
108
109 bool State::doFile(const std::string& file)
110 {
111 if (!Utils::fileExists(file))
112 {
113 std::cerr << termcolor::red << "Can not find file '" << file << "'\n"
114 << termcolor::reset;
115 return false;
116 }
117 m_filename = file;
118
119 // check if it's a bytecode file or a source code file
120 BytecodeReader bcr;
121 try
122 {
123 bcr.feed(file);
124 }
125 catch (const std::exception& e)
126 {
127 std::printf("%s\n", e.what());
128 return false;
129 }
130
131 if (bcr.timestamp() == 0) // couldn't read magic number, it's a source file
132 {
133 // check if it's in the arkscript cache
134 std::string short_filename = Utils::getFilenameFromPath(file);
135 std::string filename = short_filename.substr(0, short_filename.find_last_of('.')) + ".arkc";
136 std::filesystem::path directory = (std::filesystem::path(file)).parent_path() / ARK_CACHE_DIRNAME;
137 std::string path = (directory / filename).string();
138
139 if (!std::filesystem::exists(directory)) // create ark cache directory
140 std::filesystem::create_directory(directory);
141
142 bool compiled_successfuly = compile(file, path);
143 if (compiled_successfuly && feed(path))
144 return true;
145 }
146 else if (feed(file)) // it's a bytecode file
147 return true;
148 return false;
149 }
150
151 bool State::doString(const std::string& code)
152 {
154
155 try
156 {
157 compiler.feed(code);
158 for (auto& p : m_binded)
159 compiler.m_defined_symbols.push_back(p.first);
160 compiler.compile();
161 }
162 catch (const std::exception& e)
163 {
164 std::printf("%s\n", e.what());
165 return false;
166 }
167 catch (...)
168 {
169 std::printf("Unknown lexer-parser-or-compiler error\n");
170 return false;
171 }
172
173 return feed(compiler.bytecode());
174 }
175
176 void State::loadFunction(const std::string& name, Value::ProcType function) noexcept
177 {
178 m_binded[name] = Value(std::move(function));
179 }
180
181 void State::setArgs(const std::vector<std::string>& args) noexcept
182 {
184 for (const std::string& arg : args)
185 val.push_back(Value(arg));
186 m_binded["sys:args"] = val;
187
188 m_binded["sys:platform"] = Value(ARK_PLATFORM_NAME);
189 }
190
191 void State::setDebug(unsigned level) noexcept
192 {
193 m_debug_level = level;
194 }
195
196 void State::setLibDirs(const std::vector<std::string>& libenv) noexcept
197 {
198 m_libenv = libenv;
199 }
200
202 {
203 using namespace internal;
204
205 // configure tables and pages
206 std::size_t i = 0;
207
208 auto readNumber = [&, this](std::size_t& i) -> uint16_t {
209 uint16_t x = (static_cast<uint16_t>(m_bytecode[i]) << 8);
210 ++i;
211 uint16_t y = static_cast<uint16_t>(m_bytecode[i]);
212 return x + y;
213 };
214
215 // read tables and check if bytecode is valid
216 if (!(m_bytecode.size() > 4 && m_bytecode[i++] == 'a' &&
217 m_bytecode[i++] == 'r' && m_bytecode[i++] == 'k' &&
218 m_bytecode[i++] == Instruction::NOP))
219 throwStateError("invalid format: couldn't find magic constant");
220
221 uint16_t major = readNumber(i);
222 i++;
223 uint16_t minor = readNumber(i);
224 i++;
225 uint16_t patch = readNumber(i);
226 i++;
227
228 if (major != ARK_VERSION_MAJOR)
229 {
230 std::string str_version = std::to_string(major) + "." +
231 std::to_string(minor) + "." +
232 std::to_string(patch);
233 throwStateError("Compiler and VM versions don't match: " + str_version + " and " + ARK_VERSION_STR);
234 }
235
236 using timestamp_t = unsigned long long;
237 timestamp_t timestamp [[maybe_unused]] = 0;
238 auto aa = (static_cast<timestamp_t>(m_bytecode[i]) << 56),
239 ba = (static_cast<timestamp_t>(m_bytecode[++i]) << 48),
240 ca = (static_cast<timestamp_t>(m_bytecode[++i]) << 40),
241 da = (static_cast<timestamp_t>(m_bytecode[++i]) << 32),
242 ea = (static_cast<timestamp_t>(m_bytecode[++i]) << 24),
243 fa = (static_cast<timestamp_t>(m_bytecode[++i]) << 16),
244 ga = (static_cast<timestamp_t>(m_bytecode[++i]) << 8),
245 ha = (static_cast<timestamp_t>(m_bytecode[++i]));
246 i++;
247 timestamp = aa + ba + ca + da + ea + fa + ga + ha;
248
249 std::vector<unsigned char> hash(picosha2::k_digest_size);
250 picosha2::hash256(m_bytecode.begin() + i + picosha2::k_digest_size, m_bytecode.end(), hash);
251 // checking integrity
252 for (std::size_t j = 0; j < picosha2::k_digest_size; ++j)
253 {
254#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
255 if (hash[j] != m_bytecode[i])
256 throwStateError("Integrity check failed");
257#endif
258 ++i;
259 }
260
261 if (m_bytecode[i] == Instruction::SYM_TABLE_START)
262 {
263 i++;
264 uint16_t size = readNumber(i);
265 m_symbols.reserve(size);
266 i++;
267
268 for (uint16_t j = 0; j < size; ++j)
269 {
270 std::string symbol = "";
271 while (m_bytecode[i] != 0)
272 symbol.push_back(m_bytecode[i++]);
273 i++;
274
275 m_symbols.push_back(symbol);
276 }
277 }
278 else
279 throwStateError("Couldn't find symbols table");
280
281 if (m_bytecode[i] == Instruction::VAL_TABLE_START)
282 {
283 i++;
284 uint16_t size = readNumber(i);
285 m_constants.reserve(size);
286 i++;
287
288 for (uint16_t j = 0; j < size; ++j)
289 {
290 uint8_t type = m_bytecode[i];
291 i++;
292
293 if (type == Instruction::NUMBER_TYPE)
294 {
295 std::string val = "";
296 while (m_bytecode[i] != 0)
297 val.push_back(m_bytecode[i++]);
298 i++;
299
300 m_constants.emplace_back(std::stod(val));
301 }
302 else if (type == Instruction::STRING_TYPE)
303 {
304 std::string val = "";
305 while (m_bytecode[i] != 0)
306 val.push_back(m_bytecode[i++]);
307 i++;
308
309 m_constants.emplace_back(val);
310 }
311 else if (type == Instruction::FUNC_TYPE)
312 {
313 uint16_t addr = readNumber(i);
314 i++;
315 m_constants.emplace_back(addr);
316 i++; // skip NOP
317 }
318 else
319 throwStateError("Unknown value type for value " + std::to_string(j));
320 }
321 }
322 else
323 throwStateError("Couldn't find constants table");
324
325 while (m_bytecode[i] == Instruction::CODE_SEGMENT_START)
326 {
327 i++;
328 uint16_t size = readNumber(i);
329 i++;
330
331 m_pages.emplace_back();
332 m_pages.back().reserve(size);
333
334 for (uint16_t j = 0; j < size; ++j)
335 m_pages.back().push_back(m_bytecode[i++]);
336
337 if (i == m_bytecode.size())
338 break;
339 }
340 }
341
342 void State::reset() noexcept
343 {
344 m_symbols.clear();
345 m_constants.clear();
346 m_pages.clear();
347 m_binded.clear();
348 }
349}
350
351#ifdef _MSC_VER
352# pragma warning(pop)
353#endif
Constants used by ArkScript.
#define ARK_PLATFORM_NAME
Definition: Constants.hpp:39
constexpr int ARK_VERSION_MAJOR
Definition: Constants.hpp:16
#define ARK_NO_NAME_FILE
Definition: Constants.hpp:26
constexpr char ARK_VERSION_STR[4]
Definition: Constants.hpp:21
#define ARK_CACHE_DIRNAME
Definition: Constants.hpp:25
Lots of utilities about the filesystem.
State used by the virtual machine: it loads the bytecode, can compile it if needed,...
Lots of utilities about string, filesystem and more.
This class is just a helper to.
const bytecode_t & bytecode() noexcept
Return the bytecode object constructed.
unsigned long long timestamp()
Return the read timestamp from the bytecode file.
void feed(const std::string &file)
Construct needed data before displaying information about a given file.
The ArkScript bytecode compiler.
Definition: Compiler.hpp:36
std::vector< std::string > m_defined_symbols
Definition: Compiler.hpp:82
const bytecode_t & bytecode() noexcept
Return the constructed bytecode object.
Definition: Compiler.cpp:88
void compile()
Start the compilation.
Definition: Compiler.cpp:35
void feed(const std::string &code, const std::string &filename=ARK_NO_NAME_FILE)
Feed the differents variables with information taken from the given source code file.
Definition: Compiler.cpp:26
void saveTo(const std::string &file)
Save generated bytecode to a file.
Definition: Compiler.cpp:78
void configure()
Called to configure the state (set the bytecode, debug level, call the compiler......
Definition: State.cpp:201
State(uint16_t options=DefaultFeatures, const std::vector< std::string > &libpath={}) noexcept
Construct a new State object.
Definition: State.cpp:18
bytecode_t m_bytecode
Definition: State.hpp:140
std::string m_filename
Definition: State.hpp:142
std::vector< Value > m_constants
Definition: State.hpp:147
void throwStateError(const std::string &message)
Definition: State.hpp:133
bool feed(const std::string &bytecode_filename)
Feed the state by giving it the path to an existing bytecode file.
Definition: State.cpp:42
void setLibDirs(const std::vector< std::string > &libenv) noexcept
Set the std search paths.
Definition: State.cpp:196
void reset() noexcept
Reset State (all member variables related to execution)
Definition: State.cpp:342
uint16_t m_options
Definition: State.hpp:143
std::unordered_map< std::string, Value > m_binded
Definition: State.hpp:151
std::vector< std::string > m_symbols
Definition: State.hpp:146
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition: State.cpp:181
std::vector< bytecode_t > m_pages
Definition: State.hpp:148
void loadFunction(const std::string &name, Value::ProcType function) noexcept
Register a function in the virtual machine.
Definition: State.cpp:176
bool compile(const std::string &file, const std::string &output)
Reads and compiles code of file.
Definition: State.cpp:79
unsigned m_debug_level
Definition: State.hpp:138
void setDebug(unsigned level) noexcept
Set the debug level.
Definition: State.cpp:191
bool doFile(const std::string &filename)
Compile a file, and use the resulting bytecode.
Definition: State.cpp:109
bool doString(const std::string &code)
Compile a string (representing ArkScript code) and store resulting bytecode in m_bytecode.
Definition: State.cpp:151
std::vector< std::string > m_libenv
Definition: State.hpp:141
Value(*)(std::vector< Value > &, VM *) ProcType
Definition: Value.hpp:73
void push_back(const Value &value)
Add an element to the list held by the value (if the value type is set to list)
Definition: Value.cpp:139
std::string readFile(const std::string &name)
Helper to read a file.
Definition: Files.hpp:48
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
Definition: Files.hpp:29
std::vector< std::string > splitString(const std::string &source, char sep)
Cut a string into pieces, given a character separator.
Definition: Utils.hpp:34
std::string canonicalRelPath(const std::string &path)
Get the canonical relative path from a path.
Definition: Files.hpp:85
std::string getFilenameFromPath(const std::string &path)
Get the filename from a path.
Definition: Files.hpp:74
Definition: Builtins.hpp:21
std::vector< uint8_t > bytecode_t
Definition: Common.hpp:22