ArkScript
A small, fast, functional and scripting language for video games
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 <ranges>
7#include <fmt/core.h>
8#include <fmt/color.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
79 VM::VM(State& state) noexcept :
80 m_state(state), m_exit_code(0), m_running(false)
81 {
82 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
83 }
84
85 void VM::init() noexcept
86 {
87 ExecutionContext& context = *m_execution_contexts.back();
88 for (const auto& c : m_execution_contexts)
89 {
90 c->ip = 0;
91 c->pp = 0;
92 c->sp = 0;
93 }
94
95 context.sp = 0;
96 context.fc = 1;
97
99 context.stacked_closure_scopes.clear();
100 context.stacked_closure_scopes.emplace_back(nullptr);
101
102 context.saved_scope.reset();
103 m_exit_code = 0;
104
105 context.locals.clear();
106 context.locals.emplace_back();
107
108 // loading bound stuff
109 // put them in the global frame if we can, aka the first one
110 for (const auto& [sym_id, value] : m_state.m_binded)
111 {
112 auto it = std::ranges::find(m_state.m_symbols, sym_id);
113 if (it != m_state.m_symbols.end())
114 context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
115 }
116 }
117
118 Value& VM::operator[](const std::string& name) noexcept
119 {
120 // find id of object
121 const auto it = std::ranges::find(m_state.m_symbols, name);
122 if (it == m_state.m_symbols.end())
123 {
124 m_no_value = Builtins::nil;
125 return m_no_value;
126 }
127
128 const auto dist = std::distance(m_state.m_symbols.begin(), it);
129 if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
130 {
131 ExecutionContext& context = *m_execution_contexts.front();
132
133 const auto id = static_cast<uint16_t>(dist);
134 Value* var = findNearestVariable(id, context);
135 if (var != nullptr)
136 return *var;
137 }
138
139 m_no_value = Builtins::nil;
140 return m_no_value;
141 }
142
143 void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
144 {
145 namespace fs = std::filesystem;
146
147 const std::string file = m_state.m_constants[id].stringRef();
148
149 std::string path = file;
150 // bytecode loaded from file
152 path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
153
154 std::shared_ptr<SharedLibrary> lib;
155 // if it exists alongside the .arkc file
156 if (Utils::fileExists(path))
157 lib = std::make_shared<SharedLibrary>(path);
158 else
159 {
160 for (auto const& v : m_state.m_libenv)
161 {
162 std::string lib_path = (fs::path(v) / fs::path(file)).string();
163
164 // if it's already loaded don't do anything
165 if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
166 return (val->path() == path || val->path() == lib_path);
167 }) != m_shared_lib_objects.end())
168 return;
169
170 // check in lib_path
171 if (Utils::fileExists(lib_path))
172 {
173 lib = std::make_shared<SharedLibrary>(lib_path);
174 break;
175 }
176 }
177 }
178
179 if (!lib)
180 {
181 auto lib_path = std::accumulate(
182 std::next(m_state.m_libenv.begin()),
183 m_state.m_libenv.end(),
184 m_state.m_libenv[0].string(),
185 [](const std::string& a, const fs::path& b) -> std::string {
186 return a + "\n\t- " + b.string();
187 });
189 ErrorKind::Module,
190 fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
191 }
192
193 m_shared_lib_objects.emplace_back(lib);
194
195 // load the mapping from the dynamic library
196 try
197 {
198 const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
199 // load the mapping data
200 std::size_t i = 0;
201 while (map[i].name != nullptr)
202 {
203 // put it in the global frame, aka the first one
204 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
205 if (it != m_state.m_symbols.end())
206 context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
207
208 ++i;
209 }
210 }
211 catch (const std::system_error& e)
212 {
214 ErrorKind::Module,
215 fmt::format(
216 "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
217 file, e.what()));
218 }
219 }
220
221 void VM::exit(const int code) noexcept
222 {
223 m_exit_code = code;
224 m_running = false;
225 }
226
228 {
229 const std::lock_guard lock(m_mutex);
230
231 m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
232 ExecutionContext* ctx = m_execution_contexts.back().get();
233 ctx->stacked_closure_scopes.emplace_back(nullptr);
234
235 ctx->locals.reserve(m_execution_contexts.front()->locals.size());
236 for (const auto& local : m_execution_contexts.front()->locals)
237 ctx->locals.push_back(local);
238
239 return ctx;
240 }
241
243 {
244 const std::lock_guard lock(m_mutex);
245
246 const auto it =
247 std::ranges::remove_if(
249 [ec](const std::unique_ptr<ExecutionContext>& ctx) {
250 return ctx.get() == ec;
251 })
252 .begin();
253 m_execution_contexts.erase(it);
254 }
255
256 Future* VM::createFuture(std::vector<Value>& args)
257 {
259
260 // doing this after having created the context
261 // because the context uses the mutex and we don't want a deadlock
262 const std::lock_guard lock(m_mutex);
263 m_futures.push_back(std::make_unique<Future>(ctx, this, args));
264
265 return m_futures.back().get();
266 }
267
269 {
270 const std::lock_guard lock(m_mutex);
271
272 const auto it =
273 std::ranges::remove_if(
274 m_futures,
275 [f](const std::unique_ptr<Future>& future) {
276 return future.get() == f;
277 })
278 .begin();
279 m_futures.erase(it);
280 }
281
283 {
284 // load the mapping from the dynamic library
285 try
286 {
287 for (const auto& shared_lib : m_shared_lib_objects)
288 {
289 const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
290 // load the mapping data
291 std::size_t i = 0;
292 while (map[i].name != nullptr)
293 {
294 // put it in the global frame, aka the first one
295 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
296 if (it != m_state.m_symbols.end())
297 m_execution_contexts[0]->locals[0].push_back(
298 static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
299 Value(map[i].value));
300
301 ++i;
302 }
303 }
304
305 return true;
306 }
307 catch (const std::system_error&)
308 {
309 return false;
310 }
311 }
312
313 int VM::run(const bool fail_with_exception)
314 {
315 init();
316 safeRun(*m_execution_contexts[0], 0, fail_with_exception);
317 return m_exit_code;
318 }
319
320 int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
321 {
322#if ARK_USE_COMPUTED_GOTOS
323# define TARGET(op) TARGET_##op:
324# define DISPATCH_GOTO() \
325 _Pragma("GCC diagnostic push") \
326 _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
327 _Pragma("GCC diagnostic pop")
328# define GOTO_HALT() goto dispatch_end
329#else
330# define TARGET(op) case op:
331# define DISPATCH_GOTO() goto dispatch_opcode
332# define GOTO_HALT() break
333#endif
334
335#define NEXTOPARG() \
336 do \
337 { \
338 inst = m_state.m_pages[context.pp][context.ip]; \
339 padding = m_state.m_pages[context.pp][context.ip + 1]; \
340 arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
341 m_state.m_pages[context.pp][context.ip + 3]); \
342 context.ip += 4; \
343 } while (false)
344#define DISPATCH() \
345 NEXTOPARG(); \
346 DISPATCH_GOTO();
347#define UNPACK_ARGS() \
348 do \
349 { \
350 secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
351 primary_arg = arg & 0x0fff; \
352 } while (false)
353
354#if ARK_USE_COMPUTED_GOTOS
355# pragma GCC diagnostic push
356# pragma GCC diagnostic ignored "-Wpedantic"
357 constexpr std::array opcode_targets = {
358 &&TARGET_NOP,
359 &&TARGET_LOAD_SYMBOL,
360 &&TARGET_LOAD_CONST,
361 &&TARGET_POP_JUMP_IF_TRUE,
362 &&TARGET_STORE,
363 &&TARGET_SET_VAL,
364 &&TARGET_POP_JUMP_IF_FALSE,
365 &&TARGET_JUMP,
366 &&TARGET_RET,
367 &&TARGET_HALT,
368 &&TARGET_CALL,
369 &&TARGET_CAPTURE,
370 &&TARGET_BUILTIN,
371 &&TARGET_DEL,
372 &&TARGET_MAKE_CLOSURE,
373 &&TARGET_GET_FIELD,
374 &&TARGET_PLUGIN,
375 &&TARGET_LIST,
376 &&TARGET_APPEND,
377 &&TARGET_CONCAT,
378 &&TARGET_APPEND_IN_PLACE,
379 &&TARGET_CONCAT_IN_PLACE,
380 &&TARGET_POP_LIST,
381 &&TARGET_POP_LIST_IN_PLACE,
382 &&TARGET_SET_AT_INDEX,
383 &&TARGET_SET_AT_2_INDEX,
384 &&TARGET_POP,
385 &&TARGET_DUP,
386 &&TARGET_CREATE_SCOPE,
387 &&TARGET_POP_SCOPE,
388 &&TARGET_ADD,
389 &&TARGET_SUB,
390 &&TARGET_MUL,
391 &&TARGET_DIV,
392 &&TARGET_GT,
393 &&TARGET_LT,
394 &&TARGET_LE,
395 &&TARGET_GE,
396 &&TARGET_NEQ,
397 &&TARGET_EQ,
398 &&TARGET_LEN,
399 &&TARGET_EMPTY,
400 &&TARGET_TAIL,
401 &&TARGET_HEAD,
402 &&TARGET_ISNIL,
403 &&TARGET_ASSERT,
404 &&TARGET_TO_NUM,
405 &&TARGET_TO_STR,
406 &&TARGET_AT,
407 &&TARGET_AT_AT,
408 &&TARGET_MOD,
409 &&TARGET_TYPE,
410 &&TARGET_HASFIELD,
411 &&TARGET_NOT,
412 &&TARGET_LOAD_CONST_LOAD_CONST,
413 &&TARGET_LOAD_CONST_STORE,
414 &&TARGET_LOAD_CONST_SET_VAL,
415 &&TARGET_STORE_FROM,
416 &&TARGET_SET_VAL_FROM,
417 &&TARGET_INCREMENT,
418 &&TARGET_DECREMENT,
419 &&TARGET_STORE_TAIL,
420 &&TARGET_STORE_HEAD,
421 &&TARGET_SET_VAL_TAIL,
422 &&TARGET_SET_VAL_HEAD,
423 &&TARGET_CALL_BUILTIN
424 };
425# pragma GCC diagnostic pop
426#endif
427
428 try
429 {
430 uint8_t inst = 0;
431 uint8_t padding = 0;
432 uint16_t arg = 0;
433 uint16_t primary_arg = 0;
434 uint16_t secondary_arg = 0;
435
436 m_running = true;
437
438 DISPATCH();
439 {
440#if !ARK_USE_COMPUTED_GOTOS
441 dispatch_opcode:
442 switch (inst)
443#endif
444 {
445#pragma region "Instructions"
446 TARGET(NOP)
447 {
448 DISPATCH();
449 }
450
452 {
453 push(loadSymbol(arg, context), context);
454 DISPATCH();
455 }
456
458 {
459 push(loadConstAsPtr(arg), context);
460 DISPATCH();
461 }
462
464 {
465 if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
466 context.ip = arg * 4; // instructions are 4 bytes
467 DISPATCH();
468 }
469
471 {
472 store(arg, popAndResolveAsPtr(context), context);
473 DISPATCH();
474 }
475
477 {
478 setVal(arg, popAndResolveAsPtr(context), context);
479 DISPATCH();
480 }
481
483 {
484 if (Value boolean = *popAndResolveAsPtr(context); !boolean)
485 context.ip = arg * 4; // instructions are 4 bytes
486 DISPATCH();
487 }
488
489 TARGET(JUMP)
490 {
491 context.ip = arg * 4; // instructions are 4 bytes
492 DISPATCH();
493 }
494
495 TARGET(RET)
496 {
497 {
498 Value ip_or_val = *popAndResolveAsPtr(context);
499 // no return value on the stack
500 if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
501 {
502 context.ip = ip_or_val.pageAddr();
503 // we always push PP then IP, thus the next value
504 // MUST be the page pointer
505 context.pp = pop(context)->pageAddr();
506
507 returnFromFuncCall(context);
508 push(Builtins::nil, context);
509 }
510 // value on the stack
511 else [[likely]]
512 {
513 Value* ip;
514 do
515 {
516 ip = popAndResolveAsPtr(context);
517 } while (ip->valueType() != ValueType::InstPtr);
518
519 context.ip = ip->pageAddr();
520 context.pp = pop(context)->pageAddr();
521
522 returnFromFuncCall(context);
523 push(std::move(ip_or_val), context);
524 }
525
526 if (context.fc <= untilFrameCount)
527 GOTO_HALT();
528 }
529
530 DISPATCH();
531 }
532
533 TARGET(HALT)
534 {
535 m_running = false;
536 GOTO_HALT();
537 }
538
539 TARGET(CALL)
540 {
541 // stack pointer + 2 because we push IP and PP
542 if (context.sp + 2u >= VMStackSize) [[unlikely]]
544 ErrorKind::VM,
545 fmt::format(
546 "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
547 m_state.m_symbols[context.last_symbol]));
548 call(context, arg);
549 if (!m_running)
550 GOTO_HALT();
551 DISPATCH();
552 }
553
555 {
556 if (!context.saved_scope)
557 context.saved_scope = Scope();
558
559 Value* ptr = (context.locals.back())[arg];
560 if (!ptr)
561 throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
562 else
563 {
564 ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
565 context.saved_scope.value().push_back(arg, *ptr);
566 }
567
568 DISPATCH();
569 }
570
572 {
573 push(Builtins::builtins[arg].second, context);
574 DISPATCH();
575 }
576
577 TARGET(DEL)
578 {
579 if (Value* var = findNearestVariable(arg, context); var != nullptr)
580 {
581 if (var->valueType() == ValueType::User)
582 var->usertypeRef().del();
583 *var = Value();
584 DISPATCH();
585 }
586
587 throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
588 }
589
591 {
592 push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
593 context.saved_scope.reset();
594 DISPATCH();
595 }
596
598 {
599 Value* var = popAndResolveAsPtr(context);
600 if (var->valueType() != ValueType::Closure)
601 {
602 if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
604 ErrorKind::Type,
605 fmt::format(
606 "`{}' is a {}, not a Closure, can not get the field `{}' from it",
608 types_to_str[static_cast<std::size_t>(var->valueType())],
609 m_state.m_symbols[arg]));
610 else
611 throwVMError(ErrorKind::Type,
612 fmt::format(
613 "{} is not a Closure, can not get the field `{}' from it",
614 types_to_str[static_cast<std::size_t>(var->valueType())],
615 m_state.m_symbols[arg]));
616 }
617
618 if (Value* field = var->refClosure().refScope()[arg]; field != nullptr)
619 {
620 // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
621 if (m_state.m_pages[context.pp][context.ip] == CALL)
622 push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
623 else
624 push(field, context);
625 }
626 else
627 {
628 if (!var->refClosure().hasFieldEndingWith(m_state.m_symbols[arg], *this))
630 ErrorKind::Scope,
631 fmt::format(
632 "`{0}' isn't in the closure environment: {1}",
633 m_state.m_symbols[arg],
634 var->refClosure().toString(*this)));
636 ErrorKind::Scope,
637 fmt::format(
638 "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
639 "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
640 m_state.m_symbols[arg],
641 var->refClosure().toString(*this)));
642 }
643 DISPATCH();
644 }
645
647 {
648 loadPlugin(arg, context);
649 DISPATCH();
650 }
651
652 TARGET(LIST)
653 {
654 {
656 if (arg != 0)
657 l.list().reserve(arg);
658
659 for (uint16_t i = 0; i < arg; ++i)
660 l.push_back(*popAndResolveAsPtr(context));
661 push(std::move(l), context);
662 }
663 DISPATCH();
664 }
665
667 {
668 {
669 Value* list = popAndResolveAsPtr(context);
670 if (list->valueType() != ValueType::List)
672 "append",
673 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
674 { *list });
675
676 const auto size = static_cast<uint16_t>(list->constList().size());
677
678 Value obj { *list };
679 obj.list().reserve(size + arg);
680
681 for (uint16_t i = 0; i < arg; ++i)
682 obj.push_back(*popAndResolveAsPtr(context));
683 push(std::move(obj), context);
684 }
685 DISPATCH();
686 }
687
689 {
690 {
691 Value* list = popAndResolveAsPtr(context);
692 if (list->valueType() != ValueType::List)
694 "concat",
695 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
696 { *list });
697
698 Value obj { *list };
699
700 for (uint16_t i = 0; i < arg; ++i)
701 {
702 Value* next = popAndResolveAsPtr(context);
703
704 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
706 "concat",
708 { *list, *next });
709
710 std::ranges::copy(next->list(), std::back_inserter(obj.list()));
711 }
712 push(std::move(obj), context);
713 }
714 DISPATCH();
715 }
716
718 {
719 Value* list = popAndResolveAsPtr(context);
720
721 if (list->valueType() != ValueType::List)
723 "append!",
724 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
725 { *list });
726
727 for (uint16_t i = 0; i < arg; ++i)
728 list->push_back(*popAndResolveAsPtr(context));
729 DISPATCH();
730 }
731
733 {
734 Value* list = popAndResolveAsPtr(context);
735
736 if (list->valueType() != ValueType::List)
738 "concat",
739 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
740 { *list });
741
742 for (uint16_t i = 0; i < arg; ++i)
743 {
744 Value* next = popAndResolveAsPtr(context);
745
746 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
748 "concat!",
750 { *list, *next });
751
752 std::ranges::copy(next->list(), std::back_inserter(list->list()));
753 }
754 DISPATCH();
755 }
756
758 {
759 {
760 Value list = *popAndResolveAsPtr(context);
761 Value number = *popAndResolveAsPtr(context);
762
763 if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
765 "pop",
767 { list, number });
768
769 long idx = static_cast<long>(number.number());
770 idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
771 if (std::cmp_greater_equal(idx, list.list().size()))
773 ErrorKind::Index,
774 fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
775
776 list.list().erase(list.list().begin() + idx);
777 push(list, context);
778 }
779 DISPATCH();
780 }
781
783 {
784 {
785 Value* list = popAndResolveAsPtr(context);
786 Value number = *popAndResolveAsPtr(context);
787
788 if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
790 "pop!",
792 { *list, number });
793
794 long idx = static_cast<long>(number.number());
795 idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
796 if (std::cmp_greater_equal(idx, list->list().size()))
798 ErrorKind::Index,
799 fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
800
801 list->list().erase(list->list().begin() + idx);
802 }
803 DISPATCH();
804 }
805
807 {
808 {
809 Value* list = popAndResolveAsPtr(context);
810 Value number = *popAndResolveAsPtr(context);
811 Value new_value = *popAndResolveAsPtr(context);
812
813 if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
815 "@=",
816 { { types::Contract {
819 types::Typedef("new_value", ValueType::Any) } } },
823 types::Typedef("char", ValueType::String) } } } },
824 { *list, number });
825
826 const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
827 long idx = static_cast<long>(number.number());
828 idx = idx < 0 ? static_cast<long>(size) + idx : idx;
829 if (std::cmp_greater_equal(idx, size))
831 ErrorKind::Index,
832 fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
833
834 if (list->valueType() == ValueType::List)
835 list->list()[static_cast<std::size_t>(idx)] = new_value;
836 else
837 list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
838 }
839 DISPATCH();
840 }
841
843 {
844 {
845 Value* list = popAndResolveAsPtr(context);
846 Value x = *popAndResolveAsPtr(context);
847 Value y = *popAndResolveAsPtr(context);
848 Value new_value = *popAndResolveAsPtr(context);
849
852 "@@=",
853 { { types::Contract {
857 types::Typedef("new_value", ValueType::Any) } } } },
858 { *list, x, y });
859
860 long idx_y = static_cast<long>(x.number());
861 idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
862 if (std::cmp_greater_equal(idx_y, list->list().size()))
864 ErrorKind::Index,
865 fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
866
867 if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
868 (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
870 "@@=",
871 { { types::Contract {
875 types::Typedef("new_value", ValueType::Any) } } },
880 types::Typedef("char", ValueType::String) } } } },
881 { *list, x, y });
882
883 const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
884 const std::size_t size =
885 is_list
886 ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
887 : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
888
889 long idx_x = static_cast<long>(y.number());
890 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
891 if (std::cmp_greater_equal(idx_x, size))
893 ErrorKind::Index,
894 fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
895
896 if (is_list)
897 list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
898 else
899 list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
900 }
901 DISPATCH();
902 }
903
904 TARGET(POP)
905 {
906 pop(context);
907 DISPATCH();
908 }
909
910 TARGET(DUP)
911 {
912 context.stack[context.sp] = context.stack[context.sp - 1];
913 ++context.sp;
914 DISPATCH();
915 }
916
918 {
919 context.locals.emplace_back();
920 DISPATCH();
921 }
922
924 {
925 context.locals.pop_back();
926 DISPATCH();
927 }
928
929#pragma endregion
930
931#pragma region "Operators"
932
933 TARGET(ADD)
934 {
935 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
936
937 if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
938 push(Value(a->number() + b->number()), context);
939 else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
940 push(Value(a->string() + b->string()), context);
941 else
943 "+",
946 { *a, *b });
947 DISPATCH();
948 }
949
950 TARGET(SUB)
951 {
952 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
953
954 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
956 "-",
958 { *a, *b });
959 push(Value(a->number() - b->number()), context);
960 DISPATCH();
961 }
962
963 TARGET(MUL)
964 {
965 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
966
967 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
969 "*",
971 { *a, *b });
972 push(Value(a->number() * b->number()), context);
973 DISPATCH();
974 }
975
976 TARGET(DIV)
977 {
978 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
979
980 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
982 "/",
984 { *a, *b });
985 auto d = b->number();
986 if (d == 0)
987 throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
988
989 push(Value(a->number() / d), context);
990 DISPATCH();
991 }
992
993 TARGET(GT)
994 {
995 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
996 push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
997 DISPATCH();
998 }
999
1000 TARGET(LT)
1001 {
1002 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1003 push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
1004 DISPATCH();
1005 }
1006
1007 TARGET(LE)
1008 {
1009 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1010 push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
1011 DISPATCH();
1012 }
1013
1014 TARGET(GE)
1015 {
1016 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1017 push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
1018 DISPATCH();
1019 }
1020
1021 TARGET(NEQ)
1022 {
1023 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1024 push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
1025 DISPATCH();
1026 }
1027
1028 TARGET(EQ)
1029 {
1030 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1031 push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
1032 DISPATCH();
1033 }
1034
1035 TARGET(LEN)
1036 {
1037 Value* a = popAndResolveAsPtr(context);
1038
1039 if (a->valueType() == ValueType::List)
1040 push(Value(static_cast<int>(a->constList().size())), context);
1041 else if (a->valueType() == ValueType::String)
1042 push(Value(static_cast<int>(a->string().size())), context);
1043 else
1045 "len",
1046 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1047 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1048 { *a });
1049 DISPATCH();
1050 }
1051
1052 TARGET(EMPTY)
1053 {
1054 Value* a = popAndResolveAsPtr(context);
1055
1056 if (a->valueType() == ValueType::List)
1057 push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1058 else if (a->valueType() == ValueType::String)
1059 push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1060 else
1062 "empty?",
1063 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1064 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1065 { *a });
1066 DISPATCH();
1067 }
1068
1069 TARGET(TAIL)
1070 {
1071 Value* a = popAndResolveAsPtr(context);
1072 push(helper::tail(a), context);
1073 DISPATCH();
1074 }
1075
1076 TARGET(HEAD)
1077 {
1078 Value* a = popAndResolveAsPtr(context);
1079 push(helper::head(a), context);
1080 DISPATCH();
1081 }
1082
1083 TARGET(ISNIL)
1084 {
1085 Value* a = popAndResolveAsPtr(context);
1087 DISPATCH();
1088 }
1089
1090 TARGET(ASSERT)
1091 {
1092 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1093
1094 if (b->valueType() != ValueType::String)
1096 "assert",
1098 { *a, *b });
1099
1100 if (*a == Builtins::falseSym)
1101 throw AssertionFailed(b->stringRef());
1102 DISPATCH();
1103 }
1104
1105 TARGET(TO_NUM)
1106 {
1107 Value* a = popAndResolveAsPtr(context);
1108
1109 if (a->valueType() != ValueType::String)
1111 "toNumber",
1112 { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1113 { *a });
1114
1115 double val;
1116 if (Utils::isDouble(a->string(), &val))
1117 push(Value(val), context);
1118 else
1119 push(Builtins::nil, context);
1120 DISPATCH();
1121 }
1122
1123 TARGET(TO_STR)
1124 {
1125 Value* a = popAndResolveAsPtr(context);
1126 push(Value(a->toString(*this)), context);
1127 DISPATCH();
1128 }
1129
1130 TARGET(AT)
1131 {
1132 {
1133 Value* b = popAndResolveAsPtr(context);
1134 Value a = *popAndResolveAsPtr(context); // be careful, it's not a pointer
1135
1136 if (b->valueType() != ValueType::Number)
1138 "@",
1141 { a, *b });
1142
1143 long idx = static_cast<long>(b->number());
1144
1145 if (a.valueType() == ValueType::List)
1146 {
1147 if (std::cmp_less(std::abs(idx), a.list().size()))
1148 push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
1149 else
1151 ErrorKind::Index,
1152 fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1153 }
1154 else if (a.valueType() == ValueType::String)
1155 {
1156 if (std::cmp_less(std::abs(idx), a.string().size()))
1157 push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
1158 else
1160 ErrorKind::Index,
1161 fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1162 }
1163 else
1165 "@",
1168 { a, *b });
1169 }
1170 DISPATCH();
1171 }
1172
1173 TARGET(AT_AT)
1174 {
1175 {
1176 Value* x = popAndResolveAsPtr(context);
1177 Value* y = popAndResolveAsPtr(context);
1178 Value list = *popAndResolveAsPtr(context); // be careful, it's not a pointer
1179
1180 if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
1181 list.valueType() != ValueType::List)
1183 "@@",
1184 { { types::Contract {
1187 types::Typedef("x", ValueType::Number) } } } },
1188 { list, *y, *x });
1189
1190 long idx_y = static_cast<long>(y->number());
1191 idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
1192 if (std::cmp_greater_equal(idx_y, list.list().size()))
1194 ErrorKind::Index,
1195 fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1196
1197 const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
1198 const std::size_t size =
1199 is_list
1200 ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
1201 : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
1202
1203 long idx_x = static_cast<long>(x->number());
1204 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1205 if (std::cmp_greater_equal(idx_x, size))
1207 ErrorKind::Index,
1208 fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1209
1210 if (is_list)
1211 push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
1212 else
1213 push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
1214 }
1215 DISPATCH();
1216 }
1217
1218 TARGET(MOD)
1219 {
1220 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1221 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1223 "mod",
1225 { *a, *b });
1226 push(Value(std::fmod(a->number(), b->number())), context);
1227 DISPATCH();
1228 }
1229
1230 TARGET(TYPE)
1231 {
1232 Value* a = popAndResolveAsPtr(context);
1233 if (a == &m_undefined_value) [[unlikely]]
1235 "type",
1236 { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
1237 {});
1238
1239 push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
1240 DISPATCH();
1241 }
1242
1244 {
1245 {
1246 Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
1247 if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
1249 "hasField",
1251 { *closure, *field });
1252
1253 auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef());
1254 if (it == m_state.m_symbols.end())
1255 {
1256 push(Builtins::falseSym, context);
1257 DISPATCH();
1258 }
1259
1260 auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1261 push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1262 }
1263 DISPATCH();
1264 }
1265
1266 TARGET(NOT)
1267 {
1268 Value* a = popAndResolveAsPtr(context);
1269 push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
1270 DISPATCH();
1271 }
1272
1273#pragma endregion
1274
1275#pragma region "Super Instructions"
1277 {
1278 UNPACK_ARGS();
1279 push(loadConstAsPtr(primary_arg), context);
1280 push(loadConstAsPtr(secondary_arg), context);
1281 DISPATCH();
1282 }
1283
1285 {
1286 UNPACK_ARGS();
1287 store(secondary_arg, loadConstAsPtr(primary_arg), context);
1288 DISPATCH();
1289 }
1290
1292 {
1293 UNPACK_ARGS();
1294 setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
1295 DISPATCH();
1296 }
1297
1299 {
1300 UNPACK_ARGS();
1301 store(secondary_arg, loadSymbol(primary_arg, context), context);
1302 DISPATCH();
1303 }
1304
1306 {
1307 UNPACK_ARGS();
1308 setVal(secondary_arg, loadSymbol(primary_arg, context), context);
1309 DISPATCH();
1310 }
1311
1313 {
1314 UNPACK_ARGS();
1315 {
1316 Value* var = loadSymbol(primary_arg, context);
1317
1318 // use internal reference, shouldn't break anything so far, unless it's already a ref
1319 if (var->valueType() == ValueType::Reference)
1320 var = var->reference();
1321
1322 if (var->valueType() == ValueType::Number)
1323 push(Value(var->number() + secondary_arg), context);
1324 else
1326 "+",
1328 { *var, Value(secondary_arg) });
1329 }
1330 DISPATCH();
1331 }
1332
1334 {
1335 UNPACK_ARGS();
1336 {
1337 Value* var = loadSymbol(primary_arg, context);
1338
1339 // use internal reference, shouldn't break anything so far, unless it's already a ref
1340 if (var->valueType() == ValueType::Reference)
1341 var = var->reference();
1342
1343 if (var->valueType() == ValueType::Number)
1344 push(Value(var->number() - secondary_arg), context);
1345 else
1347 "-",
1349 { *var, Value(secondary_arg) });
1350 }
1351 DISPATCH();
1352 }
1353
1355 {
1356 UNPACK_ARGS();
1357 {
1358 Value* list = loadSymbol(primary_arg, context);
1359 Value tail = helper::tail(list);
1360 store(secondary_arg, &tail, context);
1361 }
1362 DISPATCH();
1363 }
1364
1366 {
1367 UNPACK_ARGS();
1368 {
1369 Value* list = loadSymbol(primary_arg, context);
1370 Value head = helper::head(list);
1371 store(secondary_arg, &head, context);
1372 }
1373 DISPATCH();
1374 }
1375
1377 {
1378 UNPACK_ARGS();
1379 {
1380 Value* list = loadSymbol(primary_arg, context);
1381 Value tail = helper::tail(list);
1382 setVal(secondary_arg, &tail, context);
1383 }
1384 DISPATCH();
1385 }
1386
1388 {
1389 UNPACK_ARGS();
1390 {
1391 Value* list = loadSymbol(primary_arg, context);
1392 Value head = helper::head(list);
1393 setVal(secondary_arg, &head, context);
1394 }
1395 DISPATCH();
1396 }
1397
1399 {
1400 UNPACK_ARGS();
1401 // no stack size check because we do not push IP/PP since we are just calling a builtin
1402 callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
1403 if (!m_running)
1404 GOTO_HALT();
1405 DISPATCH();
1406 }
1407#pragma endregion
1408 }
1409#if ARK_USE_COMPUTED_GOTOS
1410 dispatch_end:
1411 do
1412 {
1413 } while (false);
1414#endif
1415 }
1416 }
1417 catch (const std::exception& e)
1418 {
1419 if (fail_with_exception)
1420 throw;
1421
1422 fmt::println("{}", e.what());
1423 backtrace(context);
1424#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1425 // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1426 m_exit_code = 0;
1427#else
1428 m_exit_code = 1;
1429#endif
1430 }
1431 catch (...)
1432 {
1433 if (fail_with_exception)
1434 throw;
1435
1436#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1437 throw;
1438#endif
1439 fmt::println("Unknown error");
1440 backtrace(context);
1441 m_exit_code = 1;
1442 }
1443
1444 return m_exit_code;
1445 }
1446
1447 uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
1448 {
1449 for (auto& local : std::ranges::reverse_view(context.locals))
1450 {
1451 if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
1452 return id;
1453 }
1454 return std::numeric_limits<uint16_t>::max();
1455 }
1456
1457 void VM::throwVMError(ErrorKind kind, const std::string& message)
1458 {
1459 throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
1460 }
1461
1462 void VM::backtrace(ExecutionContext& context) noexcept
1463 {
1464 const std::size_t saved_ip = context.ip;
1465 const std::size_t saved_pp = context.pp;
1466 const uint16_t saved_sp = context.sp;
1467
1468 if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
1469 {
1470 // display call stack trace
1471 const Scope old_scope = context.locals.back();
1472
1473 while (context.fc != 0)
1474 {
1475 fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
1476 if (context.pp != 0)
1477 {
1478 const uint16_t id = findNearestVariableIdWithValue(
1479 Value(static_cast<PageAddr_t>(context.pp)),
1480 context);
1481
1482 if (id < m_state.m_symbols.size())
1483 fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)));
1484 else // should never happen
1485 fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold)));
1486
1487 Value* ip;
1488 do
1489 {
1490 ip = popAndResolveAsPtr(context);
1491 } while (ip->valueType() != ValueType::InstPtr);
1492
1493 context.ip = ip->pageAddr();
1494 context.pp = pop(context)->pageAddr();
1495 returnFromFuncCall(context);
1496 }
1497 else
1498 {
1499 fmt::println("In global scope");
1500 break;
1501 }
1502
1503 if (original_frame_count - context.fc > 7)
1504 {
1505 fmt::println("...");
1506 break;
1507 }
1508 }
1509
1510 // display variables values in the current scope
1511 fmt::println("\nCurrent scope variables values:");
1512 for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
1513 {
1514 fmt::println(
1515 "{} = {}",
1516 fmt::styled(m_state.m_symbols[old_scope.m_data[i].first], fmt::fg(fmt::color::cyan)),
1517 old_scope.m_data[i].second.toString(*this));
1518 }
1519
1520 while (context.fc != 1)
1521 {
1522 Value* tmp = pop(context);
1523 if (tmp->valueType() == ValueType::InstPtr)
1524 --context.fc;
1525 *tmp = m_no_value;
1526 }
1527 // pop the PP as well
1528 pop(context);
1529 }
1530
1531 std::cerr << "At IP: " << (saved_ip / 4) // dividing by 4 because the instructions are actually on 4 bytes
1532 << ", PP: " << saved_pp
1533 << ", SP: " << saved_sp
1534 << "\n";
1535 }
1536}
Lots of utilities about string, filesystem and more.
#define ARK_NO_NAME_FILE
Definition Constants.hpp:27
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)
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition State.hpp:32
std::vector< std::filesystem::path > m_libenv
Definition State.hpp:145
std::string m_filename
Definition State.hpp:146
std::vector< Value > m_constants
Definition State.hpp:150
std::unordered_map< std::string, Value > m_binded
Definition State.hpp:154
std::vector< std::string > m_symbols
Definition State.hpp:149
std::vector< bytecode_t > m_pages
Definition State.hpp:151
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:44
void deleteContext(internal::ExecutionContext *ec)
Free a given execution context.
Definition VM.cpp:242
std::vector< std::unique_ptr< internal::Future > > m_futures
Storing the promises while we are resolving them.
Definition VM.hpp:163
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Definition VM.hpp:159
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Definition VM.cpp:118
Value * popAndResolveAsPtr(internal::ExecutionContext &context)
Pop a value from the stack and resolve it if possible, then return it.
Definition VM.hpp:208
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
Definition VM.hpp:162
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition VM.hpp:158
Value * loadSymbol(uint16_t id, internal::ExecutionContext &context)
Definition VM.hpp:125
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Definition VM.cpp:1447
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
Definition VM.cpp:282
internal::ExecutionContext * createAndGetContext()
Create an execution context and returns it.
Definition VM.cpp:227
std::mutex m_mutex
Definition VM.hpp:161
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
Definition VM.cpp:143
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:320
void deleteFuture(internal::Future *f)
Free a given future.
Definition VM.cpp:268
bool m_running
Definition VM.hpp:160
Value call(const std::string &name, Args &&... args)
Call a function from ArkScript, by giving it arguments.
Definition VM.hpp:3
Value * pop(internal::ExecutionContext &context)
Pop a value from the stack.
Definition VM.hpp:177
void returnFromFuncCall(internal::ExecutionContext &context)
Destroy the current frame and get back to the previous one, resuming execution.
Definition VM.hpp:283
void backtrace(internal::ExecutionContext &context) noexcept
Display a backtrace when the VM encounter an exception.
Definition VM.cpp:1462
Value m_undefined_value
Definition VM.hpp:167
void store(uint16_t id, const Value *val, internal::ExecutionContext &context)
Definition VM.hpp:145
void setVal(uint16_t id, const Value *val, internal::ExecutionContext &context)
Definition VM.hpp:155
void push(const Value &value, internal::ExecutionContext &context)
Push a value on the stack.
Definition VM.hpp:187
friend class internal::Closure
Definition VM.hpp:153
State & m_state
Definition VM.hpp:157
Value * loadConstAsPtr(uint16_t id) const
Definition VM.hpp:140
void callBuiltin(internal::ExecutionContext &context, const Value &builtin, uint16_t argc)
Builtin called when the CALL_BUILTIN instruction is met in the bytecode.
Definition VM.hpp:413
void init() noexcept
Initialize the VM according to the parameters.
Definition VM.cpp:85
Value * findNearestVariable(uint16_t id, internal::ExecutionContext &context) noexcept
Find the nearest variable of a given id.
Definition VM.hpp:273
static void throwVMError(internal::ErrorKind kind, const std::string &message)
Throw a VM error message.
Definition VM.cpp:1457
friend class Value
Definition VM.hpp:152
VM(State &state) noexcept
Construct a new vm t object.
Definition VM.cpp:79
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:256
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:313
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
Definition VM.cpp:221
const std::vector< Value > & constList() const
Definition Value.hpp:127
std::vector< Value > & list()
Definition Value.hpp:129
internal::Closure & refClosure()
Definition Value.hpp:166
Value * reference() const
Definition Value.hpp:132
double number() const
Definition Value.hpp:125
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:57
ValueType valueType() const noexcept
Definition Value.hpp:114
const std::string & string() const
Definition Value.hpp:126
bool isIndexable() const noexcept
Definition Value.hpp:120
std::string toString(VM &vm) const noexcept
Definition Value.cpp:67
internal::PageAddr_t pageAddr() const
Definition Value.hpp:163
std::string & stringRef()
Definition Value.hpp:130
bool hasFieldEndingWith(const std::string &end, VM &vm) const
Used when generating error messages in the VM, to see if a symbol might have been wrongly fully quali...
Definition Closure.cpp:21
const std::shared_ptr< Scope > & scopePtr() const
Definition Closure.hpp:56
std::string toString(VM &vm) const noexcept
Print the closure to a string.
Definition Closure.cpp:31
Scope & refScope() const noexcept
Definition Closure.hpp:55
A class to handle the VM scope more efficiently.
Definition Scope.hpp:28
std::vector< std::pair< uint16_t, Value > > m_data
Definition Scope.hpp:105
std::size_t size() const noexcept
Return the size of the scope.
Definition Scope.cpp:79
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
Definition Files.hpp:29
bool isDouble(const std::string &s, double *output=nullptr)
Checks if a string is a valid double.
Definition Utils.hpp:56
Value head(Value *a)
Definition VM.cpp:56
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:30
ARK_API void generateError(const std::string_view &funcname, const std::vector< Contract > &contracts, const std::vector< Value > &args)
Generate an error message based on a given set of types contracts provided argument list.
constexpr std::array types_to_str
Definition Value.hpp:52
constexpr std::size_t VMStackSize
Definition Constants.hpp:68
@ Any
Used only for typechecking.
std::array< Value, VMStackSize > stack
std::optional< Scope > saved_scope
Scope created by CAPTURE <x> instructions, used by the MAKE_CLOSURE instruction.
std::size_t ip
Instruction pointer.
std::vector< std::shared_ptr< Scope > > stacked_closure_scopes
Stack the closure scopes to keep the closure alive as long as we are calling them.
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