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/termcolor.hpp>
15 
16 namespace 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  {
47  BytecodeReader bcr;
48  bcr.feed(bytecode_filename);
49  m_bytecode = bcr.bytecode();
50 
51  m_filename = bytecode_filename;
52  configure();
53  }
54  catch (const std::exception& e)
55  {
56  result = false;
57  std::printf("%s\n", e.what());
58  }
59 
60  return result;
61  }
62 
63  bool State::feed(const bytecode_t& bytecode)
64  {
65  bool result = true;
66  try
67  {
68  m_bytecode = bytecode;
69  configure();
70  }
71  catch (const std::exception& e)
72  {
73  result = false;
74  std::printf("%s\n", e.what());
75  }
76 
77  return result;
78  }
79 
80  bool State::compile(const std::string& file, const std::string& output)
81  {
83 
84  try
85  {
86  compiler.feed(Utils::readFile(file), file);
87  for (auto& p : m_binded)
88  compiler.m_defined_symbols.push_back(p.first);
89  compiler.compile();
90 
91  if (output != "")
92  compiler.saveTo(output);
93  else
94  compiler.saveTo(file.substr(0, file.find_last_of('.')) + ".arkc");
95  }
96  catch (const std::exception& e)
97  {
98  std::printf("%s\n", e.what());
99  return false;
100  }
101  catch (...)
102  {
103  std::printf("Unknown lexer-parser-or-compiler error (%s)\n", file.c_str());
104  return false;
105  }
106 
107  return true;
108  }
109 
110  bool State::doFile(const std::string& file)
111  {
112  if (!Utils::fileExists(file))
113  {
114  std::cerr << termcolor::red << "Can not find file '" << file << "'\n"
115  << termcolor::reset;
116  return false;
117  }
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  {
183  Value val(ValueType::List);
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  if (hash[j] != m_bytecode[i])
255  throwStateError("Integrity check failed");
256  ++i;
257  }
258 
260  {
261  i++;
262  uint16_t size = readNumber(i);
263  m_symbols.reserve(size);
264  i++;
265 
266  for (uint16_t j = 0; j < size; ++j)
267  {
268  std::string symbol = "";
269  while (m_bytecode[i] != 0)
270  symbol.push_back(m_bytecode[i++]);
271  i++;
272 
273  m_symbols.push_back(symbol);
274  }
275  }
276  else
277  throwStateError("Couldn't find symbols table");
278 
280  {
281  i++;
282  uint16_t size = readNumber(i);
283  m_constants.reserve(size);
284  i++;
285 
286  for (uint16_t j = 0; j < size; ++j)
287  {
288  uint8_t type = m_bytecode[i];
289  i++;
290 
291  if (type == Instruction::NUMBER_TYPE)
292  {
293  std::string val = "";
294  while (m_bytecode[i] != 0)
295  val.push_back(m_bytecode[i++]);
296  i++;
297 
298  m_constants.emplace_back(std::stod(val));
299  }
300  else if (type == Instruction::STRING_TYPE)
301  {
302  std::string val = "";
303  while (m_bytecode[i] != 0)
304  val.push_back(m_bytecode[i++]);
305  i++;
306 
307  m_constants.emplace_back(val);
308  }
309  else if (type == Instruction::FUNC_TYPE)
310  {
311  uint16_t addr = readNumber(i);
312  i++;
313  m_constants.emplace_back(addr);
314  i++; // skip NOP
315  }
316  else
317  throwStateError("Unknown value type for value " + std::to_string(j));
318  }
319  }
320  else
321  throwStateError("Couldn't find constants table");
322 
324  {
325  i++;
326  uint16_t size = readNumber(i);
327  i++;
328 
329  m_pages.emplace_back();
330  m_pages.back().reserve(size);
331 
332  for (uint16_t j = 0; j < size; ++j)
333  m_pages.back().push_back(m_bytecode[i++]);
334 
335  if (i == m_bytecode.size())
336  break;
337  }
338  }
339 
340  void State::reset() noexcept
341  {
342  m_symbols.clear();
343  m_constants.clear();
344  m_pages.clear();
345  m_binded.clear();
346  }
347 }
348 
349 #ifdef _MSC_VER
350 # pragma warning(pop)
351 #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:86
void compile()
Start the compilation.
Definition: Compiler.cpp:33
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:24
void saveTo(const std::string &file)
Save generated bytecode to a file.
Definition: Compiler.cpp:76
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:340
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:80
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:110
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:74
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::string canonicalRelPath(const std::string &path)
Get the canonical relative path from a path.
Definition: Files.hpp:85
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 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