2012 lines
61 KiB
C
2012 lines
61 KiB
C
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "wren.h"
|
|
#include "wren_common.h"
|
|
#include "wren_compiler.h"
|
|
#include "wren_core.h"
|
|
#include "wren_debug.h"
|
|
#include "wren_primitive.h"
|
|
#include "wren_vm.h"
|
|
|
|
#include <term.h>
|
|
|
|
#if WREN_OPT_META
|
|
#include "wren_opt_meta.h"
|
|
#endif
|
|
#if WREN_OPT_RANDOM
|
|
#include "wren_opt_random.h"
|
|
#endif
|
|
|
|
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
|
|
#include <time.h>
|
|
#include <printf.h>
|
|
#endif
|
|
|
|
// The behavior of realloc() when the size is 0 is implementation defined. It
|
|
// may return a non-NULL pointer which must not be dereferenced but nevertheless
|
|
// should be freed. To prevent that, we avoid calling realloc() with a zero
|
|
// size.
|
|
static void* defaultReallocate(void* ptr, size_t newSize, void* _)
|
|
{
|
|
if (newSize == 0)
|
|
{
|
|
free(ptr);
|
|
return NULL;
|
|
}
|
|
|
|
return realloc(ptr, newSize);
|
|
}
|
|
|
|
int wrenGetVersionNumber(void)
|
|
{
|
|
return WREN_VERSION_NUMBER;
|
|
}
|
|
|
|
void wrenInitConfiguration(WrenConfiguration* config)
|
|
{
|
|
config->reallocateFn = defaultReallocate;
|
|
config->resolveModuleFn = NULL;
|
|
config->loadModuleFn = NULL;
|
|
config->bindForeignMethodFn = NULL;
|
|
config->bindForeignClassFn = NULL;
|
|
config->writeFn = NULL;
|
|
config->errorFn = NULL;
|
|
config->initialHeapSize = 1024 * 1024 * 10;
|
|
config->minHeapSize = 1024 * 1024;
|
|
config->heapGrowthPercent = 50;
|
|
config->userData = NULL;
|
|
}
|
|
|
|
WrenVM* wrenNewVM(WrenConfiguration* config)
|
|
{
|
|
WrenReallocateFn reallocate = defaultReallocate;
|
|
void* userData = NULL;
|
|
if (config != NULL) {
|
|
userData = config->userData;
|
|
reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate;
|
|
}
|
|
|
|
WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData);
|
|
memset(vm, 0, sizeof(WrenVM));
|
|
|
|
// Copy the configuration if given one.
|
|
if (config != NULL)
|
|
{
|
|
memcpy(&vm->config, config, sizeof(WrenConfiguration));
|
|
|
|
// We choose to set this after copying,
|
|
// rather than modifying the user config pointer
|
|
vm->config.reallocateFn = reallocate;
|
|
}
|
|
else
|
|
{
|
|
wrenInitConfiguration(&vm->config);
|
|
}
|
|
|
|
// TODO: Should we allocate and free this during a GC?
|
|
vm->grayCount = 0;
|
|
// TODO: Tune this.
|
|
vm->grayCapacity = 4;
|
|
vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData);
|
|
vm->nextGC = vm->config.initialHeapSize;
|
|
|
|
wrenSymbolTableInit(&vm->methodNames);
|
|
|
|
vm->modules = wrenNewMap(vm);
|
|
wrenInitializeCore(vm);
|
|
return vm;
|
|
}
|
|
|
|
void wrenFreeVM(WrenVM* vm)
|
|
{
|
|
ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed.");
|
|
|
|
// Free all of the GC objects.
|
|
Obj* obj = vm->first;
|
|
while (obj != NULL)
|
|
{
|
|
Obj* next = obj->next;
|
|
wrenFreeObj(vm, obj);
|
|
obj = next;
|
|
}
|
|
|
|
// Free up the GC gray set.
|
|
vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData);
|
|
|
|
// Tell the user if they didn't free any handles. We don't want to just free
|
|
// them here because the host app may still have pointers to them that they
|
|
// may try to use. Better to tell them about the bug early.
|
|
ASSERT(vm->handles == NULL, "All handles have not been released.");
|
|
|
|
wrenSymbolTableClear(vm, &vm->methodNames);
|
|
|
|
DEALLOCATE(vm, vm);
|
|
}
|
|
|
|
void wrenCollectGarbage(WrenVM* vm)
|
|
{
|
|
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
|
|
printf("-- gc --\n");
|
|
|
|
size_t before = vm->bytesAllocated;
|
|
double startTime = (double)clock() / CLOCKS_PER_SEC;
|
|
#endif
|
|
|
|
// Mark all reachable objects.
|
|
|
|
// Reset this. As we mark objects, their size will be counted again so that
|
|
// we can track how much memory is in use without needing to know the size
|
|
// of each *freed* object.
|
|
//
|
|
// This is important because when freeing an unmarked object, we don't always
|
|
// know how much memory it is using. For example, when freeing an instance,
|
|
// we need to know its class to know how big it is, but its class may have
|
|
// already been freed.
|
|
vm->bytesAllocated = 0;
|
|
|
|
wrenGrayObj(vm, (Obj*)vm->modules);
|
|
|
|
// Temporary roots.
|
|
for (int i = 0; i < vm->numTempRoots; i++)
|
|
{
|
|
wrenGrayObj(vm, vm->tempRoots[i]);
|
|
}
|
|
|
|
// The current fiber.
|
|
wrenGrayObj(vm, (Obj*)vm->fiber);
|
|
|
|
// The handles.
|
|
for (WrenHandle* handle = vm->handles;
|
|
handle != NULL;
|
|
handle = handle->next)
|
|
{
|
|
wrenGrayValue(vm, handle->value);
|
|
}
|
|
|
|
// Any object the compiler is using (if there is one).
|
|
if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler);
|
|
|
|
// Method names.
|
|
wrenBlackenSymbolTable(vm, &vm->methodNames);
|
|
|
|
// Now that we have grayed the roots, do a depth-first search over all of the
|
|
// reachable objects.
|
|
wrenBlackenObjects(vm);
|
|
|
|
// Collect the white objects.
|
|
Obj** obj = &vm->first;
|
|
while (*obj != NULL)
|
|
{
|
|
if (!((*obj)->isDark))
|
|
{
|
|
// This object wasn't reached, so remove it from the list and free it.
|
|
Obj* unreached = *obj;
|
|
*obj = unreached->next;
|
|
wrenFreeObj(vm, unreached);
|
|
}
|
|
else
|
|
{
|
|
// This object was reached, so unmark it (for the next GC) and move on to
|
|
// the next.
|
|
(*obj)->isDark = false;
|
|
obj = &(*obj)->next;
|
|
}
|
|
}
|
|
|
|
// Calculate the next gc point, this is the current allocation plus
|
|
// a configured percentage of the current allocation.
|
|
vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100);
|
|
if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize;
|
|
|
|
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
|
|
double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime;
|
|
// Explicit cast because size_t has different sizes on 32-bit and 64-bit and
|
|
// we need a consistent type for the format string.
|
|
printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n",
|
|
(unsigned long)before,
|
|
(unsigned long)vm->bytesAllocated,
|
|
(unsigned long)(before - vm->bytesAllocated),
|
|
(unsigned long)vm->nextGC,
|
|
elapsed*1000.0);
|
|
#endif
|
|
}
|
|
|
|
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize)
|
|
{
|
|
#if WREN_DEBUG_TRACE_MEMORY
|
|
// Explicit cast because size_t has different sizes on 32-bit and 64-bit and
|
|
// we need a consistent type for the format string.
|
|
printf("reallocate %p %lu -> %lu\n",
|
|
memory, (unsigned long)oldSize, (unsigned long)newSize);
|
|
#endif
|
|
|
|
// If new bytes are being allocated, add them to the total count. If objects
|
|
// are being completely deallocated, we don't track that (since we don't
|
|
// track the original size). Instead, that will be handled while marking
|
|
// during the next GC.
|
|
vm->bytesAllocated += newSize - oldSize;
|
|
|
|
#if WREN_DEBUG_GC_STRESS
|
|
// Since collecting calls this function to free things, make sure we don't
|
|
// recurse.
|
|
if (newSize > 0) wrenCollectGarbage(vm);
|
|
#else
|
|
if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm);
|
|
#endif
|
|
|
|
return vm->config.reallocateFn(memory, newSize, vm->config.userData);
|
|
}
|
|
|
|
// Captures the local variable [local] into an [Upvalue]. If that local is
|
|
// already in an upvalue, the existing one will be used. (This is important to
|
|
// ensure that multiple closures closing over the same variable actually see
|
|
// the same variable.) Otherwise, it will create a new open upvalue and add it
|
|
// the fiber's list of upvalues.
|
|
static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local)
|
|
{
|
|
// If there are no open upvalues at all, we must need a new one.
|
|
if (fiber->openUpvalues == NULL)
|
|
{
|
|
fiber->openUpvalues = wrenNewUpvalue(vm, local);
|
|
return fiber->openUpvalues;
|
|
}
|
|
|
|
ObjUpvalue* prevUpvalue = NULL;
|
|
ObjUpvalue* upvalue = fiber->openUpvalues;
|
|
|
|
// Walk towards the bottom of the stack until we find a previously existing
|
|
// upvalue or pass where it should be.
|
|
while (upvalue != NULL && upvalue->value > local)
|
|
{
|
|
prevUpvalue = upvalue;
|
|
upvalue = upvalue->next;
|
|
}
|
|
|
|
// Found an existing upvalue for this local.
|
|
if (upvalue != NULL && upvalue->value == local) return upvalue;
|
|
|
|
// We've walked past this local on the stack, so there must not be an
|
|
// upvalue for it already. Make a new one and link it in in the right
|
|
// place to keep the list sorted.
|
|
ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local);
|
|
if (prevUpvalue == NULL)
|
|
{
|
|
// The new one is the first one in the list.
|
|
fiber->openUpvalues = createdUpvalue;
|
|
}
|
|
else
|
|
{
|
|
prevUpvalue->next = createdUpvalue;
|
|
}
|
|
|
|
createdUpvalue->next = upvalue;
|
|
return createdUpvalue;
|
|
}
|
|
|
|
// Closes any open upvalues that have been created for stack slots at [last]
|
|
// and above.
|
|
static void closeUpvalues(ObjFiber* fiber, Value* last)
|
|
{
|
|
while (fiber->openUpvalues != NULL &&
|
|
fiber->openUpvalues->value >= last)
|
|
{
|
|
ObjUpvalue* upvalue = fiber->openUpvalues;
|
|
|
|
// Move the value into the upvalue itself and point the upvalue to it.
|
|
upvalue->closed = *upvalue->value;
|
|
upvalue->value = &upvalue->closed;
|
|
|
|
// Remove it from the open upvalue list.
|
|
fiber->openUpvalues = upvalue->next;
|
|
}
|
|
}
|
|
|
|
// Looks up a foreign method in [moduleName] on [className] with [signature].
|
|
//
|
|
// This will try the host's foreign method binder first. If that fails, it
|
|
// falls back to handling the built-in modules.instruction
|
|
static WrenForeignMethodFn findForeignMethod(WrenVM* vm,
|
|
const char* moduleName,
|
|
const char* className,
|
|
bool isStatic,
|
|
const char* signature)
|
|
{
|
|
WrenForeignMethodFn method = NULL;
|
|
|
|
if (vm->config.bindForeignMethodFn != NULL)
|
|
{
|
|
method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic,
|
|
signature);
|
|
}
|
|
|
|
// If the host didn't provide it, see if it's an optional one.
|
|
if (method == NULL)
|
|
{
|
|
#if WREN_OPT_META
|
|
if (strcmp(moduleName, "meta") == 0)
|
|
{
|
|
method = wrenMetaBindForeignMethod(vm, className, isStatic, signature);
|
|
}
|
|
#endif
|
|
#if WREN_OPT_RANDOM
|
|
if (strcmp(moduleName, "random") == 0)
|
|
{
|
|
method = wrenRandomBindForeignMethod(vm, className, isStatic, signature);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return method;
|
|
}
|
|
|
|
// Defines [methodValue] as a method on [classObj].
|
|
//
|
|
// Handles both foreign methods where [methodValue] is a string containing the
|
|
// method's signature and Wren methods where [methodValue] is a function.
|
|
//
|
|
// Aborts the current fiber if the method is a foreign method that could not be
|
|
// found.
|
|
static void bindMethod(WrenVM* vm, int methodType, int symbol,
|
|
ObjModule* module, ObjClass* classObj, Value methodValue)
|
|
{
|
|
const char* className = classObj->name->value;
|
|
if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj;
|
|
|
|
Method method;
|
|
if (IS_STRING(methodValue))
|
|
{
|
|
const char* name = AS_CSTRING(methodValue);
|
|
method.type = METHOD_FOREIGN;
|
|
method.as.foreign = findForeignMethod(vm, module->name->value,
|
|
className,
|
|
methodType == CODE_METHOD_STATIC,
|
|
name);
|
|
|
|
if (method.as.foreign == NULL)
|
|
{
|
|
vm->fiber->error = wrenStringFormat(vm,
|
|
"Could not find foreign method '@' for class $ in module '$'.",
|
|
methodValue, classObj->name->value, module->name->value);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
method.as.closure = AS_CLOSURE(methodValue);
|
|
method.type = METHOD_BLOCK;
|
|
|
|
// Patch up the bytecode now that we know the superclass.
|
|
wrenBindMethodCode(classObj, method.as.closure->fn);
|
|
}
|
|
|
|
wrenBindMethod(vm, classObj, symbol, method);
|
|
}
|
|
|
|
static void callForeign(WrenVM* vm, ObjFiber* fiber,
|
|
WrenForeignMethodFn foreign, int numArgs)
|
|
{
|
|
ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call.");
|
|
vm->apiStack = fiber->stackTop - numArgs;
|
|
|
|
foreign(vm);
|
|
|
|
// Discard the stack slots for the arguments and temporaries but leave one
|
|
// for the result.
|
|
fiber->stackTop = vm->apiStack + 1;
|
|
|
|
vm->apiStack = NULL;
|
|
}
|
|
|
|
// Handles the current fiber having aborted because of an error.
|
|
//
|
|
// Walks the call chain of fibers, aborting each one until it hits a fiber that
|
|
// handles the error. If none do, tells the VM to stop.
|
|
static void runtimeError(WrenVM* vm)
|
|
{
|
|
ASSERT(wrenHasError(vm->fiber), "Should only call this after an error.");
|
|
|
|
ObjFiber* current = vm->fiber;
|
|
Value error = current->error;
|
|
|
|
while (current != NULL)
|
|
{
|
|
// Every fiber along the call chain gets aborted with the same error.
|
|
current->error = error;
|
|
|
|
// If the caller ran this fiber using "try", give it the error and stop.
|
|
if (current->state == FIBER_TRY)
|
|
{
|
|
// Make the caller's try method return the error message.
|
|
current->caller->stackTop[-1] = vm->fiber->error;
|
|
vm->fiber = current->caller;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, unhook the caller since we will never re}sume and return to it.
|
|
ObjFiber* caller = current->caller;
|
|
current->caller = NULL;
|
|
current = caller;
|
|
}
|
|
|
|
// If we got here, nothing caught the error, so show the stack trace.
|
|
wrenDebugPrintStackTrace(vm);
|
|
vm->fiber = NULL;
|
|
vm->apiStack = NULL;
|
|
}
|
|
|
|
// Aborts the current fiber with an appropriate method not found error for a
|
|
// method with [symbol] on [classObj].
|
|
static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
|
|
{
|
|
vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.",
|
|
OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value);
|
|
}
|
|
|
|
// Looks up the previously loaded module with [name].
|
|
//
|
|
// Returns `NULL` if no module with that name has been loaded.
|
|
static ObjModule* getModule(WrenVM* vm, Value name)
|
|
{
|
|
Value moduleValue = wrenMapGet(vm->modules, name);
|
|
return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL;
|
|
}
|
|
|
|
static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source,
|
|
bool isExpression, bool printErrors)
|
|
{
|
|
// See if the module has already been loaded.
|
|
ObjModule* module = getModule(vm, name);
|
|
if (module == NULL)
|
|
{
|
|
module = wrenNewModule(vm, AS_STRING(name));
|
|
|
|
// It's possible for the wrenMapSet below to resize the modules map,
|
|
// and trigger a GC while doing so. When this happens it will collect
|
|
// the module we've just created. Once in the map it is safe.
|
|
wrenPushRoot(vm, (Obj*)module);
|
|
|
|
// Store it in the VM's module registry so we don't load the same module
|
|
// multiple times.
|
|
wrenMapSet(vm, vm->modules, name, OBJ_VAL(module));
|
|
|
|
wrenPopRoot(vm);
|
|
|
|
// Implicitly import the core module.
|
|
ObjModule* coreModule = getModule(vm, NULL_VAL);
|
|
for (int i = 0; i < coreModule->variables.count; i++)
|
|
{
|
|
wrenDefineVariable(vm, module,
|
|
coreModule->variableNames.data[i]->value,
|
|
coreModule->variableNames.data[i]->length,
|
|
coreModule->variables.data[i], NULL);
|
|
}
|
|
}
|
|
|
|
ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors);
|
|
if (fn == NULL)
|
|
{
|
|
// TODO: Should we still store the module even if it didn't compile?
|
|
return NULL;
|
|
}
|
|
|
|
// Functions are always wrapped in closures.
|
|
wrenPushRoot(vm, (Obj*)fn);
|
|
ObjClosure* closure = wrenNewClosure(vm, fn);
|
|
wrenPopRoot(vm); // fn.
|
|
|
|
return closure;
|
|
}
|
|
|
|
// Verifies that [superclassValue] is a valid object to inherit from. That
|
|
// means it must be a class and cannot be the class of any built-in type.
|
|
//
|
|
// Also validates that it doesn't result in a class with too many fields and
|
|
// the other limitations foreign classes have.
|
|
//
|
|
// If successful, returns `null`. Otherwise, returns a string for the runtime
|
|
// error message.
|
|
static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue,
|
|
int numFields)
|
|
{
|
|
// Make sure the superclass is a class.
|
|
if (!IS_CLASS(superclassValue))
|
|
{
|
|
return wrenStringFormat(vm,
|
|
"Class '@' cannot inherit from a non-class object.",
|
|
name);
|
|
}
|
|
|
|
// Make sure it doesn't inherit from a sealed built-in type. Primitive methods
|
|
// on these classes assume the instance is one of the other Obj___ types and
|
|
// will fail horribly if it's actually an ObjInstance.
|
|
ObjClass* superclass = AS_CLASS(superclassValue);
|
|
if (superclass == vm->classClass ||
|
|
superclass == vm->fiberClass ||
|
|
superclass == vm->fnClass || // Includes OBJ_CLOSURE.
|
|
superclass == vm->listClass ||
|
|
superclass == vm->mapClass ||
|
|
superclass == vm->rangeClass ||
|
|
superclass == vm->stringClass ||
|
|
superclass == vm->boolClass ||
|
|
superclass == vm->nullClass ||
|
|
superclass == vm->numClass)
|
|
{
|
|
return wrenStringFormat(vm,
|
|
"Class '@' cannot inherit from built-in class '@'.",
|
|
name, OBJ_VAL(superclass->name));
|
|
}
|
|
|
|
if (superclass->numFields == -1)
|
|
{
|
|
return wrenStringFormat(vm,
|
|
"Class '@' cannot inherit from foreign class '@'.",
|
|
name, OBJ_VAL(superclass->name));
|
|
}
|
|
|
|
if (numFields == -1 && superclass->numFields > 0)
|
|
{
|
|
return wrenStringFormat(vm,
|
|
"Foreign class '@' may not inherit from a class with fields.",
|
|
name);
|
|
}
|
|
|
|
if (superclass->numFields + numFields > MAX_FIELDS)
|
|
{
|
|
return wrenStringFormat(vm,
|
|
"Class '@' may not have more than 255 fields, including inherited "
|
|
"ones.", name);
|
|
}
|
|
|
|
return NULL_VAL;
|
|
}
|
|
|
|
static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
|
|
{
|
|
WrenForeignClassMethods methods;
|
|
methods.allocate = NULL;
|
|
methods.finalize = NULL;
|
|
|
|
// Check the optional built-in module first so the host can override it.
|
|
|
|
if (vm->config.bindForeignClassFn != NULL)
|
|
{
|
|
methods = vm->config.bindForeignClassFn(vm, module->name->value,
|
|
classObj->name->value);
|
|
}
|
|
|
|
// If the host didn't provide it, see if it's a built in optional module.
|
|
if (methods.allocate == NULL && methods.finalize == NULL)
|
|
{
|
|
#if WREN_OPT_RANDOM
|
|
if (strcmp(module->name->value, "random") == 0)
|
|
{
|
|
methods = wrenRandomBindForeignClass(vm, module->name->value,
|
|
classObj->name->value);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Method method;
|
|
method.type = METHOD_FOREIGN;
|
|
|
|
// Add the symbol even if there is no allocator so we can ensure that the
|
|
// symbol itself is always in the symbol table.
|
|
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>", 10);
|
|
if (methods.allocate != NULL)
|
|
{
|
|
method.as.foreign = methods.allocate;
|
|
wrenBindMethod(vm, classObj, symbol, method);
|
|
}
|
|
|
|
// Add the symbol even if there is no finalizer so we can ensure that the
|
|
// symbol itself is always in the symbol table.
|
|
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
|
|
if (methods.finalize != NULL)
|
|
{
|
|
method.as.foreign = (WrenForeignMethodFn)methods.finalize;
|
|
wrenBindMethod(vm, classObj, symbol, method);
|
|
}
|
|
}
|
|
|
|
// Completes the process for creating a new class.
|
|
//
|
|
// The class attributes instance and the class itself should be on the
|
|
// top of the fiber's stack.
|
|
//
|
|
// This process handles moving the attribute data for a class from
|
|
// compile time to runtime, since it now has all the attributes associated
|
|
// with a class, including for methods.
|
|
static void endClass(WrenVM* vm)
|
|
{
|
|
// Pull the attributes and class off the stack
|
|
Value attributes = vm->fiber->stackTop[-2];
|
|
Value classValue = vm->fiber->stackTop[-1];
|
|
|
|
// Remove the stack items
|
|
vm->fiber->stackTop -= 2;
|
|
|
|
ObjClass* classObj = AS_CLASS(classValue);
|
|
classObj->attributes = attributes;
|
|
}
|
|
|
|
// Creates a new class.
|
|
//
|
|
// If [numFields] is -1, the class is a foreign class. The name and superclass
|
|
// should be on top of the fiber's stack. After calling this, the top of the
|
|
// stack will contain the new class.
|
|
//
|
|
// Aborts the current fiber if an error occurs.
|
|
static void createClass(WrenVM* vm, int numFields, ObjModule* module)
|
|
{
|
|
// Pull the name and superclass off the stack.
|
|
Value name = vm->fiber->stackTop[-2];
|
|
Value superclass = vm->fiber->stackTop[-1];
|
|
|
|
// We have two values on the stack and we are going to leave one, so discard
|
|
// the other slot.
|
|
vm->fiber->stackTop--;
|
|
|
|
vm->fiber->error = validateSuperclass(vm, name, superclass, numFields);
|
|
if (wrenHasError(vm->fiber)) return;
|
|
|
|
ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields,
|
|
AS_STRING(name));
|
|
vm->fiber->stackTop[-1] = OBJ_VAL(classObj);
|
|
|
|
if (numFields == -1) bindForeignClass(vm, classObj, module);
|
|
}
|
|
|
|
static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
|
|
{
|
|
ObjClass* classObj = AS_CLASS(stack[0]);
|
|
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
|
|
|
|
// TODO: Don't look up every time.
|
|
int symbol = wrenSymbolTableFind(&vm->methodNames, "<allocate>", 10);
|
|
ASSERT(symbol != -1, "Should have defined <allocate> symbol.");
|
|
|
|
ASSERT(classObj->methods.count > symbol, "Class should have allocator.");
|
|
Method* method = &classObj->methods.data[symbol];
|
|
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
|
|
|
|
// Pass the constructor arguments to the allocator as well.
|
|
ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call.");
|
|
vm->apiStack = stack;
|
|
|
|
method->as.foreign(vm);
|
|
|
|
vm->apiStack = NULL;
|
|
}
|
|
|
|
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
|
|
{
|
|
// TODO: Don't look up every time.
|
|
int symbol = wrenSymbolTableFind(&vm->methodNames, "<finalize>", 10);
|
|
ASSERT(symbol != -1, "Should have defined <finalize> symbol.");
|
|
|
|
// If there are no finalizers, don't finalize it.
|
|
if (symbol == -1) return;
|
|
|
|
// If the class doesn't have a finalizer, bail out.
|
|
ObjClass* classObj = foreign->obj.classObj;
|
|
if (symbol >= classObj->methods.count) return;
|
|
|
|
Method* method = &classObj->methods.data[symbol];
|
|
if (method->type == METHOD_NONE) return;
|
|
|
|
ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign.");
|
|
|
|
WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign;
|
|
finalizer(foreign->data);
|
|
}
|
|
|
|
// Let the host resolve an imported module name if it wants to.
|
|
static Value resolveModule(WrenVM* vm, Value name)
|
|
{
|
|
// If the host doesn't care to resolve, leave the name alone.
|
|
if (vm->config.resolveModuleFn == NULL) return name;
|
|
|
|
ObjFiber* fiber = vm->fiber;
|
|
ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn;
|
|
ObjString* importer = fn->module->name;
|
|
|
|
const char* resolved = vm->config.resolveModuleFn(vm, importer->value,
|
|
AS_CSTRING(name));
|
|
if (resolved == NULL)
|
|
{
|
|
vm->fiber->error = wrenStringFormat(vm,
|
|
"Could not resolve module '@' imported from '@'.",
|
|
name, OBJ_VAL(importer));
|
|
return NULL_VAL;
|
|
}
|
|
|
|
// If they resolved to the exact same string, we don't need to copy it.
|
|
if (resolved == AS_CSTRING(name)) return name;
|
|
|
|
// Copy the string into a Wren String object.
|
|
name = wrenNewString(vm, resolved);
|
|
DEALLOCATE(vm, (char*)resolved);
|
|
return name;
|
|
}
|
|
|
|
static Value importModule(WrenVM* vm, Value name)
|
|
{
|
|
name = resolveModule(vm, name);
|
|
|
|
// If the module is already loaded, we don't need to do anything.
|
|
Value existing = wrenMapGet(vm->modules, name);
|
|
if (!IS_UNDEFINED(existing)) return existing;
|
|
|
|
wrenPushRoot(vm, AS_OBJ(name));
|
|
|
|
WrenLoadModuleResult result = {0};
|
|
const char* source = NULL;
|
|
|
|
// Let the host try to provide the module.
|
|
if (vm->config.loadModuleFn != NULL)
|
|
{
|
|
result = vm->config.loadModuleFn(vm, AS_CSTRING(name));
|
|
}
|
|
|
|
// If the host didn't provide it, see if it's a built in optional module.
|
|
if (result.source == NULL)
|
|
{
|
|
result.onComplete = NULL;
|
|
ObjString* nameString = AS_STRING(name);
|
|
#if WREN_OPT_META
|
|
if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource();
|
|
#endif
|
|
#if WREN_OPT_RANDOM
|
|
if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource();
|
|
#endif
|
|
}
|
|
|
|
if (result.source == NULL)
|
|
{
|
|
vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name);
|
|
wrenPopRoot(vm); // name.
|
|
return NULL_VAL;
|
|
}
|
|
|
|
ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true);
|
|
|
|
// Now that we're done, give the result back in case there's cleanup to do.
|
|
if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result);
|
|
|
|
if (moduleClosure == NULL)
|
|
{
|
|
vm->fiber->error = wrenStringFormat(vm,
|
|
"Could not compile module '@'.", name);
|
|
wrenPopRoot(vm); // name.
|
|
return NULL_VAL;
|
|
}
|
|
|
|
wrenPopRoot(vm); // name.
|
|
|
|
// Return the closure that executes the module.
|
|
return OBJ_VAL(moduleClosure);
|
|
}
|
|
|
|
static Value getModuleVariable(WrenVM* vm, ObjModule* module,
|
|
Value variableName)
|
|
{
|
|
ObjString* variable = AS_STRING(variableName);
|
|
uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames,
|
|
variable->value,
|
|
variable->length);
|
|
|
|
// It's a runtime error if the imported variable does not exist.
|
|
if (variableEntry != UINT32_MAX)
|
|
{
|
|
return module->variables.data[variableEntry];
|
|
}
|
|
|
|
vm->fiber->error = wrenStringFormat(vm,
|
|
"Could not find a variable named '@' in module '@'.",
|
|
variableName, OBJ_VAL(module->name));
|
|
return NULL_VAL;
|
|
}
|
|
|
|
inline static bool checkArity(WrenVM* vm, Value value, int numArgs)
|
|
{
|
|
ASSERT(IS_CLOSURE(value), "Receiver must be a closure.");
|
|
ObjFn* fn = AS_CLOSURE(value)->fn;
|
|
|
|
// We only care about missing arguments, not extras. The "- 1" is because
|
|
// numArgs includes the receiver, the function itself, which we don't want to
|
|
// count.
|
|
if (numArgs - 1 >= fn->arity) return true;
|
|
|
|
vm->fiber->error = CONST_STRING(vm, "Function expects more arguments.");
|
|
return false;
|
|
}
|
|
|
|
extern volatile int must_yield;
|
|
extern jmp_buf sched_ctxbuf;
|
|
// restore execution after a yield
|
|
__attribute__((noreturn)) void wrenResume(WrenVM *vm) { longjmp(vm->ctxbuf, 1); }
|
|
|
|
// The main bytecode interpreter loop. This is where the magic happens. It is
|
|
// also, as you can imagine, highly performance critical.
|
|
static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
|
{
|
|
// Remember the current fiber so we can find it if a GC happens.
|
|
vm->fiber = fiber;
|
|
fiber->state = FIBER_ROOT;
|
|
|
|
// Hoist these into local variables. They are accessed frequently in the loop
|
|
// but assigned less frequently. Keeping them in locals and updating them when
|
|
// a call frame has been pushed or popped gives a large speed boost.
|
|
register CallFrame* frame;
|
|
register Value* stackStart;
|
|
register uint8_t* ip;
|
|
register ObjFn* fn;
|
|
|
|
// These macros are designed to only be invoked within this function.
|
|
#define PUSH(value) (*fiber->stackTop++ = value)
|
|
#define POP() (*(--fiber->stackTop))
|
|
#define DROP() (fiber->stackTop--)
|
|
#define PEEK() (*(fiber->stackTop - 1))
|
|
#define PEEK2() (*(fiber->stackTop - 2))
|
|
#define READ_BYTE() (*ip++)
|
|
#define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1]))
|
|
|
|
// Use this before a CallFrame is pushed to store the local variables back
|
|
// into the current one.
|
|
#define STORE_FRAME() frame->ip = ip
|
|
|
|
// Use this after a CallFrame has been pushed or popped to refresh the local
|
|
// variables.
|
|
#define LOAD_FRAME() \
|
|
do \
|
|
{ \
|
|
frame = &fiber->frames[fiber->numFrames - 1]; \
|
|
stackStart = frame->stackStart; \
|
|
ip = frame->ip; \
|
|
fn = frame->closure->fn; \
|
|
} while (false)
|
|
|
|
// Terminates the current fiber with error string [error]. If another calling
|
|
// fiber is willing to catch the error, transfers control to it, otherwise
|
|
// exits the interpreter.
|
|
#define RUNTIME_ERROR() \
|
|
do \
|
|
{ \
|
|
STORE_FRAME(); \
|
|
runtimeError(vm); \
|
|
if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \
|
|
fiber = vm->fiber; \
|
|
LOAD_FRAME(); \
|
|
DISPATCH(); \
|
|
} while (false)
|
|
|
|
#if WREN_DEBUG_TRACE_INSTRUCTIONS
|
|
// Prints the stack and instruction before each instruction is executed.
|
|
#define DEBUG_TRACE_INSTRUCTIONS() \
|
|
do \
|
|
{ \
|
|
wrenDumpStack(fiber); \
|
|
wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \
|
|
} while (false)
|
|
#else
|
|
#define DEBUG_TRACE_INSTRUCTIONS() do { } while (false)
|
|
#endif
|
|
|
|
#define CHECK_SCHEDULER() \
|
|
do \
|
|
{ \
|
|
__builtin_expect(must_yield == 0, 1); \
|
|
if (must_yield) { \
|
|
if (!setjmp(vm->ctxbuf)) \
|
|
longjmp(sched_ctxbuf, 1); \
|
|
} \
|
|
} while (false)
|
|
|
|
#if WREN_COMPUTED_GOTO
|
|
|
|
static void* dispatchTable[] = {
|
|
#define OPCODE(name, _) &&code_##name,
|
|
#include "wren_opcodes.h"
|
|
#undef OPCODE
|
|
};
|
|
|
|
#define INTERPRET_LOOP DISPATCH();
|
|
#define CASE_CODE(name) code_##name
|
|
|
|
#define DISPATCH() \
|
|
do \
|
|
{ \
|
|
CHECK_SCHEDULER(); \
|
|
DEBUG_TRACE_INSTRUCTIONS(); \
|
|
goto *dispatchTable[instruction = (Code)READ_BYTE()]; \
|
|
} while (false)
|
|
|
|
#else
|
|
|
|
#define INTERPRET_LOOP \
|
|
loop: \
|
|
CHECK_SCHEDULER(); \
|
|
DEBUG_TRACE_INSTRUCTIONS(); \
|
|
switch (instruction = (Code)READ_BYTE())
|
|
|
|
#define CASE_CODE(name) case CODE_##name
|
|
#define DISPATCH() goto loop
|
|
|
|
#endif
|
|
|
|
LOAD_FRAME();
|
|
|
|
Code instruction;
|
|
INTERPRET_LOOP
|
|
{
|
|
CASE_CODE(LOAD_LOCAL_0):
|
|
CASE_CODE(LOAD_LOCAL_1):
|
|
CASE_CODE(LOAD_LOCAL_2):
|
|
CASE_CODE(LOAD_LOCAL_3):
|
|
CASE_CODE(LOAD_LOCAL_4):
|
|
CASE_CODE(LOAD_LOCAL_5):
|
|
CASE_CODE(LOAD_LOCAL_6):
|
|
CASE_CODE(LOAD_LOCAL_7):
|
|
CASE_CODE(LOAD_LOCAL_8):
|
|
PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]);
|
|
DISPATCH();
|
|
|
|
CASE_CODE(LOAD_LOCAL):
|
|
PUSH(stackStart[READ_BYTE()]);
|
|
DISPATCH();
|
|
|
|
CASE_CODE(LOAD_FIELD_THIS):
|
|
{
|
|
uint8_t field = READ_BYTE();
|
|
Value receiver = stackStart[0];
|
|
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
|
|
PUSH(instance->fields[field]);
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(POP): DROP(); DISPATCH();
|
|
CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH();
|
|
CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH();
|
|
CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH();
|
|
|
|
CASE_CODE(STORE_LOCAL):
|
|
stackStart[READ_BYTE()] = PEEK();
|
|
DISPATCH();
|
|
|
|
CASE_CODE(CONSTANT):
|
|
PUSH(fn->constants.data[READ_SHORT()]);
|
|
DISPATCH();
|
|
|
|
{
|
|
// The opcodes for doing method and superclass calls share a lot of code.
|
|
// However, doing an if() test in the middle of the instruction sequence
|
|
// to handle the bit that is special to super calls makes the non-super
|
|
// call path noticeably slower.
|
|
//
|
|
// Instead, we do this old school using an explicit goto to share code for
|
|
// everything at the tail end of the call-handling code that is the same
|
|
// between normal and superclass calls.
|
|
int numArgs;
|
|
int symbol;
|
|
|
|
Value* args;
|
|
ObjClass* classObj;
|
|
|
|
Method* method;
|
|
|
|
CASE_CODE(CALL_0):
|
|
CASE_CODE(CALL_1):
|
|
CASE_CODE(CALL_2):
|
|
CASE_CODE(CALL_3):
|
|
CASE_CODE(CALL_4):
|
|
CASE_CODE(CALL_5):
|
|
CASE_CODE(CALL_6):
|
|
CASE_CODE(CALL_7):
|
|
CASE_CODE(CALL_8):
|
|
CASE_CODE(CALL_9):
|
|
CASE_CODE(CALL_10):
|
|
CASE_CODE(CALL_11):
|
|
CASE_CODE(CALL_12):
|
|
CASE_CODE(CALL_13):
|
|
CASE_CODE(CALL_14):
|
|
CASE_CODE(CALL_15):
|
|
CASE_CODE(CALL_16):
|
|
// Add one for the implicit receiver argument.
|
|
numArgs = instruction - CODE_CALL_0 + 1;
|
|
symbol = READ_SHORT();
|
|
|
|
// The receiver is the first argument.
|
|
args = fiber->stackTop - numArgs;
|
|
classObj = wrenGetClassInline(vm, args[0]);
|
|
goto completeCall;
|
|
|
|
CASE_CODE(SUPER_0):
|
|
CASE_CODE(SUPER_1):
|
|
CASE_CODE(SUPER_2):
|
|
CASE_CODE(SUPER_3):
|
|
CASE_CODE(SUPER_4):
|
|
CASE_CODE(SUPER_5):
|
|
CASE_CODE(SUPER_6):
|
|
CASE_CODE(SUPER_7):
|
|
CASE_CODE(SUPER_8):
|
|
CASE_CODE(SUPER_9):
|
|
CASE_CODE(SUPER_10):
|
|
CASE_CODE(SUPER_11):
|
|
CASE_CODE(SUPER_12):
|
|
CASE_CODE(SUPER_13):
|
|
CASE_CODE(SUPER_14):
|
|
CASE_CODE(SUPER_15):
|
|
CASE_CODE(SUPER_16):
|
|
// Add one for the implicit receiver argument.
|
|
numArgs = instruction - CODE_SUPER_0 + 1;
|
|
symbol = READ_SHORT();
|
|
|
|
// The receiver is the first argument.
|
|
args = fiber->stackTop - numArgs;
|
|
|
|
// The superclass is stored in a constant.
|
|
classObj = AS_CLASS(fn->constants.data[READ_SHORT()]);
|
|
goto completeCall;
|
|
|
|
completeCall:
|
|
// If the class's method table doesn't include the symbol, bail.
|
|
if (symbol >= classObj->methods.count ||
|
|
(method = &classObj->methods.data[symbol])->type == METHOD_NONE)
|
|
{
|
|
methodNotFound(vm, classObj, symbol);
|
|
RUNTIME_ERROR();
|
|
}
|
|
|
|
switch (method->type)
|
|
{
|
|
case METHOD_PRIMITIVE:
|
|
if (method->as.primitive(vm, args))
|
|
{
|
|
// The result is now in the first arg slot. Discard the other
|
|
// stack slots.
|
|
fiber->stackTop -= numArgs - 1;
|
|
} else {
|
|
// An error, fiber switch, or call frame change occurred.
|
|
STORE_FRAME();
|
|
|
|
// If we don't have a fiber to switch to, stop interpreting.
|
|
fiber = vm->fiber;
|
|
if (fiber == NULL) return WREN_RESULT_SUCCESS;
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
LOAD_FRAME();
|
|
}
|
|
break;
|
|
|
|
case METHOD_FUNCTION_CALL:
|
|
if (!checkArity(vm, args[0], numArgs)) {
|
|
RUNTIME_ERROR();
|
|
break;
|
|
}
|
|
|
|
STORE_FRAME();
|
|
method->as.primitive(vm, args);
|
|
LOAD_FRAME();
|
|
break;
|
|
|
|
case METHOD_FOREIGN:
|
|
callForeign(vm, fiber, method->as.foreign, numArgs);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
break;
|
|
|
|
case METHOD_BLOCK:
|
|
STORE_FRAME();
|
|
wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs);
|
|
LOAD_FRAME();
|
|
break;
|
|
|
|
case METHOD_NONE:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(LOAD_UPVALUE):
|
|
{
|
|
ObjUpvalue** upvalues = frame->closure->upvalues;
|
|
PUSH(*upvalues[READ_BYTE()]->value);
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(STORE_UPVALUE):
|
|
{
|
|
ObjUpvalue** upvalues = frame->closure->upvalues;
|
|
*upvalues[READ_BYTE()]->value = PEEK();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(LOAD_MODULE_VAR):
|
|
PUSH(fn->module->variables.data[READ_SHORT()]);
|
|
DISPATCH();
|
|
|
|
CASE_CODE(STORE_MODULE_VAR):
|
|
fn->module->variables.data[READ_SHORT()] = PEEK();
|
|
DISPATCH();
|
|
|
|
CASE_CODE(STORE_FIELD_THIS):
|
|
{
|
|
uint8_t field = READ_BYTE();
|
|
Value receiver = stackStart[0];
|
|
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
|
|
instance->fields[field] = PEEK();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(LOAD_FIELD):
|
|
{
|
|
uint8_t field = READ_BYTE();
|
|
Value receiver = POP();
|
|
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
|
|
PUSH(instance->fields[field]);
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(STORE_FIELD):
|
|
{
|
|
uint8_t field = READ_BYTE();
|
|
Value receiver = POP();
|
|
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
|
|
instance->fields[field] = PEEK();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(JUMP):
|
|
{
|
|
uint16_t offset = READ_SHORT();
|
|
ip += offset;
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(LOOP):
|
|
{
|
|
// Jump back to the top of the loop.
|
|
uint16_t offset = READ_SHORT();
|
|
ip -= offset;
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(JUMP_IF):
|
|
{
|
|
uint16_t offset = READ_SHORT();
|
|
Value condition = POP();
|
|
|
|
if (wrenIsFalsyValue(condition)) ip += offset;
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(AND):
|
|
{
|
|
uint16_t offset = READ_SHORT();
|
|
Value condition = PEEK();
|
|
|
|
if (wrenIsFalsyValue(condition))
|
|
{
|
|
// Short-circuit the right hand side.
|
|
ip += offset;
|
|
}
|
|
else
|
|
{
|
|
// Discard the condition and evaluate the right hand side.
|
|
DROP();
|
|
}
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(OR):
|
|
{
|
|
uint16_t offset = READ_SHORT();
|
|
Value condition = PEEK();
|
|
|
|
if (wrenIsFalsyValue(condition))
|
|
{
|
|
// Discard the condition and evaluate the right hand side.
|
|
DROP();
|
|
}
|
|
else
|
|
{
|
|
// Short-circuit the right hand side.
|
|
ip += offset;
|
|
}
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(CLOSE_UPVALUE):
|
|
// Close the upvalue for the local if we have one.
|
|
closeUpvalues(fiber, fiber->stackTop - 1);
|
|
DROP();
|
|
DISPATCH();
|
|
|
|
CASE_CODE(RETURN):
|
|
{
|
|
Value result = POP();
|
|
fiber->numFrames--;
|
|
|
|
// Close any upvalues still in scope.
|
|
closeUpvalues(fiber, stackStart);
|
|
|
|
// If the fiber is complete, end it.
|
|
if (fiber->numFrames == 0)
|
|
{
|
|
// See if there's another fiber to return to. If not, we're done.
|
|
if (fiber->caller == NULL)
|
|
{
|
|
// Store the final result value at the beginning of the stack so the
|
|
// C API can get it.
|
|
fiber->stack[0] = result;
|
|
fiber->stackTop = fiber->stack + 1;
|
|
return WREN_RESULT_SUCCESS;
|
|
}
|
|
|
|
ObjFiber* resumingFiber = fiber->caller;
|
|
fiber->caller = NULL;
|
|
fiber = resumingFiber;
|
|
vm->fiber = resumingFiber;
|
|
|
|
// Store the result in the resuming fiber.
|
|
fiber->stackTop[-1] = result;
|
|
}
|
|
else
|
|
{
|
|
// Store the result of the block in the first slot, which is where the
|
|
// caller expects it.
|
|
stackStart[0] = result;
|
|
|
|
// Discard the stack slots for the call frame (leaving one slot for the
|
|
// result).
|
|
fiber->stackTop = frame->stackStart + 1;
|
|
}
|
|
|
|
LOAD_FRAME();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(CONSTRUCT):
|
|
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
|
|
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
|
|
DISPATCH();
|
|
|
|
CASE_CODE(FOREIGN_CONSTRUCT):
|
|
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
|
|
createForeign(vm, fiber, stackStart);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
DISPATCH();
|
|
|
|
CASE_CODE(CLOSURE):
|
|
{
|
|
// Create the closure and push it on the stack before creating upvalues
|
|
// so that it doesn't get collected.
|
|
ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]);
|
|
ObjClosure* closure = wrenNewClosure(vm, function);
|
|
PUSH(OBJ_VAL(closure));
|
|
|
|
// Capture upvalues, if any.
|
|
for (int i = 0; i < function->numUpvalues; i++)
|
|
{
|
|
uint8_t isLocal = READ_BYTE();
|
|
uint8_t index = READ_BYTE();
|
|
if (isLocal)
|
|
{
|
|
// Make an new upvalue to close over the parent's local variable.
|
|
closure->upvalues[i] = captureUpvalue(vm, fiber,
|
|
frame->stackStart + index);
|
|
}
|
|
else
|
|
{
|
|
// Use the same upvalue as the current call frame.
|
|
closure->upvalues[i] = frame->closure->upvalues[index];
|
|
}
|
|
}
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(END_CLASS):
|
|
{
|
|
endClass(vm);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(CLASS):
|
|
{
|
|
createClass(vm, READ_BYTE(), NULL);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(FOREIGN_CLASS):
|
|
{
|
|
createClass(vm, -1, fn->module);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(METHOD_INSTANCE):
|
|
CASE_CODE(METHOD_STATIC):
|
|
{
|
|
uint16_t symbol = READ_SHORT();
|
|
ObjClass* classObj = AS_CLASS(PEEK());
|
|
Value method = PEEK2();
|
|
bindMethod(vm, instruction, symbol, fn->module, classObj, method);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
DROP();
|
|
DROP();
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(END_MODULE):
|
|
{
|
|
vm->lastModule = fn->module;
|
|
PUSH(NULL_VAL);
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(IMPORT_MODULE):
|
|
{
|
|
// Make a slot on the stack for the module's fiber to place the return
|
|
// value. It will be popped after this fiber is resumed. Store the
|
|
// imported module's closure in the slot in case a GC happens when
|
|
// invoking the closure.
|
|
PUSH(importModule(vm, fn->constants.data[READ_SHORT()]));
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
|
|
// If we get a closure, call it to execute the module body.
|
|
if (IS_CLOSURE(PEEK()))
|
|
{
|
|
STORE_FRAME();
|
|
ObjClosure* closure = AS_CLOSURE(PEEK());
|
|
wrenCallFunction(vm, fiber, closure, 1);
|
|
LOAD_FRAME();
|
|
}
|
|
else
|
|
{
|
|
// The module has already been loaded. Remember it so we can import
|
|
// variables from it if needed.
|
|
vm->lastModule = AS_MODULE(PEEK());
|
|
}
|
|
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(IMPORT_VARIABLE):
|
|
{
|
|
Value variable = fn->constants.data[READ_SHORT()];
|
|
ASSERT(vm->lastModule != NULL, "Should have already imported module.");
|
|
Value result = getModuleVariable(vm, vm->lastModule, variable);
|
|
if (wrenHasError(fiber)) RUNTIME_ERROR();
|
|
|
|
PUSH(result);
|
|
DISPATCH();
|
|
}
|
|
|
|
CASE_CODE(END):
|
|
// A CODE_END should always be preceded by a CODE_RETURN. If we get here,
|
|
// the compiler generated wrong code.
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// We should only exit this function from an explicit return from CODE_RETURN
|
|
// or a runtime error.
|
|
UNREACHABLE();
|
|
return WREN_RESULT_RUNTIME_ERROR;
|
|
|
|
#undef READ_BYTE
|
|
#undef READ_SHORT
|
|
}
|
|
|
|
WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature)
|
|
{
|
|
ASSERT(signature != NULL, "Signature cannot be NULL.");
|
|
|
|
int signatureLength = (int)strlen(signature);
|
|
ASSERT(signatureLength > 0, "Signature cannot be empty.");
|
|
|
|
// Count the number parameters the method expects.
|
|
int numParams = 0;
|
|
if (signature[signatureLength - 1] == ')')
|
|
{
|
|
for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--)
|
|
{
|
|
if (signature[i] == '_') numParams++;
|
|
}
|
|
}
|
|
|
|
// Count subscript arguments.
|
|
if (signature[0] == '[')
|
|
{
|
|
for (int i = 0; i < signatureLength && signature[i] != ']'; i++)
|
|
{
|
|
if (signature[i] == '_') numParams++;
|
|
}
|
|
}
|
|
|
|
// Add the signatue to the method table.
|
|
int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
|
|
signature, signatureLength);
|
|
|
|
// Create a little stub function that assumes the arguments are on the stack
|
|
// and calls the method.
|
|
ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1);
|
|
|
|
// Wrap the function in a closure and then in a handle. Do this here so it
|
|
// doesn't get collected as we fill it in.
|
|
WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn));
|
|
value->value = OBJ_VAL(wrenNewClosure(vm, fn));
|
|
|
|
wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams));
|
|
wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff);
|
|
wrenByteBufferWrite(vm, &fn->code, method & 0xff);
|
|
wrenByteBufferWrite(vm, &fn->code, CODE_RETURN);
|
|
wrenByteBufferWrite(vm, &fn->code, CODE_END);
|
|
wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5);
|
|
wrenFunctionBindName(vm, fn, signature, signatureLength);
|
|
|
|
return value;
|
|
}
|
|
|
|
WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method)
|
|
{
|
|
ASSERT(method != NULL, "Method cannot be NULL.");
|
|
ASSERT(IS_CLOSURE(method->value), "Method must be a method handle.");
|
|
ASSERT(vm->fiber != NULL, "Must set up arguments for call first.");
|
|
ASSERT(vm->apiStack != NULL, "Must set up arguments for call first.");
|
|
ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method.");
|
|
|
|
ObjClosure* closure = AS_CLOSURE(method->value);
|
|
|
|
ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity,
|
|
"Stack must have enough arguments for method.");
|
|
|
|
// Clear the API stack. Now that wrenCall() has control, we no longer need
|
|
// it. We use this being non-null to tell if re-entrant calls to foreign
|
|
// methods are happening, so it's important to clear it out now so that you
|
|
// can call foreign methods from within calls to wrenCall().
|
|
vm->apiStack = NULL;
|
|
|
|
// Discard any extra temporary slots. We take for granted that the stub
|
|
// function has exactly one slot for each argument.
|
|
vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots];
|
|
|
|
wrenCallFunction(vm, vm->fiber, closure, 0);
|
|
WrenInterpretResult result = runInterpreter(vm, vm->fiber);
|
|
|
|
// If the call didn't abort, then set up the API stack to point to the
|
|
// beginning of the stack so the host can access the call's return value.
|
|
if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack;
|
|
|
|
return result;
|
|
}
|
|
|
|
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value)
|
|
{
|
|
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
|
|
|
|
// Make a handle for it.
|
|
WrenHandle* handle = ALLOCATE(vm, WrenHandle);
|
|
handle->value = value;
|
|
|
|
if (IS_OBJ(value)) wrenPopRoot(vm);
|
|
|
|
// Add it to the front of the linked list of handles.
|
|
if (vm->handles != NULL) vm->handles->prev = handle;
|
|
handle->prev = NULL;
|
|
handle->next = vm->handles;
|
|
vm->handles = handle;
|
|
|
|
return handle;
|
|
}
|
|
|
|
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle)
|
|
{
|
|
ASSERT(handle != NULL, "Handle cannot be NULL.");
|
|
|
|
// Update the VM's head pointer if we're releasing the first handle.
|
|
if (vm->handles == handle) vm->handles = handle->next;
|
|
|
|
// Unlink it from the list.
|
|
if (handle->prev != NULL) handle->prev->next = handle->next;
|
|
if (handle->next != NULL) handle->next->prev = handle->prev;
|
|
|
|
// Clear it out. This isn't strictly necessary since we're going to free it,
|
|
// but it makes for easier debugging.
|
|
handle->prev = NULL;
|
|
handle->next = NULL;
|
|
handle->value = NULL_VAL;
|
|
DEALLOCATE(vm, handle);
|
|
}
|
|
|
|
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
|
|
const char* source)
|
|
{
|
|
ObjClosure* closure = wrenCompileSource(vm, module, source, false, true);
|
|
if (closure == NULL) return WREN_RESULT_COMPILE_ERROR;
|
|
|
|
wrenPushRoot(vm, (Obj*)closure);
|
|
ObjFiber* fiber = wrenNewFiber(vm, closure);
|
|
wrenPopRoot(vm); // closure.
|
|
vm->apiStack = NULL;
|
|
|
|
return runInterpreter(vm, fiber);
|
|
}
|
|
|
|
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source,
|
|
bool isExpression, bool printErrors)
|
|
{
|
|
Value nameValue = NULL_VAL;
|
|
if (module != NULL)
|
|
{
|
|
nameValue = wrenNewString(vm, module);
|
|
wrenPushRoot(vm, AS_OBJ(nameValue));
|
|
}
|
|
|
|
ObjClosure* closure = compileInModule(vm, nameValue, source,
|
|
isExpression, printErrors);
|
|
|
|
if (module != NULL) wrenPopRoot(vm); // nameValue.
|
|
return closure;
|
|
}
|
|
|
|
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName)
|
|
{
|
|
ObjModule* module = getModule(vm, moduleName);
|
|
if (module == NULL)
|
|
{
|
|
vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.",
|
|
moduleName);
|
|
return NULL_VAL;
|
|
}
|
|
|
|
return getModuleVariable(vm, module, variableName);
|
|
}
|
|
|
|
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name)
|
|
{
|
|
int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name));
|
|
return module->variables.data[symbol];
|
|
}
|
|
|
|
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
|
size_t length, int line)
|
|
{
|
|
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
|
|
|
// Implicitly defined variables get a "value" that is the line where the
|
|
// variable is first used. We'll use that later to report an error on the
|
|
// right line.
|
|
wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line));
|
|
return wrenSymbolTableAdd(vm, &module->variableNames, name, length);
|
|
}
|
|
|
|
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
|
size_t length, Value value, int* line)
|
|
{
|
|
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
|
|
|
if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
|
|
|
|
// See if the variable is already explicitly or implicitly declared.
|
|
int symbol = wrenSymbolTableFind(&module->variableNames, name, length);
|
|
|
|
if (symbol == -1)
|
|
{
|
|
// Brand new variable.
|
|
symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length);
|
|
wrenValueBufferWrite(vm, &module->variables, value);
|
|
}
|
|
else if (IS_NUM(module->variables.data[symbol]))
|
|
{
|
|
// An implicitly declared variable's value will always be a number.
|
|
// Now we have a real definition.
|
|
if(line) *line = (int)AS_NUM(module->variables.data[symbol]);
|
|
module->variables.data[symbol] = value;
|
|
|
|
// If this was a localname we want to error if it was
|
|
// referenced before this definition.
|
|
if (wrenIsLocalName(name)) symbol = -3;
|
|
}
|
|
else
|
|
{
|
|
// Already explicitly declared.
|
|
symbol = -1;
|
|
}
|
|
|
|
if (IS_OBJ(value)) wrenPopRoot(vm);
|
|
|
|
return symbol;
|
|
}
|
|
|
|
// TODO: Inline?
|
|
void wrenPushRoot(WrenVM* vm, Obj* obj)
|
|
{
|
|
ASSERT(obj != NULL, "Can't root NULL.");
|
|
ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots.");
|
|
|
|
vm->tempRoots[vm->numTempRoots++] = obj;
|
|
}
|
|
|
|
void wrenPopRoot(WrenVM* vm)
|
|
{
|
|
ASSERT(vm->numTempRoots > 0, "No temporary roots to release.");
|
|
vm->numTempRoots--;
|
|
}
|
|
|
|
int wrenGetSlotCount(WrenVM* vm)
|
|
{
|
|
if (vm->apiStack == NULL) return 0;
|
|
|
|
return (int)(vm->fiber->stackTop - vm->apiStack);
|
|
}
|
|
|
|
void wrenEnsureSlots(WrenVM* vm, int numSlots)
|
|
{
|
|
// If we don't have a fiber accessible, create one for the API to use.
|
|
if (vm->apiStack == NULL)
|
|
{
|
|
vm->fiber = wrenNewFiber(vm, NULL);
|
|
vm->apiStack = vm->fiber->stack;
|
|
}
|
|
|
|
int currentSize = (int)(vm->fiber->stackTop - vm->apiStack);
|
|
if (currentSize >= numSlots) return;
|
|
|
|
// Grow the stack if needed.
|
|
int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots;
|
|
wrenEnsureStack(vm, vm->fiber, needed);
|
|
|
|
vm->fiber->stackTop = vm->apiStack + numSlots;
|
|
}
|
|
|
|
// Ensures that [slot] is a valid index into the API's stack of slots.
|
|
static void validateApiSlot(WrenVM* vm, int slot)
|
|
{
|
|
ASSERT(slot >= 0, "Slot cannot be negative.");
|
|
ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots.");
|
|
}
|
|
|
|
// Gets the type of the object in [slot].
|
|
WrenType wrenGetSlotType(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL;
|
|
if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM;
|
|
if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN;
|
|
if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST;
|
|
if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP;
|
|
if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL;
|
|
if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING;
|
|
|
|
return WREN_TYPE_UNKNOWN;
|
|
}
|
|
|
|
bool wrenGetSlotBool(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool.");
|
|
|
|
return AS_BOOL(vm->apiStack[slot]);
|
|
}
|
|
|
|
const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
|
|
|
|
ObjString* string = AS_STRING(vm->apiStack[slot]);
|
|
*length = string->length;
|
|
return string->value;
|
|
}
|
|
|
|
double wrenGetSlotDouble(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number.");
|
|
|
|
return AS_NUM(vm->apiStack[slot]);
|
|
}
|
|
|
|
void* wrenGetSlotForeign(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_FOREIGN(vm->apiStack[slot]),
|
|
"Slot must hold a foreign instance.");
|
|
|
|
return AS_FOREIGN(vm->apiStack[slot])->data;
|
|
}
|
|
|
|
const char* wrenGetSlotString(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string.");
|
|
|
|
return AS_CSTRING(vm->apiStack[slot]);
|
|
}
|
|
|
|
WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
return wrenMakeHandle(vm, vm->apiStack[slot]);
|
|
}
|
|
|
|
// Stores [value] in [slot] in the foreign call stack.
|
|
static void setSlot(WrenVM* vm, int slot, Value value)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
vm->apiStack[slot] = value;
|
|
}
|
|
|
|
void wrenSetSlotBool(WrenVM* vm, int slot, bool value)
|
|
{
|
|
setSlot(vm, slot, BOOL_VAL(value));
|
|
}
|
|
|
|
void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length)
|
|
{
|
|
ASSERT(bytes != NULL, "Byte array cannot be NULL.");
|
|
setSlot(vm, slot, wrenNewStringLength(vm, bytes, length));
|
|
}
|
|
|
|
void wrenSetSlotDouble(WrenVM* vm, int slot, double value)
|
|
{
|
|
setSlot(vm, slot, NUM_VAL(value));
|
|
}
|
|
|
|
void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
validateApiSlot(vm, classSlot);
|
|
ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class.");
|
|
|
|
ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]);
|
|
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
|
|
|
|
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
|
|
vm->apiStack[slot] = OBJ_VAL(foreign);
|
|
|
|
return (void*)foreign->data;
|
|
}
|
|
|
|
void wrenSetSlotNewList(WrenVM* vm, int slot)
|
|
{
|
|
setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
|
|
}
|
|
|
|
void wrenSetSlotNewMap(WrenVM* vm, int slot)
|
|
{
|
|
setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm)));
|
|
}
|
|
|
|
void wrenSetSlotNull(WrenVM* vm, int slot)
|
|
{
|
|
setSlot(vm, slot, NULL_VAL);
|
|
}
|
|
|
|
void wrenSetSlotString(WrenVM* vm, int slot, const char* text)
|
|
{
|
|
ASSERT(text != NULL, "String cannot be NULL.");
|
|
|
|
setSlot(vm, slot, wrenNewString(vm, text));
|
|
}
|
|
|
|
void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle)
|
|
{
|
|
ASSERT(handle != NULL, "Handle cannot be NULL.");
|
|
|
|
setSlot(vm, slot, handle->value);
|
|
}
|
|
|
|
int wrenGetListCount(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list.");
|
|
|
|
ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements;
|
|
return elements.count;
|
|
}
|
|
|
|
void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot)
|
|
{
|
|
validateApiSlot(vm, listSlot);
|
|
validateApiSlot(vm, elementSlot);
|
|
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list.");
|
|
|
|
ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements;
|
|
|
|
uint32_t usedIndex = wrenValidateIndex(elements.count, index);
|
|
ASSERT(usedIndex != UINT32_MAX, "Index out of bounds.");
|
|
|
|
vm->apiStack[elementSlot] = elements.data[usedIndex];
|
|
}
|
|
|
|
void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot)
|
|
{
|
|
validateApiSlot(vm, listSlot);
|
|
validateApiSlot(vm, elementSlot);
|
|
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list.");
|
|
|
|
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
|
|
|
|
uint32_t usedIndex = wrenValidateIndex(list->elements.count, index);
|
|
ASSERT(usedIndex != UINT32_MAX, "Index out of bounds.");
|
|
|
|
list->elements.data[usedIndex] = vm->apiStack[elementSlot];
|
|
}
|
|
|
|
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
|
|
{
|
|
validateApiSlot(vm, listSlot);
|
|
validateApiSlot(vm, elementSlot);
|
|
ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list.");
|
|
|
|
ObjList* list = AS_LIST(vm->apiStack[listSlot]);
|
|
|
|
// Negative indices count from the end.
|
|
// We don't use wrenValidateIndex here because insert allows 1 past the end.
|
|
if (index < 0) index = list->elements.count + 1 + index;
|
|
|
|
ASSERT(index <= list->elements.count, "Index out of bounds.");
|
|
|
|
wrenListInsert(vm, list, vm->apiStack[elementSlot], index);
|
|
}
|
|
|
|
int wrenGetMapCount(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map.");
|
|
|
|
ObjMap* map = AS_MAP(vm->apiStack[slot]);
|
|
return map->count;
|
|
}
|
|
|
|
bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot)
|
|
{
|
|
validateApiSlot(vm, mapSlot);
|
|
validateApiSlot(vm, keySlot);
|
|
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
|
|
|
|
Value key = vm->apiStack[keySlot];
|
|
ASSERT(wrenMapIsValidKey(key), "Key must be a value type");
|
|
if (!validateKey(vm, key)) return false;
|
|
|
|
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
|
|
Value value = wrenMapGet(map, key);
|
|
|
|
return !IS_UNDEFINED(value);
|
|
}
|
|
|
|
void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
|
|
{
|
|
validateApiSlot(vm, mapSlot);
|
|
validateApiSlot(vm, keySlot);
|
|
validateApiSlot(vm, valueSlot);
|
|
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
|
|
|
|
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
|
|
Value value = wrenMapGet(map, vm->apiStack[keySlot]);
|
|
if (IS_UNDEFINED(value)) {
|
|
value = NULL_VAL;
|
|
}
|
|
|
|
vm->apiStack[valueSlot] = value;
|
|
}
|
|
|
|
void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
|
|
{
|
|
validateApiSlot(vm, mapSlot);
|
|
validateApiSlot(vm, keySlot);
|
|
validateApiSlot(vm, valueSlot);
|
|
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map.");
|
|
|
|
Value key = vm->apiStack[keySlot];
|
|
ASSERT(wrenMapIsValidKey(key), "Key must be a value type");
|
|
|
|
if (!validateKey(vm, key)) {
|
|
return;
|
|
}
|
|
|
|
Value value = vm->apiStack[valueSlot];
|
|
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
|
|
|
|
wrenMapSet(vm, map, key, value);
|
|
}
|
|
|
|
void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
|
|
int removedValueSlot)
|
|
{
|
|
validateApiSlot(vm, mapSlot);
|
|
validateApiSlot(vm, keySlot);
|
|
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
|
|
|
|
Value key = vm->apiStack[keySlot];
|
|
if (!validateKey(vm, key)) {
|
|
return;
|
|
}
|
|
|
|
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
|
|
Value removed = wrenMapRemoveKey(vm, map, key);
|
|
setSlot(vm, removedValueSlot, removed);
|
|
}
|
|
|
|
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
|
|
int slot)
|
|
{
|
|
ASSERT(module != NULL, "Module cannot be NULL.");
|
|
ASSERT(name != NULL, "Variable name cannot be NULL.");
|
|
|
|
Value moduleName = wrenStringFormat(vm, "$", module);
|
|
wrenPushRoot(vm, AS_OBJ(moduleName));
|
|
|
|
ObjModule* moduleObj = getModule(vm, moduleName);
|
|
ASSERT(moduleObj != NULL, "Could not find module.");
|
|
|
|
wrenPopRoot(vm); // moduleName.
|
|
|
|
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
|
|
name, strlen(name));
|
|
ASSERT(variableSlot != -1, "Could not find variable.");
|
|
|
|
setSlot(vm, slot, moduleObj->variables.data[variableSlot]);
|
|
}
|
|
|
|
bool wrenHasVariable(WrenVM* vm, const char* module, const char* name)
|
|
{
|
|
ASSERT(module != NULL, "Module cannot be NULL.");
|
|
ASSERT(name != NULL, "Variable name cannot be NULL.");
|
|
|
|
Value moduleName = wrenStringFormat(vm, "$", module);
|
|
wrenPushRoot(vm, AS_OBJ(moduleName));
|
|
|
|
//We don't use wrenHasModule since we want to use the module object.
|
|
ObjModule* moduleObj = getModule(vm, moduleName);
|
|
ASSERT(moduleObj != NULL, "Could not find module.");
|
|
|
|
wrenPopRoot(vm); // moduleName.
|
|
|
|
int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames,
|
|
name, strlen(name));
|
|
|
|
return variableSlot != -1;
|
|
}
|
|
|
|
bool wrenHasModule(WrenVM* vm, const char* module)
|
|
{
|
|
ASSERT(module != NULL, "Module cannot be NULL.");
|
|
|
|
Value moduleName = wrenStringFormat(vm, "$", module);
|
|
wrenPushRoot(vm, AS_OBJ(moduleName));
|
|
|
|
ObjModule* moduleObj = getModule(vm, moduleName);
|
|
|
|
wrenPopRoot(vm); // moduleName.
|
|
|
|
return moduleObj != NULL;
|
|
}
|
|
|
|
void wrenAbortFiber(WrenVM* vm, int slot)
|
|
{
|
|
validateApiSlot(vm, slot);
|
|
vm->fiber->error = vm->apiStack[slot];
|
|
}
|
|
|
|
void* wrenGetUserData(WrenVM* vm)
|
|
{
|
|
return vm->config.userData;
|
|
}
|
|
|
|
void wrenSetUserData(WrenVM* vm, void* userData)
|
|
{
|
|
vm->config.userData = userData;
|
|
}
|