23 using namespace internal;
34 std::vector<Value> tmp(a->
constList().size() - 1);
35 for (std::size_t i = 1, end = a->
constList().size(); i < end; ++i)
37 return Value(std::move(tmp));
41 if (a->
string().size() < 2)
45 b.
stringRef().erase(b.stringRef().begin());
80 m_state(state), m_exit_code(0), m_running(
false)
82 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
106 context.
locals.emplace_back();
121 const auto it = std::ranges::find(m_state.m_symbols, name);
122 if (it == m_state.m_symbols.end())
128 const auto dist = std::distance(m_state.m_symbols.begin(), it);
129 if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
133 const auto id =
static_cast<uint16_t
>(dist);
134 Value* var = findNearestVariable(
id, context);
145 namespace fs = std::filesystem;
149 std::string path = file;
152 path = (fs::path(
m_state.
m_filename).parent_path() / fs::path(file)).relative_path().string();
154 std::shared_ptr<SharedLibrary> lib;
157 lib = std::make_shared<SharedLibrary>(path);
162 std::string lib_path = (fs::path(v) / fs::path(file)).
string();
166 return (val->path() == path || val->path() == lib_path);
173 lib = std::make_shared<SharedLibrary>(lib_path);
181 auto lib_path = std::accumulate(
185 [](
const std::string& a,
const fs::path& b) -> std::string {
186 return a +
"\n\t- " + b.string();
190 fmt::format(
"Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
201 while (map[i].name !=
nullptr)
211 catch (
const std::system_error& e)
216 "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
229 const std::lock_guard lock(
m_mutex);
237 ctx->
locals.push_back(local);
244 const std::lock_guard lock(
m_mutex);
247 std::ranges::remove_if(
249 [ec](
const std::unique_ptr<ExecutionContext>& ctx) {
250 return ctx.get() == ec;
262 const std::lock_guard lock(
m_mutex);
263 m_futures.push_back(std::make_unique<Future>(ctx,
this, args));
270 const std::lock_guard lock(
m_mutex);
273 std::ranges::remove_if(
275 [f](
const std::unique_ptr<Future>& future) {
276 return future.get() == f;
289 const mapping* map = shared_lib->template get<mapping* (*)()>(
"getFunctionsMapping")();
292 while (map[i].name !=
nullptr)
299 Value(map[i].value));
307 catch (
const std::system_error&)
322#if ARK_USE_COMPUTED_GOTOS
323# define TARGET(op) TARGET_##op:
324# define DISPATCH_GOTO() \
325 _Pragma("GCC diagnostic push") \
326 _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
327 _Pragma(
"GCC diagnostic pop")
328# define GOTO_HALT() goto dispatch_end
330# define TARGET(op) case op:
331# define DISPATCH_GOTO() goto dispatch_opcode
332# define GOTO_HALT() break
338 inst = m_state.m_pages[context.pp][context.ip]; \
339 padding = m_state.m_pages[context.pp][context.ip + 1]; \
340 arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
341 m_state.m_pages[context.pp][context.ip + 3]); \
347#define UNPACK_ARGS() \
350 secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
351 primary_arg = arg & 0x0fff; \
354#if ARK_USE_COMPUTED_GOTOS
355# pragma GCC diagnostic push
356# pragma GCC diagnostic ignored "-Wpedantic"
357 constexpr std::array opcode_targets = {
359 &&TARGET_LOAD_SYMBOL,
361 &&TARGET_POP_JUMP_IF_TRUE,
364 &&TARGET_POP_JUMP_IF_FALSE,
372 &&TARGET_MAKE_CLOSURE,
378 &&TARGET_APPEND_IN_PLACE,
379 &&TARGET_CONCAT_IN_PLACE,
381 &&TARGET_POP_LIST_IN_PLACE,
382 &&TARGET_SET_AT_INDEX,
383 &&TARGET_SET_AT_2_INDEX,
386 &&TARGET_CREATE_SCOPE,
412 &&TARGET_LOAD_CONST_LOAD_CONST,
413 &&TARGET_LOAD_CONST_STORE,
414 &&TARGET_LOAD_CONST_SET_VAL,
416 &&TARGET_SET_VAL_FROM,
421 &&TARGET_SET_VAL_TAIL,
422 &&TARGET_SET_VAL_HEAD,
423 &&TARGET_CALL_BUILTIN
425# pragma GCC diagnostic pop
433 uint16_t primary_arg = 0;
434 uint16_t secondary_arg = 0;
440#if !ARK_USE_COMPUTED_GOTOS
445#pragma region "Instructions"
466 context.
ip = arg * 4;
485 context.
ip = arg * 4;
491 context.
ip = arg * 4;
523 push(std::move(ip_or_val), context);
526 if (context.
fc <= untilFrameCount)
546 "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
582 var->usertypeRef().del();
606 "`{}' is a {}, not a Closure, can not get the field `{}' from it",
613 "{} is not a Closure, can not get the field `{}' from it",
624 push(field, context);
632 "`{0}' isn't in the closure environment: {1}",
638 "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
639 "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
657 l.
list().reserve(arg);
659 for (uint16_t i = 0; i < arg; ++i)
661 push(std::move(l), context);
676 const auto size =
static_cast<uint16_t
>(list->
constList().size());
679 obj.
list().reserve(size + arg);
681 for (uint16_t i = 0; i < arg; ++i)
683 push(std::move(obj), context);
700 for (uint16_t i = 0; i < arg; ++i)
710 std::ranges::copy(next->
list(), std::back_inserter(obj.list()));
712 push(std::move(obj), context);
727 for (uint16_t i = 0; i < arg; ++i)
742 for (uint16_t i = 0; i < arg; ++i)
752 std::ranges::copy(next->
list(), std::back_inserter(list->
list()));
769 long idx =
static_cast<long>(number.
number());
770 idx = idx < 0 ? static_cast<long>(list.
list().size()) + idx : idx;
771 if (std::cmp_greater_equal(idx, list.
list().size()))
774 fmt::format(
"pop index ({}) out of range (list size: {})", idx, list.
list().size()));
776 list.
list().erase(list.
list().begin() + idx);
794 long idx =
static_cast<long>(number.
number());
795 idx = idx < 0 ? static_cast<long>(list->
list().size()) + idx : idx;
796 if (std::cmp_greater_equal(idx, list->
list().size()))
799 fmt::format(
"pop! index ({}) out of range (list size: {})", idx, list->
list().size()));
801 list->
list().erase(list->
list().begin() + idx);
827 long idx =
static_cast<long>(number.
number());
828 idx = idx < 0 ? static_cast<long>(size) + idx : idx;
829 if (std::cmp_greater_equal(idx, size))
832 fmt::format(
"@= index ({}) out of range (indexable size: {})", idx, size));
835 list->
list()[
static_cast<std::size_t
>(idx)] = new_value;
837 list->
stringRef()[
static_cast<std::size_t
>(idx)] = new_value.
string()[0];
860 long idx_y =
static_cast<long>(x.
number());
861 idx_y = idx_y < 0 ? static_cast<long>(list->
list().size()) + idx_y : idx_y;
862 if (std::cmp_greater_equal(idx_y, list->
list().size()))
865 fmt::format(
"@@= index (y: {}) out of range (list size: {})", idx_y, list->
list().size()));
867 if (!list->
list()[
static_cast<std::size_t
>(idx_y)].isIndexable() ||
883 const bool is_list = list->
list()[
static_cast<std::size_t
>(idx_y)].valueType() ==
ValueType::List;
884 const std::size_t size =
886 ? list->
list()[
static_cast<std::size_t
>(idx_y)].list().size()
887 : list->
list()[
static_cast<std::size_t
>(idx_y)].stringRef().size();
889 long idx_x =
static_cast<long>(y.
number());
890 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
891 if (std::cmp_greater_equal(idx_x, size))
894 fmt::format(
"@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
897 list->
list()[
static_cast<std::size_t
>(idx_y)].list()[
static_cast<std::size_t
>(idx_x)] = new_value;
899 list->
list()[
static_cast<std::size_t
>(idx_y)].stringRef()[
static_cast<std::size_t
>(idx_x)] = new_value.
string()[0];
919 context.
locals.emplace_back();
925 context.
locals.pop_back();
931#pragma region "Operators"
987 throwVMError(ErrorKind::DivisionByZero, fmt::format(
"Can not compute expression (/ {} {})", a->toString(*
this), b->
toString(*
this)));
1143 long idx =
static_cast<long>(b->
number());
1147 if (std::cmp_less(std::abs(idx), a.
list().size()))
1148 push(a.
list()[
static_cast<std::size_t
>(idx < 0 ?
static_cast<long>(a.
list().size()) + idx : idx)], context);
1152 fmt::format(
"{} out of range {} (length {})", idx, a.
toString(*
this), a.
list().size()));
1156 if (std::cmp_less(std::abs(idx), a.
string().size()))
1157 push(
Value(std::string(1, a.
string()[
static_cast<std::size_t
>(idx < 0 ?
static_cast<long>(a.
string().size()) + idx : idx)])), context);
1161 fmt::format(
"{} out of range \"{}\" (length {})", idx, a.
string(), a.
string().size()));
1190 long idx_y =
static_cast<long>(y->
number());
1191 idx_y = idx_y < 0 ? static_cast<long>(list.
list().size()) + idx_y : idx_y;
1192 if (std::cmp_greater_equal(idx_y, list.
list().size()))
1195 fmt::format(
"@@ index ({}) out of range (list size: {})", idx_y, list.
list().size()));
1197 const bool is_list = list.
list()[
static_cast<std::size_t
>(idx_y)].valueType() ==
ValueType::List;
1198 const std::size_t size =
1200 ? list.
list()[
static_cast<std::size_t
>(idx_y)].list().size()
1201 : list.
list()[
static_cast<std::size_t
>(idx_y)].stringRef().size();
1203 long idx_x =
static_cast<long>(x->
number());
1204 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1205 if (std::cmp_greater_equal(idx_x, size))
1208 fmt::format(
"@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1211 push(list.
list()[
static_cast<std::size_t
>(idx_y)].list()[
static_cast<std::size_t
>(idx_x)], context);
1213 push(
Value(std::string(1, list.
list()[
static_cast<std::size_t
>(idx_y)].stringRef()[
static_cast<std::size_t
>(idx_x)])), context);
1251 { *closure, *field });
1260 auto id =
static_cast<std::uint16_t
>(std::distance(
m_state.
m_symbols.begin(), it));
1275#pragma region "Super Instructions"
1328 { *var,
Value(secondary_arg) });
1349 { *var,
Value(secondary_arg) });
1360 store(secondary_arg, &tail, context);
1371 store(secondary_arg, &head, context);
1382 setVal(secondary_arg, &tail, context);
1393 setVal(secondary_arg, &head, context);
1409#if ARK_USE_COMPUTED_GOTOS
1417 catch (
const std::exception& e)
1419 if (fail_with_exception)
1422 fmt::println(
"{}", e.what());
1424#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1433 if (fail_with_exception)
1436#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1439 fmt::println(
"Unknown error");
1449 for (
auto& local : std::ranges::reverse_view(context.locals))
1451 if (
const auto id = local.idFromValue(value);
id < m_state.m_symbols.size())
1454 return std::numeric_limits<uint16_t>::max();
1459 throw std::runtime_error(std::string(
errorKinds[
static_cast<std::size_t
>(kind)]) +
": " + message +
"\n");
1464 const std::size_t saved_ip = context.ip;
1465 const std::size_t saved_pp = context.pp;
1466 const uint16_t saved_sp = context.sp;
1468 if (
const uint16_t original_frame_count = context.fc; original_frame_count > 1)
1471 const Scope old_scope = context.locals.back();
1473 while (context.fc != 0)
1475 fmt::print(
"[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
1476 if (context.pp != 0)
1478 const uint16_t
id = findNearestVariableIdWithValue(
1482 if (
id < m_state.m_symbols.size())
1483 fmt::println(
"In function `{}'", fmt::styled(m_state.m_symbols[
id], fmt::fg(fmt::color::green)));
1485 fmt::println(
"In function `{}'", fmt::styled(
"???", fmt::fg(fmt::color::gold)));
1490 ip = popAndResolveAsPtr(context);
1494 context.pp = pop(context)->pageAddr();
1495 returnFromFuncCall(context);
1499 fmt::println(
"In global scope");
1503 if (original_frame_count - context.fc > 7)
1505 fmt::println(
"...");
1511 fmt::println(
"\nCurrent scope variables values:");
1512 for (std::size_t i = 0, size = old_scope.
size(); i < size; ++i)
1516 fmt::styled(m_state.m_symbols[old_scope.
m_data[i].first], fmt::fg(fmt::color::cyan)),
1517 old_scope.
m_data[i].second.toString(*
this));
1520 while (context.fc != 1)
1522 Value* tmp = pop(context);
1531 std::cerr <<
"At IP: " << (saved_ip / 4)
1532 <<
", PP: " << saved_pp
1533 <<
", SP: " << saved_sp
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)
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::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.
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::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)
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
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 backtrace(internal::ExecutionContext &context) noexcept
Display a backtrace when the VM encounter an exception.
void store(uint16_t id, const Value *val, internal::ExecutionContext &context)
void setVal(uint16_t id, const Value *val, internal::ExecutionContext &context)
void push(const Value &value, internal::ExecutionContext &context)
Push a value on the stack.
friend class internal::Closure
Value * loadConstAsPtr(uint16_t id) const
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.
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()
bool hasFieldEndingWith(const std::string &end, 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< Scope > & scopePtr() const
std::string toString(VM &vm) const noexcept
Print the closure to a string.
Scope & refScope() const noexcept
A class to handle the VM scope more efficiently.
std::vector< std::pair< uint16_t, Value > > m_data
std::size_t size() const noexcept
Return the size of the scope.
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
ARK_API void generateError(const std::string_view &funcname, const std::vector< Contract > &contracts, const std::vector< Value > &args)
Generate an error message based on a given set of types contracts provided argument list.
constexpr std::array types_to_str
constexpr std::size_t VMStackSize
@ Any
Used only for typechecking.
std::array< Value, VMStackSize > stack
std::optional< Scope > saved_scope
Scope created by CAPTURE <x> instructions, used by the MAKE_CLOSURE instruction.
std::size_t pp
Page pointer.
std::vector< Scope > locals
uint16_t sp
Stack pointer.
std::size_t ip
Instruction pointer.
std::vector< std::shared_ptr< Scope > > stacked_closure_scopes
Stack the closure scopes to keep the closure alive as long as we are calling them.
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 *)