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 | |