ArkScript
A small, fast, functional and scripting language for video games
VM.cpp
Go to the documentation of this file.
1#define NOMINMAX
2
3#include <Ark/VM/VM.hpp>
4
5#include <numeric>
6#include <limits>
7
8#include <termcolor/termcolor.hpp>
9#include <Ark/Files.hpp>
10#include <Ark/Utils.hpp>
11#include <Ark/TypeChecker.hpp>
12
13#ifdef ARK_PROFILER_MIPS
14# include <chrono>
15#endif
16
17struct mapping
18{
19 char* name;
20 Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
21};
22
23namespace Ark
24{
25 using namespace internal;
26
27 VM::VM(State& state) noexcept :
28 m_state(state), m_exit_code(0), m_fc(0),
29 m_running(false),
30 m_until_frame_count(0), m_user_pointer(nullptr)
31 {
32 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
33 }
34
35 void VM::init() noexcept
36 {
37 ExecutionContext& context = *m_execution_contexts.back();
38
39 context.sp = 0;
40 m_fc = 1;
41
43 context.scope_count_to_delete.clear();
44 context.scope_count_to_delete.emplace_back(0);
45
46 context.saved_scope.reset();
47 m_exit_code = 0;
48
49 context.locals.clear();
50 createNewScope(context);
51
52 if (context.locals.size() == 0)
53 {
54 // if persistance is set but not scopes are present, add one
55 createNewScope(context);
56 }
57
58 // loading binded stuff
59 // put them in the global frame if we can, aka the first one
60 for (auto name_val : m_state.m_binded)
61 {
62 auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), name_val.first);
63 if (it != m_state.m_symbols.end())
64 (*context.locals[0]).push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), name_val.second);
65 }
66 }
67
68 Value& VM::operator[](const std::string& name) noexcept
69 {
70 ExecutionContext& context = *m_execution_contexts.back();
71
72 const std::lock_guard<std::mutex> lock(m_mutex);
73
74 // find id of object
75 auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), name);
76 if (it == m_state.m_symbols.end())
77 {
78 m_no_value = Builtins::nil;
79 return m_no_value;
80 }
81
82 uint16_t id = static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it));
83 Value* var = findNearestVariable(id, context);
84 if (var != nullptr)
85 return *var;
86 m_no_value = Builtins::nil;
87 return m_no_value;
88 }
89
90 void VM::loadPlugin(uint16_t id, ExecutionContext& context)
91 {
92 namespace fs = std::filesystem;
93
94 const std::string file = m_state.m_constants[id].stringRef().toString();
95
96 std::string path = file;
97 // bytecode loaded from file
99 path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
100
101 std::shared_ptr<SharedLibrary> lib;
102 for (auto const& v : m_state.m_libenv)
103 {
104 std::string lib_path = (fs::path(v) / fs::path(file)).string();
105
106 // if it's already loaded don't do anything
107 if (std::find_if(m_shared_lib_objects.begin(), m_shared_lib_objects.end(), [&](const auto& val) {
108 return (val->path() == path || val->path() == lib_path);
109 }) != m_shared_lib_objects.end())
110 return;
111
112 // if it exists alongside the .arkc file
113 if (Utils::fileExists(path))
114 {
115 lib = std::make_shared<SharedLibrary>(path);
116 break;
117 }
118 // check in lib_path otherwise
119 else if (Utils::fileExists(lib_path))
120 {
121 lib = std::make_shared<SharedLibrary>(lib_path);
122 break;
123 }
124 }
125
126 if (!lib)
127 {
128 auto lib_path = std::accumulate(
129 std::next(m_state.m_libenv.begin()),
130 m_state.m_libenv.end(),
131 m_state.m_libenv[0],
132 [](const std::string& a, const std::string& b) -> std::string {
133 return a + "\n\t- " + b;
134 });
135 throwVMError("Could not find module '" + file + "'. Searched in\n\t- " + path + "\n\t- " + lib_path);
136 }
137
138 m_shared_lib_objects.emplace_back(lib);
139
140 // load the mapping from the dynamic library
141 mapping* map = nullptr;
142 try
143 {
144 map = m_shared_lib_objects.back()->template get<mapping* (*)()>("getFunctionsMapping")();
145 }
146 catch (const std::system_error& e)
147 {
149 "An error occurred while loading module '" + file + "': " + std::string(e.what()) + "\n" +
150 "It is most likely because the versions of the module and the language don't match.");
151 }
152
153 // load the mapping data
154 std::size_t i = 0;
155 while (map[i].name != nullptr)
156 {
157 // put it in the global frame, aka the first one
158 auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), std::string(map[i].name));
159 if (it != m_state.m_symbols.end())
160 (*context.locals[0]).push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
161
162 // free memory because we have used it and don't need it anymore
163 // no need to free map[i].value since it's a pointer to a function in the DLL
164 delete[] map[i].name;
165 ++i;
166 }
167
168 // free memory
169 delete[] map;
170 }
171
172 void VM::exit(int code) noexcept
173 {
174 m_exit_code = code;
175 m_running = false;
176 }
177
178 // ------------------------------------------
179 // user pointer
180 // ------------------------------------------
181
182 void VM::setUserPointer(void* ptr) noexcept
183 {
184 m_user_pointer = ptr;
185 }
186
187 void* VM::getUserPointer() noexcept
188 {
189 return m_user_pointer;
190 }
191
192 // ------------------------------------------
193 // execution
194 // ------------------------------------------
195
196 int VM::run() noexcept
197 {
198 init();
200
201 // reset VM after each run
202 for (auto& context : m_execution_contexts)
203 {
204 context->ip = 0;
205 context->pp = 0;
206 }
207
208 return m_exit_code;
209 }
210
211 int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount)
212 {
213 m_until_frame_count = untilFrameCount;
214
215#ifdef ARK_PROFILER_MIPS
216 auto start_time = std::chrono::system_clock::now();
217 unsigned long long instructions_executed = 0;
218#endif
219
220 try
221 {
222 m_running = true;
224 {
225 // get current instruction
226 uint8_t inst = m_state.m_pages[context.pp][context.ip];
227
228 // and it's time to du-du-du-du-duel!
229 switch (inst)
230 {
231#pragma region "Instructions"
232
234 {
235 /*
236 Argument: symbol id (two bytes, big endian)
237 Job: Load a symbol from its id onto the stack
238 */
239
240 ++context.ip;
241 context.last_symbol = readNumber(context);
242
243 if (Value* var = findNearestVariable(context.last_symbol, context); var != nullptr)
244 // push internal reference, shouldn't break anything so far
245 push(var, context);
246 else
247 throwVMError("unbound variable: " + m_state.m_symbols[context.last_symbol]);
248
249 COZ_PROGRESS_NAMED("ark vm load_symbol");
250 break;
251 }
252
254 {
255 /*
256 Argument: constant id (two bytes, big endian)
257 Job: Load a constant from its id onto the stack. Should check for a saved environment
258 and push a Closure with the page address + environment instead of the constant
259 */
260
261 ++context.ip;
262 uint16_t id = readNumber(context);
263
264 if (context.saved_scope && m_state.m_constants[id].valueType() == ValueType::PageAddr)
265 {
266 push(Value(Closure(context.saved_scope.value(), m_state.m_constants[id].pageAddr())), context);
267 context.saved_scope.reset();
268 }
269 else
270 {
271 // push internal ref
272 push(&(m_state.m_constants[id]), context);
273 }
274
275 COZ_PROGRESS_NAMED("ark vm load_const");
276 break;
277 }
278
280 {
281 /*
282 Argument: absolute address to jump to (two bytes, big endian)
283 Job: Jump to the provided address if the last value on the stack was equal to true.
284 Remove the value from the stack no matter what it is
285 */
286
287 ++context.ip;
288 uint16_t id = readNumber(context);
289
290 if (*popAndResolveAsPtr(context) == Builtins::trueSym)
291 context.ip = static_cast<int16_t>(id) - 1; // because we are doing a ++context.ip right after this
292 break;
293 }
294
296 {
297 /*
298 Argument: symbol id (two bytes, big endian)
299 Job: Take the value on top of the stack and put it inside a variable named following
300 the symbol id (cf symbols table), in the nearest scope. Raise an error if it
301 couldn't find a scope where the variable exists
302 */
303
304 ++context.ip;
305 uint16_t id = readNumber(context);
306
307 if (Value* var = findNearestVariable(id, context); var != nullptr)
308 {
309 if (var->isConst())
310 throwVMError("can not modify a constant: " + m_state.m_symbols[id]);
311
312 *var = *popAndResolveAsPtr(context);
313 var->setConst(false);
314 break;
315 }
316
317 COZ_PROGRESS_NAMED("ark vm store");
318
319 throwVMError("unbound variable " + m_state.m_symbols[id] + ", can not change its value");
320 break;
321 }
322
323 case Instruction::LET:
324 {
325 /*
326 Argument: symbol id (two bytes, big endian)
327 Job: Take the value on top of the stack and create a constant in the current scope, named
328 following the given symbol id (cf symbols table)
329 */
330
331 ++context.ip;
332 uint16_t id = readNumber(context);
333
334 // check if we are redefining a variable
335 if (auto val = (*context.locals.back())[id]; val != nullptr)
336 throwVMError("can not use 'let' to redefine the variable " + m_state.m_symbols[id]);
337
338 Value val = *popAndResolveAsPtr(context);
339 val.setConst(true);
340 (*context.locals.back()).push_back(id, val);
341
342 COZ_PROGRESS_NAMED("ark vm let");
343 break;
344 }
345
347 {
348 /*
349 Argument: absolute address to jump to (two bytes, big endian)
350 Job: Jump to the provided address if the last value on the stack was equal to false. Remove
351 the value from the stack no matter what it is
352 */
353
354 ++context.ip;
355 uint16_t id = readNumber(context);
356
357 if (*popAndResolveAsPtr(context) == Builtins::falseSym)
358 context.ip = static_cast<int16_t>(id) - 1; // because we are doing a ++context.ip right after this
359 break;
360 }
361
363 {
364 /*
365 Argument: absolute address to jump to (two byte, big endian)
366 Job: Jump to the provided address
367 */
368
369 ++context.ip;
370 uint16_t id = readNumber(context);
371
372 context.ip = static_cast<int16_t>(id) - 1; // because we are doing a ++context.ip right after this
373 break;
374 }
375
376 case Instruction::RET:
377 {
378 /*
379 Argument: none
380 Job: If in a code segment other than the main one, quit it, and push the value on top of
381 the stack to the new stack ; should as well delete the current environment.
382 */
383
384 Value ip_or_val = *popAndResolveAsPtr(context);
385 // no return value on the stack
386 if (ip_or_val.valueType() == ValueType::InstPtr)
387 {
388 context.ip = ip_or_val.pageAddr();
389 // we always push PP then IP, thus the next value
390 // MUST be the page pointer
391 context.pp = pop(context)->pageAddr();
392
393 returnFromFuncCall(context);
394 push(Builtins::nil, context);
395 }
396 // value on the stack
397 else
398 {
399 Value* ip;
400 do
401 {
402 ip = popAndResolveAsPtr(context);
403 } while (ip->valueType() != ValueType::InstPtr);
404
405 context.ip = ip->pageAddr();
406 context.pp = pop(context)->pageAddr();
407
408 returnFromFuncCall(context);
409 push(std::move(ip_or_val), context);
410 }
411
412 COZ_PROGRESS_NAMED("ark vm ret");
413 break;
414 }
415
417 m_running = false;
418 break;
419
421 call(context);
422 break;
423
425 {
426 /*
427 Argument: symbol id (two bytes, big endian)
428 Job: Used to tell the Virtual Machine to capture the variable from the current environment.
429 Main goal is to be able to handle closures, which need to save the environment in which
430 they were created
431 */
432
433 ++context.ip;
434 uint16_t id = readNumber(context);
435
436 if (!context.saved_scope)
437 context.saved_scope = std::make_shared<Scope>();
438 // if it's a captured variable, it can not be nullptr
439 Value* ptr = (*context.locals.back())[id];
440 ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
441 (*context.saved_scope.value()).push_back(id, *ptr);
442
443 COZ_PROGRESS_NAMED("ark vm capture");
444 break;
445 }
446
448 {
449 /*
450 Argument: id of builtin (two bytes, big endian)
451 Job: Push the builtin function object on the stack
452 */
453
454 ++context.ip;
455 uint16_t id = readNumber(context);
456
457 push(Builtins::builtins[id].second, context);
458
459 COZ_PROGRESS_NAMED("ark vm builtin");
460 break;
461 }
462
463 case Instruction::MUT:
464 {
465 /*
466 Argument: symbol id (two bytes, big endian)
467 Job: Take the value on top of the stack and create a variable in the current scope,
468 named following the given symbol id (cf symbols table)
469 */
470
471 ++context.ip;
472 uint16_t id = readNumber(context);
473
474 Value val = *popAndResolveAsPtr(context);
475 val.setConst(false);
476
477 // avoid adding the pair (id, _) multiple times, with different values
478 Value* local = (*context.locals.back())[id];
479 if (local == nullptr)
480 (*context.locals.back()).push_back(id, val);
481 else
482 *local = val;
483
484 COZ_PROGRESS_NAMED("ark vm mut");
485 break;
486 }
487
488 case Instruction::DEL:
489 {
490 /*
491 Argument: symbol id (two bytes, big endian)
492 Job: Remove a variable/constant named following the given symbol id (cf symbols table)
493 */
494
495 ++context.ip;
496 uint16_t id = readNumber(context);
497
498 if (Value* var = findNearestVariable(id, context); var != nullptr)
499 {
500 if (var->valueType() == ValueType::User)
501 var->usertypeRef().del();
502 *var = Value();
503 break;
504 }
505
506 COZ_PROGRESS_NAMED("ark vm del");
507
508 throwVMError("unbound variable: " + m_state.m_symbols[id]);
509 break;
510 }
511
513 {
514 /*
515 Argument: none
516 Job: Save the current environment, useful for quoted code
517 */
518 context.saved_scope = context.locals.back();
519
520 COZ_PROGRESS_NAMED("ark vm save_scope");
521 break;
522 }
523
525 {
526 /*
527 Argument: symbol id (two bytes, big endian)
528 Job: Used to read the field named following the given symbol id (cf symbols table) of a `Closure`
529 stored in TS. Pop TS and push the value of field read on the stack
530 */
531
532 ++context.ip;
533 uint16_t id = readNumber(context);
534
535 Value* var = popAndResolveAsPtr(context);
536 if (var->valueType() != ValueType::Closure)
537 throwVMError("the variable `" + m_state.m_symbols[context.last_symbol] + "' isn't a closure, can not get the field `" + m_state.m_symbols[id] + "' from it");
538
539 if (Value* field = (*var->refClosure().scope())[id]; field != nullptr)
540 {
541 // check for CALL instruction
542 if (static_cast<std::size_t>(context.ip) + 1 < m_state.m_pages[context.pp].size() && m_state.m_pages[context.pp][context.ip + 1] == Instruction::CALL)
543 {
544 context.locals.push_back(var->refClosure().scope());
545 ++context.scope_count_to_delete.back();
546 }
547
548 push(field, context);
549 break;
550 }
551
552 throwVMError("couldn't find the variable " + m_state.m_symbols[id] + " in the closure enviroment");
553 break;
554 }
555
557 {
558 /*
559 Argument: constant id (two bytes, big endian)
560 Job: Load a module named following the constant id (cf constants table).
561 Raise an error if it couldn't find the module.
562 */
563
564 ++context.ip;
565 uint16_t id = readNumber(context);
566
567 loadPlugin(id, context);
568
569 COZ_PROGRESS_NAMED("ark vm plugin");
570 break;
571 }
572
574 {
575 /*
576 Takes at least 0 arguments and push a list on the stack.
577 The content is pushed in reverse order
578 */
579 ++context.ip;
580 uint16_t count = readNumber(context);
581
583 if (count != 0)
584 l.list().reserve(count);
585
586 for (uint16_t i = 0; i < count; ++i)
587 l.push_back(*popAndResolveAsPtr(context));
588 push(std::move(l), context);
589
590 COZ_PROGRESS_NAMED("ark vm list");
591 break;
592 }
593
595 {
596 ++context.ip;
597 uint16_t count = readNumber(context);
598
599 Value* list = popAndResolveAsPtr(context);
600 if (list->valueType() != ValueType::List)
602 "append",
603 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
604 { *list });
605
606 const uint16_t size = list->constList().size();
607
608 Value obj = Value(*list);
609 obj.list().reserve(size + count);
610
611 for (uint16_t i = 0; i < count; ++i)
612 obj.push_back(*popAndResolveAsPtr(context));
613 push(std::move(obj), context);
614
615 COZ_PROGRESS_NAMED("ark vm append");
616 break;
617 }
618
620 {
621 ++context.ip;
622 uint16_t count = readNumber(context);
623
624 Value* list = popAndResolveAsPtr(context);
625 if (list->valueType() != ValueType::List)
627 "concat",
628 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
629 { *list });
630
631 Value obj = Value(*list);
632
633 for (uint16_t i = 0; i < count; ++i)
634 {
635 Value* next = popAndResolveAsPtr(context);
636
637 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
639 "concat",
641 { *list, *next });
642
643 for (auto it = next->list().begin(), end = next->list().end(); it != end; ++it)
644 obj.push_back(*it);
645 }
646 push(std::move(obj), context);
647
648 COZ_PROGRESS_NAMED("ark vm concat");
649 break;
650 }
651
653 {
654 ++context.ip;
655 uint16_t count = readNumber(context);
656
657 Value* list = popAndResolveAsPtr(context);
658
659 if (list->isConst())
660 throwVMError("can not modify a constant list using `append!'");
661 if (list->valueType() != ValueType::List)
663 "append!",
664 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
665 { *list });
666
667 for (uint16_t i = 0; i < count; ++i)
668 list->push_back(*popAndResolveAsPtr(context));
669
670 push(Nil, context);
671
672 COZ_PROGRESS_NAMED("ark vm append!");
673 break;
674 }
675
677 {
678 ++context.ip;
679 uint16_t count = readNumber(context);
680
681 Value* list = popAndResolveAsPtr(context);
682
683 if (list->isConst())
684 throwVMError("can not modify a constant list using `concat!'");
685 if (list->valueType() != ValueType::List)
687 "concat",
688 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
689 { *list });
690
691 for (uint16_t i = 0; i < count; ++i)
692 {
693 Value* next = popAndResolveAsPtr(context);
694
695 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
697 "concat!",
699 { *list, *next });
700
701 for (auto it = next->list().begin(), end = next->list().end(); it != end; ++it)
702 list->push_back(*it);
703 }
704
705 push(Nil, context);
706
707 COZ_PROGRESS_NAMED("ark vm concat!");
708 break;
709 }
710
712 {
713 Value list = *popAndResolveAsPtr(context);
714 Value number = *popAndResolveAsPtr(context);
715
716 if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
718 "pop",
720 { list, number });
721
722 long idx = static_cast<long>(number.number());
723 idx = (idx < 0 ? list.list().size() + idx : idx);
724 if (static_cast<std::size_t>(idx) >= list.list().size())
725 throw std::runtime_error("pop: index out of range");
726
727 list.list().erase(list.list().begin() + idx);
728 push(list, context);
729 break;
730 }
731
733 {
734 Value* list = popAndResolveAsPtr(context);
735 Value number = *popAndResolveAsPtr(context);
736
737 if (list->isConst())
738 throwVMError("can not modify a constant list using `pop!'");
739 if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
741 "pop!",
743 { *list, number });
744
745 long idx = static_cast<long>(number.number());
746 idx = (idx < 0 ? list->list().size() + idx : idx);
747 if (static_cast<std::size_t>(idx) >= list->list().size())
748 throw std::runtime_error("pop!: index out of range");
749
750 list->list().erase(list->list().begin() + idx);
751 break;
752 }
753
754 case Instruction::POP:
755 {
756 pop(context);
757 break;
758 }
759
760#pragma endregion
761
762#pragma region "Operators"
763
764 case Instruction::ADD:
765 {
766 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
767
768 if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
769 push(Value(a->number() + b->number()), context);
770 else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
771 push(Value(a->string() + b->string()), context);
772 else
774 "+",
777 { *a, *b });
778 break;
779 }
780
781 case Instruction::SUB:
782 {
783 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
784
785 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
787 "-",
789 { *a, *b });
790 else
791 push(Value(a->number() - b->number()), context);
792 break;
793 }
794
795 case Instruction::MUL:
796 {
797 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
798
799 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
801 "*",
803 { *a, *b });
804 else
805 push(Value(a->number() * b->number()), context);
806 break;
807 }
808
809 case Instruction::DIV:
810 {
811 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
812
813 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
815 "/",
817 { *a, *b });
818 else
819 {
820 auto d = b->number();
821 if (d == 0)
822 throw ZeroDivisionError();
823
824 push(Value(a->number() / d), context);
825 }
826 break;
827 }
828
829 case Instruction::GT:
830 {
831 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
832
833 push((!(*a == *b) && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
834 break;
835 }
836
837 case Instruction::LT:
838 {
839 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
840
841 push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
842 break;
843 }
844
845 case Instruction::LE:
846 {
847 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
848
849 push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
850 break;
851 }
852
853 case Instruction::GE:
854 {
855 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
856
857 push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
858 break;
859 }
860
861 case Instruction::NEQ:
862 {
863 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
864
865 push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
866 break;
867 }
868
869 case Instruction::EQ:
870 {
871 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
872
873 push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
874 break;
875 }
876
877 case Instruction::LEN:
878 {
879 Value* a = popAndResolveAsPtr(context);
880
881 if (a->valueType() == ValueType::List)
882 push(Value(static_cast<int>(a->constList().size())), context);
883 else if (a->valueType() == ValueType::String)
884 push(Value(static_cast<int>(a->string().size())), context);
885 else
887 "len",
888 { { types::Contract { { types::Typedef("value", ValueType::List) } },
890 { *a });
891 break;
892 }
893
895 {
896 Value* a = popAndResolveAsPtr(context);
897
898 if (a->valueType() == ValueType::List)
899 push((a->constList().size() == 0) ? Builtins::trueSym : Builtins::falseSym, context);
900 else if (a->valueType() == ValueType::String)
901 push((a->string().size() == 0) ? Builtins::trueSym : Builtins::falseSym, context);
902 else
904 "empty?",
905 { { types::Contract { { types::Typedef("value", ValueType::List) } },
907 { *a });
908 break;
909 }
910
912 {
913 Value* a = popAndResolveAsPtr(context);
914
915 if (a->valueType() == ValueType::List)
916 {
917 if (a->constList().size() < 2)
918 push(Value(ValueType::List), context);
919 else
920 {
921 std::vector<Value> tmp(a->constList().size() - 1);
922 for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
923 tmp[i - 1] = a->constList()[i];
924 push(Value(std::move(tmp)), context);
925 }
926 }
927 else if (a->valueType() == ValueType::String)
928 {
929 if (a->string().size() < 2)
930 push(Value(ValueType::String), context);
931 else
932 {
933 Value b = *a;
934 b.stringRef().erase_front(0);
935 push(std::move(b), context);
936 }
937 }
938 else
940 "tail",
941 { { types::Contract { { types::Typedef("value", ValueType::List) } },
943 { *a });
944
945 break;
946 }
947
949 {
950 Value* a = popAndResolveAsPtr(context);
951
952 if (a->valueType() == ValueType::List)
953 {
954 if (a->constList().size() == 0)
955 push(Builtins::nil, context);
956 else
957 {
958 Value b = a->constList()[0];
959 push(b, context);
960 }
961 }
962 else if (a->valueType() == ValueType::String)
963 {
964 if (a->string().size() == 0)
965 push(Value(ValueType::String), context);
966 else
967 push(Value(std::string(1, a->stringRef()[0])), context);
968 }
969 else
971 "head",
972 { { types::Contract { { types::Typedef("value", ValueType::List) } },
974 { *a });
975
976 break;
977 }
978
980 {
981 Value* a = popAndResolveAsPtr(context);
983 break;
984 }
985
987 {
988 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
989
990 if (b->valueType() != ValueType::String)
992 "assert",
994 { *a, *b });
995
996 if (*a == Builtins::falseSym)
997 throw AssertionFailed(b->stringRef().toString());
998 break;
999 }
1000
1002 {
1003 Value* a = popAndResolveAsPtr(context);
1004
1005 if (a->valueType() != ValueType::String)
1007 "toNumber",
1008 { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1009 { *a });
1010
1011 double val;
1012 if (Utils::isDouble(a->string().c_str(), &val))
1013 push(Value(val), context);
1014 else
1015 push(Builtins::nil, context);
1016 break;
1017 }
1018
1020 {
1021 std::stringstream ss;
1022 Value* a = popAndResolveAsPtr(context);
1023 ss << (*a);
1024 push(Value(ss.str()), context);
1025 break;
1026 }
1027
1028 case Instruction::AT:
1029 {
1030 Value* b = popAndResolveAsPtr(context);
1031 Value a = *popAndResolveAsPtr(context); // be careful, it's not a pointer
1032
1033 if (b->valueType() != ValueType::Number)
1035 "@",
1038 { a, *b });
1039
1040 long idx = static_cast<long>(b->number());
1041
1042 if (a.valueType() == ValueType::List)
1043 push(a.list()[idx < 0 ? a.list().size() + idx : idx], context);
1044 else if (a.valueType() == ValueType::String)
1045 push(Value(std::string(1, a.string()[idx < 0 ? a.string().size() + idx : idx])), context);
1046 else
1048 "@",
1051 { a, *b });
1052 break;
1053 }
1054
1055 case Instruction::AND_:
1056 {
1057 Value *a = popAndResolveAsPtr(context), *b = popAndResolveAsPtr(context);
1058
1060 break;
1061 }
1062
1063 case Instruction::OR_:
1064 {
1065 Value *a = popAndResolveAsPtr(context), *b = popAndResolveAsPtr(context);
1066
1068 break;
1069 }
1070
1071 case Instruction::MOD:
1072 {
1073 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1074
1075 if (a->valueType() != ValueType::Number)
1076 throw TypeError("Arguments of mod should be Numbers");
1077 if (b->valueType() != ValueType::Number)
1078 throw TypeError("Arguments of mod should be Numbers");
1079
1080 push(Value(std::fmod(a->number(), b->number())), context);
1081 break;
1082 }
1083
1084 case Instruction::TYPE:
1085 {
1086 Value* a = popAndResolveAsPtr(context);
1087
1088 push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
1089 break;
1090 }
1091
1093 {
1094 Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
1095
1096 if (closure->valueType() != ValueType::Closure)
1097 throw TypeError("Argument no 1 of hasField should be a Closure");
1098 if (field->valueType() != ValueType::String)
1099 throw TypeError("Argument no 2 of hasField should be a String");
1100
1101 auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef().toString());
1102 if (it == m_state.m_symbols.end())
1103 {
1104 push(Builtins::falseSym, context);
1105 break;
1106 }
1107
1108 uint16_t id = static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1109 push((*closure->refClosure().refScope())[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1110
1111 break;
1112 }
1113
1114 case Instruction::NOT:
1115 {
1116 Value* a = popAndResolveAsPtr(context);
1117
1118 push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
1119 break;
1120 }
1121
1122#pragma endregion
1123
1124 default:
1125 throwVMError("unknown instruction: " + std::to_string(static_cast<std::size_t>(inst)));
1126 break;
1127 }
1128
1129 // move forward
1130 ++context.ip;
1131
1132#ifdef ARK_PROFILER_MIPS
1133 ++instructions_executed;
1134#endif
1135 }
1136 }
1137 catch (const std::exception& e)
1138 {
1139 std::printf("%s\n", e.what());
1140 backtrace(context);
1141 m_exit_code = 1;
1142 }
1143 catch (...)
1144 {
1145 std::printf("Unknown error\n");
1146 backtrace(context);
1147 m_exit_code = 1;
1148 }
1149
1150 if (m_state.m_debug_level > 0)
1151 std::cout << "Estimated stack trashing: " << context.sp << "/" << VMStackSize << "\n";
1152
1153#ifdef ARK_PROFILER_MIPS
1154 auto end_time = std::chrono::system_clock::now();
1155 auto d = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
1156
1157 std::cout << "\nInstructions executed: " << instructions_executed << "\n"
1158 << "Time spent: " << d.count() << " us\n"
1159 << (static_cast<double>(instructions_executed) / d.count()) << " MIPS\n";
1160#endif
1161
1162 return m_exit_code;
1163 }
1164
1165 // ------------------------------------------
1166 // error handling
1167 // ------------------------------------------
1168
1169 uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
1170 {
1171 for (auto it = context.locals.rbegin(), it_end = context.locals.rend(); it != it_end; ++it)
1172 {
1173 if (auto id = (*it)->idFromValue(value); id < m_state.m_symbols.size())
1174 return id;
1175 }
1176 return std::numeric_limits<uint16_t>::max();
1177 }
1178
1179 void VM::throwVMError(const std::string& message)
1180 {
1181 throw std::runtime_error(message);
1182 }
1183
1184 void VM::backtrace(ExecutionContext& context) noexcept
1185 {
1186 int saved_ip = context.ip;
1187 std::size_t saved_pp = context.pp;
1188 uint16_t saved_sp = context.sp;
1189
1190 if (m_fc > 1)
1191 {
1192 // display call stack trace
1193 uint16_t it = m_fc;
1194 Scope old_scope = *context.locals.back().get();
1195
1196 while (it != 0)
1197 {
1198 std::cerr << "[" << termcolor::cyan << it << termcolor::reset << "] ";
1199 if (context.pp != 0)
1200 {
1201 uint16_t id = findNearestVariableIdWithValue(
1202 Value(static_cast<PageAddr_t>(context.pp)),
1203 context);
1204
1205 if (id < m_state.m_symbols.size())
1206 std::cerr << "In function `" << termcolor::green << m_state.m_symbols[id] << termcolor::reset << "'\n";
1207 else // should never happen
1208 std::cerr << "In function `" << termcolor::yellow << "???" << termcolor::reset << "'\n";
1209
1210 Value* ip;
1211 do
1212 {
1213 ip = popAndResolveAsPtr(context);
1214 } while (ip->valueType() != ValueType::InstPtr);
1215
1216 context.ip = ip->pageAddr();
1217 context.pp = pop(context)->pageAddr();
1218 returnFromFuncCall(context);
1219 --it;
1220 }
1221 else
1222 {
1223 std::printf("In global scope\n");
1224 break;
1225 }
1226
1227 if (m_fc - it > 7)
1228 {
1229 std::printf("...\n");
1230 break;
1231 }
1232 }
1233
1234 // display variables values in the current scope
1235 std::printf("\nCurrent scope variables values:\n");
1236 for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
1237 std::cerr << termcolor::cyan << m_state.m_symbols[old_scope.m_data[i].first] << termcolor::reset
1238 << " = " << old_scope.m_data[i].second << "\n";
1239
1240 while (m_fc != 1)
1241 {
1242 Value* tmp = pop(context);
1243 if (tmp->valueType() == ValueType::InstPtr)
1244 --m_fc;
1245 *tmp = m_no_value;
1246 }
1247 // pop the PP as well
1248 pop(context);
1249 }
1250
1251 std::cerr << termcolor::reset
1252 << "At IP: " << (saved_ip != -1 ? saved_ip : 0)
1253 << ", PP: " << saved_pp
1254 << ", SP: " << saved_sp
1255 << "\n";
1256 }
1257}
#define ARK_NO_NAME_FILE
Definition: Constants.hpp:26
Lots of utilities about the filesystem.
#define COZ_PROGRESS_NAMED(name)
Definition: Profiling.hpp:9
Lots of utilities about string, filesystem and more.
The ArkScript virtual machine.
An assertion error, only triggered from ArkScript code through (assert expr error-message)
Definition: Exceptions.hpp:71
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition: State.hpp:31
std::string m_filename
Definition: State.hpp:141
std::vector< Value > m_constants
Definition: State.hpp:146
std::unordered_map< std::string, Value > m_binded
Definition: State.hpp:150
std::vector< std::string > m_symbols
Definition: State.hpp:145
std::vector< bytecode_t > m_pages
Definition: State.hpp:147
unsigned m_debug_level
Definition: State.hpp:137
std::vector< std::string > m_libenv
Definition: State.hpp:140
A type error triggered when types don't match.
Definition: Exceptions.hpp:29
The ArkScript virtual machine, executing ArkScript bytecode.
Definition: VM.hpp:46
int run() noexcept
Run the bytecode held in the state.
Definition: VM.cpp:196
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Definition: VM.hpp:127
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Definition: VM.cpp:68
Value * popAndResolveAsPtr(internal::ExecutionContext &context)
Pop a value from the stack and resolve it if possible, then return it.
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
Definition: VM.hpp:132
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition: VM.hpp:126
std::size_t m_until_frame_count
Definition: VM.hpp:130
void * m_user_pointer
needed to pass data around when binding ArkScript in a program
Definition: VM.hpp:137
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Definition: VM.cpp:1169
void setUserPointer(void *ptr) noexcept
Set the User Pointer object.
Definition: VM.cpp:182
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
Definition: VM.cpp:90
bool m_running
Definition: VM.hpp:129
void push(const Value &val, internal::ExecutionContext &context)
Push a value on the stack.
Value call(const std::string &name, Args &&... args)
Call a function from ArkScript, by giving it arguments.
void throwVMError(const std::string &message)
Throw a VM error message.
Definition: VM.cpp:1179
Value * pop(internal::ExecutionContext &context)
Pop a value from the stack.
void returnFromFuncCall(internal::ExecutionContext &context)
Destroy the current frame and get back to the previous one, resuming execution.
void backtrace(internal::ExecutionContext &context) noexcept
Display a backtrace when the VM encounter an exception.
Definition: VM.cpp:1184
int safeRun(internal::ExecutionContext &context, std::size_t untilFrameCount=0)
Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if...
Definition: VM.cpp:211
void createNewScope(internal::ExecutionContext &context) noexcept
State & m_state
Definition: VM.hpp:125
uint16_t m_fc
current frames count
Definition: VM.hpp:128
void init() noexcept
Initialize the VM according to the parameters.
Definition: VM.cpp:35
Value * findNearestVariable(uint16_t id, internal::ExecutionContext &context) noexcept
Find the nearest variable of a given id.
void * getUserPointer() noexcept
Retrieves the stored pointer.
Definition: VM.cpp:187
friend class Value
Definition: VM.hpp:121
VM(State &state) noexcept
Construct a new vm t object.
Definition: VM.cpp:27
uint16_t readNumber(internal::ExecutionContext &context)
Read a 2 bytes number from the current bytecode page, starting at the current instruction.
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
Definition: VM.cpp:172
const std::vector< Value > & constList() const
Return the stored list.
std::vector< Value > & list()
Return the stored list as a reference.
Definition: Value.cpp:112
bool isConst() const noexcept
Check if the value is const or not.
internal::Closure & refClosure()
Return a reference to the closure held by the value.
Definition: Value.cpp:117
const String & string() const
Return the stored string.
Value * reference() const
Return the stored internal object reference.
Definition: Value.cpp:132
double number() const
Return the stored number.
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:139
ValueType valueType() const noexcept
Return the value type.
String & stringRef()
Return the stored string as a reference.
Definition: Value.cpp:122
void setConst(bool value) noexcept
Set the Const object.
internal::PageAddr_t pageAddr() const
Return the page address held by the value.
A special zero division error triggered when a number is divided by 0.
Definition: Exceptions.hpp:41
Closure management.
Definition: Closure.hpp:40
const Scope_t & scope() const noexcept
Return the scope held by the object.
Definition: Closure.cpp:22
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:87
std::size_t size() const noexcept
Return the size of the scope.
Definition: Scope.cpp:47
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:58
const std::vector< std::pair< std::string, Value > > builtins
uint16_t PageAddr_t
Definition: Closure.hpp:33
ARK_API void generateError(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.
Definition: TypeChecker.cpp:82
Definition: Builtins.hpp:21
constexpr std::size_t VMStackSize
Definition: Constants.hpp:53
const Value Nil
ArkScript Nil value.
Definition: VM.hpp:293
const std::array< std::string, 13 > types_to_str
Definition: Value.hpp:59
std::size_t pp
Page pointer.
std::optional< Scope_t > saved_scope
std::vector< uint8_t > scope_count_to_delete
A contract is a list of typed arguments that a function can follow.
Definition: TypeChecker.hpp:90
A type definition within a contract.
Definition: TypeChecker.hpp:71
Definition: VM.cpp:18
Ark::Value(* value)(std::vector< Ark::Value > &, Ark::VM *)
Definition: VM.cpp:20
char * name
Definition: VM.cpp:19