ArkScript
A small, lisp-inspired, functional scripting language
VM.cpp
Go to the documentation of this file.
1#include <Ark/VM/VM.hpp>
2
3#include <utility>
4#include <numeric>
5#include <limits>
6#include <fmt/core.h>
7#include <fmt/color.h>
8#include <fmt/ostream.h>
9
10#include <Ark/Files.hpp>
11#include <Ark/Utils.hpp>
12#include <Ark/TypeChecker.hpp>
14
15struct mapping
16{
17 char* name;
18 Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
19};
20
21namespace Ark
22{
23 using namespace internal;
24
25 namespace helper
26 {
27 inline Value tail(Value* a)
28 {
29 if (a->valueType() == ValueType::List)
30 {
31 if (a->constList().size() < 2)
32 return Value(ValueType::List);
33
34 std::vector<Value> tmp(a->constList().size() - 1);
35 for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
36 tmp[i - 1] = a->constList()[i];
37 return Value(std::move(tmp));
38 }
39 if (a->valueType() == ValueType::String)
40 {
41 if (a->string().size() < 2)
43
44 Value b { *a };
45 b.stringRef().erase(b.stringRef().begin());
46 return b;
47 }
48
50 "tail",
53 { *a });
54 }
55
56 inline Value head(Value* a)
57 {
58 if (a->valueType() == ValueType::List)
59 {
60 if (a->constList().empty())
61 return Builtins::nil;
62 return a->constList()[0];
63 }
64 if (a->valueType() == ValueType::String)
65 {
66 if (a->string().empty())
68 return Value(std::string(1, a->stringRef()[0]));
69 }
70
72 "head",
75 { *a });
76 }
77
78 inline Value at(Value& container, Value& index, VM& vm)
79 {
80 if (index.valueType() != ValueType::Number)
82 "@",
85 { container, index });
86
87 const auto num = static_cast<long>(index.number());
88
89 if (container.valueType() == ValueType::List)
90 {
91 const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.list().size()) + num : num);
92 if (i < container.list().size())
93 return container.list()[i];
94 else
96 ErrorKind::Index,
97 fmt::format("{} out of range {} (length {})", num, container.toString(vm), container.list().size()));
98 }
99 else if (container.valueType() == ValueType::String)
100 {
101 const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.string().size()) + num : num);
102 if (i < container.string().size())
103 return Value(std::string(1, container.string()[i]));
104 else
106 ErrorKind::Index,
107 fmt::format("{} out of range \"{}\" (length {})", num, container.string(), container.string().size()));
108 }
109 else
111 "@",
114 { container, index });
115 }
116 }
117
118 VM::VM(State& state) noexcept :
119 m_state(state), m_exit_code(0), m_running(false)
120 {
121 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
122 }
123
124 void VM::init() noexcept
125 {
126 ExecutionContext& context = *m_execution_contexts.back();
127 for (const auto& c : m_execution_contexts)
128 {
129 c->ip = 0;
130 c->pp = 0;
131 c->sp = 0;
132 }
133
134 context.sp = 0;
135 context.fc = 1;
136
137 m_shared_lib_objects.clear();
138 context.stacked_closure_scopes.clear();
139 context.stacked_closure_scopes.emplace_back(nullptr);
140
141 context.saved_scope.reset();
142 m_exit_code = 0;
143
144 context.locals.clear();
145 context.locals.reserve(128);
146 context.locals.emplace_back(context.scopes_storage.data(), 0);
147
148 // loading bound stuff
149 // put them in the global frame if we can, aka the first one
150 for (const auto& [sym_id, value] : m_state.m_binded)
151 {
152 auto it = std::ranges::find(m_state.m_symbols, sym_id);
153 if (it != m_state.m_symbols.end())
154 context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
155 }
156 }
157
158 Value VM::getField(Value* closure, const uint16_t id, ExecutionContext& context)
159 {
160 if (closure->valueType() != ValueType::Closure)
161 {
162 if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
164 ErrorKind::Type,
165 fmt::format(
166 "`{}' is a {}, not a Closure, can not get the field `{}' from it",
168 types_to_str[static_cast<std::size_t>(closure->valueType())],
169 m_state.m_symbols[id]));
170 else
171 throwVMError(ErrorKind::Type,
172 fmt::format(
173 "{} is not a Closure, can not get the field `{}' from it",
174 types_to_str[static_cast<std::size_t>(closure->valueType())],
175 m_state.m_symbols[id]));
176 }
177
178 if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
179 {
180 // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
181 if (m_state.inst(context.pp, context.ip) == CALL)
182 return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
183 else
184 return *field;
185 }
186 else
187 {
188 if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
190 ErrorKind::Scope,
191 fmt::format(
192 "`{0}' isn't in the closure environment: {1}",
193 m_state.m_symbols[id],
194 closure->refClosure().toString(*this)));
196 ErrorKind::Scope,
197 fmt::format(
198 "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
199 "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
200 m_state.m_symbols[id],
201 closure->refClosure().toString(*this)));
202 }
203 }
204
205 Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
206 {
208 if (count != 0)
209 l.list().reserve(count);
210
211 for (uint16_t i = 0; i < count; ++i)
212 l.push_back(*popAndResolveAsPtr(context));
213
214 return l;
215 }
216
217 void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
218 {
219 if (list->valueType() != ValueType::List)
220 {
221 std::vector<Value> args = { *list };
222 for (std::size_t i = 0; i < count; ++i)
223 args.push_back(*popAndResolveAsPtr(context));
225 "append!",
226 { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
227 args);
228 }
229
230 for (std::size_t i = 0; i < count; ++i)
231 list->push_back(*popAndResolveAsPtr(context));
232 }
233
234 Value& VM::operator[](const std::string& name) noexcept
235 {
236 // find id of object
237 const auto it = std::ranges::find(m_state.m_symbols, name);
238 if (it == m_state.m_symbols.end())
239 {
240 m_no_value = Builtins::nil;
241 return m_no_value;
242 }
243
244 const auto dist = std::distance(m_state.m_symbols.begin(), it);
245 if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
246 {
247 ExecutionContext& context = *m_execution_contexts.front();
248
249 const auto id = static_cast<uint16_t>(dist);
250 Value* var = findNearestVariable(id, context);
251 if (var != nullptr)
252 return *var;
253 }
254
255 m_no_value = Builtins::nil;
256 return m_no_value;
257 }
258
259 void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
260 {
261 namespace fs = std::filesystem;
262
263 const std::string file = m_state.m_constants[id].stringRef();
264
265 std::string path = file;
266 // bytecode loaded from file
268 path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
269
270 std::shared_ptr<SharedLibrary> lib;
271 // if it exists alongside the .arkc file
272 if (Utils::fileExists(path))
273 lib = std::make_shared<SharedLibrary>(path);
274 else
275 {
276 for (auto const& v : m_state.m_libenv)
277 {
278 std::string lib_path = (fs::path(v) / fs::path(file)).string();
279
280 // if it's already loaded don't do anything
281 if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
282 return (val->path() == path || val->path() == lib_path);
283 }) != m_shared_lib_objects.end())
284 return;
285
286 // check in lib_path
287 if (Utils::fileExists(lib_path))
288 {
289 lib = std::make_shared<SharedLibrary>(lib_path);
290 break;
291 }
292 }
293 }
294
295 if (!lib)
296 {
297 auto lib_path = std::accumulate(
298 std::next(m_state.m_libenv.begin()),
299 m_state.m_libenv.end(),
300 m_state.m_libenv[0].string(),
301 [](const std::string& a, const fs::path& b) -> std::string {
302 return a + "\n\t- " + b.string();
303 });
305 ErrorKind::Module,
306 fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
307 }
308
309 m_shared_lib_objects.emplace_back(lib);
310
311 // load the mapping from the dynamic library
312 try
313 {
314 const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
315 // load the mapping data
316 std::size_t i = 0;
317 while (map[i].name != nullptr)
318 {
319 // put it in the global frame, aka the first one
320 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
321 if (it != m_state.m_symbols.end())
322 context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
323
324 ++i;
325 }
326 }
327 catch (const std::system_error& e)
328 {
330 ErrorKind::Module,
331 fmt::format(
332 "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
333 file, e.what()));
334 }
335 }
336
337 void VM::exit(const int code) noexcept
338 {
339 m_exit_code = code;
340 m_running = false;
341 }
342
344 {
345 const std::lock_guard lock(m_mutex);
346
347 m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
348 ExecutionContext* ctx = m_execution_contexts.back().get();
349 ctx->stacked_closure_scopes.emplace_back(nullptr);
350
351 ctx->locals.reserve(m_execution_contexts.front()->locals.size());
352 ctx->scopes_storage = m_execution_contexts.front()->scopes_storage;
353 for (const auto& local : m_execution_contexts.front()->locals)
354 {
355 auto& scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), local.m_start);
356 scope.m_size = local.m_size;
357 scope.m_min_id = local.m_min_id;
358 scope.m_max_id = local.m_max_id;
359 }
360
361 return ctx;
362 }
363
365 {
366 const std::lock_guard lock(m_mutex);
367
368 const auto it =
369 std::ranges::remove_if(
371 [ec](const std::unique_ptr<ExecutionContext>& ctx) {
372 return ctx.get() == ec;
373 })
374 .begin();
375 m_execution_contexts.erase(it);
376 }
377
378 Future* VM::createFuture(std::vector<Value>& args)
379 {
381 // so that we have access to the presumed symbol id of the function we are calling
382 // assuming that the callee is always the global context
383 ctx->last_symbol = m_execution_contexts.front()->last_symbol;
384
385 // doing this after having created the context
386 // because the context uses the mutex and we don't want a deadlock
387 const std::lock_guard lock(m_mutex);
388 m_futures.push_back(std::make_unique<Future>(ctx, this, args));
389
390 return m_futures.back().get();
391 }
392
394 {
395 const std::lock_guard lock(m_mutex);
396
397 const auto it =
398 std::ranges::remove_if(
399 m_futures,
400 [f](const std::unique_ptr<Future>& future) {
401 return future.get() == f;
402 })
403 .begin();
404 m_futures.erase(it);
405 }
406
408 {
409 // load the mapping from the dynamic library
410 try
411 {
412 for (const auto& shared_lib : m_shared_lib_objects)
413 {
414 const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
415 // load the mapping data
416 std::size_t i = 0;
417 while (map[i].name != nullptr)
418 {
419 // put it in the global frame, aka the first one
420 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
421 if (it != m_state.m_symbols.end())
422 m_execution_contexts[0]->locals[0].push_back(
423 static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
424 Value(map[i].value));
425
426 ++i;
427 }
428 }
429
430 return true;
431 }
432 catch (const std::system_error&)
433 {
434 return false;
435 }
436 }
437
438 void VM::throwVMError(ErrorKind kind, const std::string& message)
439 {
440 throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
441 }
442
443 int VM::run(const bool fail_with_exception)
444 {
445 init();
446 safeRun(*m_execution_contexts[0], 0, fail_with_exception);
447 return m_exit_code;
448 }
449
450 int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
451 {
452#if ARK_USE_COMPUTED_GOTOS
453# define TARGET(op) TARGET_##op:
454# define DISPATCH_GOTO() \
455 _Pragma("GCC diagnostic push") \
456 _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
457 _Pragma("GCC diagnostic pop")
458# define GOTO_HALT() goto dispatch_end
459#else
460# define TARGET(op) case op:
461# define DISPATCH_GOTO() goto dispatch_opcode
462# define GOTO_HALT() break
463#endif
464
465#define NEXTOPARG() \
466 do \
467 { \
468 inst = m_state.inst(context.pp, context.ip); \
469 padding = m_state.inst(context.pp, context.ip + 1); \
470 arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) + \
471 m_state.inst(context.pp, context.ip + 3)); \
472 context.ip += 4; \
473 } while (false)
474#define DISPATCH() \
475 NEXTOPARG(); \
476 DISPATCH_GOTO();
477#define UNPACK_ARGS() \
478 do \
479 { \
480 secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
481 primary_arg = arg & 0x0fff; \
482 } while (false)
483
484#if ARK_USE_COMPUTED_GOTOS
485# pragma GCC diagnostic push
486# pragma GCC diagnostic ignored "-Wpedantic"
487 constexpr std::array opcode_targets = {
488 // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
489 &&TARGET_NOP,
490 &&TARGET_LOAD_SYMBOL,
491 &&TARGET_LOAD_SYMBOL_BY_INDEX,
492 &&TARGET_LOAD_CONST,
493 &&TARGET_POP_JUMP_IF_TRUE,
494 &&TARGET_STORE,
495 &&TARGET_SET_VAL,
496 &&TARGET_POP_JUMP_IF_FALSE,
497 &&TARGET_JUMP,
498 &&TARGET_RET,
499 &&TARGET_HALT,
500 &&TARGET_PUSH_RETURN_ADDRESS,
501 &&TARGET_CALL,
502 &&TARGET_CAPTURE,
503 &&TARGET_BUILTIN,
504 &&TARGET_DEL,
505 &&TARGET_MAKE_CLOSURE,
506 &&TARGET_GET_FIELD,
507 &&TARGET_PLUGIN,
508 &&TARGET_LIST,
509 &&TARGET_APPEND,
510 &&TARGET_CONCAT,
511 &&TARGET_APPEND_IN_PLACE,
512 &&TARGET_CONCAT_IN_PLACE,
513 &&TARGET_POP_LIST,
514 &&TARGET_POP_LIST_IN_PLACE,
515 &&TARGET_SET_AT_INDEX,
516 &&TARGET_SET_AT_2_INDEX,
517 &&TARGET_POP,
518 &&TARGET_SHORTCIRCUIT_AND,
519 &&TARGET_SHORTCIRCUIT_OR,
520 &&TARGET_CREATE_SCOPE,
521 &&TARGET_RESET_SCOPE_JUMP,
522 &&TARGET_POP_SCOPE,
523 &&TARGET_GET_CURRENT_PAGE_ADDR,
524 &&TARGET_ADD,
525 &&TARGET_SUB,
526 &&TARGET_MUL,
527 &&TARGET_DIV,
528 &&TARGET_GT,
529 &&TARGET_LT,
530 &&TARGET_LE,
531 &&TARGET_GE,
532 &&TARGET_NEQ,
533 &&TARGET_EQ,
534 &&TARGET_LEN,
535 &&TARGET_EMPTY,
536 &&TARGET_TAIL,
537 &&TARGET_HEAD,
538 &&TARGET_ISNIL,
539 &&TARGET_ASSERT,
540 &&TARGET_TO_NUM,
541 &&TARGET_TO_STR,
542 &&TARGET_AT,
543 &&TARGET_AT_AT,
544 &&TARGET_MOD,
545 &&TARGET_TYPE,
546 &&TARGET_HASFIELD,
547 &&TARGET_NOT,
548 &&TARGET_LOAD_CONST_LOAD_CONST,
549 &&TARGET_LOAD_CONST_STORE,
550 &&TARGET_LOAD_CONST_SET_VAL,
551 &&TARGET_STORE_FROM,
552 &&TARGET_STORE_FROM_INDEX,
553 &&TARGET_SET_VAL_FROM,
554 &&TARGET_SET_VAL_FROM_INDEX,
555 &&TARGET_INCREMENT,
556 &&TARGET_INCREMENT_BY_INDEX,
557 &&TARGET_INCREMENT_STORE,
558 &&TARGET_DECREMENT,
559 &&TARGET_DECREMENT_BY_INDEX,
560 &&TARGET_DECREMENT_STORE,
561 &&TARGET_STORE_TAIL,
562 &&TARGET_STORE_TAIL_BY_INDEX,
563 &&TARGET_STORE_HEAD,
564 &&TARGET_STORE_HEAD_BY_INDEX,
565 &&TARGET_STORE_LIST,
566 &&TARGET_SET_VAL_TAIL,
567 &&TARGET_SET_VAL_TAIL_BY_INDEX,
568 &&TARGET_SET_VAL_HEAD,
569 &&TARGET_SET_VAL_HEAD_BY_INDEX,
570 &&TARGET_CALL_BUILTIN,
571 &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
572 &&TARGET_LT_CONST_JUMP_IF_FALSE,
573 &&TARGET_LT_CONST_JUMP_IF_TRUE,
574 &&TARGET_LT_SYM_JUMP_IF_FALSE,
575 &&TARGET_GT_CONST_JUMP_IF_TRUE,
576 &&TARGET_GT_CONST_JUMP_IF_FALSE,
577 &&TARGET_GT_SYM_JUMP_IF_FALSE,
578 &&TARGET_EQ_CONST_JUMP_IF_TRUE,
579 &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
580 &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
581 &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
582 &&TARGET_CALL_SYMBOL,
583 &&TARGET_CALL_CURRENT_PAGE,
584 &&TARGET_GET_FIELD_FROM_SYMBOL,
585 &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
586 &&TARGET_AT_SYM_SYM,
587 &&TARGET_AT_SYM_INDEX_SYM_INDEX,
588 &&TARGET_CHECK_TYPE_OF,
589 &&TARGET_CHECK_TYPE_OF_BY_INDEX,
590 &&TARGET_APPEND_IN_PLACE_SYM,
591 &&TARGET_APPEND_IN_PLACE_SYM_INDEX
592 };
593
594 static_assert(opcode_targets.size() == static_cast<std::size_t>(Instruction::InstructionsCount) && "Some instructions are not implemented in the VM");
595# pragma GCC diagnostic pop
596#endif
597
598 try
599 {
600 uint8_t inst = 0;
601 uint8_t padding = 0;
602 uint16_t arg = 0;
603 uint16_t primary_arg = 0;
604 uint16_t secondary_arg = 0;
605
606 m_running = true;
607
608 DISPATCH();
609 // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
610 {
611#if !ARK_USE_COMPUTED_GOTOS
612 dispatch_opcode:
613 switch (inst)
614#endif
615 {
616#pragma region "Instructions"
617 TARGET(NOP)
618 {
619 DISPATCH();
620 }
621
623 {
624 push(loadSymbol(arg, context), context);
625 DISPATCH();
626 }
627
629 {
630 push(loadSymbolFromIndex(arg, context), context);
631 DISPATCH();
632 }
633
635 {
636 push(loadConstAsPtr(arg), context);
637 DISPATCH();
638 }
639
641 {
642 if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
643 context.ip = arg * 4; // instructions are 4 bytes
644 DISPATCH();
645 }
646
648 {
649 store(arg, popAndResolveAsPtr(context), context);
650 DISPATCH();
651 }
652
654 {
655 setVal(arg, popAndResolveAsPtr(context), context);
656 DISPATCH();
657 }
658
660 {
661 if (Value boolean = *popAndResolveAsPtr(context); !boolean)
662 context.ip = arg * 4; // instructions are 4 bytes
663 DISPATCH();
664 }
665
666 TARGET(JUMP)
667 {
668 context.ip = arg * 4; // instructions are 4 bytes
669 DISPATCH();
670 }
671
672 TARGET(RET)
673 {
674 {
675 Value ip_or_val = *popAndResolveAsPtr(context);
676 // no return value on the stack
677 if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
678 {
679 context.ip = ip_or_val.pageAddr();
680 // we always push PP then IP, thus the next value
681 // MUST be the page pointer
682 context.pp = pop(context)->pageAddr();
683
684 returnFromFuncCall(context);
685 push(Builtins::nil, context);
686 }
687 // value on the stack
688 else [[likely]]
689 {
690 const Value* ip = popAndResolveAsPtr(context);
691 assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
692 context.ip = ip->pageAddr();
693 context.pp = pop(context)->pageAddr();
694
695 returnFromFuncCall(context);
696 push(std::move(ip_or_val), context);
697 }
698
699 if (context.fc <= untilFrameCount)
700 GOTO_HALT();
701 }
702
703 DISPATCH();
704 }
705
706 TARGET(HALT)
707 {
708 m_running = false;
709 GOTO_HALT();
710 }
711
713 {
714 push(Value(static_cast<PageAddr_t>(context.pp)), context);
715 // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
716 push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
717 DISPATCH();
718 }
719
720 TARGET(CALL)
721 {
722 call(context, arg);
723 if (!m_running)
724 GOTO_HALT();
725 DISPATCH();
726 }
727
729 {
730 if (!context.saved_scope)
731 context.saved_scope = ClosureScope();
732
733 const Value* ptr = findNearestVariable(arg, context);
734 if (!ptr)
735 throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
736 else
737 {
738 ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
739 context.saved_scope.value().push_back(arg, *ptr);
740 }
741
742 DISPATCH();
743 }
744
746 {
747 push(Builtins::builtins[arg].second, context);
748 DISPATCH();
749 }
750
751 TARGET(DEL)
752 {
753 if (Value* var = findNearestVariable(arg, context); var != nullptr)
754 {
755 if (var->valueType() == ValueType::User)
756 var->usertypeRef().del();
757 *var = Value();
758 DISPATCH();
759 }
760
761 throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
762 }
763
765 {
766 push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
767 context.saved_scope.reset();
768 DISPATCH();
769 }
770
772 {
773 Value* var = popAndResolveAsPtr(context);
774 push(getField(var, arg, context), context);
775 DISPATCH();
776 }
777
779 {
780 loadPlugin(arg, context);
781 DISPATCH();
782 }
783
784 TARGET(LIST)
785 {
786 {
787 Value l = createList(arg, context);
788 push(std::move(l), context);
789 }
790 DISPATCH();
791 }
792
794 {
795 {
796 Value* list = popAndResolveAsPtr(context);
797 if (list->valueType() != ValueType::List)
798 {
799 std::vector<Value> args = { *list };
800 for (uint16_t i = 0; i < arg; ++i)
801 args.push_back(*popAndResolveAsPtr(context));
803 "append",
804 { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
805 args);
806 }
807
808 const auto size = static_cast<uint16_t>(list->constList().size());
809
810 Value obj { *list };
811 obj.list().reserve(size + arg);
812
813 for (uint16_t i = 0; i < arg; ++i)
814 obj.push_back(*popAndResolveAsPtr(context));
815 push(std::move(obj), context);
816 }
817 DISPATCH();
818 }
819
821 {
822 {
823 Value* list = popAndResolveAsPtr(context);
824 Value obj { *list };
825
826 for (uint16_t i = 0; i < arg; ++i)
827 {
828 Value* next = popAndResolveAsPtr(context);
829
830 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
832 "concat",
834 { *list, *next });
835
836 std::ranges::copy(next->list(), std::back_inserter(obj.list()));
837 }
838 push(std::move(obj), context);
839 }
840 DISPATCH();
841 }
842
844 {
845 Value* list = popAndResolveAsPtr(context);
846 listAppendInPlace(list, arg, context);
847 DISPATCH();
848 }
849
851 {
852 Value* list = popAndResolveAsPtr(context);
853
854 for (uint16_t i = 0; i < arg; ++i)
855 {
856 Value* next = popAndResolveAsPtr(context);
857
858 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
860 "concat!",
862 { *list, *next });
863
864 std::ranges::copy(next->list(), std::back_inserter(list->list()));
865 }
866 DISPATCH();
867 }
868
870 {
871 {
872 Value list = *popAndResolveAsPtr(context);
873 Value number = *popAndResolveAsPtr(context);
874
875 if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
877 "pop",
879 { list, number });
880
881 long idx = static_cast<long>(number.number());
882 idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
883 if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
885 ErrorKind::Index,
886 fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
887
888 list.list().erase(list.list().begin() + idx);
889 push(list, context);
890 }
891 DISPATCH();
892 }
893
895 {
896 {
897 Value* list = popAndResolveAsPtr(context);
898 Value number = *popAndResolveAsPtr(context);
899
900 if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
902 "pop!",
904 { *list, number });
905
906 long idx = static_cast<long>(number.number());
907 idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
908 if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
910 ErrorKind::Index,
911 fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
912
913 list->list().erase(list->list().begin() + idx);
914 }
915 DISPATCH();
916 }
917
919 {
920 {
921 Value* list = popAndResolveAsPtr(context);
922 Value number = *popAndResolveAsPtr(context);
923 Value new_value = *popAndResolveAsPtr(context);
924
925 if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
927 "@=",
928 { { types::Contract {
931 types::Typedef("new_value", ValueType::Any) } } },
935 types::Typedef("char", ValueType::String) } } } },
936 { *list, number, new_value });
937
938 const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
939 long idx = static_cast<long>(number.number());
940 idx = idx < 0 ? static_cast<long>(size) + idx : idx;
941 if (std::cmp_greater_equal(idx, size) || idx < 0)
943 ErrorKind::Index,
944 fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
945
946 if (list->valueType() == ValueType::List)
947 list->list()[static_cast<std::size_t>(idx)] = new_value;
948 else
949 list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
950 }
951 DISPATCH();
952 }
953
955 {
956 {
957 Value* list = popAndResolveAsPtr(context);
958 Value x = *popAndResolveAsPtr(context);
959 Value y = *popAndResolveAsPtr(context);
960 Value new_value = *popAndResolveAsPtr(context);
961
964 "@@=",
965 { { types::Contract {
969 types::Typedef("new_value", ValueType::Any) } } } },
970 { *list, x, y, new_value });
971
972 long idx_y = static_cast<long>(x.number());
973 idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
974 if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
976 ErrorKind::Index,
977 fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
978
979 if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
980 (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
982 "@@=",
983 { { types::Contract {
987 types::Typedef("new_value", ValueType::Any) } } },
992 types::Typedef("char", ValueType::String) } } } },
993 { *list, x, y, new_value });
994
995 const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
996 const std::size_t size =
997 is_list
998 ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
999 : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
1000
1001 long idx_x = static_cast<long>(y.number());
1002 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1003 if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
1005 ErrorKind::Index,
1006 fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1007
1008 if (is_list)
1009 list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
1010 else
1011 list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
1012 }
1013 DISPATCH();
1014 }
1015
1016 TARGET(POP)
1017 {
1018 pop(context);
1019 DISPATCH();
1020 }
1021
1023 {
1024 if (!*peekAndResolveAsPtr(context))
1025 context.ip = arg * 4;
1026 else
1027 pop(context);
1028 DISPATCH();
1029 }
1030
1032 {
1033 if (!!*peekAndResolveAsPtr(context))
1034 context.ip = arg * 4;
1035 else
1036 pop(context);
1037 DISPATCH();
1038 }
1039
1041 {
1042 context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
1043 DISPATCH();
1044 }
1045
1047 {
1048 context.locals.back().reset();
1049 context.ip = arg * 4; // instructions are 4 bytes
1050 DISPATCH();
1051 }
1052
1054 {
1055 context.locals.pop_back();
1056 DISPATCH();
1057 }
1058
1060 {
1061 context.last_symbol = arg;
1062 push(Value(static_cast<PageAddr_t>(context.pp)), context);
1063 DISPATCH();
1064 }
1065
1066#pragma endregion
1067
1068#pragma region "Operators"
1069
1070 TARGET(ADD)
1071 {
1072 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1073
1074 if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
1075 push(Value(a->number() + b->number()), context);
1076 else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
1077 push(Value(a->string() + b->string()), context);
1078 else
1080 "+",
1083 { *a, *b });
1084 DISPATCH();
1085 }
1086
1087 TARGET(SUB)
1088 {
1089 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1090
1091 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1093 "-",
1095 { *a, *b });
1096 push(Value(a->number() - b->number()), context);
1097 DISPATCH();
1098 }
1099
1100 TARGET(MUL)
1101 {
1102 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1103
1104 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1106 "*",
1108 { *a, *b });
1109 push(Value(a->number() * b->number()), context);
1110 DISPATCH();
1111 }
1112
1113 TARGET(DIV)
1114 {
1115 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1116
1117 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1119 "/",
1121 { *a, *b });
1122 auto d = b->number();
1123 if (d == 0)
1124 throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1125
1126 push(Value(a->number() / d), context);
1127 DISPATCH();
1128 }
1129
1130 TARGET(GT)
1131 {
1132 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1133 push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
1134 DISPATCH();
1135 }
1136
1137 TARGET(LT)
1138 {
1139 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1140 push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
1141 DISPATCH();
1142 }
1143
1144 TARGET(LE)
1145 {
1146 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1147 push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
1148 DISPATCH();
1149 }
1150
1151 TARGET(GE)
1152 {
1153 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1154 push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
1155 DISPATCH();
1156 }
1157
1158 TARGET(NEQ)
1159 {
1160 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1161 push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1162 DISPATCH();
1163 }
1164
1165 TARGET(EQ)
1166 {
1167 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1168 push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
1169 DISPATCH();
1170 }
1171
1172 TARGET(LEN)
1173 {
1174 const Value* a = popAndResolveAsPtr(context);
1175
1176 if (a->valueType() == ValueType::List)
1177 push(Value(static_cast<int>(a->constList().size())), context);
1178 else if (a->valueType() == ValueType::String)
1179 push(Value(static_cast<int>(a->string().size())), context);
1180 else
1182 "len",
1183 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1184 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1185 { *a });
1186 DISPATCH();
1187 }
1188
1189 TARGET(EMPTY)
1190 {
1191 const Value* a = popAndResolveAsPtr(context);
1192
1193 if (a->valueType() == ValueType::List)
1194 push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1195 else if (a->valueType() == ValueType::String)
1196 push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1197 else
1199 "empty?",
1200 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1201 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1202 { *a });
1203 DISPATCH();
1204 }
1205
1206 TARGET(TAIL)
1207 {
1208 Value* const a = popAndResolveAsPtr(context);
1209 push(helper::tail(a), context);
1210 DISPATCH();
1211 }
1212
1213 TARGET(HEAD)
1214 {
1215 Value* const a = popAndResolveAsPtr(context);
1216 push(helper::head(a), context);
1217 DISPATCH();
1218 }
1219
1220 TARGET(ISNIL)
1221 {
1222 const Value* a = popAndResolveAsPtr(context);
1224 DISPATCH();
1225 }
1226
1227 TARGET(ASSERT)
1228 {
1229 Value* const b = popAndResolveAsPtr(context);
1230 Value* const a = popAndResolveAsPtr(context);
1231
1232 if (b->valueType() != ValueType::String)
1234 "assert",
1236 { *a, *b });
1237
1238 if (*a == Builtins::falseSym)
1239 throw AssertionFailed(b->stringRef());
1240 DISPATCH();
1241 }
1242
1243 TARGET(TO_NUM)
1244 {
1245 const Value* a = popAndResolveAsPtr(context);
1246
1247 if (a->valueType() != ValueType::String)
1249 "toNumber",
1250 { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1251 { *a });
1252
1253 double val;
1254 if (Utils::isDouble(a->string(), &val))
1255 push(Value(val), context);
1256 else
1257 push(Builtins::nil, context);
1258 DISPATCH();
1259 }
1260
1261 TARGET(TO_STR)
1262 {
1263 const Value* a = popAndResolveAsPtr(context);
1264 push(Value(a->toString(*this)), context);
1265 DISPATCH();
1266 }
1267
1268 TARGET(AT)
1269 {
1270 Value& b = *popAndResolveAsPtr(context);
1271 Value& a = *popAndResolveAsPtr(context);
1272 push(helper::at(a, b, *this), context);
1273 DISPATCH();
1274 }
1275
1276 TARGET(AT_AT)
1277 {
1278 {
1279 const Value* x = popAndResolveAsPtr(context);
1280 const Value* y = popAndResolveAsPtr(context);
1281 Value& list = *popAndResolveAsPtr(context);
1282
1283 if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
1284 list.valueType() != ValueType::List)
1286 "@@",
1287 { { types::Contract {
1290 types::Typedef("x", ValueType::Number) } } } },
1291 { list, *y, *x });
1292
1293 long idx_y = static_cast<long>(y->number());
1294 idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
1295 if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
1297 ErrorKind::Index,
1298 fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1299
1300 const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
1301 const std::size_t size =
1302 is_list
1303 ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
1304 : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
1305
1306 long idx_x = static_cast<long>(x->number());
1307 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1308 if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
1310 ErrorKind::Index,
1311 fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1312
1313 if (is_list)
1314 push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
1315 else
1316 push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
1317 }
1318 DISPATCH();
1319 }
1320
1321 TARGET(MOD)
1322 {
1323 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1324 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1326 "mod",
1328 { *a, *b });
1329 push(Value(std::fmod(a->number(), b->number())), context);
1330 DISPATCH();
1331 }
1332
1333 TARGET(TYPE)
1334 {
1335 const Value* a = popAndResolveAsPtr(context);
1336 push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
1337 DISPATCH();
1338 }
1339
1341 {
1342 {
1343 Value* const field = popAndResolveAsPtr(context);
1344 Value* const closure = popAndResolveAsPtr(context);
1345 if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
1347 "hasField",
1349 { *closure, *field });
1350
1351 auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
1352 if (it == m_state.m_symbols.end())
1353 {
1354 push(Builtins::falseSym, context);
1355 DISPATCH();
1356 }
1357
1358 auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1359 push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1360 }
1361 DISPATCH();
1362 }
1363
1364 TARGET(NOT)
1365 {
1366 const Value* a = popAndResolveAsPtr(context);
1367 push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
1368 DISPATCH();
1369 }
1370
1371#pragma endregion
1372
1373#pragma region "Super Instructions"
1375 {
1376 UNPACK_ARGS();
1377 push(loadConstAsPtr(primary_arg), context);
1378 push(loadConstAsPtr(secondary_arg), context);
1379 DISPATCH();
1380 }
1381
1383 {
1384 UNPACK_ARGS();
1385 store(secondary_arg, loadConstAsPtr(primary_arg), context);
1386 DISPATCH();
1387 }
1388
1390 {
1391 UNPACK_ARGS();
1392 setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
1393 DISPATCH();
1394 }
1395
1397 {
1398 UNPACK_ARGS();
1399 store(secondary_arg, loadSymbol(primary_arg, context), context);
1400 DISPATCH();
1401 }
1402
1404 {
1405 UNPACK_ARGS();
1406 store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1407 DISPATCH();
1408 }
1409
1411 {
1412 UNPACK_ARGS();
1413 setVal(secondary_arg, loadSymbol(primary_arg, context), context);
1414 DISPATCH();
1415 }
1416
1418 {
1419 UNPACK_ARGS();
1420 setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1421 DISPATCH();
1422 }
1423
1425 {
1426 UNPACK_ARGS();
1427 {
1428 Value* var = loadSymbol(primary_arg, context);
1429
1430 // use internal reference, shouldn't break anything so far, unless it's already a ref
1431 if (var->valueType() == ValueType::Reference)
1432 var = var->reference();
1433
1434 if (var->valueType() == ValueType::Number)
1435 push(Value(var->number() + secondary_arg), context);
1436 else
1438 "+",
1440 { *var, Value(secondary_arg) });
1441 }
1442 DISPATCH();
1443 }
1444
1446 {
1447 UNPACK_ARGS();
1448 {
1449 Value* var = loadSymbolFromIndex(primary_arg, context);
1450
1451 // use internal reference, shouldn't break anything so far, unless it's already a ref
1452 if (var->valueType() == ValueType::Reference)
1453 var = var->reference();
1454
1455 if (var->valueType() == ValueType::Number)
1456 push(Value(var->number() + secondary_arg), context);
1457 else
1459 "+",
1461 { *var, Value(secondary_arg) });
1462 }
1463 DISPATCH();
1464 }
1465
1467 {
1468 UNPACK_ARGS();
1469 {
1470 Value* var = loadSymbol(primary_arg, context);
1471
1472 // use internal reference, shouldn't break anything so far, unless it's already a ref
1473 if (var->valueType() == ValueType::Reference)
1474 var = var->reference();
1475
1476 if (var->valueType() == ValueType::Number)
1477 {
1478 Value val = Value(var->number() + secondary_arg);
1479 setVal(primary_arg, &val, context);
1480 }
1481 else
1483 "+",
1485 { *var, Value(secondary_arg) });
1486 }
1487 DISPATCH();
1488 }
1489
1491 {
1492 UNPACK_ARGS();
1493 {
1494 Value* var = loadSymbol(primary_arg, context);
1495
1496 // use internal reference, shouldn't break anything so far, unless it's already a ref
1497 if (var->valueType() == ValueType::Reference)
1498 var = var->reference();
1499
1500 if (var->valueType() == ValueType::Number)
1501 push(Value(var->number() - secondary_arg), context);
1502 else
1504 "-",
1506 { *var, Value(secondary_arg) });
1507 }
1508 DISPATCH();
1509 }
1510
1512 {
1513 UNPACK_ARGS();
1514 {
1515 Value* var = loadSymbolFromIndex(primary_arg, context);
1516
1517 // use internal reference, shouldn't break anything so far, unless it's already a ref
1518 if (var->valueType() == ValueType::Reference)
1519 var = var->reference();
1520
1521 if (var->valueType() == ValueType::Number)
1522 push(Value(var->number() - secondary_arg), context);
1523 else
1525 "-",
1527 { *var, Value(secondary_arg) });
1528 }
1529 DISPATCH();
1530 }
1531
1533 {
1534 UNPACK_ARGS();
1535 {
1536 Value* var = loadSymbol(primary_arg, context);
1537
1538 // use internal reference, shouldn't break anything so far, unless it's already a ref
1539 if (var->valueType() == ValueType::Reference)
1540 var = var->reference();
1541
1542 if (var->valueType() == ValueType::Number)
1543 {
1544 Value val = Value(var->number() - secondary_arg);
1545 setVal(primary_arg, &val, context);
1546 }
1547 else
1549 "-",
1551 { *var, Value(secondary_arg) });
1552 }
1553 DISPATCH();
1554 }
1555
1557 {
1558 UNPACK_ARGS();
1559 {
1560 Value* list = loadSymbol(primary_arg, context);
1561 Value tail = helper::tail(list);
1562 store(secondary_arg, &tail, context);
1563 }
1564 DISPATCH();
1565 }
1566
1568 {
1569 UNPACK_ARGS();
1570 {
1571 Value* list = loadSymbolFromIndex(primary_arg, context);
1572 Value tail = helper::tail(list);
1573 store(secondary_arg, &tail, context);
1574 }
1575 DISPATCH();
1576 }
1577
1579 {
1580 UNPACK_ARGS();
1581 {
1582 Value* list = loadSymbol(primary_arg, context);
1583 Value head = helper::head(list);
1584 store(secondary_arg, &head, context);
1585 }
1586 DISPATCH();
1587 }
1588
1590 {
1591 UNPACK_ARGS();
1592 {
1593 Value* list = loadSymbolFromIndex(primary_arg, context);
1594 Value head = helper::head(list);
1595 store(secondary_arg, &head, context);
1596 }
1597 DISPATCH();
1598 }
1599
1601 {
1602 UNPACK_ARGS();
1603 {
1604 Value l = createList(primary_arg, context);
1605 store(secondary_arg, &l, context);
1606 }
1607 DISPATCH();
1608 }
1609
1611 {
1612 UNPACK_ARGS();
1613 {
1614 Value* list = loadSymbol(primary_arg, context);
1615 Value tail = helper::tail(list);
1616 setVal(secondary_arg, &tail, context);
1617 }
1618 DISPATCH();
1619 }
1620
1622 {
1623 UNPACK_ARGS();
1624 {
1625 Value* list = loadSymbolFromIndex(primary_arg, context);
1626 Value tail = helper::tail(list);
1627 setVal(secondary_arg, &tail, context);
1628 }
1629 DISPATCH();
1630 }
1631
1633 {
1634 UNPACK_ARGS();
1635 {
1636 Value* list = loadSymbol(primary_arg, context);
1637 Value head = helper::head(list);
1638 setVal(secondary_arg, &head, context);
1639 }
1640 DISPATCH();
1641 }
1642
1644 {
1645 UNPACK_ARGS();
1646 {
1647 Value* list = loadSymbolFromIndex(primary_arg, context);
1648 Value head = helper::head(list);
1649 setVal(secondary_arg, &head, context);
1650 }
1651 DISPATCH();
1652 }
1653
1655 {
1656 UNPACK_ARGS();
1657 // no stack size check because we do not push IP/PP since we are just calling a builtin
1658 callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
1659 if (!m_running)
1660 GOTO_HALT();
1661 DISPATCH();
1662 }
1663
1665 {
1666 UNPACK_ARGS();
1667 // no stack size check because we do not push IP/PP since we are just calling a builtin
1668 callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
1669 if (!m_running)
1670 GOTO_HALT();
1671 DISPATCH();
1672 }
1673
1675 {
1676 UNPACK_ARGS();
1677 const Value* sym = popAndResolveAsPtr(context);
1678 if (!(*sym < *loadConstAsPtr(primary_arg)))
1679 context.ip = secondary_arg * 4;
1680 DISPATCH();
1681 }
1682
1684 {
1685 UNPACK_ARGS();
1686 const Value* sym = popAndResolveAsPtr(context);
1687 if (*sym < *loadConstAsPtr(primary_arg))
1688 context.ip = secondary_arg * 4;
1689 DISPATCH();
1690 }
1691
1693 {
1694 UNPACK_ARGS();
1695 const Value* sym = popAndResolveAsPtr(context);
1696 if (!(*sym < *loadSymbol(primary_arg, context)))
1697 context.ip = secondary_arg * 4;
1698 DISPATCH();
1699 }
1700
1702 {
1703 UNPACK_ARGS();
1704 const Value* sym = popAndResolveAsPtr(context);
1705 const Value* cst = loadConstAsPtr(primary_arg);
1706 if (*cst < *sym)
1707 context.ip = secondary_arg * 4;
1708 DISPATCH();
1709 }
1710
1712 {
1713 UNPACK_ARGS();
1714 const Value* sym = popAndResolveAsPtr(context);
1715 const Value* cst = loadConstAsPtr(primary_arg);
1716 if (!(*cst < *sym))
1717 context.ip = secondary_arg * 4;
1718 DISPATCH();
1719 }
1720
1722 {
1723 UNPACK_ARGS();
1724 const Value* sym = popAndResolveAsPtr(context);
1725 const Value* rhs = loadSymbol(primary_arg, context);
1726 if (!(*rhs < *sym))
1727 context.ip = secondary_arg * 4;
1728 DISPATCH();
1729 }
1730
1732 {
1733 UNPACK_ARGS();
1734 const Value* sym = popAndResolveAsPtr(context);
1735 if (*sym == *loadConstAsPtr(primary_arg))
1736 context.ip = secondary_arg * 4;
1737 DISPATCH();
1738 }
1739
1741 {
1742 UNPACK_ARGS();
1743 const Value* sym = popAndResolveAsPtr(context);
1744 if (*sym == *loadSymbolFromIndex(primary_arg, context))
1745 context.ip = secondary_arg * 4;
1746 DISPATCH();
1747 }
1748
1750 {
1751 UNPACK_ARGS();
1752 const Value* sym = popAndResolveAsPtr(context);
1753 if (*sym != *loadConstAsPtr(primary_arg))
1754 context.ip = secondary_arg * 4;
1755 DISPATCH();
1756 }
1757
1759 {
1760 UNPACK_ARGS();
1761 const Value* sym = popAndResolveAsPtr(context);
1762 if (*sym == *loadSymbol(primary_arg, context))
1763 context.ip = secondary_arg * 4;
1764 DISPATCH();
1765 }
1766
1768 {
1769 UNPACK_ARGS();
1770 call(context, secondary_arg, loadSymbol(primary_arg, context));
1771 if (!m_running)
1772 GOTO_HALT();
1773 DISPATCH();
1774 }
1775
1777 {
1778 UNPACK_ARGS();
1779 context.last_symbol = primary_arg;
1780 call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
1781 if (!m_running)
1782 GOTO_HALT();
1783 DISPATCH();
1784 }
1785
1787 {
1788 UNPACK_ARGS();
1789 push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1790 DISPATCH();
1791 }
1792
1794 {
1795 UNPACK_ARGS();
1796 push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
1797 DISPATCH();
1798 }
1799
1801 {
1802 UNPACK_ARGS();
1803 push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
1804 DISPATCH();
1805 }
1806
1808 {
1809 UNPACK_ARGS();
1810 push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
1811 DISPATCH();
1812 }
1813
1815 {
1816 UNPACK_ARGS();
1817 const Value* sym = loadSymbol(primary_arg, context);
1818 const Value* cst = loadConstAsPtr(secondary_arg);
1819 push(
1820 cst->valueType() == ValueType::String &&
1821 types_to_str[static_cast<unsigned>(sym->valueType())] == cst->string()
1824 context);
1825 DISPATCH();
1826 }
1827
1829 {
1830 UNPACK_ARGS();
1831 const Value* sym = loadSymbolFromIndex(primary_arg, context);
1832 const Value* cst = loadConstAsPtr(secondary_arg);
1833 push(
1834 cst->valueType() == ValueType::String &&
1835 types_to_str[static_cast<unsigned>(sym->valueType())] == cst->string()
1838 context);
1839 DISPATCH();
1840 }
1841
1843 {
1844 UNPACK_ARGS();
1845 listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1846 DISPATCH();
1847 }
1848
1850 {
1851 UNPACK_ARGS();
1852 listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
1853 DISPATCH();
1854 }
1855#pragma endregion
1856 }
1857#if ARK_USE_COMPUTED_GOTOS
1858 dispatch_end:
1859 do
1860 {
1861 } while (false);
1862#endif
1863 }
1864 }
1865 catch (const Error& e)
1866 {
1867 if (fail_with_exception)
1868 {
1869 std::stringstream stream;
1870 backtrace(context, stream, /* colorize= */ false);
1871 // It's important we have an Ark::Error here, as the constructor for NestedError
1872 // does more than just aggregate error messages, hence the code duplication.
1873 throw NestedError(e, stream.str());
1874 }
1875 else
1876 showBacktraceWithException(Error(e.details(/* colorize= */ true)), context);
1877 }
1878 catch (const std::exception& e)
1879 {
1880 if (fail_with_exception)
1881 {
1882 std::stringstream stream;
1883 backtrace(context, stream, /* colorize= */ false);
1884 throw NestedError(e, stream.str());
1885 }
1886 else
1887 showBacktraceWithException(e, context);
1888 }
1889 catch (...)
1890 {
1891 if (fail_with_exception)
1892 throw;
1893
1894#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1895 throw;
1896#endif
1897 fmt::println("Unknown error");
1898 backtrace(context);
1899 m_exit_code = 1;
1900 }
1901
1902 return m_exit_code;
1903 }
1904
1905 uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
1906 {
1907 for (auto& local : std::ranges::reverse_view(context.locals))
1908 {
1909 if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
1910 return id;
1911 }
1912 return std::numeric_limits<uint16_t>::max();
1913 }
1914
1915 void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
1916 {
1917 std::vector<std::string> arg_names;
1918 arg_names.reserve(expected_arg_count + 1);
1919 if (expected_arg_count > 0)
1920 arg_names.emplace_back(""); // for formatting, so that we have a space between the function and the args
1921
1922 std::size_t index = 0;
1923 while (m_state.inst(context.pp, index) == STORE)
1924 {
1925 const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
1926 arg_names.push_back(m_state.m_symbols[id]);
1927 index += 4;
1928 }
1929 // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
1930 if (arg_names.size() == 1 && index == 0)
1931 {
1932 assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
1933 for (std::size_t i = 0; i < expected_arg_count; ++i)
1934 arg_names.push_back(std::string(1, static_cast<char>('a' + i)));
1935 }
1936
1937 std::vector<std::string> arg_vals;
1938 arg_vals.reserve(passed_arg_count + 1);
1939 if (passed_arg_count > 0)
1940 arg_vals.emplace_back(""); // for formatting, so that we have a space between the function and the args
1941
1942 for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.sp; ++i)
1943 // -1 on the stack because we always point to the next available slot
1944 arg_vals.push_back(context.stack[context.sp - i - 1].toString(*this));
1945
1946 // set ip/pp to the callee location so that the error can pinpoint the line
1947 // where the bad call happened
1948 if (context.sp >= 2 + passed_arg_count)
1949 {
1950 context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
1951 context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
1952 returnFromFuncCall(context);
1953 }
1954
1955 std::string function_name = (context.last_symbol < m_state.m_symbols.size())
1956 ? m_state.m_symbols[context.last_symbol]
1957 : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
1958
1960 ErrorKind::Arity,
1961 fmt::format(
1962 "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
1963 function_name,
1964 fmt::join(arg_vals, " "),
1965 passed_arg_count,
1966 passed_arg_count > 1 ? "s" : "",
1967 expected_arg_count,
1968 function_name,
1969 fmt::join(arg_names, " ")));
1970 }
1971
1972 void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
1973 {
1974 std::string text = e.what();
1975 if (!text.empty() && text.back() != '\n')
1976 text += '\n';
1977 fmt::println("{}", text);
1978 backtrace(context);
1979#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1980 // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1981 m_exit_code = 0;
1982#else
1983 m_exit_code = 1;
1984#endif
1985 }
1986
1987 std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp)
1988 {
1989 std::optional<InstLoc> match = std::nullopt;
1990
1991 for (const auto location : m_state.m_inst_locations)
1992 {
1993 if (location.page_pointer == pp && !match)
1994 match = location;
1995
1996 // select the best match: we want to find the location that's nearest our instruction pointer,
1997 // but not equal to it as the IP will always be pointing to the next instruction,
1998 // not yet executed. Thus, the erroneous instruction is the previous one.
1999 if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
2000 match = location;
2001
2002 // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2003 if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
2004 break;
2005 }
2006
2007 return match;
2008 }
2009
2011 {
2012 const auto& context = m_execution_contexts.front();
2013 auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
2014 if (maybe_source_loc)
2015 {
2016 const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
2017 return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
2018 }
2019 return "No source location found";
2020 }
2021
2022 void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
2023 {
2024 const std::size_t saved_ip = context.ip;
2025 const std::size_t saved_pp = context.pp;
2026 const uint16_t saved_sp = context.sp;
2027 const std::size_t max_consecutive_traces = 7;
2028
2029 const auto maybe_location = findSourceLocation(context.ip, context.pp);
2030 if (maybe_location)
2031 {
2032 const auto filename = m_state.m_filenames[maybe_location->filename_id];
2033
2034 if (Utils::fileExists(filename))
2036 os,
2037 filename,
2038 /* expr= */ std::nullopt,
2039 /* sym_size= */ 0,
2040 maybe_location->line,
2041 /* col_start= */ 0,
2042 /* maybe_context= */ std::nullopt,
2043 /* whole_line= */ true,
2044 /* colorize= */ colorize);
2045 fmt::println(os, "");
2046 }
2047
2048 if (context.fc > 1)
2049 {
2050 // display call stack trace
2051 const ScopeView old_scope = context.locals.back();
2052
2053 std::string previous_trace;
2054 std::size_t displayed_traces = 0;
2055 std::size_t consecutive_similar_traces = 0;
2056
2057 while (context.fc != 0)
2058 {
2059 const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2060 const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
2061
2062 if (context.pp != 0)
2063 {
2064 const uint16_t id = findNearestVariableIdWithValue(
2065 Value(static_cast<PageAddr_t>(context.pp)),
2066 context);
2067 const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2068
2069 if (func_name + loc_as_text != previous_trace)
2070 {
2071 fmt::println(
2072 os,
2073 "[{:4}] In function `{}'{}",
2074 fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2075 fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
2076 loc_as_text);
2077 previous_trace = func_name + loc_as_text;
2078 ++displayed_traces;
2079 consecutive_similar_traces = 0;
2080 }
2081 else if (consecutive_similar_traces == 0)
2082 {
2083 fmt::println(os, " ...");
2084 ++consecutive_similar_traces;
2085 }
2086
2087 const Value* ip;
2088 do
2089 {
2090 ip = popAndResolveAsPtr(context);
2091 } while (ip->valueType() != ValueType::InstPtr);
2092
2093 context.ip = ip->pageAddr();
2094 context.pp = pop(context)->pageAddr();
2095 returnFromFuncCall(context);
2096 }
2097
2098 if (displayed_traces > max_consecutive_traces)
2099 {
2100 fmt::println(os, " ...");
2101 break;
2102 }
2103 }
2104
2105 if (context.pp == 0)
2106 {
2107 const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2108 const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
2109 fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
2110 }
2111
2112 // display variables values in the current scope
2113 fmt::println(os, "\nCurrent scope variables values:");
2114 for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
2115 {
2116 fmt::println(
2117 os,
2118 "{} = {}",
2119 fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2120 old_scope.atPos(i).second.toString(*this));
2121 }
2122 }
2123
2124 fmt::println(
2125 os,
2126 "At IP: {}, PP: {}, SP: {}",
2127 // dividing by 4 because the instructions are actually on 4 bytes
2128 fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2129 fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
2130 fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
2131 }
2132}
Lots of utilities about string, filesystem and more.
#define ARK_NO_NAME_FILE
Definition Constants.hpp:26
Lots of utilities about the filesystem.
The different instructions used by the compiler and virtual machine.
#define GOTO_HALT()
#define TARGET(op)
#define UNPACK_ARGS()
#define DISPATCH()
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.
Definition State.hpp:33
std::vector< std::filesystem::path > m_libenv
Definition State.hpp:146
std::string m_filename
Definition State.hpp:147
std::vector< Value > m_constants
Definition State.hpp:151
std::vector< internal::InstLoc > m_inst_locations
Definition State.hpp:153
std::vector< std::string > m_filenames
Definition State.hpp:152
std::unordered_map< std::string, Value > m_binded
Values binded to the State, to be used by the VM.
Definition State.hpp:158
constexpr uint8_t inst(const std::size_t pp, const std::size_t ip) const noexcept
Get an instruction in a given page, with a given instruction pointer.
Definition State.hpp:167
std::vector< std::string > m_symbols
Definition State.hpp:150
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:46
void deleteContext(internal::ExecutionContext *ec)
Free a given execution context.
Definition VM.cpp:364
void showBacktraceWithException(const std::exception &e, internal::ExecutionContext &context)
Definition VM.cpp:1972
std::vector< std::unique_ptr< internal::Future > > m_futures
Storing the promises while we are resolving them.
Definition VM.hpp:171
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Definition VM.hpp:167
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Definition VM.cpp:234
Value * popAndResolveAsPtr(internal::ExecutionContext &context)
Pop a value from the stack and resolve it if possible, then return it.
Definition VM.hpp:202
std::optional< internal::InstLoc > findSourceLocation(std::size_t ip, std::size_t pp)
Find the nearest source location information given instruction and page pointers.
Definition VM.cpp:1987
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
Definition VM.hpp:170
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition VM.hpp:166
Value * peekAndResolveAsPtr(internal::ExecutionContext &context)
Return a pointer to the top of the stack without consuming it, and resolve it if possible.
Definition VM.hpp:171
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,...
Definition VM.hpp:99
void listAppendInPlace(Value *list, std::size_t count, internal::ExecutionContext &context)
Definition VM.cpp:217
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Definition VM.cpp:1905
Value * loadSymbolFromIndex(uint16_t index, internal::ExecutionContext &context)
Load a symbol by its (reversed) index in the current scope.
Definition VM.hpp:114
void callBuiltin(internal::ExecutionContext &context, const Value &builtin, uint16_t argc, bool remove_return_address=true)
Builtin called when the CALL_BUILTIN instruction is met in the bytecode.
Definition VM.hpp:340
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
Definition VM.cpp:407
void backtrace(internal::ExecutionContext &context, std::ostream &os=std::cout, bool colorize=true)
Display a backtrace when the VM encounter an exception.
Definition VM.cpp:2022
internal::ExecutionContext * createAndGetContext()
Create an execution context and returns it.
Definition VM.cpp:343
std::mutex m_mutex
Definition VM.hpp:169
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
Definition VM.cpp:259
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...
Definition VM.cpp:450
void deleteFuture(internal::Future *f)
Free a given future.
Definition VM.cpp:393
bool m_running
Definition VM.hpp:168
Value call(const std::string &name, Args &&... args)
Call a function from ArkScript, by giving it arguments.
Definition VM.hpp:6
Value * pop(internal::ExecutionContext &context)
Pop a value from the stack.
Definition VM.hpp:161
void returnFromFuncCall(internal::ExecutionContext &context)
Destroy the current frame and get back to the previous one, resuming execution.
Definition VM.hpp:222
void store(uint16_t id, const Value *val, internal::ExecutionContext &context)
Create a new symbol with an associated value in the current scope.
Definition VM.hpp:129
std::string debugShowSource()
Definition VM.cpp:2010
void setVal(uint16_t id, const Value *val, internal::ExecutionContext &context)
Change the value of a symbol given its identifier.
Definition VM.hpp:139
void push(const Value &value, internal::ExecutionContext &context)
Push a value on the stack.
Definition VM.hpp:183
friend class internal::Closure
Definition VM.hpp:161
State & m_state
Definition VM.hpp:165
Value * loadConstAsPtr(uint16_t id) const
Load a constant from the constant table by its id.
Definition VM.hpp:124
Value getField(Value *closure, uint16_t id, internal::ExecutionContext &context)
Definition VM.cpp:158
Value createList(std::size_t count, internal::ExecutionContext &context)
Definition VM.cpp:205
void init() noexcept
Initialize the VM according to the parameters.
Definition VM.cpp:124
Value * findNearestVariable(uint16_t id, internal::ExecutionContext &context) noexcept
Find the nearest variable of a given id.
Definition VM.hpp:212
void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext &context)
Definition VM.cpp:1915
static void throwVMError(internal::ErrorKind kind, const std::string &message)
Throw a VM error message.
Definition VM.cpp:438
friend class Value
Definition VM.hpp:160
VM(State &state) noexcept
Construct a new vm t object.
Definition VM.cpp:118
internal::Future * createFuture(std::vector< Value > &args)
Create a Future object from a function and its arguments and return a managed pointer to it.
Definition VM.cpp:378
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:443
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
Definition VM.cpp:337
const std::vector< Value > & constList() const
Definition Value.hpp:137
std::vector< Value > & list()
Definition Value.hpp:139
internal::Closure & refClosure()
Definition Value.hpp:176
Value * reference() const
Definition Value.hpp:142
double number() const
Definition Value.hpp:135
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:62
ValueType valueType() const noexcept
Definition Value.hpp:124
const std::string & string() const
Definition Value.hpp:136
bool isIndexable() const noexcept
Definition Value.hpp:130
std::string toString(VM &vm) const noexcept
Definition Value.cpp:72
internal::PageAddr_t pageAddr() const
Definition Value.hpp:173
std::string & stringRef()
Definition Value.hpp:140
A class to store fields captured by a closure.
std::string toString(VM &vm) const noexcept
Print the closure to a string.
Definition Closure.cpp:27
ClosureScope & refScope() const noexcept
Definition Closure.hpp:54
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...
Definition Closure.cpp:20
const std::shared_ptr< ClosureScope > & scopePtr() const
Definition Closure.hpp:55
A class to handle the VM scope more efficiently.
Definition ScopeView.hpp:27
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.
Definition ScopeView.hpp:98
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.
Definition Files.hpp:28
bool isDouble(const std::string &s, double *output=nullptr)
Checks if a string is a valid double.
Definition Utils.hpp:55
Value head(Value *a)
Definition VM.cpp:56
Value at(Value &container, Value &index, VM &vm)
Definition VM.cpp:78
Value tail(Value *a)
Definition VM.cpp:27
ARK_API const std::vector< std::pair< std::string, Value > > builtins
constexpr std::array< std::string_view, 7 > errorKinds
Definition ErrorKind.hpp:20
uint16_t PageAddr_t
Definition Closure.hpp:27
@ CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
constexpr std::array types_to_str
Definition Value.hpp:52
@ 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::vector< ScopeView > locals
std::size_t ip
Instruction pointer.
std::optional< ClosureScope > saved_scope
Scope created by CAPTURE <x> instructions, used by the MAKE_CLOSURE instruction.
const char * name
Definition Module.hpp:11
A contract is a list of typed arguments that a function can follow.
A type definition within a contract.
Definition VM.cpp:16
Ark::Value(* value)(std::vector< Ark::Value > &, Ark::VM *)
Definition VM.cpp:18
char * name
Definition VM.cpp:17