2021-02-07 15:40:00 +08:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2021 Thakee Nathees
|
|
|
|
* Licensed under: MIT License
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "vm.h"
|
|
|
|
|
2021-02-11 01:23:48 +08:00
|
|
|
#include "core.h"
|
2021-02-18 02:27:24 +08:00
|
|
|
#include "debug.h"
|
2021-02-11 01:23:48 +08:00
|
|
|
#include "utils.h"
|
|
|
|
|
2021-02-25 17:03:06 +08:00
|
|
|
#define HAS_ERROR() (vm->fiber->error != NULL)
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
// Initially allocated call frame capacity. Will grow dynamically.
|
|
|
|
#define INITIAL_CALL_FRAMES 4
|
|
|
|
|
|
|
|
// Minimum size of the stack.
|
2021-02-15 20:49:19 +08:00
|
|
|
#define MIN_STACK_SIZE 128
|
2021-02-12 01:35:43 +08:00
|
|
|
|
2021-04-25 23:19:39 +08:00
|
|
|
static void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
|
|
|
|
if (new_size == 0) {
|
|
|
|
free(memory);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return realloc(memory, new_size);
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void* vmRealloc(PKVM* self, void* memory, size_t old_size, size_t new_size) {
|
2021-02-07 15:40:00 +08:00
|
|
|
|
2021-04-25 23:19:39 +08:00
|
|
|
// TODO: Debug trace allocations here.
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
// Track the total allocated memory of the VM to trigger the GC.
|
2021-05-04 18:24:26 +08:00
|
|
|
// if vmRealloc is called for freeing, the old_size would be 0 since
|
2021-02-12 01:35:43 +08:00
|
|
|
// deallocated bytes are traced by garbage collector.
|
|
|
|
self->bytes_allocated += new_size - old_size;
|
2021-02-07 15:40:00 +08:00
|
|
|
|
2021-04-25 23:19:39 +08:00
|
|
|
if (new_size > 0 && self->bytes_allocated > self->next_gc) {
|
|
|
|
vmCollectGarbage(self);
|
|
|
|
}
|
2021-04-26 17:34:30 +08:00
|
|
|
Function* f = (Function*)memory;
|
2021-02-12 01:35:43 +08:00
|
|
|
if (new_size == 0) {
|
|
|
|
free(memory);
|
|
|
|
return NULL;
|
|
|
|
}
|
2021-02-07 15:40:00 +08:00
|
|
|
|
2021-04-25 23:19:39 +08:00
|
|
|
return self->config.realloc_fn(memory, new_size, self->config.user_data);
|
2021-02-07 15:40:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void pkInitConfiguration(pkConfiguration* config) {
|
2021-04-26 17:34:30 +08:00
|
|
|
config->realloc_fn = defaultRealloc;
|
|
|
|
|
|
|
|
// TODO: Handle Null functions before calling them.
|
|
|
|
config->error_fn = NULL;
|
|
|
|
config->write_fn = NULL;
|
|
|
|
|
|
|
|
config->load_script_fn = NULL;
|
2021-05-07 17:41:19 +08:00
|
|
|
config->resolve_path_fn = NULL;
|
2021-04-26 17:34:30 +08:00
|
|
|
config->user_data = NULL;
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
PKVM* pkNewVM(pkConfiguration* config) {
|
2021-05-06 22:19:30 +08:00
|
|
|
|
2021-05-08 18:54:07 +08:00
|
|
|
// TODO: If the [config] is NULL, initialize a default one.
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
pkReallocFn realloc_fn = defaultRealloc;
|
2021-05-06 22:19:30 +08:00
|
|
|
void* user_data = NULL;
|
|
|
|
if (config != NULL) {
|
|
|
|
realloc_fn = config->realloc_fn;
|
|
|
|
user_data = config->user_data;
|
|
|
|
}
|
2021-05-09 18:28:00 +08:00
|
|
|
PKVM* vm = (PKVM*)realloc_fn(NULL, sizeof(PKVM), user_data);
|
|
|
|
memset(vm, 0, sizeof(PKVM));
|
2021-05-06 22:19:30 +08:00
|
|
|
|
|
|
|
vm->config = *config;
|
|
|
|
vm->gray_list_count = 0;
|
|
|
|
vm->gray_list_capacity = MIN_CAPACITY;
|
|
|
|
vm->gray_list = (Object**)vm->config.realloc_fn(
|
|
|
|
NULL, sizeof(Object*) * vm->gray_list_capacity, NULL);
|
|
|
|
vm->next_gc = 1024 * 1024 * 10; // TODO:
|
|
|
|
|
|
|
|
vm->scripts = newMap(vm);
|
|
|
|
|
|
|
|
// TODO: no need to initialize if already done by another vm.
|
|
|
|
initializeCore(vm);
|
|
|
|
|
2021-04-26 17:34:30 +08:00
|
|
|
return vm;
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void pkFreeVM(PKVM* self) {
|
2021-04-26 17:34:30 +08:00
|
|
|
|
|
|
|
Object* obj = self->first;
|
|
|
|
while (obj != NULL) {
|
|
|
|
Object* next = obj->next;
|
|
|
|
freeObject(self, obj);
|
|
|
|
obj = next;
|
|
|
|
}
|
|
|
|
|
2021-05-01 18:13:39 +08:00
|
|
|
self->gray_list = (Object**)self->config.realloc_fn(
|
|
|
|
self->gray_list, 0, self->config.user_data);
|
2021-02-12 01:35:43 +08:00
|
|
|
|
2021-05-06 22:19:30 +08:00
|
|
|
DEALLOCATE(self, self);
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void vmPushTempRef(PKVM* self, Object* obj) {
|
2021-02-12 01:35:43 +08:00
|
|
|
ASSERT(obj != NULL, "Cannot reference to NULL.");
|
2021-04-25 23:19:39 +08:00
|
|
|
ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE,
|
|
|
|
"Too many temp references");
|
2021-02-12 01:35:43 +08:00
|
|
|
self->temp_reference[self->temp_reference_count++] = obj;
|
2021-02-07 15:40:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void vmPopTempRef(PKVM* self) {
|
2021-02-12 01:35:43 +08:00
|
|
|
ASSERT(self->temp_reference_count > 0, "Temporary reference is empty to pop.");
|
|
|
|
self->temp_reference_count--;
|
2021-02-07 15:40:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void vmCollectGarbage(PKVM* self) {
|
2021-04-25 23:19:39 +08:00
|
|
|
|
|
|
|
// Reset VM's bytes_allocated value and count it again so that we don't
|
|
|
|
// required to know the size of each object that'll be freeing.
|
|
|
|
self->bytes_allocated = 0;
|
|
|
|
|
2021-04-26 17:34:30 +08:00
|
|
|
// Mark core objects (mostlikely builtin functions).
|
|
|
|
markCoreObjects(self);
|
|
|
|
|
2021-05-06 22:19:30 +08:00
|
|
|
// Mark the scripts cache.
|
|
|
|
grayObject(&self->scripts->_super, self);
|
2021-04-26 17:34:30 +08:00
|
|
|
|
|
|
|
// Mark temp references.
|
|
|
|
for (int i = 0; i < self->temp_reference_count; i++) {
|
2021-05-01 18:13:39 +08:00
|
|
|
grayObject(self->temp_reference[i], self);
|
2021-04-26 17:34:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Garbage collection triggered at the middle of a compilation.
|
|
|
|
if (self->compiler != NULL) {
|
|
|
|
compilerMarkObjects(self->compiler, self);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Garbage collection triggered at the middle of runtime.
|
|
|
|
if (self->script != NULL) {
|
2021-05-01 18:13:39 +08:00
|
|
|
grayObject(&self->script->_super, self);
|
2021-04-26 17:34:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (self->fiber != NULL) {
|
2021-05-01 18:13:39 +08:00
|
|
|
grayObject(&self->fiber->_super, self);
|
2021-04-26 17:34:30 +08:00
|
|
|
}
|
|
|
|
|
2021-05-01 18:13:39 +08:00
|
|
|
blackenObjects(self);
|
|
|
|
|
|
|
|
TODO; // Sweep.
|
2021-04-25 23:19:39 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void* pkGetUserData(PKVM* vm) {
|
2021-02-17 02:28:03 +08:00
|
|
|
return vm->config.user_data;
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void pkSetUserData(PKVM* vm, void* user_data) {
|
2021-02-17 02:28:03 +08:00
|
|
|
vm->config.user_data = user_data;
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
static Script* getScript(PKVM* vm, String* name) {
|
2021-05-07 15:21:34 +08:00
|
|
|
Var scr = mapGet(vm->scripts, VAR_OBJ(&name->_super));
|
|
|
|
if (IS_UNDEF(scr)) return NULL;
|
|
|
|
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
|
|
|
|
return (Script*)AS_OBJ(scr);
|
|
|
|
}
|
|
|
|
|
2021-02-11 01:23:48 +08:00
|
|
|
/******************************************************************************
|
|
|
|
* RUNTIME *
|
|
|
|
*****************************************************************************/
|
|
|
|
|
2021-05-07 17:41:19 +08:00
|
|
|
// If failed to resolve it'll return false. Parameter [resolved] will be
|
|
|
|
// updated with a resolved path.
|
2021-05-09 18:28:00 +08:00
|
|
|
static bool resolveScriptPath(PKVM* vm, String** resolved) {
|
2021-05-07 17:41:19 +08:00
|
|
|
if (vm->config.resolve_path_fn == NULL) return true;
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
pkStringResult result;
|
2021-05-07 17:41:19 +08:00
|
|
|
const char* path = (*resolved)->data;
|
2021-05-07 15:21:34 +08:00
|
|
|
|
|
|
|
Fiber* fiber = vm->fiber;
|
2021-05-07 17:41:19 +08:00
|
|
|
if (fiber == NULL || fiber->frame_count <= 0) {
|
|
|
|
// fiber == NULL => vm haven't started yet and it's a root script.
|
|
|
|
result = vm->config.resolve_path_fn(vm, NULL, path);
|
|
|
|
} else {
|
|
|
|
Function* fn = fiber->frames[fiber->frame_count - 1].fn;
|
|
|
|
result = vm->config.resolve_path_fn(vm, fn->owner->name->data, path);
|
|
|
|
}
|
2021-05-07 15:21:34 +08:00
|
|
|
if (!result.success) return false;
|
|
|
|
|
|
|
|
// If the resolved string is the SAME as [path] don't allocate a new string.
|
2021-05-07 17:41:19 +08:00
|
|
|
if (result.string != path) *resolved = newString(vm, result.string);
|
2021-05-07 15:21:34 +08:00
|
|
|
if (result.on_done != NULL) result.on_done(vm, result);
|
|
|
|
|
|
|
|
return true;
|
2021-05-06 22:19:30 +08:00
|
|
|
}
|
|
|
|
|
2021-05-07 15:21:34 +08:00
|
|
|
// Import and return Script object as Var. If the script is imported and
|
|
|
|
// compiled here it'll set [is_new_script] to true oterwise (using the cached
|
|
|
|
// script) set to false.
|
2021-05-09 20:31:36 +08:00
|
|
|
static Var importScript(PKVM* vm, String* name, bool is_core,
|
|
|
|
bool* is_new_script) {
|
|
|
|
if (is_core) {
|
|
|
|
ASSERT(is_new_script == NULL, OOPS);
|
|
|
|
Script* core_lib = getCoreLib(name);
|
|
|
|
if (core_lib != NULL) {
|
|
|
|
return VAR_OBJ(&core_lib->_super);
|
|
|
|
}
|
|
|
|
vm->fiber->error = stringFormat(vm, "Failed to import core library @",
|
|
|
|
name);
|
|
|
|
return VAR_NULL;
|
2021-05-07 17:41:19 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 20:31:36 +08:00
|
|
|
// Relative path import.
|
|
|
|
|
|
|
|
ASSERT(is_new_script != NULL, OOPS);
|
2021-05-07 17:41:19 +08:00
|
|
|
if (!resolveScriptPath(vm, &name)) {
|
2021-05-07 15:21:34 +08:00
|
|
|
vm->fiber->error = stringFormat(vm, "Failed to resolve script @ from @",
|
|
|
|
name, vm->fiber->func->owner->name);
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
2021-05-09 20:31:36 +08:00
|
|
|
|
2021-05-07 15:21:34 +08:00
|
|
|
vmPushTempRef(vm, &name->_super);
|
2021-05-06 22:19:30 +08:00
|
|
|
|
|
|
|
// Check if the script is already cached (then use it).
|
|
|
|
Var scr = mapGet(vm->scripts, VAR_OBJ(&name->_super));
|
|
|
|
if (!IS_UNDEF(scr)) {
|
|
|
|
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
|
|
|
|
*is_new_script = false;
|
|
|
|
return scr;
|
|
|
|
}
|
|
|
|
*is_new_script = true;
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
pkStringResult result = { false, NULL, NULL };
|
2021-05-14 18:44:57 +08:00
|
|
|
if (vm->config.load_script_fn != NULL) {
|
2021-05-06 22:19:30 +08:00
|
|
|
result = vm->config.load_script_fn(vm, name->data);
|
2021-05-14 18:44:57 +08:00
|
|
|
}
|
2021-05-06 22:19:30 +08:00
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
vmPopTempRef(vm); // name
|
|
|
|
|
|
|
|
String* err_msg = stringFormat(vm, "Cannot import script '@'", name);
|
|
|
|
vm->fiber->error = err_msg; //< Set the error msg.
|
|
|
|
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-05-07 17:41:19 +08:00
|
|
|
Script* script = newScript(vm, name);
|
2021-05-07 15:21:34 +08:00
|
|
|
vmPushTempRef(vm, &script->_super);
|
|
|
|
mapSet(vm->scripts, vm, VAR_OBJ(&name->_super), VAR_OBJ(&script->_super));
|
|
|
|
vmPopTempRef(vm);
|
2021-05-06 22:19:30 +08:00
|
|
|
|
2021-05-07 17:41:19 +08:00
|
|
|
vmPopTempRef(vm); // name
|
|
|
|
|
2021-05-07 15:21:34 +08:00
|
|
|
bool success = compile(vm, script, result.string);
|
|
|
|
if (result.on_done) result.on_done(vm, result);
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
vm->fiber->error = stringFormat(vm, "Compilation of script '@' failed.",
|
|
|
|
name);
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return VAR_OBJ(&script->_super);
|
2021-05-06 22:19:30 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
static void ensureStackSize(PKVM* vm, int size) {
|
2021-02-25 17:03:06 +08:00
|
|
|
if (vm->fiber->stack_size > size) return;
|
2021-02-12 01:35:43 +08:00
|
|
|
TODO;
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
static inline void pushCallFrame(PKVM* vm, Function* fn) {
|
2021-02-12 01:35:43 +08:00
|
|
|
ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
|
|
|
|
|
|
|
|
// Grow the stack frame if needed.
|
2021-02-25 17:03:06 +08:00
|
|
|
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
|
|
|
|
int new_capacity = vm->fiber->frame_capacity * 2;
|
|
|
|
vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames,
|
|
|
|
sizeof(CallFrame) * vm->fiber->frame_capacity,
|
2021-02-12 01:35:43 +08:00
|
|
|
sizeof(CallFrame) * new_capacity);
|
2021-02-25 17:03:06 +08:00
|
|
|
vm->fiber->frame_capacity = new_capacity;
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Grow the stack if needed.
|
2021-02-25 17:03:06 +08:00
|
|
|
int stack_size = (int)(vm->fiber->sp - vm->fiber->stack);
|
2021-02-12 01:35:43 +08:00
|
|
|
int needed = stack_size + fn->fn->stack_size;
|
|
|
|
ensureStackSize(vm, needed);
|
|
|
|
|
2021-02-25 17:03:06 +08:00
|
|
|
CallFrame* frame = &vm->fiber->frames[vm->fiber->frame_count++];
|
|
|
|
frame->rbp = vm->fiber->ret;
|
2021-02-12 01:35:43 +08:00
|
|
|
frame->fn = fn;
|
|
|
|
frame->ip = fn->fn->opcodes.data;
|
|
|
|
}
|
|
|
|
|
2021-05-13 17:10:57 +08:00
|
|
|
void pkSetRuntimeError(PKVM* vm, const char* message) {
|
2021-05-14 18:44:57 +08:00
|
|
|
vm->fiber->error = newString(vm, message);
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void vmReportError(PKVM* vm) {
|
2021-02-12 01:35:43 +08:00
|
|
|
ASSERT(HAS_ERROR(), "runtimeError() should be called after an error.");
|
2021-05-14 18:44:57 +08:00
|
|
|
|
|
|
|
// TODO: pass the error to the caller of the fiber.
|
|
|
|
|
|
|
|
reportStackTrace(vm);
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
2021-05-12 18:57:35 +08:00
|
|
|
// FIXME: temp.
|
|
|
|
PKInterpretResult pkInterpretSource(PKVM* vm, const char* source) {
|
|
|
|
String* name = newString(vm, "@module");
|
|
|
|
vmPushTempRef(vm, &name->_super);
|
|
|
|
|
|
|
|
Script* scr = newScript(vm, name);
|
|
|
|
vmPushTempRef(vm, &scr->_super);
|
|
|
|
mapSet(vm->scripts, vm, VAR_OBJ(&name->_super), VAR_OBJ(&scr->_super));
|
|
|
|
vmPopTempRef(vm);
|
|
|
|
|
|
|
|
vmPopTempRef(vm); // name
|
|
|
|
|
|
|
|
// Compile the source.
|
|
|
|
bool success = compile(vm, scr, source);
|
|
|
|
//if (res.on_done) res.on_done(vm, res);
|
|
|
|
|
|
|
|
if (!success) return PK_RESULT_COMPILE_ERROR;
|
|
|
|
vm->script = scr;
|
|
|
|
|
|
|
|
return vmRunScript(vm, scr);
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
PKInterpretResult pkInterpret(PKVM* vm, const char* file) {
|
2021-02-12 01:35:43 +08:00
|
|
|
|
2021-05-07 15:21:34 +08:00
|
|
|
// Resolve file path.
|
2021-05-07 17:41:19 +08:00
|
|
|
String* name = newString(vm, file);
|
|
|
|
vmPushTempRef(vm, &name->_super);
|
|
|
|
|
|
|
|
if (!resolveScriptPath(vm, &name)) {
|
2021-05-14 18:44:57 +08:00
|
|
|
if (vm->config.error_fn != NULL) {
|
|
|
|
vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1,
|
|
|
|
stringFormat(vm, "Failed to resolve path '$'.", file)->data);
|
|
|
|
}
|
2021-05-09 18:28:00 +08:00
|
|
|
return PK_RESULT_COMPILE_ERROR;
|
2021-05-07 15:21:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load the script source.
|
2021-05-09 18:28:00 +08:00
|
|
|
pkStringResult res = vm->config.load_script_fn(vm, name->data);
|
2021-05-07 15:21:34 +08:00
|
|
|
if (!res.success) {
|
2021-05-14 18:44:57 +08:00
|
|
|
if (vm->config.error_fn != NULL) {
|
|
|
|
vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1,
|
|
|
|
stringFormat(vm, "Failed to load script '@'.", name)->data);
|
|
|
|
}
|
2021-05-09 18:28:00 +08:00
|
|
|
return PK_RESULT_COMPILE_ERROR;
|
2021-05-07 15:21:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load a new script to the vm's scripts cache.
|
|
|
|
Script* scr = getScript(vm, name);
|
|
|
|
if (scr == NULL) {
|
2021-05-07 17:41:19 +08:00
|
|
|
scr = newScript(vm, name);
|
2021-05-07 15:21:34 +08:00
|
|
|
vmPushTempRef(vm, &scr->_super);
|
|
|
|
mapSet(vm->scripts, vm, VAR_OBJ(&name->_super), VAR_OBJ(&scr->_super));
|
|
|
|
vmPopTempRef(vm);
|
|
|
|
}
|
|
|
|
vmPopTempRef(vm); // name
|
|
|
|
|
|
|
|
// Compile the source.
|
|
|
|
bool success = compile(vm, scr, res.string);
|
|
|
|
if (res.on_done) res.on_done(vm, res);
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
if (!success) return PK_RESULT_COMPILE_ERROR;
|
2021-05-07 15:21:34 +08:00
|
|
|
vm->script = scr;
|
|
|
|
|
|
|
|
return vmRunScript(vm, scr);
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|
|
|
|
|
2021-02-25 17:03:06 +08:00
|
|
|
#ifdef DEBUG
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
// FIXME: for temp debugging. (implement dump stack frames).
|
2021-05-09 18:28:00 +08:00
|
|
|
void _debugRuntime(PKVM* vm) {
|
2021-02-25 17:03:06 +08:00
|
|
|
return;
|
|
|
|
system("cls");
|
|
|
|
Fiber* fiber = vm->fiber;
|
|
|
|
|
|
|
|
for (int i = fiber->frame_count - 1; i >= 0; i--) {
|
|
|
|
CallFrame frame = fiber->frames[i];
|
|
|
|
|
|
|
|
Var* top = fiber->sp - 1;
|
|
|
|
if (i != fiber->frame_count - 1) {
|
|
|
|
top = fiber->frames[i + 1].rbp - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; top >= frame.rbp; top--) {
|
|
|
|
printf("[*]: ");
|
|
|
|
dumpValue(vm, *top); printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("----------------\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
PKInterpretResult vmRunScript(PKVM* vm, Script* _script) {
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2021-05-14 18:44:57 +08:00
|
|
|
// Reference to the instruction pointer in the call frame.
|
|
|
|
register uint8_t** ip;
|
|
|
|
#define IP (*ip) // Convinent macro to the instruction pointer.
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
register Var* rbp; //< Stack base pointer register.
|
|
|
|
register CallFrame* frame; //< Current call frame.
|
2021-02-13 01:40:19 +08:00
|
|
|
register Script* script; //< Currently executing script.
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2021-02-25 17:03:06 +08:00
|
|
|
vm->fiber = newFiber(vm);
|
|
|
|
vm->fiber->func = _script->body;
|
|
|
|
|
|
|
|
// Allocate stack.
|
|
|
|
int stack_size = utilPowerOf2Ceil(vm->fiber->func->fn->stack_size + 1);
|
|
|
|
if (stack_size < MIN_STACK_SIZE) stack_size = MIN_STACK_SIZE;
|
|
|
|
vm->fiber->stack_size = stack_size;
|
|
|
|
vm->fiber->stack = ALLOCATE_ARRAY(vm, Var, vm->fiber->stack_size);
|
|
|
|
vm->fiber->sp = vm->fiber->stack;
|
|
|
|
vm->fiber->ret = vm->fiber->stack;
|
|
|
|
|
|
|
|
// Allocate call frames.
|
|
|
|
vm->fiber->frame_capacity = INITIAL_CALL_FRAMES;
|
|
|
|
vm->fiber->frames = ALLOCATE_ARRAY(vm, CallFrame, vm->fiber->frame_capacity);
|
|
|
|
vm->fiber->frame_count = 1;
|
|
|
|
|
|
|
|
// Initialize VM's first frame.
|
|
|
|
vm->fiber->frames[0].ip = _script->body->fn->opcodes.data;
|
|
|
|
vm->fiber->frames[0].fn = _script->body;
|
|
|
|
vm->fiber->frames[0].rbp = vm->fiber->stack;
|
|
|
|
|
|
|
|
#define PUSH(value) (*vm->fiber->sp++ = (value))
|
|
|
|
#define POP() (*(--vm->fiber->sp))
|
|
|
|
#define DROP() (--vm->fiber->sp)
|
|
|
|
#define PEEK() (*(vm->fiber->sp - 1))
|
2021-05-14 18:44:57 +08:00
|
|
|
#define READ_BYTE() (*IP++)
|
|
|
|
#define READ_SHORT() (IP+=2, (uint16_t)((IP[-2] << 8) | IP[-1]))
|
2021-02-11 01:23:48 +08:00
|
|
|
|
|
|
|
// Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR.
|
2021-05-09 18:28:00 +08:00
|
|
|
#define CHECK_ERROR() \
|
|
|
|
do { \
|
|
|
|
if (HAS_ERROR()) { \
|
|
|
|
vmReportError(vm); \
|
|
|
|
return PK_RESULT_RUNTIME_ERROR; \
|
|
|
|
} \
|
2021-02-12 01:35:43 +08:00
|
|
|
} while (false)
|
|
|
|
|
2021-05-13 17:10:57 +08:00
|
|
|
// [err_msg] must be of type String.
|
|
|
|
#define RUNTIME_ERROR(err_msg) \
|
2021-02-17 02:28:03 +08:00
|
|
|
do { \
|
2021-05-13 17:10:57 +08:00
|
|
|
vm->fiber->error = err_msg; \
|
2021-02-17 02:28:03 +08:00
|
|
|
vmReportError(vm); \
|
2021-05-09 18:28:00 +08:00
|
|
|
return PK_RESULT_RUNTIME_ERROR; \
|
2021-02-12 01:35:43 +08:00
|
|
|
} while (false)
|
|
|
|
|
2021-05-14 18:44:57 +08:00
|
|
|
// Load the last call frame to vm's execution variables to resume/run the
|
|
|
|
// function.
|
2021-02-25 17:03:06 +08:00
|
|
|
#define LOAD_FRAME() \
|
|
|
|
do { \
|
|
|
|
frame = &vm->fiber->frames[vm->fiber->frame_count-1]; \
|
2021-05-14 18:44:57 +08:00
|
|
|
ip = &(frame->ip); \
|
2021-02-25 17:03:06 +08:00
|
|
|
rbp = frame->rbp; \
|
|
|
|
script = frame->fn->owner; \
|
2021-02-12 01:35:43 +08:00
|
|
|
} while (false)
|
2021-02-11 01:23:48 +08:00
|
|
|
|
|
|
|
#ifdef OPCODE
|
|
|
|
#error "OPCODE" should not be deifined here.
|
|
|
|
#endif
|
|
|
|
|
2021-02-25 17:03:06 +08:00
|
|
|
#if DEBUG
|
|
|
|
#define DEBUG_INSTRUCTION() _debugRuntime(vm)
|
|
|
|
#else
|
|
|
|
#define DEBUG_INSTRUCTION() do { } while (false)
|
|
|
|
#endif
|
|
|
|
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
#define SWITCH(code) \
|
|
|
|
L_vm_main_loop: \
|
|
|
|
DEBUG_INSTRUCTION(); \
|
|
|
|
switch (code = (Opcode)READ_BYTE())
|
2021-02-11 01:23:48 +08:00
|
|
|
#define OPCODE(code) case OP_##code
|
|
|
|
#define DISPATCH() goto L_vm_main_loop
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
PUSH(VAR_NULL); // Return value of the script body.
|
|
|
|
LOAD_FRAME();
|
|
|
|
|
|
|
|
Opcode instruction;
|
|
|
|
SWITCH(instruction) {
|
|
|
|
|
|
|
|
OPCODE(CONSTANT):
|
2021-02-18 02:27:24 +08:00
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t index = READ_SHORT();
|
2021-02-18 02:27:24 +08:00
|
|
|
ASSERT_INDEX(index, script->literals.count);
|
|
|
|
PUSH(script->literals.data[index]);
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
2021-02-18 02:27:24 +08:00
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
OPCODE(PUSH_NULL):
|
|
|
|
PUSH(VAR_NULL);
|
|
|
|
DISPATCH();
|
|
|
|
|
|
|
|
OPCODE(PUSH_TRUE):
|
|
|
|
PUSH(VAR_TRUE);
|
|
|
|
DISPATCH();
|
|
|
|
|
|
|
|
OPCODE(PUSH_FALSE):
|
|
|
|
PUSH(VAR_FALSE);
|
|
|
|
DISPATCH();
|
|
|
|
|
2021-02-13 01:40:19 +08:00
|
|
|
OPCODE(PUSH_LIST):
|
|
|
|
{
|
|
|
|
List* list = newList(vm, (uint32_t)READ_SHORT());
|
|
|
|
PUSH(VAR_OBJ(&list->_super));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-05-06 13:00:02 +08:00
|
|
|
OPCODE(PUSH_MAP):
|
|
|
|
{
|
|
|
|
Map* map = newMap(vm);
|
|
|
|
PUSH(VAR_OBJ(&map->_super));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-13 01:40:19 +08:00
|
|
|
OPCODE(LIST_APPEND):
|
|
|
|
{
|
|
|
|
Var elem = POP();
|
2021-05-06 13:00:02 +08:00
|
|
|
Var list = PEEK();
|
2021-02-13 01:40:19 +08:00
|
|
|
ASSERT(IS_OBJ(list) && AS_OBJ(list)->type == OBJ_LIST, OOPS);
|
|
|
|
varBufferWrite(&((List*)AS_OBJ(list))->elements, vm, elem);
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-05-06 13:00:02 +08:00
|
|
|
OPCODE(MAP_INSERT):
|
|
|
|
{
|
|
|
|
Var value = POP();
|
|
|
|
Var key = POP();
|
|
|
|
Var on = PEEK();
|
|
|
|
|
2021-05-13 17:10:57 +08:00
|
|
|
ASSERT(IS_OBJ(on) && AS_OBJ(on)->type == OBJ_MAP, OOPS);
|
|
|
|
|
|
|
|
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
|
|
|
|
RUNTIME_ERROR(stringFormat(vm, "$ type is not hashable.", varTypeName(key)));
|
|
|
|
} else {
|
|
|
|
mapSet((Map*)AS_OBJ(on), vm, key, value);
|
|
|
|
}
|
|
|
|
|
2021-05-06 13:00:02 +08:00
|
|
|
varsetSubscript(vm, on, key, value);
|
|
|
|
CHECK_ERROR();
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(PUSH_LOCAL_0):
|
|
|
|
OPCODE(PUSH_LOCAL_1):
|
|
|
|
OPCODE(PUSH_LOCAL_2):
|
|
|
|
OPCODE(PUSH_LOCAL_3):
|
|
|
|
OPCODE(PUSH_LOCAL_4):
|
|
|
|
OPCODE(PUSH_LOCAL_5):
|
|
|
|
OPCODE(PUSH_LOCAL_6):
|
|
|
|
OPCODE(PUSH_LOCAL_7):
|
|
|
|
OPCODE(PUSH_LOCAL_8):
|
|
|
|
{
|
|
|
|
int index = (int)(instruction - OP_PUSH_LOCAL_0);
|
2021-02-25 17:03:06 +08:00
|
|
|
PUSH(rbp[index + 1]); // +1: rbp[0] is return value.
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
OPCODE(PUSH_LOCAL_N):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t index = READ_SHORT();
|
2021-02-25 17:03:06 +08:00
|
|
|
PUSH(rbp[index + 1]); // +1: rbp[0] is return value.
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
|
|
|
OPCODE(STORE_LOCAL_0):
|
|
|
|
OPCODE(STORE_LOCAL_1):
|
|
|
|
OPCODE(STORE_LOCAL_2):
|
|
|
|
OPCODE(STORE_LOCAL_3):
|
|
|
|
OPCODE(STORE_LOCAL_4):
|
|
|
|
OPCODE(STORE_LOCAL_5):
|
|
|
|
OPCODE(STORE_LOCAL_6):
|
|
|
|
OPCODE(STORE_LOCAL_7):
|
|
|
|
OPCODE(STORE_LOCAL_8):
|
2021-02-12 14:53:52 +08:00
|
|
|
{
|
|
|
|
int index = (int)(instruction - OP_STORE_LOCAL_0);
|
2021-02-25 17:03:06 +08:00
|
|
|
rbp[index + 1] = PEEK(); // +1: rbp[0] is return value.
|
2021-02-12 14:53:52 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(STORE_LOCAL_N):
|
2021-02-12 14:53:52 +08:00
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t index = READ_SHORT();
|
2021-02-25 17:03:06 +08:00
|
|
|
rbp[index + 1] = PEEK(); // +1: rbp[0] is return value.
|
2021-02-12 14:53:52 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
OPCODE(PUSH_GLOBAL):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t index = READ_SHORT();
|
2021-02-13 01:40:19 +08:00
|
|
|
ASSERT(index < script->globals.count, OOPS);
|
2021-02-12 01:35:43 +08:00
|
|
|
PUSH(script->globals.data[index]);
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
|
|
|
OPCODE(STORE_GLOBAL):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t index = READ_SHORT();
|
2021-02-13 01:40:19 +08:00
|
|
|
ASSERT(index < script->globals.count, OOPS);
|
2021-02-13 21:57:59 +08:00
|
|
|
script->globals.data[index] = PEEK();
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
|
|
|
OPCODE(PUSH_FN):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t index = READ_SHORT();
|
2021-02-13 01:40:19 +08:00
|
|
|
ASSERT(index < script->functions.count, OOPS);
|
2021-02-12 01:35:43 +08:00
|
|
|
Function* fn = script->functions.data[index];
|
|
|
|
PUSH(VAR_OBJ(&fn->_super));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
|
|
|
OPCODE(PUSH_BUILTIN_FN):
|
|
|
|
{
|
|
|
|
Function* fn = getBuiltinFunction(READ_SHORT());
|
|
|
|
PUSH(VAR_OBJ(&fn->_super));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
|
|
|
OPCODE(POP):
|
|
|
|
DROP();
|
|
|
|
DISPATCH();
|
|
|
|
|
2021-05-06 22:19:30 +08:00
|
|
|
OPCODE(IMPORT) :
|
|
|
|
{
|
2021-05-09 20:31:36 +08:00
|
|
|
String* name = script->names.data[READ_SHORT()];
|
|
|
|
PUSH(importScript(vm, name, true, NULL));
|
2021-05-06 22:19:30 +08:00
|
|
|
CHECK_ERROR();
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(CALL):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t argc = READ_SHORT();
|
2021-02-25 17:03:06 +08:00
|
|
|
Var* callable = vm->fiber->sp - argc - 1;
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) {
|
|
|
|
|
|
|
|
Function* fn = (Function*)AS_OBJ(*callable);
|
2021-02-13 01:40:19 +08:00
|
|
|
|
|
|
|
// -1 argument means multiple number of args.
|
|
|
|
if (fn->arity != -1 && fn->arity != argc) {
|
2021-05-13 17:10:57 +08:00
|
|
|
String* arg_str = toString(vm, VAR_NUM(fn->arity), false);
|
|
|
|
vmPushTempRef(vm, &arg_str->_super);
|
|
|
|
String* msg = stringFormat(vm, "Expected excatly @ argument(s).",
|
|
|
|
arg_str);
|
|
|
|
vmPopTempRef(vm); // arg_str.
|
|
|
|
RUNTIME_ERROR(msg);
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now the function will never needed in the stack and it'll
|
|
|
|
// initialized with VAR_NULL as return value.
|
|
|
|
*callable = VAR_NULL;
|
|
|
|
|
2021-02-12 14:53:52 +08:00
|
|
|
// Next call frame starts here. (including return value).
|
2021-02-25 17:03:06 +08:00
|
|
|
vm->fiber->ret = callable;
|
2021-02-12 14:53:52 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
if (fn->is_native) {
|
2021-05-06 13:07:58 +08:00
|
|
|
if (fn->native == NULL) {
|
2021-05-13 17:10:57 +08:00
|
|
|
RUNTIME_ERROR(stringFormat(vm,
|
|
|
|
"Native function pointer of $ was NULL.", fn->name));
|
2021-05-06 13:07:58 +08:00
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
fn->native(vm);
|
2021-02-12 14:53:52 +08:00
|
|
|
// Pop function arguments except for the return value.
|
2021-02-25 17:03:06 +08:00
|
|
|
vm->fiber->sp = vm->fiber->ret + 1;
|
2021-02-12 01:35:43 +08:00
|
|
|
CHECK_ERROR();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
pushCallFrame(vm, fn);
|
2021-05-14 18:44:57 +08:00
|
|
|
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2021-05-13 17:10:57 +08:00
|
|
|
RUNTIME_ERROR(newString(vm, "Expected a function in call."));
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-05-11 14:38:23 +08:00
|
|
|
OPCODE(ITER):
|
2021-02-13 01:40:19 +08:00
|
|
|
{
|
2021-02-25 17:03:06 +08:00
|
|
|
Var* iter_value = (vm->fiber->sp - 1);
|
|
|
|
Var* iterator = (vm->fiber->sp - 2);
|
|
|
|
Var* container = (vm->fiber->sp - 3);
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t jump_offset = READ_SHORT();
|
2021-05-13 17:10:57 +08:00
|
|
|
|
2021-05-14 18:44:57 +08:00
|
|
|
bool iterated = varIterate(vm, *container, iterator, iter_value);
|
2021-05-13 17:10:57 +08:00
|
|
|
CHECK_ERROR();
|
2021-05-14 18:44:57 +08:00
|
|
|
if (!iterated) {
|
|
|
|
IP += jump_offset;
|
2021-02-13 01:40:19 +08:00
|
|
|
}
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-04-26 17:34:30 +08:00
|
|
|
OPCODE(JUMP):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t offset = READ_SHORT();
|
2021-05-14 18:44:57 +08:00
|
|
|
IP += offset;
|
2021-02-13 01:40:19 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-04-26 17:34:30 +08:00
|
|
|
OPCODE(LOOP):
|
|
|
|
{
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t offset = READ_SHORT();
|
2021-05-14 18:44:57 +08:00
|
|
|
IP -= offset;
|
2021-02-13 01:40:19 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OPCODE(JUMP_IF):
|
2021-02-15 20:49:19 +08:00
|
|
|
{
|
|
|
|
Var cond = POP();
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t offset = READ_SHORT();
|
2021-02-15 20:49:19 +08:00
|
|
|
if (toBool(cond)) {
|
2021-05-14 18:44:57 +08:00
|
|
|
IP += offset;
|
2021-02-15 20:49:19 +08:00
|
|
|
}
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(JUMP_IF_NOT):
|
2021-02-15 20:49:19 +08:00
|
|
|
{
|
|
|
|
Var cond = POP();
|
2021-05-05 12:55:27 +08:00
|
|
|
uint16_t offset = READ_SHORT();
|
2021-02-15 20:49:19 +08:00
|
|
|
if (!toBool(cond)) {
|
2021-05-14 18:44:57 +08:00
|
|
|
IP += offset;
|
2021-02-15 20:49:19 +08:00
|
|
|
}
|
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-12 14:53:52 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(RETURN):
|
2021-02-12 14:53:52 +08:00
|
|
|
{
|
2021-05-14 18:44:57 +08:00
|
|
|
// TODO: handle caller fiber.
|
|
|
|
|
2021-02-12 14:53:52 +08:00
|
|
|
Var ret = POP();
|
2021-05-14 18:44:57 +08:00
|
|
|
|
|
|
|
// Pop the last frame.
|
2021-02-25 17:03:06 +08:00
|
|
|
vm->fiber->frame_count--;
|
2021-02-12 14:53:52 +08:00
|
|
|
|
|
|
|
// If no more call frames. We're done.
|
2021-02-25 17:03:06 +08:00
|
|
|
if (vm->fiber->frame_count == 0) {
|
|
|
|
vm->fiber->sp = vm->fiber->stack;
|
2021-02-12 14:53:52 +08:00
|
|
|
PUSH(ret);
|
2021-05-09 18:28:00 +08:00
|
|
|
return PK_RESULT_SUCCESS;
|
2021-02-12 14:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the return value.
|
2021-02-25 17:03:06 +08:00
|
|
|
*(frame->rbp) = ret;
|
2021-02-12 14:53:52 +08:00
|
|
|
|
|
|
|
// Pop the locals and update stack pointer.
|
2021-02-25 17:03:06 +08:00
|
|
|
vm->fiber->sp = frame->rbp + 1; // +1: rbp is returned value.
|
2021-02-12 14:53:52 +08:00
|
|
|
|
|
|
|
LOAD_FRAME();
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(GET_ATTRIB):
|
2021-02-16 02:51:00 +08:00
|
|
|
{
|
|
|
|
Var on = POP();
|
|
|
|
String* name = script->names.data[READ_SHORT()];
|
|
|
|
PUSH(varGetAttrib(vm, on, name));
|
2021-05-14 18:44:57 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-16 02:51:00 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-05-10 03:00:59 +08:00
|
|
|
OPCODE(GET_ATTRIB_KEEP):
|
2021-02-16 02:51:00 +08:00
|
|
|
{
|
2021-05-10 03:00:59 +08:00
|
|
|
Var on = PEEK();
|
2021-02-16 02:51:00 +08:00
|
|
|
String* name = script->names.data[READ_SHORT()];
|
|
|
|
PUSH(varGetAttrib(vm, on, name));
|
2021-05-14 18:44:57 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-16 02:51:00 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(SET_ATTRIB):
|
2021-05-11 14:38:23 +08:00
|
|
|
{
|
|
|
|
Var value = POP();
|
|
|
|
Var on = POP();
|
|
|
|
String* name = script->names.data[READ_SHORT()];
|
|
|
|
varSetAttrib(vm, on, name, value);
|
|
|
|
PUSH(value);
|
2021-05-14 18:44:57 +08:00
|
|
|
CHECK_ERROR();
|
2021-05-11 14:38:23 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-16 02:51:00 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(GET_SUBSCRIPT):
|
2021-02-16 02:51:00 +08:00
|
|
|
{
|
|
|
|
Var key = POP();
|
|
|
|
Var on = POP();
|
|
|
|
PUSH(varGetSubscript(vm, on, key));
|
|
|
|
CHECK_ERROR();
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-05-11 14:38:23 +08:00
|
|
|
OPCODE(GET_SUBSCRIPT_KEEP):
|
2021-02-16 02:51:00 +08:00
|
|
|
{
|
2021-02-25 17:03:06 +08:00
|
|
|
Var key = *(vm->fiber->sp - 1);
|
|
|
|
Var on = *(vm->fiber->sp - 2);
|
2021-02-16 02:51:00 +08:00
|
|
|
PUSH(varGetSubscript(vm, on, key));
|
|
|
|
CHECK_ERROR();
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(SET_SUBSCRIPT):
|
2021-02-16 02:51:00 +08:00
|
|
|
{
|
|
|
|
Var value = POP();
|
|
|
|
Var key = POP();
|
|
|
|
Var on = POP();
|
|
|
|
|
|
|
|
varsetSubscript(vm, on, key, value);
|
|
|
|
CHECK_ERROR();
|
|
|
|
PUSH(value);
|
|
|
|
|
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-13 01:40:19 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(NEGATIVE):
|
2021-02-13 01:40:19 +08:00
|
|
|
{
|
|
|
|
Var num = POP();
|
|
|
|
if (!IS_NUM(num)) {
|
2021-05-13 17:10:57 +08:00
|
|
|
RUNTIME_ERROR(newString(vm, "Cannot negate a non numeric value."));
|
2021-02-13 01:40:19 +08:00
|
|
|
}
|
|
|
|
PUSH(VAR_NUM(-AS_NUM(num)));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(NOT):
|
2021-02-15 20:49:19 +08:00
|
|
|
{
|
|
|
|
Var val = POP();
|
|
|
|
PUSH(VAR_BOOL(!toBool(val)));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(BIT_NOT):
|
|
|
|
TODO;
|
|
|
|
|
2021-05-12 18:57:35 +08:00
|
|
|
// Do not ever use PUSH(binaryOp(vm, POP(), POP()));
|
|
|
|
// Function parameters are not evaluated in a defined order in C.
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(ADD):
|
2021-05-12 18:57:35 +08:00
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(varAdd(vm, l, r));
|
2021-02-13 01:40:19 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
OPCODE(SUBTRACT):
|
2021-05-12 18:57:35 +08:00
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(varSubtract(vm, l, r));
|
2021-02-13 01:40:19 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
OPCODE(MULTIPLY):
|
2021-05-12 18:57:35 +08:00
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(varMultiply(vm, l, r));
|
2021-02-13 01:40:19 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
OPCODE(DIVIDE):
|
2021-05-12 18:57:35 +08:00
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(varDivide(vm, l, r));
|
2021-02-13 01:40:19 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-12 01:35:43 +08:00
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-02-12 01:35:43 +08:00
|
|
|
|
|
|
|
OPCODE(MOD):
|
2021-05-12 18:57:35 +08:00
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
2021-05-14 18:44:57 +08:00
|
|
|
PUSH(varModulo(vm, l, r));
|
2021-05-12 15:57:51 +08:00
|
|
|
CHECK_ERROR();
|
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-05-12 15:57:51 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(BIT_AND):
|
|
|
|
OPCODE(BIT_OR):
|
|
|
|
OPCODE(BIT_XOR):
|
|
|
|
OPCODE(BIT_LSHIFT):
|
|
|
|
OPCODE(BIT_RSHIFT):
|
|
|
|
OPCODE(AND):
|
2021-05-11 14:38:23 +08:00
|
|
|
TODO;
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(OR):
|
2021-05-11 14:38:23 +08:00
|
|
|
{
|
|
|
|
TODO;
|
|
|
|
// Python like or operator.
|
|
|
|
//Var v1 = POP(), v2 = POP();
|
|
|
|
//if (toBool(v1)) {
|
|
|
|
// PUSH(v1);
|
|
|
|
//} else {
|
|
|
|
// PUSH(v2);
|
|
|
|
//}
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-05-12 18:57:35 +08:00
|
|
|
OPCODE(EQEQ) :
|
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(VAR_BOOL(isValuesEqual(l, r)));
|
2021-05-11 14:38:23 +08:00
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-05-11 14:38:23 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(NOTEQ):
|
2021-05-12 18:57:35 +08:00
|
|
|
{
|
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(VAR_BOOL(!isValuesEqual(l, r)));
|
2021-05-11 14:38:23 +08:00
|
|
|
DISPATCH();
|
2021-05-12 18:57:35 +08:00
|
|
|
}
|
2021-02-15 20:49:19 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(LT):
|
2021-02-15 20:49:19 +08:00
|
|
|
{
|
2021-05-12 18:57:35 +08:00
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(VAR_BOOL(varLesser(vm, l, r)));
|
2021-02-16 02:51:00 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-15 20:49:19 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(LTEQ):
|
2021-05-11 14:38:23 +08:00
|
|
|
{
|
2021-05-12 18:57:35 +08:00
|
|
|
Var r = POP(), l = POP();
|
2021-05-11 14:38:23 +08:00
|
|
|
bool lteq = varLesser(vm, l, r);
|
|
|
|
CHECK_ERROR();
|
|
|
|
|
|
|
|
if (!lteq) {
|
|
|
|
lteq = isValuesEqual(l, r);
|
|
|
|
CHECK_ERROR();
|
|
|
|
}
|
|
|
|
|
|
|
|
PUSH(VAR_BOOL(lteq));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-15 20:49:19 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(GT):
|
2021-02-15 20:49:19 +08:00
|
|
|
{
|
2021-05-12 18:57:35 +08:00
|
|
|
Var r = POP(), l = POP();
|
|
|
|
PUSH(VAR_BOOL(varGreater(vm, l, r)));
|
2021-02-16 02:51:00 +08:00
|
|
|
CHECK_ERROR();
|
2021-02-15 20:49:19 +08:00
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(GTEQ):
|
2021-05-11 14:38:23 +08:00
|
|
|
{
|
2021-05-12 18:57:35 +08:00
|
|
|
Var r = POP(), l = POP();
|
2021-05-11 14:38:23 +08:00
|
|
|
bool gteq = varGreater(vm, l, r);
|
|
|
|
CHECK_ERROR();
|
|
|
|
|
|
|
|
if (!gteq) {
|
|
|
|
gteq = isValuesEqual(l, r);
|
|
|
|
CHECK_ERROR();
|
|
|
|
}
|
|
|
|
|
|
|
|
PUSH(VAR_BOOL(gteq));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
2021-02-12 14:53:52 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(RANGE):
|
2021-02-12 14:53:52 +08:00
|
|
|
{
|
|
|
|
Var to = POP();
|
|
|
|
Var from = POP();
|
|
|
|
if (!IS_NUM(from) || !IS_NUM(to)) {
|
2021-05-13 17:10:57 +08:00
|
|
|
RUNTIME_ERROR(newString(vm, "Range arguments must be number."));
|
2021-02-12 14:53:52 +08:00
|
|
|
}
|
|
|
|
PUSH(VAR_OBJ(newRange(vm, AS_NUM(from), AS_NUM(to))));
|
|
|
|
DISPATCH();
|
|
|
|
}
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(IN):
|
2021-05-11 14:38:23 +08:00
|
|
|
// TODO: Implement bool varContaines(vm, on, value);
|
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
OPCODE(END):
|
2021-02-12 14:53:52 +08:00
|
|
|
TODO;
|
2021-02-12 01:35:43 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
return PK_RESULT_SUCCESS;
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|