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