9#include <fmt/ostream.h>
24 using namespace internal;
35 std::vector<Value> tmp(a->
constList().size() - 1);
36 for (std::size_t i = 1, end = a->
constList().size(); i < end; ++i)
38 return Value(std::move(tmp));
42 if (a->
string().size() < 2)
46 b.
stringRef().erase(b.stringRef().begin());
81 m_state(state), m_exit_code(0), m_running(
false)
83 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
122 const auto it = std::ranges::find(m_state.m_symbols, name);
123 if (it == m_state.m_symbols.end())
129 const auto dist = std::distance(m_state.m_symbols.begin(), it);
130 if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
134 const auto id =
static_cast<uint16_t
>(dist);
135 Value* var = findNearestVariable(
id, context);
146 namespace fs = std::filesystem;
150 std::string path = file;
153 path = (fs::path(
m_state.
m_filename).parent_path() / fs::path(file)).relative_path().string();
155 std::shared_ptr<SharedLibrary> lib;
158 lib = std::make_shared<SharedLibrary>(path);
163 std::string lib_path = (fs::path(v) / fs::path(file)).
string();
167 return (val->path() == path || val->path() == lib_path);
174 lib = std::make_shared<SharedLibrary>(lib_path);
182 auto lib_path = std::accumulate(
186 [](
const std::string& a,
const fs::path& b) -> std::string {
187 return a +
"\n\t- " + b.string();
191 fmt::format(
"Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
202 while (map[i].name !=
nullptr)
212 catch (
const std::system_error& e)
217 "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
230 const std::lock_guard lock(
m_mutex);
241 scope.m_size = local.m_size;
242 scope.m_min_id = local.m_min_id;
243 scope.m_max_id = local.m_max_id;
251 const std::lock_guard lock(
m_mutex);
254 std::ranges::remove_if(
256 [ec](
const std::unique_ptr<ExecutionContext>& ctx) {
257 return ctx.get() == ec;
272 const std::lock_guard lock(
m_mutex);
273 m_futures.push_back(std::make_unique<Future>(ctx,
this, args));
280 const std::lock_guard lock(
m_mutex);
283 std::ranges::remove_if(
285 [f](
const std::unique_ptr<Future>& future) {
286 return future.get() == f;
299 const mapping* map = shared_lib->template get<mapping* (*)()>(
"getFunctionsMapping")();
302 while (map[i].name !=
nullptr)
309 Value(map[i].value));
317 catch (
const std::system_error&)
332#if ARK_USE_COMPUTED_GOTOS
333# define TARGET(op) TARGET_##op:
334# define DISPATCH_GOTO() \
335 _Pragma("GCC diagnostic push") \
336 _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
337 _Pragma(
"GCC diagnostic pop")
338# define GOTO_HALT() goto dispatch_end
340# define TARGET(op) case op:
341# define DISPATCH_GOTO() goto dispatch_opcode
342# define GOTO_HALT() break
348 inst = m_state.m_pages[context.pp][context.ip]; \
349 padding = m_state.m_pages[context.pp][context.ip + 1]; \
350 arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
351 m_state.m_pages[context.pp][context.ip + 3]); \
357#define UNPACK_ARGS() \
360 secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
361 primary_arg = arg & 0x0fff; \
364#if ARK_USE_COMPUTED_GOTOS
365# pragma GCC diagnostic push
366# pragma GCC diagnostic ignored "-Wpedantic"
367 constexpr std::array opcode_targets = {
370 &&TARGET_LOAD_SYMBOL,
371 &&TARGET_LOAD_SYMBOL_BY_INDEX,
373 &&TARGET_POP_JUMP_IF_TRUE,
376 &&TARGET_POP_JUMP_IF_FALSE,
384 &&TARGET_MAKE_CLOSURE,
390 &&TARGET_APPEND_IN_PLACE,
391 &&TARGET_CONCAT_IN_PLACE,
393 &&TARGET_POP_LIST_IN_PLACE,
394 &&TARGET_SET_AT_INDEX,
395 &&TARGET_SET_AT_2_INDEX,
398 &&TARGET_CREATE_SCOPE,
399 &&TARGET_RESET_SCOPE,
425 &&TARGET_LOAD_CONST_LOAD_CONST,
426 &&TARGET_LOAD_CONST_STORE,
427 &&TARGET_LOAD_CONST_SET_VAL,
429 &&TARGET_STORE_FROM_INDEX,
430 &&TARGET_SET_VAL_FROM,
431 &&TARGET_SET_VAL_FROM_INDEX,
433 &&TARGET_INCREMENT_BY_INDEX,
435 &&TARGET_DECREMENT_BY_INDEX,
437 &&TARGET_STORE_TAIL_BY_INDEX,
439 &&TARGET_STORE_HEAD_BY_INDEX,
440 &&TARGET_SET_VAL_TAIL,
441 &&TARGET_SET_VAL_TAIL_BY_INDEX,
442 &&TARGET_SET_VAL_HEAD,
443 &&TARGET_SET_VAL_HEAD_BY_INDEX,
444 &&TARGET_CALL_BUILTIN
447 static_assert(opcode_targets.size() ==
static_cast<std::size_t
>(Instruction::InstructionsCount) &&
"Some instructions are not implemented in the VM");
448# pragma GCC diagnostic pop
456 uint16_t primary_arg = 0;
457 uint16_t secondary_arg = 0;
464#if !ARK_USE_COMPUTED_GOTOS
469#pragma region "Instructions"
496 context.
ip = arg * 4;
515 context.
ip = arg * 4;
521 context.
ip = arg * 4;
549 push(std::move(ip_or_val), context);
552 if (context.
fc <= untilFrameCount)
572 "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
608 var->usertypeRef().del();
632 "`{}' is a {}, not a Closure, can not get the field `{}' from it",
639 "{} is not a Closure, can not get the field `{}' from it",
650 push(field, context);
658 "`{0}' isn't in the closure environment: {1}",
664 "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
665 "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
683 l.
list().reserve(arg);
685 for (uint16_t i = 0; i < arg; ++i)
687 push(std::move(l), context);
698 std::vector<Value> args = { *list };
699 for (uint16_t i = 0; i < arg; ++i)
707 const auto size =
static_cast<uint16_t
>(list->
constList().size());
710 obj.
list().reserve(size + arg);
712 for (uint16_t i = 0; i < arg; ++i)
714 push(std::move(obj), context);
725 for (uint16_t i = 0; i < arg; ++i)
735 std::ranges::copy(next->
list(), std::back_inserter(obj.list()));
737 push(std::move(obj), context);
748 std::vector<Value> args = { *list };
749 for (uint16_t i = 0; i < arg; ++i)
757 for (uint16_t i = 0; i < arg; ++i)
766 for (uint16_t i = 0; i < arg; ++i)
776 std::ranges::copy(next->
list(), std::back_inserter(list->
list()));
793 long idx =
static_cast<long>(number.
number());
794 idx = idx < 0 ? static_cast<long>(list.
list().size()) + idx : idx;
795 if (std::cmp_greater_equal(idx, list.
list().size()))
798 fmt::format(
"pop index ({}) out of range (list size: {})", idx, list.
list().size()));
800 list.
list().erase(list.
list().begin() + idx);
818 long idx =
static_cast<long>(number.
number());
819 idx = idx < 0 ? static_cast<long>(list->
list().size()) + idx : idx;
820 if (std::cmp_greater_equal(idx, list->
list().size()))
823 fmt::format(
"pop! index ({}) out of range (list size: {})", idx, list->
list().size()));
825 list->
list().erase(list->
list().begin() + idx);
848 { *list, number, new_value });
851 long idx =
static_cast<long>(number.
number());
852 idx = idx < 0 ? static_cast<long>(size) + idx : idx;
853 if (std::cmp_greater_equal(idx, size))
856 fmt::format(
"@= index ({}) out of range (indexable size: {})", idx, size));
859 list->
list()[
static_cast<std::size_t
>(idx)] = new_value;
861 list->
stringRef()[
static_cast<std::size_t
>(idx)] = new_value.
string()[0];
882 { *list, x, y, new_value });
884 long idx_y =
static_cast<long>(x.
number());
885 idx_y = idx_y < 0 ? static_cast<long>(list->
list().size()) + idx_y : idx_y;
886 if (std::cmp_greater_equal(idx_y, list->
list().size()))
889 fmt::format(
"@@= index (y: {}) out of range (list size: {})", idx_y, list->
list().size()));
891 if (!list->
list()[
static_cast<std::size_t
>(idx_y)].isIndexable() ||
905 { *list, x, y, new_value });
907 const bool is_list = list->
list()[
static_cast<std::size_t
>(idx_y)].valueType() ==
ValueType::List;
908 const std::size_t size =
910 ? list->
list()[
static_cast<std::size_t
>(idx_y)].list().size()
911 : list->
list()[
static_cast<std::size_t
>(idx_y)].stringRef().size();
913 long idx_x =
static_cast<long>(y.
number());
914 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
915 if (std::cmp_greater_equal(idx_x, size))
918 fmt::format(
"@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
921 list->
list()[
static_cast<std::size_t
>(idx_y)].list()[
static_cast<std::size_t
>(idx_x)] = new_value;
923 list->
list()[
static_cast<std::size_t
>(idx_y)].stringRef()[
static_cast<std::size_t
>(idx_x)] = new_value.
string()[0];
949 context.
locals.back().reset();
955 context.
locals.pop_back();
961#pragma region "Operators"
1017 throwVMError(ErrorKind::DivisionByZero, fmt::format(
"Can not compute expression (/ {} {})", a->toString(*
this), b->
toString(*
this)));
1174 long idx =
static_cast<long>(b->
number());
1178 if (std::cmp_less(std::abs(idx), a.
list().size()))
1179 push(a.
list()[
static_cast<std::size_t
>(idx < 0 ?
static_cast<long>(a.
list().size()) + idx : idx)], context);
1183 fmt::format(
"{} out of range {} (length {})", idx, a.
toString(*
this), a.
list().size()));
1187 if (std::cmp_less(std::abs(idx), a.
string().size()))
1188 push(
Value(std::string(1, a.
string()[
static_cast<std::size_t
>(idx < 0 ?
static_cast<long>(a.
string().size()) + idx : idx)])), context);
1192 fmt::format(
"{} out of range \"{}\" (length {})", idx, a.
string(), a.
string().size()));
1221 long idx_y =
static_cast<long>(y->
number());
1222 idx_y = idx_y < 0 ? static_cast<long>(list.
list().size()) + idx_y : idx_y;
1223 if (std::cmp_greater_equal(idx_y, list.
list().size()))
1226 fmt::format(
"@@ index ({}) out of range (list size: {})", idx_y, list.
list().size()));
1228 const bool is_list = list.
list()[
static_cast<std::size_t
>(idx_y)].valueType() ==
ValueType::List;
1229 const std::size_t size =
1231 ? list.
list()[
static_cast<std::size_t
>(idx_y)].list().size()
1232 : list.
list()[
static_cast<std::size_t
>(idx_y)].stringRef().size();
1234 long idx_x =
static_cast<long>(x->
number());
1235 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1236 if (std::cmp_greater_equal(idx_x, size))
1239 fmt::format(
"@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1242 push(list.
list()[
static_cast<std::size_t
>(idx_y)].list()[
static_cast<std::size_t
>(idx_x)], context);
1244 push(
Value(std::string(1, list.
list()[
static_cast<std::size_t
>(idx_y)].stringRef()[
static_cast<std::size_t
>(idx_x)])), context);
1283 { *closure, *field });
1292 auto id =
static_cast<std::uint16_t
>(std::distance(
m_state.
m_symbols.begin(), it));
1307#pragma region "Super Instructions"
1374 { *var,
Value(secondary_arg) });
1395 { *var,
Value(secondary_arg) });
1416 { *var,
Value(secondary_arg) });
1437 { *var,
Value(secondary_arg) });
1448 store(secondary_arg, &tail, context);
1459 store(secondary_arg, &tail, context);
1470 store(secondary_arg, &head, context);
1481 store(secondary_arg, &head, context);
1492 setVal(secondary_arg, &tail, context);
1503 setVal(secondary_arg, &tail, context);
1514 setVal(secondary_arg, &head, context);
1525 setVal(secondary_arg, &head, context);
1541#if ARK_USE_COMPUTED_GOTOS
1549 catch (
const Error& e)
1551 if (fail_with_exception)
1553 std::stringstream stream;
1562 catch (
const std::exception& e)
1564 if (fail_with_exception)
1566 std::stringstream stream;
1575 if (fail_with_exception)
1578#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1581 fmt::println(
"Unknown error");
1591 for (
auto& local : std::ranges::reverse_view(context.locals))
1593 if (
const auto id = local.idFromValue(value);
id < m_state.m_symbols.size())
1596 return std::numeric_limits<uint16_t>::max();
1601 throw std::runtime_error(std::string(
errorKinds[
static_cast<std::size_t
>(kind)]) +
": " + message +
"\n");
1606 std::vector<std::string> arg_names;
1607 arg_names.reserve(expected_arg_count + 1);
1608 if (expected_arg_count > 0)
1609 arg_names.emplace_back(
"");
1611 std::size_t index = 0;
1619 std::vector<std::string> arg_vals;
1620 arg_vals.reserve(passed_arg_count + 1);
1621 if (passed_arg_count > 0)
1622 arg_vals.emplace_back(
"");
1624 for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.
sp; ++i)
1626 arg_vals.push_back(context.
stack[context.
sp - i - 1].toString(*
this));
1630 if (context.
sp >= 2 + passed_arg_count)
1632 context.
ip = context.
stack[context.
sp - 1 - passed_arg_count].pageAddr();
1633 context.
pp = context.
stack[context.
sp - 2 - passed_arg_count].pageAddr();
1644 "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
1646 fmt::join(arg_vals,
" "),
1648 passed_arg_count > 1 ?
"s" :
"",
1651 fmt::join(arg_names,
" ")));
1656 std::string text = e.what();
1657 if (!text.empty() && text.back() !=
'\n')
1659 fmt::println(
"{}", text);
1661#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1671 std::optional<InstLoc> match = std::nullopt;
1675 if (location.page_pointer == pp && !match)
1681 if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
1685 if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
1694 const std::size_t saved_ip = context.
ip;
1695 const std::size_t saved_pp = context.
pp;
1696 const uint16_t saved_sp = context.
sp;
1709 maybe_location->line,
1714 fmt::println(os,
"");
1722 std::string previous_trace;
1723 std::size_t displayed_traces = 0;
1724 std::size_t consecutive_similar_traces = 0;
1726 while (context.
fc != 0)
1729 const auto loc_as_text = maybe_call_loc ? fmt::format(
" ({}:{})",
m_state.
m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) :
"";
1731 if (context.
pp != 0)
1738 if (func_name + loc_as_text != previous_trace)
1742 "[{:4}] In function `{}'{}",
1743 fmt::styled(context.
fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1744 fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
1746 previous_trace = func_name + loc_as_text;
1748 consecutive_similar_traces = 0;
1750 else if (consecutive_similar_traces == 0)
1752 fmt::println(os,
" ...");
1753 ++consecutive_similar_traces;
1768 fmt::println(os,
"[{:4}] In global scope{}", fmt::styled(context.
fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
1772 if (displayed_traces > 7)
1774 fmt::println(os,
"...");
1780 fmt::println(os,
"\nCurrent scope variables values:");
1781 for (std::size_t i = 0, size = old_scope.
size(); i < size; ++i)
1786 fmt::styled(
m_state.
m_symbols[old_scope.
atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1787 old_scope.
atPos(i).second.toString(*
this));
1793 "At IP: {}, PP: {}, SP: {}",
1795 fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1796 fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
1797 fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
Lots of utilities about string, filesystem and more.
Lots of utilities about the filesystem.
The different instructions used by the compiler and virtual machine.
The ArkScript virtual machine.
An assertion error, only triggered from ArkScript code through (assert expr error-message)
virtual std::string details(bool colorize) const
Ark state to handle the dirty job of loading and compiling ArkScript code.
std::vector< std::filesystem::path > m_libenv
std::vector< Value > m_constants
std::vector< internal::InstLoc > m_inst_locations
std::vector< std::string > m_filenames
std::unordered_map< std::string, Value > m_binded
std::vector< std::string > m_symbols
std::vector< bytecode_t > m_pages
The ArkScript virtual machine, executing ArkScript bytecode.
void deleteContext(internal::ExecutionContext *ec)
Free a given execution context.
void showBacktraceWithException(const std::exception &e, internal::ExecutionContext &context)
std::vector< std::unique_ptr< internal::Future > > m_futures
Storing the promises while we are resolving them.
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Value * popAndResolveAsPtr(internal::ExecutionContext &context)
Pop a value from the stack and resolve it if possible, then return it.
std::optional< internal::InstLoc > findSourceLocation(std::size_t ip, std::size_t pp)
Find the nearest source location information given instruction and page pointers.
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Value * loadSymbol(uint16_t id, internal::ExecutionContext &context)
Load a symbol by its id in the current context. Performs a lookup in the scope stack,...
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Value * loadSymbolFromIndex(uint16_t index, internal::ExecutionContext &context)
Load a symbol by its (reversed) index in the current scope.
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
void backtrace(internal::ExecutionContext &context, std::ostream &os=std::cout, bool colorize=true)
Display a backtrace when the VM encounter an exception.
internal::ExecutionContext * createAndGetContext()
Create an execution context and returns it.
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
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...
void deleteFuture(internal::Future *f)
Free a given future.
Value call(const std::string &name, Args &&... args)
Call a function from ArkScript, by giving it arguments.
Value * pop(internal::ExecutionContext &context)
Pop a value from the stack.
void returnFromFuncCall(internal::ExecutionContext &context)
Destroy the current frame and get back to the previous one, resuming execution.
void store(uint16_t id, const Value *val, internal::ExecutionContext &context)
Create a new symbol with an associated value in the current scope.
void setVal(uint16_t id, const Value *val, internal::ExecutionContext &context)
Change the value of a symbol given its identifier.
void push(const Value &value, internal::ExecutionContext &context)
Push a value on the stack.
friend class internal::Closure
Value * loadConstAsPtr(uint16_t id) const
Load a constant from the constant table by its id.
void callBuiltin(internal::ExecutionContext &context, const Value &builtin, uint16_t argc)
Builtin called when the CALL_BUILTIN instruction is met in the bytecode.
void init() noexcept
Initialize the VM according to the parameters.
Value * findNearestVariable(uint16_t id, internal::ExecutionContext &context) noexcept
Find the nearest variable of a given id.
void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext &context)
static void throwVMError(internal::ErrorKind kind, const std::string &message)
Throw a VM error message.
VM(State &state) noexcept
Construct a new vm t object.
internal::Future * createFuture(std::vector< Value > &args)
Create a Future object from a function and its arguments and return a managed pointer to it.
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
const std::vector< Value > & constList() const
std::vector< Value > & list()
internal::Closure & refClosure()
Value * reference() const
void push_back(const Value &value)
Add an element to the list held by the value (if the value type is set to list)
ValueType valueType() const noexcept
const std::string & string() const
bool isIndexable() const noexcept
std::string toString(VM &vm) const noexcept
internal::PageAddr_t pageAddr() const
std::string & stringRef()
A class to store fields captured by a closure.
std::string toString(VM &vm) const noexcept
Print the closure to a string.
ClosureScope & refScope() const noexcept
bool hasFieldEndingWith(const std::string &end, const VM &vm) const
Used when generating error messages in the VM, to see if a symbol might have been wrongly fully quali...
const std::shared_ptr< ClosureScope > & scopePtr() const
A class to handle the VM scope more efficiently.
std::size_t size() const noexcept
Return the size of the scope.
const pair_t & atPos(const std::size_t i) const noexcept
Return the element at index in scope.
ARK_API void makeContext(std::ostream &os, const std::string &filename, const std::optional< std::string > &expr, std::size_t sym_size, std::size_t target_line, std::size_t col_start, const std::optional< CodeErrorContext > &maybe_context, bool whole_line, bool colorize)
Helper to create a colorized context to report errors to the user.
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
bool isDouble(const std::string &s, double *output=nullptr)
Checks if a string is a valid double.
ARK_API const std::vector< std::pair< std::string, Value > > builtins
constexpr std::array< std::string_view, 7 > errorKinds
constexpr std::array types_to_str
constexpr std::size_t VMStackSize
@ Any
Used only for typechecking.
std::array< ScopeView::pair_t, ScopeStackSize > scopes_storage
All the ScopeView use this array to store id->value.
std::vector< std::shared_ptr< ClosureScope > > stacked_closure_scopes
Stack the closure scopes to keep the closure alive as long as we are calling them.
std::array< Value, VMStackSize > stack
std::size_t pp
Page pointer.
std::vector< ScopeView > locals
uint16_t sp
Stack pointer.
std::size_t ip
Instruction pointer.
std::optional< ClosureScope > saved_scope
Scope created by CAPTURE <x> instructions, used by the MAKE_CLOSURE instruction.
A contract is a list of typed arguments that a function can follow.
A type definition within a contract.
Ark::Value(* value)(std::vector< Ark::Value > &, Ark::VM *)