ArkScript
A small, lisp-inspired, functional scripting language
State.cpp
Go to the documentation of this file.
1#include <Ark/State.hpp>
2
3#include <Ark/Constants.hpp>
4#include <Ark/Utils/Files.hpp>
6
7#ifdef _MSC_VER
8# pragma warning(push)
9# pragma warning(disable : 4996)
10#endif
11
12#include <Proxy/Picosha2.hpp>
14#include <fmt/core.h>
15#include <fmt/color.h>
16
17namespace Ark
18{
19 State::State(const std::vector<std::filesystem::path>& libenv) noexcept :
20 m_debug_level(0),
21 m_features(0),
22 m_libenv(libenv),
23 m_filename(ARK_NO_NAME_FILE),
24 m_max_page_size(0)
25 {
26 // default value for builtin__sys:args is empty list
27 const Value val(ValueType::List);
28 m_bound[std::string(internal::Language::SysArgs)] = val;
29
30 m_bound[std::string(internal::Language::SysProgramName)] = Value("");
31 }
32
33 bool State::feed(const std::string& bytecode_filename, const bool fail_with_exception)
34 {
35 if (!Utils::fileExists(bytecode_filename))
36 return false;
37
38 return feed(Utils::readFileAsBytes(bytecode_filename), fail_with_exception);
39 }
40
41 bool State::feed(const bytecode_t& bytecode, const bool fail_with_exception)
42 {
44 bcr.feed(bytecode);
45 if (!bcr.checkMagic())
46 return false;
47
48 m_bytecode = bytecode;
49
50 try
51 {
52 configure(bcr);
53 return true;
54 }
55 catch (const std::exception& e)
56 {
57 if (fail_with_exception)
58 throw;
59
60 fmt::println("{}", e.what());
61 return false;
62 }
63 }
64
65 bool State::compile(const std::string& file, const std::string& output, std::ostream* stream)
66 {
68 if (stream != nullptr)
69 welder.redirectLogsTo(*stream);
70
71 for (const auto& key : m_bound | std::views::keys)
72 welder.registerSymbol(key);
73
74 if (!welder.computeASTFromFile(file))
75 return false;
76 if (!welder.generateBytecode())
77 return false;
78
79 const std::string destination = output.empty() ? (file.substr(0, file.find_last_of('.')) + ".arkc") : output;
80 if ((m_features & DisableCache) == 0 && !welder.saveBytecodeToFile(destination))
81 return false;
82 if (!feed(welder.bytecode()))
83 return false;
84
85 return true;
86 }
87
88 bool State::doFile(const std::string& file_path, const uint16_t features, std::ostream* stream)
89 {
90 m_features = features;
91
92 if (!Utils::fileExists(file_path))
93 {
94 fmt::print(fmt::fg(fmt::color::red), "Can not find file '{}'\n", file_path);
95 return false;
96 }
97 m_filename = file_path;
98 m_bound[std::string(internal::Language::SysProgramName)] = Value(std::filesystem::path(m_filename).filename().string());
99
100 const bytecode_t bytecode = Utils::readFileAsBytes(file_path);
101 BytecodeReader bcr;
102 bcr.feed(bytecode);
103 if (!bcr.checkMagic()) // couldn't read magic number, it's a source file
104 {
105 // check if it's in the arkscript cache
106 const std::string filename = std::filesystem::path(file_path).filename().replace_extension(".arkc").string();
107 const std::filesystem::path cache_directory = std::filesystem::path(file_path).parent_path() / ARK_CACHE_DIRNAME;
108 const std::string bytecode_path = (cache_directory / filename).string();
109
110 if (!exists(cache_directory) && (m_features & DisableCache) == 0)
111 {
112 try
113 {
114 create_directory(cache_directory);
115 }
116 catch (const std::filesystem::filesystem_error&)
117 {
119 }
120 }
121
122 if (compile(file_path, bytecode_path, stream))
123 return true;
124 }
125 else if (feed(bytecode)) // it's a bytecode file
126 return true;
127 return false;
128 }
129
130 bool State::doString(const std::string& code, const uint16_t features, std::ostream* stream)
131 {
132 m_features = features;
133
135 if (stream != nullptr)
136 welder.redirectLogsTo(*stream);
137
138 for (const auto& p : m_bound)
139 welder.registerSymbol(p.first);
140
141 if (!welder.computeASTFromString(code))
142 return false;
143 if (!welder.generateBytecode())
144 return false;
145 return feed(welder.bytecode());
146 }
147
148 void State::loadFunction(const std::string& name, Procedure::CallbackType&& function) noexcept
149 {
150 m_bound[name] = Value(std::move(function));
151 }
152
153 void State::setArgs(const std::vector<std::string>& args) noexcept
154 {
156 std::ranges::transform(args, std::back_inserter(val.list()), [](const std::string& arg) {
157 return Value(arg);
158 });
159
160 m_bound[std::string(internal::Language::SysArgs)] = val;
161 }
162
163 void State::setDebug(const unsigned level) noexcept
164 {
165 m_debug_level = level;
166 }
167
168 void State::setLibDirs(const std::vector<std::filesystem::path>& libenv) noexcept
169 {
170 m_libenv = libenv;
171 }
172
174 {
175 using namespace internal;
176
177 const auto [major, minor, patch] = bcr.version();
178 if (major != ARK_VERSION_MAJOR)
179 {
180 const std::string str_version = fmt::format("{}.{}.{}", major, minor, patch);
181 throwStateError(fmt::format("Compiler and VM versions don't match: got {} while running {}", str_version, ARK_VERSION));
182 }
183
184 const auto bytecode_hash = bcr.sha256();
185
186 std::vector<unsigned char> hash(picosha2::k_digest_size);
187 picosha2::hash256(m_bytecode.begin() + bytecode::HeaderSize + picosha2::k_digest_size, m_bytecode.end(), hash);
188 // checking integrity
189 for (std::size_t j = 0; j < picosha2::k_digest_size; ++j)
190 {
191#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
192 if (hash[j] != bytecode_hash[j])
193 throwStateError("Integrity check failed");
194#endif
195 }
196
197 const auto syms = bcr.symbols();
198 const auto vals = bcr.values(syms);
199 const auto files = bcr.filenames(vals);
200 const auto inst_locs = bcr.instLocations(files);
201 const auto [pages, _] = bcr.code(inst_locs);
202
203 m_symbols = syms.symbols;
204 m_constants = vals.values;
205 m_filenames = files.filenames;
206 m_inst_locations = inst_locs.locations;
207 m_pages = pages;
209
210 // Make m_code as a big contiguous chunk of instructions,
211 // aligned on the biggest page size.
212 // This might have a downside when we have a single big page and
213 // a bunch of smaller ones, though I couldn't measure it while testing.
214 m_code.resize(m_max_page_size * pages.size(), Instruction::NOP);
215 addPagesToContiguousBytecode(pages, /* start= */ 0);
216 }
217
218 void State::reset() noexcept
219 {
220 m_symbols.clear();
221 m_constants.clear();
222 m_filenames.clear();
223 m_inst_locations.clear();
224 m_max_page_size = 0;
225 m_code.clear();
226 m_bound.clear();
227
228 // default value for builtin__sys:args is empty list
229 const Value val(ValueType::List);
230 m_bound[std::string(internal::Language::SysArgs)] = val;
231
233 }
234
235 void State::addPagesToContiguousBytecode(const std::vector<bytecode_t>& pages, const std::size_t start)
236 {
237 for (std::size_t i = 0, end = pages.size(); i < end; ++i)
238 {
239 for (std::size_t j = 0, end_j = pages[i].size(); j < end_j; ++j)
240 m_code[(start + i) * m_max_page_size + j] = pages[i][j];
241 }
242 }
243
244 std::size_t State::maxPageSize(const std::vector<bytecode_t>& pages)
245 {
246 return std::ranges::max(pages, {}, &bytecode_t::size).size();
247 }
248
249 void State::extendBytecode(const std::vector<bytecode_t>& pages, const std::vector<std::string>& symbols, const std::vector<Value>& constants)
250 {
251 m_symbols = symbols;
252 m_constants = constants;
253
254 // do not modify m_pages so that we can start over
255 m_max_page_size = std::max(m_max_page_size, maxPageSize(pages));
256
257 m_code.resize(m_max_page_size * (m_pages.size() + pages.size()), internal::Instruction::NOP);
258 addPagesToContiguousBytecode(m_pages, /* start= */ 0);
259 addPagesToContiguousBytecode(pages, /* start= */ m_pages.size());
260 }
261}
262
263#ifdef _MSC_VER
264# pragma warning(pop)
265#endif
A bytecode disassembler for ArkScript.
Constants used by ArkScript.
constexpr std::string_view ARK_VERSION
Definition Constants.hpp:22
constexpr int ARK_VERSION_MAJOR
Definition Constants.hpp:18
#define ARK_NO_NAME_FILE
Definition Constants.hpp:28
#define ARK_CACHE_DIRNAME
Definition Constants.hpp:27
Lots of utilities about the filesystem.
State used by the virtual machine: it loads the bytecode, can compile it if needed,...
In charge of welding everything needed to compile code.
This class is just a helper to.
Symbols symbols() const
Filenames filenames(const Values &values) const
InstLocations instLocations(const Filenames &filenames) const
Version version() const
Code code(const InstLocations &instLocations) const
Values values(const Symbols &symbols) const
std::vector< unsigned char > sha256() const
void feed(const std::string &file)
Construct needed data before displaying information about a given file.
std::function< Value(std::vector< Value > &, VM *)> CallbackType
Definition Procedure.hpp:29
std::vector< std::filesystem::path > m_libenv
Definition State.hpp:166
void setLibDirs(const std::vector< std::filesystem::path > &libenv) noexcept
Set the std search paths.
Definition State.cpp:168
uint16_t m_features
Definition State.hpp:163
std::unordered_map< std::string, Value > m_bound
Values bound to the State, to be used by the VM.
Definition State.hpp:179
void configure(const BytecodeReader &bcr)
Called to configure the state (set the bytecode, debug level, call the compiler......
Definition State.cpp:173
bool doFile(const std::string &file_path, uint16_t features=DefaultFeatures, std::ostream *stream=nullptr)
Compile a file, and use the resulting bytecode.
Definition State.cpp:88
bytecode_t m_bytecode
Definition State.hpp:165
std::string m_filename
Definition State.hpp:167
std::vector< Value > m_constants
Definition State.hpp:171
bool compile(const std::string &file, const std::string &output, std::ostream *stream=nullptr)
Reads and compiles code of file.
Definition State.cpp:65
static void throwStateError(const std::string &message)
Definition State.hpp:157
std::vector< internal::InstLoc > m_inst_locations
Definition State.hpp:173
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
std::vector< std::string > m_filenames
Definition State.hpp:172
static std::size_t maxPageSize(const std::vector< bytecode_t > &pages)
Compute the maximum length of the given code pages.
Definition State.cpp:244
void reset() noexcept
Reset State (all member variables related to execution)
Definition State.cpp:218
bool feed(const std::string &bytecode_filename, bool fail_with_exception=false)
Feed the state by giving it the path to an existing bytecode file.
Definition State.cpp:33
void loadFunction(const std::string &name, Procedure::CallbackType &&function) noexcept
Register a function in the virtual machine.
Definition State.cpp:148
std::vector< std::string > m_symbols
Definition State.hpp:170
void addPagesToContiguousBytecode(const std::vector< bytecode_t > &pages, std::size_t start)
Definition State.cpp:235
bytecode_t m_code
Definition State.hpp:176
std::size_t m_max_page_size
Definition State.hpp:175
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition State.cpp:153
std::vector< bytecode_t > m_pages
Definition State.hpp:174
unsigned m_debug_level
Definition State.hpp:162
State(const std::vector< std::filesystem::path > &libenv={}) noexcept
Construct a new State object.
Definition State.cpp:19
void setDebug(unsigned level) noexcept
Set the debug level.
Definition State.cpp:163
void extendBytecode(const std::vector< bytecode_t > &pages, const std::vector< std::string > &symbols, const std::vector< Value > &constants)
Used by the debugger to add code to the VM at runtime.
Definition State.cpp:249
List_t & list()
Definition Value.hpp:171
The welder joins all the compiler passes.
Definition Welder.hpp:39
void registerSymbol(const std::string &name)
Register a symbol as a global in the compiler.
Definition Welder.cpp:33
bool computeASTFromString(const std::string &code)
Definition Welder.cpp:46
const bytecode_t & bytecode() const noexcept
Definition Welder.cpp:162
void redirectLogsTo(std::ostream &os)
Redirect the logs to a given stream.
Definition Welder.cpp:138
bool saveBytecodeToFile(const std::string &filename)
Save the generated bytecode to a given file.
Definition Welder.cpp:123
bool generateBytecode()
Compile the AST processed by computeASTFromFile / computeASTFromString.
Definition Welder.cpp:62
bool computeASTFromFile(const std::string &filename)
Definition Welder.cpp:38
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
Definition Files.hpp:28
std::vector< uint8_t > readFileAsBytes(const std::string &name)
Helper to read the bytes of a file.
Definition Files.hpp:62
constexpr std::string_view SysArgs
Definition Common.hpp:131
constexpr std::string_view SysProgramName
Definition Common.hpp:132
constexpr std::size_t HeaderSize
Definition Common.hpp:39
constexpr uint16_t DisableCache
Definition Constants.hpp:60
std::vector< uint8_t > bytecode_t
Definition Common.hpp:22