| 1 | #include <stdarg.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | #include "wren.h" |
| 5 | #include "wren_common.h" |
| 6 | #include "wren_compiler.h" |
| 7 | #include "wren_core.h" |
| 8 | #include "wren_debug.h" |
| 9 | #include "wren_primitive.h" |
| 10 | #include "wren_vm.h" |
| 11 | |
| 12 | #if WREN_OPT_META |
| 13 | #include "wren_opt_meta.h" |
| 14 | #endif |
| 15 | #if WREN_OPT_RANDOM |
| 16 | #include "wren_opt_random.h" |
| 17 | #endif |
| 18 | |
| 19 | #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC |
| 20 | #include <time.h> |
| 21 | #include <stdio.h> |
| 22 | #endif |
| 23 | |
| 24 | // The behavior of realloc() when the size is 0 is implementation defined. It |
| 25 | // may return a non-NULL pointer which must not be dereferenced but nevertheless |
| 26 | // should be freed. To prevent that, we avoid calling realloc() with a zero |
| 27 | // size. |
| 28 | static void* defaultReallocate(void* ptr, size_t newSize, void* _) |
| 29 | { |
| 30 | if (newSize == 0) |
| 31 | { |
| 32 | free(ptr); |
| 33 | return NULL; |
| 34 | } |
| 35 | |
| 36 | return realloc(ptr, newSize); |
| 37 | } |
| 38 | |
| 39 | int wrenGetVersionNumber() |
| 40 | { |
| 41 | return WREN_VERSION_NUMBER; |
| 42 | } |
| 43 | |
| 44 | void wrenInitConfiguration(WrenConfiguration* config) |
| 45 | { |
| 46 | config->reallocateFn = defaultReallocate; |
| 47 | config->resolveModuleFn = NULL; |
| 48 | config->loadModuleFn = NULL; |
| 49 | config->bindForeignMethodFn = NULL; |
| 50 | config->bindForeignClassFn = NULL; |
| 51 | config->writeFn = NULL; |
| 52 | config->errorFn = NULL; |
| 53 | config->initialHeapSize = 1024 * 1024 * 10; |
| 54 | config->minHeapSize = 1024 * 1024; |
| 55 | config->heapGrowthPercent = 50; |
| 56 | config->userData = NULL; |
| 57 | } |
| 58 | |
| 59 | WrenVM* wrenNewVM(WrenConfiguration* config) |
| 60 | { |
| 61 | WrenReallocateFn reallocate = defaultReallocate; |
| 62 | void* userData = NULL; |
| 63 | if (config != NULL) { |
| 64 | userData = config->userData; |
| 65 | reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate; |
| 66 | } |
| 67 | |
| 68 | WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData); |
| 69 | memset(vm, 0, sizeof(WrenVM)); |
| 70 | |
| 71 | // Copy the configuration if given one. |
| 72 | if (config != NULL) |
| 73 | { |
| 74 | memcpy(&vm->config, config, sizeof(WrenConfiguration)); |
| 75 | |
| 76 | // We choose to set this after copying, |
| 77 | // rather than modifying the user config pointer |
| 78 | vm->config.reallocateFn = reallocate; |
| 79 | } |
| 80 | else |
| 81 | { |
| 82 | wrenInitConfiguration(&vm->config); |
| 83 | } |
| 84 | |
| 85 | // TODO: Should we allocate and free this during a GC? |
| 86 | vm->grayCount = 0; |
| 87 | // TODO: Tune this. |
| 88 | vm->grayCapacity = 4; |
| 89 | vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData); |
| 90 | vm->nextGC = vm->config.initialHeapSize; |
| 91 | |
| 92 | wrenSymbolTableInit(&vm->methodNames); |
| 93 | |
| 94 | vm->modules = wrenNewMap(vm); |
| 95 | wrenInitializeCore(vm); |
| 96 | return vm; |
| 97 | } |
| 98 | |
| 99 | void wrenFreeVM(WrenVM* vm) |
| 100 | { |
| 101 | ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed." ); |
| 102 | |
| 103 | // Free all of the GC objects. |
| 104 | Obj* obj = vm->first; |
| 105 | while (obj != NULL) |
| 106 | { |
| 107 | Obj* next = obj->next; |
| 108 | wrenFreeObj(vm, obj); |
| 109 | obj = next; |
| 110 | } |
| 111 | |
| 112 | // Free up the GC gray set. |
| 113 | vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData); |
| 114 | |
| 115 | // Tell the user if they didn't free any handles. We don't want to just free |
| 116 | // them here because the host app may still have pointers to them that they |
| 117 | // may try to use. Better to tell them about the bug early. |
| 118 | ASSERT(vm->handles == NULL, "All handles have not been released." ); |
| 119 | |
| 120 | wrenSymbolTableClear(vm, &vm->methodNames); |
| 121 | |
| 122 | DEALLOCATE(vm, vm); |
| 123 | } |
| 124 | |
| 125 | void wrenCollectGarbage(WrenVM* vm) |
| 126 | { |
| 127 | #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC |
| 128 | printf("-- gc --\n" ); |
| 129 | |
| 130 | size_t before = vm->bytesAllocated; |
| 131 | double startTime = (double)clock() / CLOCKS_PER_SEC; |
| 132 | #endif |
| 133 | |
| 134 | // Mark all reachable objects. |
| 135 | |
| 136 | // Reset this. As we mark objects, their size will be counted again so that |
| 137 | // we can track how much memory is in use without needing to know the size |
| 138 | // of each *freed* object. |
| 139 | // |
| 140 | // This is important because when freeing an unmarked object, we don't always |
| 141 | // know how much memory it is using. For example, when freeing an instance, |
| 142 | // we need to know its class to know how big it is, but its class may have |
| 143 | // already been freed. |
| 144 | vm->bytesAllocated = 0; |
| 145 | |
| 146 | wrenGrayObj(vm, (Obj*)vm->modules); |
| 147 | |
| 148 | // Temporary roots. |
| 149 | for (int i = 0; i < vm->numTempRoots; i++) |
| 150 | { |
| 151 | wrenGrayObj(vm, vm->tempRoots[i]); |
| 152 | } |
| 153 | |
| 154 | // The current fiber. |
| 155 | wrenGrayObj(vm, (Obj*)vm->fiber); |
| 156 | |
| 157 | // The handles. |
| 158 | for (WrenHandle* handle = vm->handles; |
| 159 | handle != NULL; |
| 160 | handle = handle->next) |
| 161 | { |
| 162 | wrenGrayValue(vm, handle->value); |
| 163 | } |
| 164 | |
| 165 | // Any object the compiler is using (if there is one). |
| 166 | if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); |
| 167 | |
| 168 | // Method names. |
| 169 | wrenBlackenSymbolTable(vm, &vm->methodNames); |
| 170 | |
| 171 | // Now that we have grayed the roots, do a depth-first search over all of the |
| 172 | // reachable objects. |
| 173 | wrenBlackenObjects(vm); |
| 174 | |
| 175 | // Collect the white objects. |
| 176 | Obj** obj = &vm->first; |
| 177 | while (*obj != NULL) |
| 178 | { |
| 179 | if (!((*obj)->isDark)) |
| 180 | { |
| 181 | // This object wasn't reached, so remove it from the list and free it. |
| 182 | Obj* unreached = *obj; |
| 183 | *obj = unreached->next; |
| 184 | wrenFreeObj(vm, unreached); |
| 185 | } |
| 186 | else |
| 187 | { |
| 188 | // This object was reached, so unmark it (for the next GC) and move on to |
| 189 | // the next. |
| 190 | (*obj)->isDark = false; |
| 191 | obj = &(*obj)->next; |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | // Calculate the next gc point, this is the current allocation plus |
| 196 | // a configured percentage of the current allocation. |
| 197 | vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100); |
| 198 | if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize; |
| 199 | |
| 200 | #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC |
| 201 | double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime; |
| 202 | // Explicit cast because size_t has different sizes on 32-bit and 64-bit and |
| 203 | // we need a consistent type for the format string. |
| 204 | printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n" , |
| 205 | (unsigned long)before, |
| 206 | (unsigned long)vm->bytesAllocated, |
| 207 | (unsigned long)(before - vm->bytesAllocated), |
| 208 | (unsigned long)vm->nextGC, |
| 209 | elapsed*1000.0); |
| 210 | #endif |
| 211 | } |
| 212 | |
| 213 | void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) |
| 214 | { |
| 215 | #if WREN_DEBUG_TRACE_MEMORY |
| 216 | // Explicit cast because size_t has different sizes on 32-bit and 64-bit and |
| 217 | // we need a consistent type for the format string. |
| 218 | printf("reallocate %p %lu -> %lu\n" , |
| 219 | memory, (unsigned long)oldSize, (unsigned long)newSize); |
| 220 | #endif |
| 221 | |
| 222 | // If new bytes are being allocated, add them to the total count. If objects |
| 223 | // are being completely deallocated, we don't track that (since we don't |
| 224 | // track the original size). Instead, that will be handled while marking |
| 225 | // during the next GC. |
| 226 | vm->bytesAllocated += newSize - oldSize; |
| 227 | |
| 228 | #if WREN_DEBUG_GC_STRESS |
| 229 | // Since collecting calls this function to free things, make sure we don't |
| 230 | // recurse. |
| 231 | if (newSize > 0) wrenCollectGarbage(vm); |
| 232 | #else |
| 233 | if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm); |
| 234 | #endif |
| 235 | |
| 236 | return vm->config.reallocateFn(memory, newSize, vm->config.userData); |
| 237 | } |
| 238 | |
| 239 | // Captures the local variable [local] into an [Upvalue]. If that local is |
| 240 | // already in an upvalue, the existing one will be used. (This is important to |
| 241 | // ensure that multiple closures closing over the same variable actually see |
| 242 | // the same variable.) Otherwise, it will create a new open upvalue and add it |
| 243 | // the fiber's list of upvalues. |
| 244 | static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local) |
| 245 | { |
| 246 | // If there are no open upvalues at all, we must need a new one. |
| 247 | if (fiber->openUpvalues == NULL) |
| 248 | { |
| 249 | fiber->openUpvalues = wrenNewUpvalue(vm, local); |
| 250 | return fiber->openUpvalues; |
| 251 | } |
| 252 | |
| 253 | ObjUpvalue* prevUpvalue = NULL; |
| 254 | ObjUpvalue* upvalue = fiber->openUpvalues; |
| 255 | |
| 256 | // Walk towards the bottom of the stack until we find a previously existing |
| 257 | // upvalue or pass where it should be. |
| 258 | while (upvalue != NULL && upvalue->value > local) |
| 259 | { |
| 260 | prevUpvalue = upvalue; |
| 261 | upvalue = upvalue->next; |
| 262 | } |
| 263 | |
| 264 | // Found an existing upvalue for this local. |
| 265 | if (upvalue != NULL && upvalue->value == local) return upvalue; |
| 266 | |
| 267 | // We've walked past this local on the stack, so there must not be an |
| 268 | // upvalue for it already. Make a new one and link it in in the right |
| 269 | // place to keep the list sorted. |
| 270 | ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local); |
| 271 | if (prevUpvalue == NULL) |
| 272 | { |
| 273 | // The new one is the first one in the list. |
| 274 | fiber->openUpvalues = createdUpvalue; |
| 275 | } |
| 276 | else |
| 277 | { |
| 278 | prevUpvalue->next = createdUpvalue; |
| 279 | } |
| 280 | |
| 281 | createdUpvalue->next = upvalue; |
| 282 | return createdUpvalue; |
| 283 | } |
| 284 | |
| 285 | // Closes any open upvalues that have been created for stack slots at [last] |
| 286 | // and above. |
| 287 | static void closeUpvalues(ObjFiber* fiber, Value* last) |
| 288 | { |
| 289 | while (fiber->openUpvalues != NULL && |
| 290 | fiber->openUpvalues->value >= last) |
| 291 | { |
| 292 | ObjUpvalue* upvalue = fiber->openUpvalues; |
| 293 | |
| 294 | // Move the value into the upvalue itself and point the upvalue to it. |
| 295 | upvalue->closed = *upvalue->value; |
| 296 | upvalue->value = &upvalue->closed; |
| 297 | |
| 298 | // Remove it from the open upvalue list. |
| 299 | fiber->openUpvalues = upvalue->next; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | // Looks up a foreign method in [moduleName] on [className] with [signature]. |
| 304 | // |
| 305 | // This will try the host's foreign method binder first. If that fails, it |
| 306 | // falls back to handling the built-in modules. |
| 307 | static WrenForeignMethodFn findForeignMethod(WrenVM* vm, |
| 308 | const char* moduleName, |
| 309 | const char* className, |
| 310 | bool isStatic, |
| 311 | const char* signature) |
| 312 | { |
| 313 | WrenForeignMethodFn method = NULL; |
| 314 | |
| 315 | if (vm->config.bindForeignMethodFn != NULL) |
| 316 | { |
| 317 | method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic, |
| 318 | signature); |
| 319 | } |
| 320 | |
| 321 | // If the host didn't provide it, see if it's an optional one. |
| 322 | if (method == NULL) |
| 323 | { |
| 324 | #if WREN_OPT_META |
| 325 | if (strcmp(moduleName, "meta" ) == 0) |
| 326 | { |
| 327 | method = wrenMetaBindForeignMethod(vm, className, isStatic, signature); |
| 328 | } |
| 329 | #endif |
| 330 | #if WREN_OPT_RANDOM |
| 331 | if (strcmp(moduleName, "random" ) == 0) |
| 332 | { |
| 333 | method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); |
| 334 | } |
| 335 | #endif |
| 336 | } |
| 337 | |
| 338 | return method; |
| 339 | } |
| 340 | |
| 341 | // Defines [methodValue] as a method on [classObj]. |
| 342 | // |
| 343 | // Handles both foreign methods where [methodValue] is a string containing the |
| 344 | // method's signature and Wren methods where [methodValue] is a function. |
| 345 | // |
| 346 | // Aborts the current fiber if the method is a foreign method that could not be |
| 347 | // found. |
| 348 | static void bindMethod(WrenVM* vm, int methodType, int symbol, |
| 349 | ObjModule* module, ObjClass* classObj, Value methodValue) |
| 350 | { |
| 351 | const char* className = classObj->name->value; |
| 352 | if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj; |
| 353 | |
| 354 | Method method; |
| 355 | if (IS_STRING(methodValue)) |
| 356 | { |
| 357 | const char* name = AS_CSTRING(methodValue); |
| 358 | method.type = METHOD_FOREIGN; |
| 359 | method.as.foreign = findForeignMethod(vm, module->name->value, |
| 360 | className, |
| 361 | methodType == CODE_METHOD_STATIC, |
| 362 | name); |
| 363 | |
| 364 | if (method.as.foreign == NULL) |
| 365 | { |
| 366 | vm->fiber->error = wrenStringFormat(vm, |
| 367 | "Could not find foreign method '@' for class $ in module '$'." , |
| 368 | methodValue, classObj->name->value, module->name->value); |
| 369 | return; |
| 370 | } |
| 371 | } |
| 372 | else |
| 373 | { |
| 374 | method.as.closure = AS_CLOSURE(methodValue); |
| 375 | method.type = METHOD_BLOCK; |
| 376 | |
| 377 | // Patch up the bytecode now that we know the superclass. |
| 378 | wrenBindMethodCode(classObj, method.as.closure->fn); |
| 379 | } |
| 380 | |
| 381 | wrenBindMethod(vm, classObj, symbol, method); |
| 382 | } |
| 383 | |
| 384 | static void callForeign(WrenVM* vm, ObjFiber* fiber, |
| 385 | WrenForeignMethodFn foreign, int numArgs) |
| 386 | { |
| 387 | ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call." ); |
| 388 | vm->apiStack = fiber->stackTop - numArgs; |
| 389 | |
| 390 | foreign(vm); |
| 391 | |
| 392 | // Discard the stack slots for the arguments and temporaries but leave one |
| 393 | // for the result. |
| 394 | fiber->stackTop = vm->apiStack + 1; |
| 395 | |
| 396 | vm->apiStack = NULL; |
| 397 | } |
| 398 | |
| 399 | // Handles the current fiber having aborted because of an error. |
| 400 | // |
| 401 | // Walks the call chain of fibers, aborting each one until it hits a fiber that |
| 402 | // handles the error. If none do, tells the VM to stop. |
| 403 | static void runtimeError(WrenVM* vm) |
| 404 | { |
| 405 | ASSERT(wrenHasError(vm->fiber), "Should only call this after an error." ); |
| 406 | |
| 407 | ObjFiber* current = vm->fiber; |
| 408 | Value error = current->error; |
| 409 | |
| 410 | while (current != NULL) |
| 411 | { |
| 412 | // Every fiber along the call chain gets aborted with the same error. |
| 413 | current->error = error; |
| 414 | |
| 415 | // If the caller ran this fiber using "try", give it the error and stop. |
| 416 | if (current->state == FIBER_TRY) |
| 417 | { |
| 418 | // Make the caller's try method return the error message. |
| 419 | current->caller->stackTop[-1] = vm->fiber->error; |
| 420 | vm->fiber = current->caller; |
| 421 | return; |
| 422 | } |
| 423 | |
| 424 | // Otherwise, unhook the caller since we will never resume and return to it. |
| 425 | ObjFiber* caller = current->caller; |
| 426 | current->caller = NULL; |
| 427 | current = caller; |
| 428 | } |
| 429 | |
| 430 | // If we got here, nothing caught the error, so show the stack trace. |
| 431 | wrenDebugPrintStackTrace(vm); |
| 432 | vm->fiber = NULL; |
| 433 | vm->apiStack = NULL; |
| 434 | } |
| 435 | |
| 436 | // Aborts the current fiber with an appropriate method not found error for a |
| 437 | // method with [symbol] on [classObj]. |
| 438 | static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) |
| 439 | { |
| 440 | vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'." , |
| 441 | OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value); |
| 442 | } |
| 443 | |
| 444 | // Looks up the previously loaded module with [name]. |
| 445 | // |
| 446 | // Returns `NULL` if no module with that name has been loaded. |
| 447 | static ObjModule* getModule(WrenVM* vm, Value name) |
| 448 | { |
| 449 | Value moduleValue = wrenMapGet(vm->modules, name); |
| 450 | return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL; |
| 451 | } |
| 452 | |
| 453 | static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, |
| 454 | bool isExpression, bool printErrors) |
| 455 | { |
| 456 | // See if the module has already been loaded. |
| 457 | ObjModule* module = getModule(vm, name); |
| 458 | if (module == NULL) |
| 459 | { |
| 460 | module = wrenNewModule(vm, AS_STRING(name)); |
| 461 | |
| 462 | // It's possible for the wrenMapSet below to resize the modules map, |
| 463 | // and trigger a GC while doing so. When this happens it will collect |
| 464 | // the module we've just created. Once in the map it is safe. |
| 465 | wrenPushRoot(vm, (Obj*)module); |
| 466 | |
| 467 | // Store it in the VM's module registry so we don't load the same module |
| 468 | // multiple times. |
| 469 | wrenMapSet(vm, vm->modules, name, OBJ_VAL(module)); |
| 470 | |
| 471 | wrenPopRoot(vm); |
| 472 | |
| 473 | // Implicitly import the core module. |
| 474 | ObjModule* coreModule = getModule(vm, NULL_VAL); |
| 475 | for (int i = 0; i < coreModule->variables.count; i++) |
| 476 | { |
| 477 | wrenDefineVariable(vm, module, |
| 478 | coreModule->variableNames.data[i]->value, |
| 479 | coreModule->variableNames.data[i]->length, |
| 480 | coreModule->variables.data[i], NULL); |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors); |
| 485 | if (fn == NULL) |
| 486 | { |
| 487 | // TODO: Should we still store the module even if it didn't compile? |
| 488 | return NULL; |
| 489 | } |
| 490 | |
| 491 | // Functions are always wrapped in closures. |
| 492 | wrenPushRoot(vm, (Obj*)fn); |
| 493 | ObjClosure* closure = wrenNewClosure(vm, fn); |
| 494 | wrenPopRoot(vm); // fn. |
| 495 | |
| 496 | return closure; |
| 497 | } |
| 498 | |
| 499 | // Verifies that [superclassValue] is a valid object to inherit from. That |
| 500 | // means it must be a class and cannot be the class of any built-in type. |
| 501 | // |
| 502 | // Also validates that it doesn't result in a class with too many fields and |
| 503 | // the other limitations foreign classes have. |
| 504 | // |
| 505 | // If successful, returns `null`. Otherwise, returns a string for the runtime |
| 506 | // error message. |
| 507 | static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, |
| 508 | int numFields) |
| 509 | { |
| 510 | // Make sure the superclass is a class. |
| 511 | if (!IS_CLASS(superclassValue)) |
| 512 | { |
| 513 | return wrenStringFormat(vm, |
| 514 | "Class '@' cannot inherit from a non-class object." , |
| 515 | name); |
| 516 | } |
| 517 | |
| 518 | // Make sure it doesn't inherit from a sealed built-in type. Primitive methods |
| 519 | // on these classes assume the instance is one of the other Obj___ types and |
| 520 | // will fail horribly if it's actually an ObjInstance. |
| 521 | ObjClass* superclass = AS_CLASS(superclassValue); |
| 522 | if (superclass == vm->classClass || |
| 523 | superclass == vm->fiberClass || |
| 524 | superclass == vm->fnClass || // Includes OBJ_CLOSURE. |
| 525 | superclass == vm->listClass || |
| 526 | superclass == vm->mapClass || |
| 527 | superclass == vm->rangeClass || |
| 528 | superclass == vm->stringClass || |
| 529 | superclass == vm->boolClass || |
| 530 | superclass == vm->nullClass || |
| 531 | superclass == vm->numClass) |
| 532 | { |
| 533 | return wrenStringFormat(vm, |
| 534 | "Class '@' cannot inherit from built-in class '@'." , |
| 535 | name, OBJ_VAL(superclass->name)); |
| 536 | } |
| 537 | |
| 538 | if (superclass->numFields == -1) |
| 539 | { |
| 540 | return wrenStringFormat(vm, |
| 541 | "Class '@' cannot inherit from foreign class '@'." , |
| 542 | name, OBJ_VAL(superclass->name)); |
| 543 | } |
| 544 | |
| 545 | if (numFields == -1 && superclass->numFields > 0) |
| 546 | { |
| 547 | return wrenStringFormat(vm, |
| 548 | "Foreign class '@' may not inherit from a class with fields." , |
| 549 | name); |
| 550 | } |
| 551 | |
| 552 | if (superclass->numFields + numFields > MAX_FIELDS) |
| 553 | { |
| 554 | return wrenStringFormat(vm, |
| 555 | "Class '@' may not have more than 255 fields, including inherited " |
| 556 | "ones." , name); |
| 557 | } |
| 558 | |
| 559 | return NULL_VAL; |
| 560 | } |
| 561 | |
| 562 | static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) |
| 563 | { |
| 564 | WrenForeignClassMethods methods; |
| 565 | methods.allocate = NULL; |
| 566 | methods.finalize = NULL; |
| 567 | |
| 568 | // Check the optional built-in module first so the host can override it. |
| 569 | |
| 570 | if (vm->config.bindForeignClassFn != NULL) |
| 571 | { |
| 572 | methods = vm->config.bindForeignClassFn(vm, module->name->value, |
| 573 | classObj->name->value); |
| 574 | } |
| 575 | |
| 576 | // If the host didn't provide it, see if it's a built in optional module. |
| 577 | if (methods.allocate == NULL && methods.finalize == NULL) |
| 578 | { |
| 579 | #if WREN_OPT_RANDOM |
| 580 | if (strcmp(module->name->value, "random" ) == 0) |
| 581 | { |
| 582 | methods = wrenRandomBindForeignClass(vm, module->name->value, |
| 583 | classObj->name->value); |
| 584 | } |
| 585 | #endif |
| 586 | } |
| 587 | |
| 588 | Method method; |
| 589 | method.type = METHOD_FOREIGN; |
| 590 | |
| 591 | // Add the symbol even if there is no allocator so we can ensure that the |
| 592 | // symbol itself is always in the symbol table. |
| 593 | int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>" , 10); |
| 594 | if (methods.allocate != NULL) |
| 595 | { |
| 596 | method.as.foreign = methods.allocate; |
| 597 | wrenBindMethod(vm, classObj, symbol, method); |
| 598 | } |
| 599 | |
| 600 | // Add the symbol even if there is no finalizer so we can ensure that the |
| 601 | // symbol itself is always in the symbol table. |
| 602 | symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>" , 10); |
| 603 | if (methods.finalize != NULL) |
| 604 | { |
| 605 | method.as.foreign = (WrenForeignMethodFn)methods.finalize; |
| 606 | wrenBindMethod(vm, classObj, symbol, method); |
| 607 | } |
| 608 | } |
| 609 | |
| 610 | // Completes the process for creating a new class. |
| 611 | // |
| 612 | // The class attributes instance and the class itself should be on the |
| 613 | // top of the fiber's stack. |
| 614 | // |
| 615 | // This process handles moving the attribute data for a class from |
| 616 | // compile time to runtime, since it now has all the attributes associated |
| 617 | // with a class, including for methods. |
| 618 | static void endClass(WrenVM* vm) |
| 619 | { |
| 620 | // Pull the attributes and class off the stack |
| 621 | Value attributes = vm->fiber->stackTop[-2]; |
| 622 | Value classValue = vm->fiber->stackTop[-1]; |
| 623 | |
| 624 | // Remove the stack items |
| 625 | vm->fiber->stackTop -= 2; |
| 626 | |
| 627 | ObjClass* classObj = AS_CLASS(classValue); |
| 628 | classObj->attributes = attributes; |
| 629 | } |
| 630 | |
| 631 | // Creates a new class. |
| 632 | // |
| 633 | // If [numFields] is -1, the class is a foreign class. The name and superclass |
| 634 | // should be on top of the fiber's stack. After calling this, the top of the |
| 635 | // stack will contain the new class. |
| 636 | // |
| 637 | // Aborts the current fiber if an error occurs. |
| 638 | static void createClass(WrenVM* vm, int numFields, ObjModule* module) |
| 639 | { |
| 640 | // Pull the name and superclass off the stack. |
| 641 | Value name = vm->fiber->stackTop[-2]; |
| 642 | Value superclass = vm->fiber->stackTop[-1]; |
| 643 | |
| 644 | // We have two values on the stack and we are going to leave one, so discard |
| 645 | // the other slot. |
| 646 | vm->fiber->stackTop--; |
| 647 | |
| 648 | vm->fiber->error = validateSuperclass(vm, name, superclass, numFields); |
| 649 | if (wrenHasError(vm->fiber)) return; |
| 650 | |
| 651 | ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, |
| 652 | AS_STRING(name)); |
| 653 | vm->fiber->stackTop[-1] = OBJ_VAL(classObj); |
| 654 | |
| 655 | if (numFields == -1) bindForeignClass(vm, classObj, module); |
| 656 | } |
| 657 | |
| 658 | static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) |
| 659 | { |
| 660 | ObjClass* classObj = AS_CLASS(stack[0]); |
| 661 | ASSERT(classObj->numFields == -1, "Class must be a foreign class." ); |
| 662 | |
| 663 | // TODO: Don't look up every time. |
| 664 | int symbol = wrenSymbolTableFind(&vm->methodNames, "<allocate>" , 10); |
| 665 | ASSERT(symbol != -1, "Should have defined <allocate> symbol." ); |
| 666 | |
| 667 | ASSERT(classObj->methods.count > symbol, "Class should have allocator." ); |
| 668 | Method* method = &classObj->methods.data[symbol]; |
| 669 | ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign." ); |
| 670 | |
| 671 | // Pass the constructor arguments to the allocator as well. |
| 672 | ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call." ); |
| 673 | vm->apiStack = stack; |
| 674 | |
| 675 | method->as.foreign(vm); |
| 676 | |
| 677 | vm->apiStack = NULL; |
| 678 | } |
| 679 | |
| 680 | void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) |
| 681 | { |
| 682 | // TODO: Don't look up every time. |
| 683 | int symbol = wrenSymbolTableFind(&vm->methodNames, "<finalize>" , 10); |
| 684 | ASSERT(symbol != -1, "Should have defined <finalize> symbol." ); |
| 685 | |
| 686 | // If there are no finalizers, don't finalize it. |
| 687 | if (symbol == -1) return; |
| 688 | |
| 689 | // If the class doesn't have a finalizer, bail out. |
| 690 | ObjClass* classObj = foreign->obj.classObj; |
| 691 | if (symbol >= classObj->methods.count) return; |
| 692 | |
| 693 | Method* method = &classObj->methods.data[symbol]; |
| 694 | if (method->type == METHOD_NONE) return; |
| 695 | |
| 696 | ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign." ); |
| 697 | |
| 698 | WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; |
| 699 | finalizer(foreign->data); |
| 700 | } |
| 701 | |
| 702 | // Let the host resolve an imported module name if it wants to. |
| 703 | static Value resolveModule(WrenVM* vm, Value name) |
| 704 | { |
| 705 | // If the host doesn't care to resolve, leave the name alone. |
| 706 | if (vm->config.resolveModuleFn == NULL) return name; |
| 707 | |
| 708 | ObjFiber* fiber = vm->fiber; |
| 709 | ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn; |
| 710 | ObjString* importer = fn->module->name; |
| 711 | |
| 712 | const char* resolved = vm->config.resolveModuleFn(vm, importer->value, |
| 713 | AS_CSTRING(name)); |
| 714 | if (resolved == NULL) |
| 715 | { |
| 716 | vm->fiber->error = wrenStringFormat(vm, |
| 717 | "Could not resolve module '@' imported from '@'." , |
| 718 | name, OBJ_VAL(importer)); |
| 719 | return NULL_VAL; |
| 720 | } |
| 721 | |
| 722 | // If they resolved to the exact same string, we don't need to copy it. |
| 723 | if (resolved == AS_CSTRING(name)) return name; |
| 724 | |
| 725 | // Copy the string into a Wren String object. |
| 726 | name = wrenNewString(vm, resolved); |
| 727 | DEALLOCATE(vm, (char*)resolved); |
| 728 | return name; |
| 729 | } |
| 730 | |
| 731 | static Value importModule(WrenVM* vm, Value name) |
| 732 | { |
| 733 | name = resolveModule(vm, name); |
| 734 | |
| 735 | // If the module is already loaded, we don't need to do anything. |
| 736 | Value existing = wrenMapGet(vm->modules, name); |
| 737 | if (!IS_UNDEFINED(existing)) return existing; |
| 738 | |
| 739 | wrenPushRoot(vm, AS_OBJ(name)); |
| 740 | |
| 741 | WrenLoadModuleResult result = {0}; |
| 742 | const char* source = NULL; |
| 743 | |
| 744 | // Let the host try to provide the module. |
| 745 | if (vm->config.loadModuleFn != NULL) |
| 746 | { |
| 747 | result = vm->config.loadModuleFn(vm, AS_CSTRING(name)); |
| 748 | } |
| 749 | |
| 750 | // If the host didn't provide it, see if it's a built in optional module. |
| 751 | if (result.source == NULL) |
| 752 | { |
| 753 | result.onComplete = NULL; |
| 754 | ObjString* nameString = AS_STRING(name); |
| 755 | #if WREN_OPT_META |
| 756 | if (strcmp(nameString->value, "meta" ) == 0) result.source = wrenMetaSource(); |
| 757 | #endif |
| 758 | #if WREN_OPT_RANDOM |
| 759 | if (strcmp(nameString->value, "random" ) == 0) result.source = wrenRandomSource(); |
| 760 | #endif |
| 761 | } |
| 762 | |
| 763 | if (result.source == NULL) |
| 764 | { |
| 765 | vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'." , name); |
| 766 | wrenPopRoot(vm); // name. |
| 767 | return NULL_VAL; |
| 768 | } |
| 769 | |
| 770 | ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); |
| 771 | |
| 772 | // Now that we're done, give the result back in case there's cleanup to do. |
| 773 | if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result); |
| 774 | |
| 775 | if (moduleClosure == NULL) |
| 776 | { |
| 777 | vm->fiber->error = wrenStringFormat(vm, |
| 778 | "Could not compile module '@'." , name); |
| 779 | wrenPopRoot(vm); // name. |
| 780 | return NULL_VAL; |
| 781 | } |
| 782 | |
| 783 | wrenPopRoot(vm); // name. |
| 784 | |
| 785 | // Return the closure that executes the module. |
| 786 | return OBJ_VAL(moduleClosure); |
| 787 | } |
| 788 | |
| 789 | static Value getModuleVariable(WrenVM* vm, ObjModule* module, |
| 790 | Value variableName) |
| 791 | { |
| 792 | ObjString* variable = AS_STRING(variableName); |
| 793 | uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames, |
| 794 | variable->value, |
| 795 | variable->length); |
| 796 | |
| 797 | // It's a runtime error if the imported variable does not exist. |
| 798 | if (variableEntry != UINT32_MAX) |
| 799 | { |
| 800 | return module->variables.data[variableEntry]; |
| 801 | } |
| 802 | |
| 803 | vm->fiber->error = wrenStringFormat(vm, |
| 804 | "Could not find a variable named '@' in module '@'." , |
| 805 | variableName, OBJ_VAL(module->name)); |
| 806 | return NULL_VAL; |
| 807 | } |
| 808 | |
| 809 | inline static bool checkArity(WrenVM* vm, Value value, int numArgs) |
| 810 | { |
| 811 | ASSERT(IS_CLOSURE(value), "Receiver must be a closure." ); |
| 812 | ObjFn* fn = AS_CLOSURE(value)->fn; |
| 813 | |
| 814 | // We only care about missing arguments, not extras. The "- 1" is because |
| 815 | // numArgs includes the receiver, the function itself, which we don't want to |
| 816 | // count. |
| 817 | if (numArgs - 1 >= fn->arity) return true; |
| 818 | |
| 819 | vm->fiber->error = CONST_STRING(vm, "Function expects more arguments." ); |
| 820 | return false; |
| 821 | } |
| 822 | |
| 823 | |
| 824 | // The main bytecode interpreter loop. This is where the magic happens. It is |
| 825 | // also, as you can imagine, highly performance critical. |
| 826 | static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) |
| 827 | { |
| 828 | // Remember the current fiber so we can find it if a GC happens. |
| 829 | vm->fiber = fiber; |
| 830 | fiber->state = FIBER_ROOT; |
| 831 | |
| 832 | // Hoist these into local variables. They are accessed frequently in the loop |
| 833 | // but assigned less frequently. Keeping them in locals and updating them when |
| 834 | // a call frame has been pushed or popped gives a large speed boost. |
| 835 | register CallFrame* frame; |
| 836 | register Value* stackStart; |
| 837 | register uint8_t* ip; |
| 838 | register ObjFn* fn; |
| 839 | |
| 840 | // These macros are designed to only be invoked within this function. |
| 841 | #define PUSH(value) (*fiber->stackTop++ = value) |
| 842 | #define POP() (*(--fiber->stackTop)) |
| 843 | #define DROP() (fiber->stackTop--) |
| 844 | #define PEEK() (*(fiber->stackTop - 1)) |
| 845 | #define PEEK2() (*(fiber->stackTop - 2)) |
| 846 | #define READ_BYTE() (*ip++) |
| 847 | #define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1])) |
| 848 | |
| 849 | // Use this before a CallFrame is pushed to store the local variables back |
| 850 | // into the current one. |
| 851 | #define STORE_FRAME() frame->ip = ip |
| 852 | |
| 853 | // Use this after a CallFrame has been pushed or popped to refresh the local |
| 854 | // variables. |
| 855 | #define LOAD_FRAME() \ |
| 856 | do \ |
| 857 | { \ |
| 858 | frame = &fiber->frames[fiber->numFrames - 1]; \ |
| 859 | stackStart = frame->stackStart; \ |
| 860 | ip = frame->ip; \ |
| 861 | fn = frame->closure->fn; \ |
| 862 | } while (false) |
| 863 | |
| 864 | // Terminates the current fiber with error string [error]. If another calling |
| 865 | // fiber is willing to catch the error, transfers control to it, otherwise |
| 866 | // exits the interpreter. |
| 867 | #define RUNTIME_ERROR() \ |
| 868 | do \ |
| 869 | { \ |
| 870 | STORE_FRAME(); \ |
| 871 | runtimeError(vm); \ |
| 872 | if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ |
| 873 | fiber = vm->fiber; \ |
| 874 | LOAD_FRAME(); \ |
| 875 | DISPATCH(); \ |
| 876 | } while (false) |
| 877 | |
| 878 | #if WREN_DEBUG_TRACE_INSTRUCTIONS |
| 879 | // Prints the stack and instruction before each instruction is executed. |
| 880 | #define DEBUG_TRACE_INSTRUCTIONS() \ |
| 881 | do \ |
| 882 | { \ |
| 883 | wrenDumpStack(fiber); \ |
| 884 | wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \ |
| 885 | } while (false) |
| 886 | #else |
| 887 | #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) |
| 888 | #endif |
| 889 | |
| 890 | #if WREN_COMPUTED_GOTO |
| 891 | |
| 892 | static void* dispatchTable[] = { |
| 893 | #define OPCODE(name, _) &&code_##name, |
| 894 | #include "wren_opcodes.h" |
| 895 | #undef OPCODE |
| 896 | }; |
| 897 | |
| 898 | #define INTERPRET_LOOP DISPATCH(); |
| 899 | #define CASE_CODE(name) code_##name |
| 900 | |
| 901 | #define DISPATCH() \ |
| 902 | do \ |
| 903 | { \ |
| 904 | DEBUG_TRACE_INSTRUCTIONS(); \ |
| 905 | goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ |
| 906 | } while (false) |
| 907 | |
| 908 | #else |
| 909 | |
| 910 | #define INTERPRET_LOOP \ |
| 911 | loop: \ |
| 912 | DEBUG_TRACE_INSTRUCTIONS(); \ |
| 913 | switch (instruction = (Code)READ_BYTE()) |
| 914 | |
| 915 | #define CASE_CODE(name) case CODE_##name |
| 916 | #define DISPATCH() goto loop |
| 917 | |
| 918 | #endif |
| 919 | |
| 920 | LOAD_FRAME(); |
| 921 | |
| 922 | Code instruction; |
| 923 | INTERPRET_LOOP |
| 924 | { |
| 925 | CASE_CODE(LOAD_LOCAL_0): |
| 926 | CASE_CODE(LOAD_LOCAL_1): |
| 927 | CASE_CODE(LOAD_LOCAL_2): |
| 928 | CASE_CODE(LOAD_LOCAL_3): |
| 929 | CASE_CODE(LOAD_LOCAL_4): |
| 930 | CASE_CODE(LOAD_LOCAL_5): |
| 931 | CASE_CODE(LOAD_LOCAL_6): |
| 932 | CASE_CODE(LOAD_LOCAL_7): |
| 933 | CASE_CODE(LOAD_LOCAL_8): |
| 934 | PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); |
| 935 | DISPATCH(); |
| 936 | |
| 937 | CASE_CODE(LOAD_LOCAL): |
| 938 | PUSH(stackStart[READ_BYTE()]); |
| 939 | DISPATCH(); |
| 940 | |
| 941 | CASE_CODE(LOAD_FIELD_THIS): |
| 942 | { |
| 943 | uint8_t field = READ_BYTE(); |
| 944 | Value receiver = stackStart[0]; |
| 945 | ASSERT(IS_INSTANCE(receiver), "Receiver should be instance." ); |
| 946 | ObjInstance* instance = AS_INSTANCE(receiver); |
| 947 | ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field." ); |
| 948 | PUSH(instance->fields[field]); |
| 949 | DISPATCH(); |
| 950 | } |
| 951 | |
| 952 | CASE_CODE(POP): DROP(); DISPATCH(); |
| 953 | CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH(); |
| 954 | CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH(); |
| 955 | CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH(); |
| 956 | |
| 957 | CASE_CODE(STORE_LOCAL): |
| 958 | stackStart[READ_BYTE()] = PEEK(); |
| 959 | DISPATCH(); |
| 960 | |
| 961 | CASE_CODE(CONSTANT): |
| 962 | PUSH(fn->constants.data[READ_SHORT()]); |
| 963 | DISPATCH(); |
| 964 | |
| 965 | { |
| 966 | // The opcodes for doing method and superclass calls share a lot of code. |
| 967 | // However, doing an if() test in the middle of the instruction sequence |
| 968 | // to handle the bit that is special to super calls makes the non-super |
| 969 | // call path noticeably slower. |
| 970 | // |
| 971 | // Instead, we do this old school using an explicit goto to share code for |
| 972 | // everything at the tail end of the call-handling code that is the same |
| 973 | // between normal and superclass calls. |
| 974 | int numArgs; |
| 975 | int symbol; |
| 976 | |
| 977 | Value* args; |
| 978 | ObjClass* classObj; |
| 979 | |
| 980 | Method* method; |
| 981 | |
| 982 | CASE_CODE(CALL_0): |
| 983 | CASE_CODE(CALL_1): |
| 984 | CASE_CODE(CALL_2): |
| 985 | CASE_CODE(CALL_3): |
| 986 | CASE_CODE(CALL_4): |
| 987 | CASE_CODE(CALL_5): |
| 988 | CASE_CODE(CALL_6): |
| 989 | CASE_CODE(CALL_7): |
| 990 | CASE_CODE(CALL_8): |
| 991 | CASE_CODE(CALL_9): |
| 992 | CASE_CODE(CALL_10): |
| 993 | CASE_CODE(CALL_11): |
| 994 | CASE_CODE(CALL_12): |
| 995 | CASE_CODE(CALL_13): |
| 996 | CASE_CODE(CALL_14): |
| 997 | CASE_CODE(CALL_15): |
| 998 | CASE_CODE(CALL_16): |
| 999 | // Add one for the implicit receiver argument. |
| 1000 | numArgs = instruction - CODE_CALL_0 + 1; |
| 1001 | symbol = READ_SHORT(); |
| 1002 | |
| 1003 | // The receiver is the first argument. |
| 1004 | args = fiber->stackTop - numArgs; |
| 1005 | classObj = wrenGetClassInline(vm, args[0]); |
| 1006 | goto completeCall; |
| 1007 | |
| 1008 | CASE_CODE(SUPER_0): |
| 1009 | CASE_CODE(SUPER_1): |
| 1010 | CASE_CODE(SUPER_2): |
| 1011 | CASE_CODE(SUPER_3): |
| 1012 | CASE_CODE(SUPER_4): |
| 1013 | CASE_CODE(SUPER_5): |
| 1014 | CASE_CODE(SUPER_6): |
| 1015 | CASE_CODE(SUPER_7): |
| 1016 | CASE_CODE(SUPER_8): |
| 1017 | CASE_CODE(SUPER_9): |
| 1018 | CASE_CODE(SUPER_10): |
| 1019 | CASE_CODE(SUPER_11): |
| 1020 | CASE_CODE(SUPER_12): |
| 1021 | CASE_CODE(SUPER_13): |
| 1022 | CASE_CODE(SUPER_14): |
| 1023 | CASE_CODE(SUPER_15): |
| 1024 | CASE_CODE(SUPER_16): |
| 1025 | // Add one for the implicit receiver argument. |
| 1026 | numArgs = instruction - CODE_SUPER_0 + 1; |
| 1027 | symbol = READ_SHORT(); |
| 1028 | |
| 1029 | // The receiver is the first argument. |
| 1030 | args = fiber->stackTop - numArgs; |
| 1031 | |
| 1032 | // The superclass is stored in a constant. |
| 1033 | classObj = AS_CLASS(fn->constants.data[READ_SHORT()]); |
| 1034 | goto completeCall; |
| 1035 | |
| 1036 | completeCall: |
| 1037 | // If the class's method table doesn't include the symbol, bail. |
| 1038 | if (symbol >= classObj->methods.count || |
| 1039 | (method = &classObj->methods.data[symbol])->type == METHOD_NONE) |
| 1040 | { |
| 1041 | methodNotFound(vm, classObj, symbol); |
| 1042 | RUNTIME_ERROR(); |
| 1043 | } |
| 1044 | |
| 1045 | switch (method->type) |
| 1046 | { |
| 1047 | case METHOD_PRIMITIVE: |
| 1048 | if (method->as.primitive(vm, args)) |
| 1049 | { |
| 1050 | // The result is now in the first arg slot. Discard the other |
| 1051 | // stack slots. |
| 1052 | fiber->stackTop -= numArgs - 1; |
| 1053 | } else { |
| 1054 | // An error, fiber switch, or call frame change occurred. |
| 1055 | STORE_FRAME(); |
| 1056 | |
| 1057 | // If we don't have a fiber to switch to, stop interpreting. |
| 1058 | fiber = vm->fiber; |
| 1059 | if (fiber == NULL) return WREN_RESULT_SUCCESS; |
| 1060 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1061 | LOAD_FRAME(); |
| 1062 | } |
| 1063 | break; |
| 1064 | |
| 1065 | case METHOD_FUNCTION_CALL: |
| 1066 | if (!checkArity(vm, args[0], numArgs)) { |
| 1067 | RUNTIME_ERROR(); |
| 1068 | break; |
| 1069 | } |
| 1070 | |
| 1071 | STORE_FRAME(); |
| 1072 | method->as.primitive(vm, args); |
| 1073 | LOAD_FRAME(); |
| 1074 | break; |
| 1075 | |
| 1076 | case METHOD_FOREIGN: |
| 1077 | callForeign(vm, fiber, method->as.foreign, numArgs); |
| 1078 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1079 | break; |
| 1080 | |
| 1081 | case METHOD_BLOCK: |
| 1082 | STORE_FRAME(); |
| 1083 | wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs); |
| 1084 | LOAD_FRAME(); |
| 1085 | break; |
| 1086 | |
| 1087 | case METHOD_NONE: |
| 1088 | UNREACHABLE(); |
| 1089 | break; |
| 1090 | } |
| 1091 | DISPATCH(); |
| 1092 | } |
| 1093 | |
| 1094 | CASE_CODE(LOAD_UPVALUE): |
| 1095 | { |
| 1096 | ObjUpvalue** upvalues = frame->closure->upvalues; |
| 1097 | PUSH(*upvalues[READ_BYTE()]->value); |
| 1098 | DISPATCH(); |
| 1099 | } |
| 1100 | |
| 1101 | CASE_CODE(STORE_UPVALUE): |
| 1102 | { |
| 1103 | ObjUpvalue** upvalues = frame->closure->upvalues; |
| 1104 | *upvalues[READ_BYTE()]->value = PEEK(); |
| 1105 | DISPATCH(); |
| 1106 | } |
| 1107 | |
| 1108 | CASE_CODE(LOAD_MODULE_VAR): |
| 1109 | PUSH(fn->module->variables.data[READ_SHORT()]); |
| 1110 | DISPATCH(); |
| 1111 | |
| 1112 | CASE_CODE(STORE_MODULE_VAR): |
| 1113 | fn->module->variables.data[READ_SHORT()] = PEEK(); |
| 1114 | DISPATCH(); |
| 1115 | |
| 1116 | CASE_CODE(STORE_FIELD_THIS): |
| 1117 | { |
| 1118 | uint8_t field = READ_BYTE(); |
| 1119 | Value receiver = stackStart[0]; |
| 1120 | ASSERT(IS_INSTANCE(receiver), "Receiver should be instance." ); |
| 1121 | ObjInstance* instance = AS_INSTANCE(receiver); |
| 1122 | ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field." ); |
| 1123 | instance->fields[field] = PEEK(); |
| 1124 | DISPATCH(); |
| 1125 | } |
| 1126 | |
| 1127 | CASE_CODE(LOAD_FIELD): |
| 1128 | { |
| 1129 | uint8_t field = READ_BYTE(); |
| 1130 | Value receiver = POP(); |
| 1131 | ASSERT(IS_INSTANCE(receiver), "Receiver should be instance." ); |
| 1132 | ObjInstance* instance = AS_INSTANCE(receiver); |
| 1133 | ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field." ); |
| 1134 | PUSH(instance->fields[field]); |
| 1135 | DISPATCH(); |
| 1136 | } |
| 1137 | |
| 1138 | CASE_CODE(STORE_FIELD): |
| 1139 | { |
| 1140 | uint8_t field = READ_BYTE(); |
| 1141 | Value receiver = POP(); |
| 1142 | ASSERT(IS_INSTANCE(receiver), "Receiver should be instance." ); |
| 1143 | ObjInstance* instance = AS_INSTANCE(receiver); |
| 1144 | ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field." ); |
| 1145 | instance->fields[field] = PEEK(); |
| 1146 | DISPATCH(); |
| 1147 | } |
| 1148 | |
| 1149 | CASE_CODE(JUMP): |
| 1150 | { |
| 1151 | uint16_t offset = READ_SHORT(); |
| 1152 | ip += offset; |
| 1153 | DISPATCH(); |
| 1154 | } |
| 1155 | |
| 1156 | CASE_CODE(LOOP): |
| 1157 | { |
| 1158 | // Jump back to the top of the loop. |
| 1159 | uint16_t offset = READ_SHORT(); |
| 1160 | ip -= offset; |
| 1161 | DISPATCH(); |
| 1162 | } |
| 1163 | |
| 1164 | CASE_CODE(JUMP_IF): |
| 1165 | { |
| 1166 | uint16_t offset = READ_SHORT(); |
| 1167 | Value condition = POP(); |
| 1168 | |
| 1169 | if (wrenIsFalsyValue(condition)) ip += offset; |
| 1170 | DISPATCH(); |
| 1171 | } |
| 1172 | |
| 1173 | CASE_CODE(AND): |
| 1174 | { |
| 1175 | uint16_t offset = READ_SHORT(); |
| 1176 | Value condition = PEEK(); |
| 1177 | |
| 1178 | if (wrenIsFalsyValue(condition)) |
| 1179 | { |
| 1180 | // Short-circuit the right hand side. |
| 1181 | ip += offset; |
| 1182 | } |
| 1183 | else |
| 1184 | { |
| 1185 | // Discard the condition and evaluate the right hand side. |
| 1186 | DROP(); |
| 1187 | } |
| 1188 | DISPATCH(); |
| 1189 | } |
| 1190 | |
| 1191 | CASE_CODE(OR): |
| 1192 | { |
| 1193 | uint16_t offset = READ_SHORT(); |
| 1194 | Value condition = PEEK(); |
| 1195 | |
| 1196 | if (wrenIsFalsyValue(condition)) |
| 1197 | { |
| 1198 | // Discard the condition and evaluate the right hand side. |
| 1199 | DROP(); |
| 1200 | } |
| 1201 | else |
| 1202 | { |
| 1203 | // Short-circuit the right hand side. |
| 1204 | ip += offset; |
| 1205 | } |
| 1206 | DISPATCH(); |
| 1207 | } |
| 1208 | |
| 1209 | CASE_CODE(CLOSE_UPVALUE): |
| 1210 | // Close the upvalue for the local if we have one. |
| 1211 | closeUpvalues(fiber, fiber->stackTop - 1); |
| 1212 | DROP(); |
| 1213 | DISPATCH(); |
| 1214 | |
| 1215 | CASE_CODE(RETURN): |
| 1216 | { |
| 1217 | Value result = POP(); |
| 1218 | fiber->numFrames--; |
| 1219 | |
| 1220 | // Close any upvalues still in scope. |
| 1221 | closeUpvalues(fiber, stackStart); |
| 1222 | |
| 1223 | // If the fiber is complete, end it. |
| 1224 | if (fiber->numFrames == 0) |
| 1225 | { |
| 1226 | // See if there's another fiber to return to. If not, we're done. |
| 1227 | if (fiber->caller == NULL) |
| 1228 | { |
| 1229 | // Store the final result value at the beginning of the stack so the |
| 1230 | // C API can get it. |
| 1231 | fiber->stack[0] = result; |
| 1232 | fiber->stackTop = fiber->stack + 1; |
| 1233 | return WREN_RESULT_SUCCESS; |
| 1234 | } |
| 1235 | |
| 1236 | ObjFiber* resumingFiber = fiber->caller; |
| 1237 | fiber->caller = NULL; |
| 1238 | fiber = resumingFiber; |
| 1239 | vm->fiber = resumingFiber; |
| 1240 | |
| 1241 | // Store the result in the resuming fiber. |
| 1242 | fiber->stackTop[-1] = result; |
| 1243 | } |
| 1244 | else |
| 1245 | { |
| 1246 | // Store the result of the block in the first slot, which is where the |
| 1247 | // caller expects it. |
| 1248 | stackStart[0] = result; |
| 1249 | |
| 1250 | // Discard the stack slots for the call frame (leaving one slot for the |
| 1251 | // result). |
| 1252 | fiber->stackTop = frame->stackStart + 1; |
| 1253 | } |
| 1254 | |
| 1255 | LOAD_FRAME(); |
| 1256 | DISPATCH(); |
| 1257 | } |
| 1258 | |
| 1259 | CASE_CODE(CONSTRUCT): |
| 1260 | ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class." ); |
| 1261 | stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); |
| 1262 | DISPATCH(); |
| 1263 | |
| 1264 | CASE_CODE(FOREIGN_CONSTRUCT): |
| 1265 | ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class." ); |
| 1266 | createForeign(vm, fiber, stackStart); |
| 1267 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1268 | DISPATCH(); |
| 1269 | |
| 1270 | CASE_CODE(CLOSURE): |
| 1271 | { |
| 1272 | // Create the closure and push it on the stack before creating upvalues |
| 1273 | // so that it doesn't get collected. |
| 1274 | ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]); |
| 1275 | ObjClosure* closure = wrenNewClosure(vm, function); |
| 1276 | PUSH(OBJ_VAL(closure)); |
| 1277 | |
| 1278 | // Capture upvalues, if any. |
| 1279 | for (int i = 0; i < function->numUpvalues; i++) |
| 1280 | { |
| 1281 | uint8_t isLocal = READ_BYTE(); |
| 1282 | uint8_t index = READ_BYTE(); |
| 1283 | if (isLocal) |
| 1284 | { |
| 1285 | // Make an new upvalue to close over the parent's local variable. |
| 1286 | closure->upvalues[i] = captureUpvalue(vm, fiber, |
| 1287 | frame->stackStart + index); |
| 1288 | } |
| 1289 | else |
| 1290 | { |
| 1291 | // Use the same upvalue as the current call frame. |
| 1292 | closure->upvalues[i] = frame->closure->upvalues[index]; |
| 1293 | } |
| 1294 | } |
| 1295 | DISPATCH(); |
| 1296 | } |
| 1297 | |
| 1298 | CASE_CODE(END_CLASS): |
| 1299 | { |
| 1300 | endClass(vm); |
| 1301 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1302 | DISPATCH(); |
| 1303 | } |
| 1304 | |
| 1305 | CASE_CODE(CLASS): |
| 1306 | { |
| 1307 | createClass(vm, READ_BYTE(), NULL); |
| 1308 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1309 | DISPATCH(); |
| 1310 | } |
| 1311 | |
| 1312 | CASE_CODE(FOREIGN_CLASS): |
| 1313 | { |
| 1314 | createClass(vm, -1, fn->module); |
| 1315 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1316 | DISPATCH(); |
| 1317 | } |
| 1318 | |
| 1319 | CASE_CODE(METHOD_INSTANCE): |
| 1320 | CASE_CODE(METHOD_STATIC): |
| 1321 | { |
| 1322 | uint16_t symbol = READ_SHORT(); |
| 1323 | ObjClass* classObj = AS_CLASS(PEEK()); |
| 1324 | Value method = PEEK2(); |
| 1325 | bindMethod(vm, instruction, symbol, fn->module, classObj, method); |
| 1326 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1327 | DROP(); |
| 1328 | DROP(); |
| 1329 | DISPATCH(); |
| 1330 | } |
| 1331 | |
| 1332 | CASE_CODE(END_MODULE): |
| 1333 | { |
| 1334 | vm->lastModule = fn->module; |
| 1335 | PUSH(NULL_VAL); |
| 1336 | DISPATCH(); |
| 1337 | } |
| 1338 | |
| 1339 | CASE_CODE(IMPORT_MODULE): |
| 1340 | { |
| 1341 | // Make a slot on the stack for the module's fiber to place the return |
| 1342 | // value. It will be popped after this fiber is resumed. Store the |
| 1343 | // imported module's closure in the slot in case a GC happens when |
| 1344 | // invoking the closure. |
| 1345 | PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); |
| 1346 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1347 | |
| 1348 | // If we get a closure, call it to execute the module body. |
| 1349 | if (IS_CLOSURE(PEEK())) |
| 1350 | { |
| 1351 | STORE_FRAME(); |
| 1352 | ObjClosure* closure = AS_CLOSURE(PEEK()); |
| 1353 | wrenCallFunction(vm, fiber, closure, 1); |
| 1354 | LOAD_FRAME(); |
| 1355 | } |
| 1356 | else |
| 1357 | { |
| 1358 | // The module has already been loaded. Remember it so we can import |
| 1359 | // variables from it if needed. |
| 1360 | vm->lastModule = AS_MODULE(PEEK()); |
| 1361 | } |
| 1362 | |
| 1363 | DISPATCH(); |
| 1364 | } |
| 1365 | |
| 1366 | CASE_CODE(IMPORT_VARIABLE): |
| 1367 | { |
| 1368 | Value variable = fn->constants.data[READ_SHORT()]; |
| 1369 | ASSERT(vm->lastModule != NULL, "Should have already imported module." ); |
| 1370 | Value result = getModuleVariable(vm, vm->lastModule, variable); |
| 1371 | if (wrenHasError(fiber)) RUNTIME_ERROR(); |
| 1372 | |
| 1373 | PUSH(result); |
| 1374 | DISPATCH(); |
| 1375 | } |
| 1376 | |
| 1377 | CASE_CODE(END): |
| 1378 | // A CODE_END should always be preceded by a CODE_RETURN. If we get here, |
| 1379 | // the compiler generated wrong code. |
| 1380 | UNREACHABLE(); |
| 1381 | } |
| 1382 | |
| 1383 | // We should only exit this function from an explicit return from CODE_RETURN |
| 1384 | // or a runtime error. |
| 1385 | UNREACHABLE(); |
| 1386 | return WREN_RESULT_RUNTIME_ERROR; |
| 1387 | |
| 1388 | #undef READ_BYTE |
| 1389 | #undef READ_SHORT |
| 1390 | } |
| 1391 | |
| 1392 | WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature) |
| 1393 | { |
| 1394 | ASSERT(signature != NULL, "Signature cannot be NULL." ); |
| 1395 | |
| 1396 | int signatureLength = (int)strlen(signature); |
| 1397 | ASSERT(signatureLength > 0, "Signature cannot be empty." ); |
| 1398 | |
| 1399 | // Count the number parameters the method expects. |
| 1400 | int numParams = 0; |
| 1401 | if (signature[signatureLength - 1] == ')') |
| 1402 | { |
| 1403 | for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) |
| 1404 | { |
| 1405 | if (signature[i] == '_') numParams++; |
| 1406 | } |
| 1407 | } |
| 1408 | |
| 1409 | // Count subscript arguments. |
| 1410 | if (signature[0] == '[') |
| 1411 | { |
| 1412 | for (int i = 0; i < signatureLength && signature[i] != ']'; i++) |
| 1413 | { |
| 1414 | if (signature[i] == '_') numParams++; |
| 1415 | } |
| 1416 | } |
| 1417 | |
| 1418 | // Add the signatue to the method table. |
| 1419 | int method = wrenSymbolTableEnsure(vm, &vm->methodNames, |
| 1420 | signature, signatureLength); |
| 1421 | |
| 1422 | // Create a little stub function that assumes the arguments are on the stack |
| 1423 | // and calls the method. |
| 1424 | ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1); |
| 1425 | |
| 1426 | // Wrap the function in a closure and then in a handle. Do this here so it |
| 1427 | // doesn't get collected as we fill it in. |
| 1428 | WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); |
| 1429 | value->value = OBJ_VAL(wrenNewClosure(vm, fn)); |
| 1430 | |
| 1431 | wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams)); |
| 1432 | wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff); |
| 1433 | wrenByteBufferWrite(vm, &fn->code, method & 0xff); |
| 1434 | wrenByteBufferWrite(vm, &fn->code, CODE_RETURN); |
| 1435 | wrenByteBufferWrite(vm, &fn->code, CODE_END); |
| 1436 | wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5); |
| 1437 | wrenFunctionBindName(vm, fn, signature, signatureLength); |
| 1438 | |
| 1439 | return value; |
| 1440 | } |
| 1441 | |
| 1442 | WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) |
| 1443 | { |
| 1444 | ASSERT(method != NULL, "Method cannot be NULL." ); |
| 1445 | ASSERT(IS_CLOSURE(method->value), "Method must be a method handle." ); |
| 1446 | ASSERT(vm->fiber != NULL, "Must set up arguments for call first." ); |
| 1447 | ASSERT(vm->apiStack != NULL, "Must set up arguments for call first." ); |
| 1448 | ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method." ); |
| 1449 | |
| 1450 | ObjClosure* closure = AS_CLOSURE(method->value); |
| 1451 | |
| 1452 | ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity, |
| 1453 | "Stack must have enough arguments for method." ); |
| 1454 | |
| 1455 | // Clear the API stack. Now that wrenCall() has control, we no longer need |
| 1456 | // it. We use this being non-null to tell if re-entrant calls to foreign |
| 1457 | // methods are happening, so it's important to clear it out now so that you |
| 1458 | // can call foreign methods from within calls to wrenCall(). |
| 1459 | vm->apiStack = NULL; |
| 1460 | |
| 1461 | // Discard any extra temporary slots. We take for granted that the stub |
| 1462 | // function has exactly one slot for each argument. |
| 1463 | vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots]; |
| 1464 | |
| 1465 | wrenCallFunction(vm, vm->fiber, closure, 0); |
| 1466 | WrenInterpretResult result = runInterpreter(vm, vm->fiber); |
| 1467 | |
| 1468 | // If the call didn't abort, then set up the API stack to point to the |
| 1469 | // beginning of the stack so the host can access the call's return value. |
| 1470 | if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack; |
| 1471 | |
| 1472 | return result; |
| 1473 | } |
| 1474 | |
| 1475 | WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) |
| 1476 | { |
| 1477 | if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); |
| 1478 | |
| 1479 | // Make a handle for it. |
| 1480 | WrenHandle* handle = ALLOCATE(vm, WrenHandle); |
| 1481 | handle->value = value; |
| 1482 | |
| 1483 | if (IS_OBJ(value)) wrenPopRoot(vm); |
| 1484 | |
| 1485 | // Add it to the front of the linked list of handles. |
| 1486 | if (vm->handles != NULL) vm->handles->prev = handle; |
| 1487 | handle->prev = NULL; |
| 1488 | handle->next = vm->handles; |
| 1489 | vm->handles = handle; |
| 1490 | |
| 1491 | return handle; |
| 1492 | } |
| 1493 | |
| 1494 | void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) |
| 1495 | { |
| 1496 | ASSERT(handle != NULL, "Handle cannot be NULL." ); |
| 1497 | |
| 1498 | // Update the VM's head pointer if we're releasing the first handle. |
| 1499 | if (vm->handles == handle) vm->handles = handle->next; |
| 1500 | |
| 1501 | // Unlink it from the list. |
| 1502 | if (handle->prev != NULL) handle->prev->next = handle->next; |
| 1503 | if (handle->next != NULL) handle->next->prev = handle->prev; |
| 1504 | |
| 1505 | // Clear it out. This isn't strictly necessary since we're going to free it, |
| 1506 | // but it makes for easier debugging. |
| 1507 | handle->prev = NULL; |
| 1508 | handle->next = NULL; |
| 1509 | handle->value = NULL_VAL; |
| 1510 | DEALLOCATE(vm, handle); |
| 1511 | } |
| 1512 | |
| 1513 | WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, |
| 1514 | const char* source) |
| 1515 | { |
| 1516 | ObjClosure* closure = wrenCompileSource(vm, module, source, false, true); |
| 1517 | if (closure == NULL) return WREN_RESULT_COMPILE_ERROR; |
| 1518 | |
| 1519 | wrenPushRoot(vm, (Obj*)closure); |
| 1520 | ObjFiber* fiber = wrenNewFiber(vm, closure); |
| 1521 | wrenPopRoot(vm); // closure. |
| 1522 | vm->apiStack = NULL; |
| 1523 | |
| 1524 | return runInterpreter(vm, fiber); |
| 1525 | } |
| 1526 | |
| 1527 | ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, |
| 1528 | bool isExpression, bool printErrors) |
| 1529 | { |
| 1530 | Value nameValue = NULL_VAL; |
| 1531 | if (module != NULL) |
| 1532 | { |
| 1533 | nameValue = wrenNewString(vm, module); |
| 1534 | wrenPushRoot(vm, AS_OBJ(nameValue)); |
| 1535 | } |
| 1536 | |
| 1537 | ObjClosure* closure = compileInModule(vm, nameValue, source, |
| 1538 | isExpression, printErrors); |
| 1539 | |
| 1540 | if (module != NULL) wrenPopRoot(vm); // nameValue. |
| 1541 | return closure; |
| 1542 | } |
| 1543 | |
| 1544 | Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) |
| 1545 | { |
| 1546 | ObjModule* module = getModule(vm, moduleName); |
| 1547 | if (module == NULL) |
| 1548 | { |
| 1549 | vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded." , |
| 1550 | moduleName); |
| 1551 | return NULL_VAL; |
| 1552 | } |
| 1553 | |
| 1554 | return getModuleVariable(vm, module, variableName); |
| 1555 | } |
| 1556 | |
| 1557 | Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name) |
| 1558 | { |
| 1559 | int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name)); |
| 1560 | return module->variables.data[symbol]; |
| 1561 | } |
| 1562 | |
| 1563 | int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, |
| 1564 | size_t length, int line) |
| 1565 | { |
| 1566 | if (module->variables.count == MAX_MODULE_VARS) return -2; |
| 1567 | |
| 1568 | // Implicitly defined variables get a "value" that is the line where the |
| 1569 | // variable is first used. We'll use that later to report an error on the |
| 1570 | // right line. |
| 1571 | wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line)); |
| 1572 | return wrenSymbolTableAdd(vm, &module->variableNames, name, length); |
| 1573 | } |
| 1574 | |
| 1575 | int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, |
| 1576 | size_t length, Value value, int* line) |
| 1577 | { |
| 1578 | if (module->variables.count == MAX_MODULE_VARS) return -2; |
| 1579 | |
| 1580 | if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); |
| 1581 | |
| 1582 | // See if the variable is already explicitly or implicitly declared. |
| 1583 | int symbol = wrenSymbolTableFind(&module->variableNames, name, length); |
| 1584 | |
| 1585 | if (symbol == -1) |
| 1586 | { |
| 1587 | // Brand new variable. |
| 1588 | symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length); |
| 1589 | wrenValueBufferWrite(vm, &module->variables, value); |
| 1590 | } |
| 1591 | else if (IS_NUM(module->variables.data[symbol])) |
| 1592 | { |
| 1593 | // An implicitly declared variable's value will always be a number. |
| 1594 | // Now we have a real definition. |
| 1595 | if(line) *line = (int)AS_NUM(module->variables.data[symbol]); |
| 1596 | module->variables.data[symbol] = value; |
| 1597 | |
| 1598 | // If this was a localname we want to error if it was |
| 1599 | // referenced before this definition. |
| 1600 | if (wrenIsLocalName(name)) symbol = -3; |
| 1601 | } |
| 1602 | else |
| 1603 | { |
| 1604 | // Already explicitly declared. |
| 1605 | symbol = -1; |
| 1606 | } |
| 1607 | |
| 1608 | if (IS_OBJ(value)) wrenPopRoot(vm); |
| 1609 | |
| 1610 | return symbol; |
| 1611 | } |
| 1612 | |
| 1613 | // TODO: Inline? |
| 1614 | void wrenPushRoot(WrenVM* vm, Obj* obj) |
| 1615 | { |
| 1616 | ASSERT(obj != NULL, "Can't root NULL." ); |
| 1617 | ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots." ); |
| 1618 | |
| 1619 | vm->tempRoots[vm->numTempRoots++] = obj; |
| 1620 | } |
| 1621 | |
| 1622 | void wrenPopRoot(WrenVM* vm) |
| 1623 | { |
| 1624 | ASSERT(vm->numTempRoots > 0, "No temporary roots to release." ); |
| 1625 | vm->numTempRoots--; |
| 1626 | } |
| 1627 | |
| 1628 | int wrenGetSlotCount(WrenVM* vm) |
| 1629 | { |
| 1630 | if (vm->apiStack == NULL) return 0; |
| 1631 | |
| 1632 | return (int)(vm->fiber->stackTop - vm->apiStack); |
| 1633 | } |
| 1634 | |
| 1635 | void wrenEnsureSlots(WrenVM* vm, int numSlots) |
| 1636 | { |
| 1637 | // If we don't have a fiber accessible, create one for the API to use. |
| 1638 | if (vm->apiStack == NULL) |
| 1639 | { |
| 1640 | vm->fiber = wrenNewFiber(vm, NULL); |
| 1641 | vm->apiStack = vm->fiber->stack; |
| 1642 | } |
| 1643 | |
| 1644 | int currentSize = (int)(vm->fiber->stackTop - vm->apiStack); |
| 1645 | if (currentSize >= numSlots) return; |
| 1646 | |
| 1647 | // Grow the stack if needed. |
| 1648 | int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots; |
| 1649 | wrenEnsureStack(vm, vm->fiber, needed); |
| 1650 | |
| 1651 | vm->fiber->stackTop = vm->apiStack + numSlots; |
| 1652 | } |
| 1653 | |
| 1654 | // Ensures that [slot] is a valid index into the API's stack of slots. |
| 1655 | static void validateApiSlot(WrenVM* vm, int slot) |
| 1656 | { |
| 1657 | ASSERT(slot >= 0, "Slot cannot be negative." ); |
| 1658 | ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots." ); |
| 1659 | } |
| 1660 | |
| 1661 | // Gets the type of the object in [slot]. |
| 1662 | WrenType wrenGetSlotType(WrenVM* vm, int slot) |
| 1663 | { |
| 1664 | validateApiSlot(vm, slot); |
| 1665 | if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL; |
| 1666 | if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM; |
| 1667 | if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN; |
| 1668 | if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST; |
| 1669 | if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP; |
| 1670 | if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL; |
| 1671 | if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING; |
| 1672 | |
| 1673 | return WREN_TYPE_UNKNOWN; |
| 1674 | } |
| 1675 | |
| 1676 | bool wrenGetSlotBool(WrenVM* vm, int slot) |
| 1677 | { |
| 1678 | validateApiSlot(vm, slot); |
| 1679 | ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool." ); |
| 1680 | |
| 1681 | return AS_BOOL(vm->apiStack[slot]); |
| 1682 | } |
| 1683 | |
| 1684 | const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) |
| 1685 | { |
| 1686 | validateApiSlot(vm, slot); |
| 1687 | ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string." ); |
| 1688 | |
| 1689 | ObjString* string = AS_STRING(vm->apiStack[slot]); |
| 1690 | *length = string->length; |
| 1691 | return string->value; |
| 1692 | } |
| 1693 | |
| 1694 | double wrenGetSlotDouble(WrenVM* vm, int slot) |
| 1695 | { |
| 1696 | validateApiSlot(vm, slot); |
| 1697 | ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number." ); |
| 1698 | |
| 1699 | return AS_NUM(vm->apiStack[slot]); |
| 1700 | } |
| 1701 | |
| 1702 | void* wrenGetSlotForeign(WrenVM* vm, int slot) |
| 1703 | { |
| 1704 | validateApiSlot(vm, slot); |
| 1705 | ASSERT(IS_FOREIGN(vm->apiStack[slot]), |
| 1706 | "Slot must hold a foreign instance." ); |
| 1707 | |
| 1708 | return AS_FOREIGN(vm->apiStack[slot])->data; |
| 1709 | } |
| 1710 | |
| 1711 | const char* wrenGetSlotString(WrenVM* vm, int slot) |
| 1712 | { |
| 1713 | validateApiSlot(vm, slot); |
| 1714 | ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string." ); |
| 1715 | |
| 1716 | return AS_CSTRING(vm->apiStack[slot]); |
| 1717 | } |
| 1718 | |
| 1719 | WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) |
| 1720 | { |
| 1721 | validateApiSlot(vm, slot); |
| 1722 | return wrenMakeHandle(vm, vm->apiStack[slot]); |
| 1723 | } |
| 1724 | |
| 1725 | // Stores [value] in [slot] in the foreign call stack. |
| 1726 | static void setSlot(WrenVM* vm, int slot, Value value) |
| 1727 | { |
| 1728 | validateApiSlot(vm, slot); |
| 1729 | vm->apiStack[slot] = value; |
| 1730 | } |
| 1731 | |
| 1732 | void wrenSetSlotBool(WrenVM* vm, int slot, bool value) |
| 1733 | { |
| 1734 | setSlot(vm, slot, BOOL_VAL(value)); |
| 1735 | } |
| 1736 | |
| 1737 | void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) |
| 1738 | { |
| 1739 | ASSERT(bytes != NULL, "Byte array cannot be NULL." ); |
| 1740 | setSlot(vm, slot, wrenNewStringLength(vm, bytes, length)); |
| 1741 | } |
| 1742 | |
| 1743 | void wrenSetSlotDouble(WrenVM* vm, int slot, double value) |
| 1744 | { |
| 1745 | setSlot(vm, slot, NUM_VAL(value)); |
| 1746 | } |
| 1747 | |
| 1748 | void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) |
| 1749 | { |
| 1750 | validateApiSlot(vm, slot); |
| 1751 | validateApiSlot(vm, classSlot); |
| 1752 | ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class." ); |
| 1753 | |
| 1754 | ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]); |
| 1755 | ASSERT(classObj->numFields == -1, "Class must be a foreign class." ); |
| 1756 | |
| 1757 | ObjForeign* foreign = wrenNewForeign(vm, classObj, size); |
| 1758 | vm->apiStack[slot] = OBJ_VAL(foreign); |
| 1759 | |
| 1760 | return (void*)foreign->data; |
| 1761 | } |
| 1762 | |
| 1763 | void wrenSetSlotNewList(WrenVM* vm, int slot) |
| 1764 | { |
| 1765 | setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); |
| 1766 | } |
| 1767 | |
| 1768 | void wrenSetSlotNewMap(WrenVM* vm, int slot) |
| 1769 | { |
| 1770 | setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); |
| 1771 | } |
| 1772 | |
| 1773 | void wrenSetSlotNull(WrenVM* vm, int slot) |
| 1774 | { |
| 1775 | setSlot(vm, slot, NULL_VAL); |
| 1776 | } |
| 1777 | |
| 1778 | void wrenSetSlotString(WrenVM* vm, int slot, const char* text) |
| 1779 | { |
| 1780 | ASSERT(text != NULL, "String cannot be NULL." ); |
| 1781 | |
| 1782 | setSlot(vm, slot, wrenNewString(vm, text)); |
| 1783 | } |
| 1784 | |
| 1785 | void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) |
| 1786 | { |
| 1787 | ASSERT(handle != NULL, "Handle cannot be NULL." ); |
| 1788 | |
| 1789 | setSlot(vm, slot, handle->value); |
| 1790 | } |
| 1791 | |
| 1792 | int wrenGetListCount(WrenVM* vm, int slot) |
| 1793 | { |
| 1794 | validateApiSlot(vm, slot); |
| 1795 | ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list." ); |
| 1796 | |
| 1797 | ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements; |
| 1798 | return elements.count; |
| 1799 | } |
| 1800 | |
| 1801 | void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) |
| 1802 | { |
| 1803 | validateApiSlot(vm, listSlot); |
| 1804 | validateApiSlot(vm, elementSlot); |
| 1805 | ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list." ); |
| 1806 | |
| 1807 | ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements; |
| 1808 | |
| 1809 | uint32_t usedIndex = wrenValidateIndex(elements.count, index); |
| 1810 | ASSERT(usedIndex != UINT32_MAX, "Index out of bounds." ); |
| 1811 | |
| 1812 | vm->apiStack[elementSlot] = elements.data[usedIndex]; |
| 1813 | } |
| 1814 | |
| 1815 | void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) |
| 1816 | { |
| 1817 | validateApiSlot(vm, listSlot); |
| 1818 | validateApiSlot(vm, elementSlot); |
| 1819 | ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list." ); |
| 1820 | |
| 1821 | ObjList* list = AS_LIST(vm->apiStack[listSlot]); |
| 1822 | |
| 1823 | uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); |
| 1824 | ASSERT(usedIndex != UINT32_MAX, "Index out of bounds." ); |
| 1825 | |
| 1826 | list->elements.data[usedIndex] = vm->apiStack[elementSlot]; |
| 1827 | } |
| 1828 | |
| 1829 | void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) |
| 1830 | { |
| 1831 | validateApiSlot(vm, listSlot); |
| 1832 | validateApiSlot(vm, elementSlot); |
| 1833 | ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list." ); |
| 1834 | |
| 1835 | ObjList* list = AS_LIST(vm->apiStack[listSlot]); |
| 1836 | |
| 1837 | // Negative indices count from the end. |
| 1838 | // We don't use wrenValidateIndex here because insert allows 1 past the end. |
| 1839 | if (index < 0) index = list->elements.count + 1 + index; |
| 1840 | |
| 1841 | ASSERT(index <= list->elements.count, "Index out of bounds." ); |
| 1842 | |
| 1843 | wrenListInsert(vm, list, vm->apiStack[elementSlot], index); |
| 1844 | } |
| 1845 | |
| 1846 | int wrenGetMapCount(WrenVM* vm, int slot) |
| 1847 | { |
| 1848 | validateApiSlot(vm, slot); |
| 1849 | ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map." ); |
| 1850 | |
| 1851 | ObjMap* map = AS_MAP(vm->apiStack[slot]); |
| 1852 | return map->count; |
| 1853 | } |
| 1854 | |
| 1855 | bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot) |
| 1856 | { |
| 1857 | validateApiSlot(vm, mapSlot); |
| 1858 | validateApiSlot(vm, keySlot); |
| 1859 | ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map." ); |
| 1860 | |
| 1861 | Value key = vm->apiStack[keySlot]; |
| 1862 | ASSERT(wrenMapIsValidKey(key), "Key must be a value type" ); |
| 1863 | if (!validateKey(vm, key)) return false; |
| 1864 | |
| 1865 | ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); |
| 1866 | Value value = wrenMapGet(map, key); |
| 1867 | |
| 1868 | return !IS_UNDEFINED(value); |
| 1869 | } |
| 1870 | |
| 1871 | void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) |
| 1872 | { |
| 1873 | validateApiSlot(vm, mapSlot); |
| 1874 | validateApiSlot(vm, keySlot); |
| 1875 | validateApiSlot(vm, valueSlot); |
| 1876 | ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map." ); |
| 1877 | |
| 1878 | ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); |
| 1879 | Value value = wrenMapGet(map, vm->apiStack[keySlot]); |
| 1880 | if (IS_UNDEFINED(value)) { |
| 1881 | value = NULL_VAL; |
| 1882 | } |
| 1883 | |
| 1884 | vm->apiStack[valueSlot] = value; |
| 1885 | } |
| 1886 | |
| 1887 | void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) |
| 1888 | { |
| 1889 | validateApiSlot(vm, mapSlot); |
| 1890 | validateApiSlot(vm, keySlot); |
| 1891 | validateApiSlot(vm, valueSlot); |
| 1892 | ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map." ); |
| 1893 | |
| 1894 | Value key = vm->apiStack[keySlot]; |
| 1895 | ASSERT(wrenMapIsValidKey(key), "Key must be a value type" ); |
| 1896 | |
| 1897 | if (!validateKey(vm, key)) { |
| 1898 | return; |
| 1899 | } |
| 1900 | |
| 1901 | Value value = vm->apiStack[valueSlot]; |
| 1902 | ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); |
| 1903 | |
| 1904 | wrenMapSet(vm, map, key, value); |
| 1905 | } |
| 1906 | |
| 1907 | void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, |
| 1908 | int removedValueSlot) |
| 1909 | { |
| 1910 | validateApiSlot(vm, mapSlot); |
| 1911 | validateApiSlot(vm, keySlot); |
| 1912 | ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map." ); |
| 1913 | |
| 1914 | Value key = vm->apiStack[keySlot]; |
| 1915 | if (!validateKey(vm, key)) { |
| 1916 | return; |
| 1917 | } |
| 1918 | |
| 1919 | ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); |
| 1920 | Value removed = wrenMapRemoveKey(vm, map, key); |
| 1921 | setSlot(vm, removedValueSlot, removed); |
| 1922 | } |
| 1923 | |
| 1924 | void wrenGetVariable(WrenVM* vm, const char* module, const char* name, |
| 1925 | int slot) |
| 1926 | { |
| 1927 | ASSERT(module != NULL, "Module cannot be NULL." ); |
| 1928 | ASSERT(name != NULL, "Variable name cannot be NULL." ); |
| 1929 | |
| 1930 | Value moduleName = wrenStringFormat(vm, "$" , module); |
| 1931 | wrenPushRoot(vm, AS_OBJ(moduleName)); |
| 1932 | |
| 1933 | ObjModule* moduleObj = getModule(vm, moduleName); |
| 1934 | ASSERT(moduleObj != NULL, "Could not find module." ); |
| 1935 | |
| 1936 | wrenPopRoot(vm); // moduleName. |
| 1937 | |
| 1938 | int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, |
| 1939 | name, strlen(name)); |
| 1940 | ASSERT(variableSlot != -1, "Could not find variable." ); |
| 1941 | |
| 1942 | setSlot(vm, slot, moduleObj->variables.data[variableSlot]); |
| 1943 | } |
| 1944 | |
| 1945 | bool wrenHasVariable(WrenVM* vm, const char* module, const char* name) |
| 1946 | { |
| 1947 | ASSERT(module != NULL, "Module cannot be NULL." ); |
| 1948 | ASSERT(name != NULL, "Variable name cannot be NULL." ); |
| 1949 | |
| 1950 | Value moduleName = wrenStringFormat(vm, "$" , module); |
| 1951 | wrenPushRoot(vm, AS_OBJ(moduleName)); |
| 1952 | |
| 1953 | //We don't use wrenHasModule since we want to use the module object. |
| 1954 | ObjModule* moduleObj = getModule(vm, moduleName); |
| 1955 | ASSERT(moduleObj != NULL, "Could not find module." ); |
| 1956 | |
| 1957 | wrenPopRoot(vm); // moduleName. |
| 1958 | |
| 1959 | int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, |
| 1960 | name, strlen(name)); |
| 1961 | |
| 1962 | return variableSlot != -1; |
| 1963 | } |
| 1964 | |
| 1965 | bool wrenHasModule(WrenVM* vm, const char* module) |
| 1966 | { |
| 1967 | ASSERT(module != NULL, "Module cannot be NULL." ); |
| 1968 | |
| 1969 | Value moduleName = wrenStringFormat(vm, "$" , module); |
| 1970 | wrenPushRoot(vm, AS_OBJ(moduleName)); |
| 1971 | |
| 1972 | ObjModule* moduleObj = getModule(vm, moduleName); |
| 1973 | |
| 1974 | wrenPopRoot(vm); // moduleName. |
| 1975 | |
| 1976 | return moduleObj != NULL; |
| 1977 | } |
| 1978 | |
| 1979 | void wrenAbortFiber(WrenVM* vm, int slot) |
| 1980 | { |
| 1981 | validateApiSlot(vm, slot); |
| 1982 | vm->fiber->error = vm->apiStack[slot]; |
| 1983 | } |
| 1984 | |
| 1985 | void* wrenGetUserData(WrenVM* vm) |
| 1986 | { |
| 1987 | return vm->config.userData; |
| 1988 | } |
| 1989 | |
| 1990 | void wrenSetUserData(WrenVM* vm, void* userData) |
| 1991 | { |
| 1992 | vm->config.userData = userData; |
| 1993 | } |
| 1994 | |