2013-10-22 21:16:39 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
2013-10-23 22:51:41 +02:00
|
|
|
#include <string.h>
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
#include "wren.h"
|
2013-11-28 17:11:50 +01:00
|
|
|
#include "wren_common.h"
|
|
|
|
|
#include "wren_compiler.h"
|
|
|
|
|
#include "wren_vm.h"
|
2013-11-28 17:00:55 +01:00
|
|
|
#include "wren_core.h"
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
WrenVM* wrenNewVM(WrenReallocateFn reallocateFn)
|
2013-10-23 22:51:41 +02:00
|
|
|
{
|
2013-11-12 17:32:35 +01:00
|
|
|
// TODO(bob): Get rid of explicit malloc() here.
|
2013-11-25 16:47:02 +01:00
|
|
|
WrenVM* vm = reallocateFn(NULL, 0, sizeof(WrenVM));
|
2013-11-09 20:18:27 +01:00
|
|
|
initSymbolTable(&vm->methods);
|
2013-10-26 05:32:42 +02:00
|
|
|
initSymbolTable(&vm->globalSymbols);
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
|
2013-11-12 17:32:35 +01:00
|
|
|
// TODO(bob): Get rid of explicit malloc() here.
|
|
|
|
|
vm->fiber = malloc(sizeof(Fiber));
|
|
|
|
|
vm->fiber->stackSize = 0;
|
|
|
|
|
vm->fiber->numFrames = 0;
|
|
|
|
|
vm->totalAllocated = 0;
|
|
|
|
|
|
|
|
|
|
// TODO(bob): Make this configurable.
|
2013-11-16 20:43:40 +01:00
|
|
|
vm->nextGC = 1024 * 1024 * 10;
|
2013-11-29 18:14:19 +01:00
|
|
|
vm->first = NULL;
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-11-29 18:14:19 +01:00
|
|
|
vm->pinned = NULL;
|
|
|
|
|
|
2013-11-12 17:32:35 +01:00
|
|
|
// Clear out the global variables. This ensures they are NULL before being
|
|
|
|
|
// initialized in case we do a garbage collection before one gets initialized.
|
|
|
|
|
for (int i = 0; i < MAX_SYMBOLS; i++)
|
|
|
|
|
{
|
2013-11-16 20:26:32 +01:00
|
|
|
vm->globals[i] = NULL_VAL;
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-28 17:00:55 +01:00
|
|
|
wrenInitializeCore(vm);
|
2013-10-24 22:01:24 +02:00
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
return vm;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
void wrenFreeVM(WrenVM* vm)
|
2013-10-23 22:51:41 +02:00
|
|
|
{
|
2013-11-09 20:18:27 +01:00
|
|
|
clearSymbolTable(&vm->methods);
|
|
|
|
|
clearSymbolTable(&vm->globalSymbols);
|
2013-10-23 22:51:41 +02:00
|
|
|
free(vm);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
int wrenInterpret(WrenVM* vm, const char* source)
|
|
|
|
|
{
|
|
|
|
|
ObjFn* fn = wrenCompile(vm, source);
|
|
|
|
|
if (fn == NULL) return 1;
|
|
|
|
|
|
|
|
|
|
// TODO(bob): Return error code on runtime errors.
|
|
|
|
|
interpret(vm, fn);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void collectGarbage(WrenVM* vm);
|
2013-11-25 16:14:51 +01:00
|
|
|
|
|
|
|
|
// A generic allocation that handles all memory changes, like so:
|
|
|
|
|
//
|
|
|
|
|
// - To allocate new memory, [memory] is NULL and [oldSize] is zero.
|
|
|
|
|
//
|
|
|
|
|
// - To attempt to grow an existing allocation, [memory] is the memory,
|
|
|
|
|
// [oldSize] is its previous size, and [newSize] is the desired size.
|
|
|
|
|
// It returns [memory] if it was able to grow it in place, or a new pointer
|
|
|
|
|
// if it had to move it.
|
|
|
|
|
//
|
|
|
|
|
// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
|
|
|
|
|
// but it will always return [memory]. If [newSize] is zero, the memory will
|
|
|
|
|
// be freed and `NULL` will be returned.
|
|
|
|
|
//
|
|
|
|
|
// - To free memory, [newSize] will be zero.
|
2013-11-27 03:02:10 +01:00
|
|
|
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize)
|
2013-11-25 16:14:51 +01:00
|
|
|
{
|
|
|
|
|
ASSERT(memory == NULL || oldSize > 0, "Cannot take unsized previous memory.");
|
|
|
|
|
|
|
|
|
|
vm->totalAllocated += newSize - oldSize;
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG_GC_STRESS
|
|
|
|
|
if (newSize > oldSize)
|
|
|
|
|
{
|
|
|
|
|
collectGarbage(vm);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
if (vm->totalAllocated > vm->nextGC)
|
|
|
|
|
{
|
|
|
|
|
#ifdef TRACE_MEMORY
|
|
|
|
|
size_t before = vm->totalAllocated;
|
|
|
|
|
#endif
|
|
|
|
|
collectGarbage(vm);
|
|
|
|
|
vm->nextGC = vm->totalAllocated * 3 / 2;
|
|
|
|
|
|
|
|
|
|
#ifdef TRACE_MEMORY
|
|
|
|
|
printf("GC %ld before, %ld after (%ld collected), next at %ld\n",
|
|
|
|
|
before, vm->totalAllocated, before - vm->totalAllocated, vm->nextGC);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (newSize == 0)
|
|
|
|
|
{
|
|
|
|
|
ASSERT(memory != NULL, "Must have pointer to free.");
|
|
|
|
|
free(memory);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(bob): Let external code provide allocator.
|
|
|
|
|
return realloc(memory, newSize);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-16 20:26:32 +01:00
|
|
|
static void markValue(Value value);
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-11-17 02:41:09 +01:00
|
|
|
static void markFn(ObjFn* fn)
|
|
|
|
|
{
|
|
|
|
|
// Don't recurse if already marked. Avoids getting stuck in a loop on cycles.
|
|
|
|
|
if (fn->obj.flags & FLAG_MARKED) return;
|
|
|
|
|
fn->obj.flags |= FLAG_MARKED;
|
|
|
|
|
|
|
|
|
|
// Mark the constants.
|
|
|
|
|
for (int i = 0; i < fn->numConstants; i++)
|
|
|
|
|
{
|
|
|
|
|
markValue(fn->constants[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void markClass(ObjClass* classObj)
|
2013-11-16 20:26:32 +01:00
|
|
|
{
|
2013-11-12 17:32:35 +01:00
|
|
|
// Don't recurse if already marked. Avoids getting stuck in a loop on cycles.
|
2013-11-17 02:41:09 +01:00
|
|
|
if (classObj->obj.flags & FLAG_MARKED) return;
|
|
|
|
|
classObj->obj.flags |= FLAG_MARKED;
|
|
|
|
|
|
|
|
|
|
// The metaclass.
|
|
|
|
|
if (classObj->metaclass != NULL) markClass(classObj->metaclass);
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-11-17 02:41:09 +01:00
|
|
|
// The superclass.
|
|
|
|
|
if (classObj->superclass != NULL) markClass(classObj->superclass);
|
2013-11-12 17:32:35 +01:00
|
|
|
|
2013-11-17 02:41:09 +01:00
|
|
|
// Method function objects.
|
|
|
|
|
for (int i = 0; i < MAX_SYMBOLS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (classObj->methods[i].type == METHOD_BLOCK)
|
|
|
|
|
{
|
|
|
|
|
markFn(classObj->methods[i].fn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void markInstance(ObjInstance* instance)
|
|
|
|
|
{
|
|
|
|
|
// Don't recurse if already marked. Avoids getting stuck in a loop on cycles.
|
|
|
|
|
if (instance->obj.flags & FLAG_MARKED) return;
|
|
|
|
|
instance->obj.flags |= FLAG_MARKED;
|
|
|
|
|
|
|
|
|
|
markClass(instance->classObj);
|
2013-11-23 23:55:05 +01:00
|
|
|
|
|
|
|
|
// Mark the fields.
|
|
|
|
|
for (int i = 0; i < instance->classObj->numFields; i++)
|
|
|
|
|
{
|
|
|
|
|
markValue(instance->fields[i]);
|
|
|
|
|
}
|
2013-11-17 02:41:09 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-24 22:38:31 +01:00
|
|
|
static void markList(ObjList* list)
|
|
|
|
|
{
|
|
|
|
|
Value* elements = list->elements;
|
|
|
|
|
for (int i = 0; i < list->count; i++)
|
|
|
|
|
{
|
|
|
|
|
markValue(elements[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-17 02:41:09 +01:00
|
|
|
static void markObj(Obj* obj)
|
|
|
|
|
{
|
2013-11-12 17:32:35 +01:00
|
|
|
#ifdef TRACE_MEMORY
|
|
|
|
|
static int indent = 0;
|
|
|
|
|
indent++;
|
|
|
|
|
for (int i = 0; i < indent; i++) printf(" ");
|
|
|
|
|
printf("mark ");
|
2013-11-27 03:02:10 +01:00
|
|
|
wrenPrintValue(OBJ_VAL(obj));
|
2013-11-12 17:32:35 +01:00
|
|
|
printf("\n");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Traverse the object's fields.
|
|
|
|
|
switch (obj->type)
|
|
|
|
|
{
|
2013-11-17 02:41:09 +01:00
|
|
|
case OBJ_CLASS: markClass((ObjClass*)obj); break;
|
|
|
|
|
case OBJ_FN: markFn((ObjFn*)obj); break;
|
|
|
|
|
case OBJ_INSTANCE: markInstance((ObjInstance*)obj); break;
|
2013-11-24 22:38:31 +01:00
|
|
|
case OBJ_LIST: markList((ObjList*)obj); break;
|
2013-11-12 17:32:35 +01:00
|
|
|
case OBJ_STRING:
|
2013-11-17 02:41:09 +01:00
|
|
|
// Just mark the string itself.
|
|
|
|
|
obj->flags |= FLAG_MARKED;
|
2013-11-12 17:32:35 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef TRACE_MEMORY
|
|
|
|
|
indent--;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-16 20:26:32 +01:00
|
|
|
void markValue(Value value)
|
|
|
|
|
{
|
2013-11-16 20:41:29 +01:00
|
|
|
if (!IS_OBJ(value)) return;
|
2013-11-17 23:20:15 +01:00
|
|
|
markObj(AS_OBJ(value));
|
2013-11-16 20:26:32 +01:00
|
|
|
}
|
|
|
|
|
|
refactor: rename internal value creation functions to wren-prefixed public API names
Move `allocate`, `initObj`, `newClass`, and related static helpers from `vm.c` into `value.c` with consistent `wren` prefix, and update all call sites in `compiler.c`, `primitives.c`, and `vm.c` to use the new names (`wrenNewFunction`, `wrenNewString`, `wrenNewClass`, `wrenNewInstance`, `wrenNewList`). Remove the old declarations from `vm.h` and add comprehensive documentation comments to `value.h` explaining the NaN-tagging representation and type system.
2013-11-28 05:00:03 +01:00
|
|
|
static void* deallocate(WrenVM* vm, void* memory, size_t oldSize)
|
|
|
|
|
{
|
|
|
|
|
return wrenReallocate(vm, memory, oldSize, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
static void freeObj(WrenVM* vm, Obj* obj)
|
2013-11-12 17:32:35 +01:00
|
|
|
{
|
|
|
|
|
#ifdef TRACE_MEMORY
|
|
|
|
|
printf("free ");
|
2013-11-27 03:02:10 +01:00
|
|
|
wrenPrintValue(OBJ_VAL(obj));
|
2013-11-12 17:32:35 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Free any additional heap data allocated by the object.
|
|
|
|
|
size_t size;
|
|
|
|
|
|
|
|
|
|
switch (obj->type)
|
|
|
|
|
{
|
2013-11-24 22:38:31 +01:00
|
|
|
case OBJ_CLASS:
|
|
|
|
|
size = sizeof(ObjClass);
|
|
|
|
|
break;
|
|
|
|
|
|
2013-11-12 17:32:35 +01:00
|
|
|
case OBJ_FN:
|
|
|
|
|
{
|
|
|
|
|
// TODO(bob): Don't hardcode array sizes.
|
2013-11-25 16:14:51 +01:00
|
|
|
size = sizeof(ObjFn);
|
2013-11-16 20:26:32 +01:00
|
|
|
ObjFn* fn = (ObjFn*)obj;
|
2013-11-25 16:14:51 +01:00
|
|
|
deallocate(vm, fn->bytecode, sizeof(Code) * 1024);
|
|
|
|
|
deallocate(vm, fn->constants, sizeof(Value) * 256);
|
2013-11-12 17:32:35 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-24 22:38:31 +01:00
|
|
|
case OBJ_INSTANCE:
|
|
|
|
|
{
|
|
|
|
|
size = sizeof(ObjInstance);
|
|
|
|
|
|
|
|
|
|
// Include the size of the field array.
|
|
|
|
|
ObjInstance* instance = (ObjInstance*)obj;
|
|
|
|
|
size += sizeof(Value) * instance->classObj->numFields;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OBJ_LIST:
|
|
|
|
|
{
|
|
|
|
|
size = sizeof(ObjList);
|
|
|
|
|
ObjList* list = (ObjList*)obj;
|
2013-11-25 16:14:51 +01:00
|
|
|
if (list->elements != NULL)
|
|
|
|
|
{
|
|
|
|
|
deallocate(vm, list->elements, sizeof(Value) * list->count);
|
|
|
|
|
}
|
2013-11-24 22:38:31 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-12 17:32:35 +01:00
|
|
|
case OBJ_STRING:
|
2013-11-16 20:26:32 +01:00
|
|
|
{
|
2013-11-25 16:14:51 +01:00
|
|
|
size = sizeof(ObjString);
|
2013-11-16 20:26:32 +01:00
|
|
|
ObjString* string = (ObjString*)obj;
|
2013-11-25 16:14:51 +01:00
|
|
|
// TODO(bob): O(n) calculation here is lame!
|
|
|
|
|
deallocate(vm, string->value, strlen(string->value));
|
2013-11-12 17:32:35 +01:00
|
|
|
break;
|
2013-11-16 20:26:32 +01:00
|
|
|
}
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:14:51 +01:00
|
|
|
deallocate(vm, obj, size);
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
static void collectGarbage(WrenVM* vm)
|
2013-11-12 17:32:35 +01:00
|
|
|
{
|
|
|
|
|
// Mark all reachable objects.
|
|
|
|
|
#ifdef TRACE_MEMORY
|
|
|
|
|
printf("-- gc --\n");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Global variables.
|
|
|
|
|
for (int i = 0; i < vm->globalSymbols.count; i++)
|
|
|
|
|
{
|
|
|
|
|
// Check for NULL to handle globals that have been defined (at compile time)
|
|
|
|
|
// but not yet initialized.
|
2013-11-16 20:26:32 +01:00
|
|
|
if (!IS_NULL(vm->globals[i])) markValue(vm->globals[i]);
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pinned objects.
|
2013-11-29 18:14:19 +01:00
|
|
|
PinnedObj* pinned = vm->pinned;
|
|
|
|
|
while (pinned != NULL)
|
2013-11-12 17:32:35 +01:00
|
|
|
{
|
2013-11-29 18:14:19 +01:00
|
|
|
markObj(pinned->obj);
|
|
|
|
|
pinned = pinned->previous;
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stack functions.
|
|
|
|
|
for (int k = 0; k < vm->fiber->numFrames; k++)
|
|
|
|
|
{
|
2013-11-17 02:41:09 +01:00
|
|
|
markFn(vm->fiber->frames[k].fn);
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stack variables.
|
|
|
|
|
for (int l = 0; l < vm->fiber->stackSize; l++)
|
|
|
|
|
{
|
2013-11-16 20:26:32 +01:00
|
|
|
markValue(vm->fiber->stack[l]);
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect any unmarked objects.
|
|
|
|
|
Obj** obj = &vm->first;
|
|
|
|
|
while (*obj != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (!((*obj)->flags & FLAG_MARKED))
|
|
|
|
|
{
|
|
|
|
|
// This object wasn't reached, so remove it from the list and free it.
|
|
|
|
|
Obj* unreached = *obj;
|
|
|
|
|
*obj = unreached->next;
|
|
|
|
|
freeObj(vm, unreached);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// This object was reached, so unmark it (for the next GC) and move on to
|
|
|
|
|
// the next.
|
|
|
|
|
(*obj)->flags &= ~FLAG_MARKED;
|
|
|
|
|
obj = &(*obj)->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
void initSymbolTable(SymbolTable* symbols)
|
|
|
|
|
{
|
|
|
|
|
symbols->count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void clearSymbolTable(SymbolTable* symbols)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < symbols->count; i++)
|
|
|
|
|
{
|
|
|
|
|
free(symbols->names[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 18:19:03 +01:00
|
|
|
void truncateSymbolTable(SymbolTable* symbols, int count)
|
|
|
|
|
{
|
|
|
|
|
ASSERT(count <= symbols->count, "Cannot truncate to larger size.");
|
|
|
|
|
for (int i = count; i < symbols->count; i++)
|
|
|
|
|
{
|
|
|
|
|
free(symbols->names[i]);
|
|
|
|
|
}
|
|
|
|
|
symbols->count = count;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
int addSymbolUnchecked(SymbolTable* symbols, const char* name, size_t length)
|
|
|
|
|
{
|
2013-11-12 17:32:35 +01:00
|
|
|
// TODO(bob): Get rid of explicit malloc here.
|
2013-10-24 00:32:59 +02:00
|
|
|
symbols->names[symbols->count] = malloc(length + 1);
|
|
|
|
|
strncpy(symbols->names[symbols->count], name, length);
|
|
|
|
|
symbols->names[symbols->count][length] = '\0';
|
|
|
|
|
|
|
|
|
|
return symbols->count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int addSymbol(SymbolTable* symbols, const char* name, size_t length)
|
|
|
|
|
{
|
|
|
|
|
// If already present, return an error.
|
|
|
|
|
if (findSymbol(symbols, name, length) != -1) return -1;
|
|
|
|
|
|
|
|
|
|
return addSymbolUnchecked(symbols, name, length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ensureSymbol(SymbolTable* symbols, const char* name, size_t length)
|
|
|
|
|
{
|
|
|
|
|
// See if the symbol is already defined.
|
|
|
|
|
int existing = findSymbol(symbols, name, length);
|
|
|
|
|
if (existing != -1) return existing;
|
|
|
|
|
|
|
|
|
|
// New symbol, so add it.
|
|
|
|
|
return addSymbolUnchecked(symbols, name, length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int findSymbol(SymbolTable* symbols, const char* name, size_t length)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
// See if the symbol is already defined.
|
|
|
|
|
// TODO(bob): O(n). Do something better.
|
2013-10-24 00:32:59 +02:00
|
|
|
for (int i = 0; i < symbols->count; i++)
|
2013-10-23 22:51:41 +02:00
|
|
|
{
|
2013-10-24 00:32:59 +02:00
|
|
|
if (strlen(symbols->names[i]) == length &&
|
|
|
|
|
strncmp(symbols->names[i], name, length) == 0) return i;
|
2013-10-23 22:51:41 +02:00
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
return -1;
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-25 06:32:17 +02:00
|
|
|
const char* getSymbolName(SymbolTable* symbols, int symbol)
|
|
|
|
|
{
|
|
|
|
|
return symbols->names[symbol];
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
Value findGlobal(WrenVM* vm, const char* name)
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
{
|
|
|
|
|
int symbol = findSymbol(&vm->globalSymbols, name, strlen(name));
|
|
|
|
|
// TODO(bob): Handle failure.
|
|
|
|
|
return vm->globals[symbol];
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
int dumpInstruction(WrenVM* vm, ObjFn* fn, int i)
|
2013-11-06 00:40:21 +01:00
|
|
|
{
|
2013-11-18 07:38:59 +01:00
|
|
|
int start = i;
|
2013-11-09 20:18:27 +01:00
|
|
|
unsigned char* bytecode = fn->bytecode;
|
2013-11-18 07:38:59 +01:00
|
|
|
unsigned char code = bytecode[i++];
|
|
|
|
|
|
|
|
|
|
printf("%04d ", i);
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
switch (code)
|
|
|
|
|
{
|
|
|
|
|
case CODE_CONSTANT:
|
2013-11-06 00:40:21 +01:00
|
|
|
{
|
2013-11-18 07:38:59 +01:00
|
|
|
int constant = bytecode[i++];
|
|
|
|
|
printf("CONSTANT ");
|
2013-11-27 03:02:10 +01:00
|
|
|
wrenPrintValue(fn->constants[constant]);
|
2013-11-18 07:38:59 +01:00
|
|
|
printf("\n");
|
|
|
|
|
printf("%04d | constant %d\n", i, constant);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_NULL:
|
|
|
|
|
printf("NULL\n");
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_FALSE:
|
|
|
|
|
printf("FALSE\n");
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_TRUE:
|
|
|
|
|
printf("TRUE\n");
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_CLASS:
|
2013-11-24 22:38:31 +01:00
|
|
|
{
|
|
|
|
|
int numFields = bytecode[i++];
|
2013-11-18 07:38:59 +01:00
|
|
|
printf("CLASS\n");
|
2013-11-24 22:38:31 +01:00
|
|
|
printf("%04d | num fields %d\n", i, numFields);
|
2013-11-18 07:38:59 +01:00
|
|
|
break;
|
2013-11-24 22:38:31 +01:00
|
|
|
}
|
2013-11-16 20:26:32 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_SUBCLASS:
|
2013-11-24 22:38:31 +01:00
|
|
|
{
|
|
|
|
|
int numFields = bytecode[i++];
|
2013-11-18 07:38:59 +01:00
|
|
|
printf("SUBCLASS\n");
|
2013-11-24 22:38:31 +01:00
|
|
|
printf("%04d | num fields %d\n", i, numFields);
|
2013-11-18 07:38:59 +01:00
|
|
|
break;
|
2013-11-24 22:38:31 +01:00
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-20 16:20:16 +01:00
|
|
|
case CODE_METHOD_INSTANCE:
|
|
|
|
|
{
|
|
|
|
|
int symbol = bytecode[i++];
|
|
|
|
|
int constant = bytecode[i++];
|
|
|
|
|
printf("METHOD_INSTANCE \"%s\"\n", getSymbolName(&vm->methods, symbol));
|
|
|
|
|
printf("%04d | symbol %d\n", i - 1, symbol);
|
|
|
|
|
printf("%04d | constant %d\n", i, constant);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case CODE_METHOD_STATIC:
|
|
|
|
|
{
|
|
|
|
|
int symbol = bytecode[i++];
|
|
|
|
|
int constant = bytecode[i++];
|
|
|
|
|
printf("METHOD_STATIC \"%s\"\n", getSymbolName(&vm->methods, symbol));
|
|
|
|
|
printf("%04d | symbol %d\n", i - 1, symbol);
|
|
|
|
|
printf("%04d | constant %d\n", i, constant);
|
2013-11-18 07:38:59 +01:00
|
|
|
break;
|
2013-11-20 16:20:16 +01:00
|
|
|
}
|
2013-11-10 20:46:13 +01:00
|
|
|
|
2013-11-20 16:20:16 +01:00
|
|
|
case CODE_METHOD_CTOR:
|
2013-11-18 07:38:59 +01:00
|
|
|
{
|
|
|
|
|
int symbol = bytecode[i++];
|
|
|
|
|
int constant = bytecode[i++];
|
2013-11-20 16:20:16 +01:00
|
|
|
printf("METHOD_CTOR \"%s\"\n", getSymbolName(&vm->methods, symbol));
|
2013-11-18 07:38:59 +01:00
|
|
|
printf("%04d | symbol %d\n", i - 1, symbol);
|
|
|
|
|
printf("%04d | constant %d\n", i, constant);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-24 22:38:31 +01:00
|
|
|
case CODE_LIST:
|
|
|
|
|
{
|
|
|
|
|
int count = bytecode[i++];
|
|
|
|
|
printf("LIST\n");
|
|
|
|
|
printf("%04d | count %d\n", i, count);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_LOAD_LOCAL:
|
|
|
|
|
{
|
|
|
|
|
int local = bytecode[i++];
|
|
|
|
|
printf("LOAD_LOCAL %d\n", local);
|
|
|
|
|
printf("%04d | local %d\n", i, local);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_STORE_LOCAL:
|
|
|
|
|
{
|
|
|
|
|
int local = bytecode[i++];
|
|
|
|
|
printf("STORE_LOCAL %d\n", local);
|
|
|
|
|
printf("%04d | local %d\n", i, local);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_LOAD_GLOBAL:
|
|
|
|
|
{
|
|
|
|
|
int global = bytecode[i++];
|
|
|
|
|
printf("LOAD_GLOBAL \"%s\"\n",
|
|
|
|
|
getSymbolName(&vm->globalSymbols, global));
|
|
|
|
|
printf("%04d | global %d\n", i, global);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_STORE_GLOBAL:
|
|
|
|
|
{
|
|
|
|
|
int global = bytecode[i++];
|
|
|
|
|
printf("STORE_GLOBAL \"%s\"\n",
|
|
|
|
|
getSymbolName(&vm->globalSymbols, global));
|
|
|
|
|
printf("%04d | global %d\n", i, global);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-23 23:55:05 +01:00
|
|
|
case CODE_LOAD_FIELD:
|
|
|
|
|
{
|
|
|
|
|
int field = bytecode[i++];
|
|
|
|
|
printf("LOAD_FIELD %d\n", field);
|
|
|
|
|
printf("%04d | field %d\n", i, field);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case CODE_STORE_FIELD:
|
|
|
|
|
{
|
|
|
|
|
int field = bytecode[i++];
|
|
|
|
|
printf("STORE_FIELD %d\n", field);
|
|
|
|
|
printf("%04d | field %d\n", i, field);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_DUP:
|
|
|
|
|
printf("DUP\n");
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_POP:
|
|
|
|
|
printf("POP\n");
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_CALL_0:
|
|
|
|
|
case CODE_CALL_1:
|
|
|
|
|
case CODE_CALL_2:
|
|
|
|
|
case CODE_CALL_3:
|
|
|
|
|
case CODE_CALL_4:
|
|
|
|
|
case CODE_CALL_5:
|
|
|
|
|
case CODE_CALL_6:
|
|
|
|
|
case CODE_CALL_7:
|
|
|
|
|
case CODE_CALL_8:
|
|
|
|
|
case CODE_CALL_9:
|
|
|
|
|
case CODE_CALL_10:
|
|
|
|
|
{
|
|
|
|
|
// Add one for the implicit receiver argument.
|
|
|
|
|
int numArgs = bytecode[i - 1] - CODE_CALL_0;
|
|
|
|
|
int symbol = bytecode[i++];
|
|
|
|
|
printf("CALL_%d \"%s\"\n", numArgs,
|
|
|
|
|
getSymbolName(&vm->methods, symbol));
|
|
|
|
|
printf("%04d | symbol %d\n", i, symbol);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_JUMP:
|
|
|
|
|
{
|
|
|
|
|
int offset = bytecode[i++];
|
|
|
|
|
printf("JUMP %d\n", offset);
|
|
|
|
|
printf("%04d | offset %d\n", i, offset);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_LOOP:
|
|
|
|
|
{
|
|
|
|
|
int offset = bytecode[i++];
|
|
|
|
|
printf("LOOP %d\n", offset);
|
|
|
|
|
printf("%04d | offset -%d\n", i, offset);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_JUMP_IF:
|
|
|
|
|
{
|
|
|
|
|
int offset = bytecode[i++];
|
|
|
|
|
printf("JUMP_IF %d\n", offset);
|
|
|
|
|
printf("%04d | offset %d\n", i, offset);
|
|
|
|
|
break;
|
|
|
|
|
}
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
|
2013-11-19 16:35:25 +01:00
|
|
|
case CODE_AND:
|
|
|
|
|
{
|
|
|
|
|
int offset = bytecode[i++];
|
|
|
|
|
printf("AND %d\n", offset);
|
|
|
|
|
printf("%04d | offset %d\n", i, offset);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 03:24:58 +01:00
|
|
|
case CODE_OR:
|
|
|
|
|
{
|
|
|
|
|
int offset = bytecode[i++];
|
|
|
|
|
printf("OR %d\n", offset);
|
|
|
|
|
printf("%04d | offset %d\n", i, offset);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_IS:
|
|
|
|
|
printf("CODE_IS\n");
|
|
|
|
|
break;
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
case CODE_END:
|
|
|
|
|
printf("CODE_END\n");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
printf("UKNOWN! [%d]\n", bytecode[i - 1]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return how many bytes this instruction takes, or -1 if it's an END.
|
|
|
|
|
if (code == CODE_END) return -1;
|
|
|
|
|
return i - start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(bob): For debugging. Move to separate module.
|
2013-11-25 16:47:02 +01:00
|
|
|
void dumpCode(WrenVM* vm, ObjFn* fn)
|
2013-11-18 07:38:59 +01:00
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
int offset = dumpInstruction(vm, fn, i);
|
|
|
|
|
if (offset == -1) break;
|
|
|
|
|
i += offset;
|
2013-11-06 00:40:21 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 07:38:59 +01:00
|
|
|
void dumpStack(Fiber* fiber)
|
|
|
|
|
{
|
|
|
|
|
printf(":: ");
|
|
|
|
|
for (int i = 0; i < fiber->stackSize; i++)
|
|
|
|
|
{
|
2013-11-27 03:02:10 +01:00
|
|
|
wrenPrintValue(fiber->stack[i]);
|
2013-11-18 07:38:59 +01:00
|
|
|
printf(" | ");
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-25 16:47:02 +01:00
|
|
|
Value interpret(WrenVM* vm, ObjFn* fn)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-11-12 17:32:35 +01:00
|
|
|
Fiber* fiber = vm->fiber;
|
|
|
|
|
callFunction(fiber, fn, 0);
|
2013-10-23 22:51:41 +02:00
|
|
|
|
2013-11-10 20:46:13 +01:00
|
|
|
// These macros are designed to only be invoked within this function.
|
|
|
|
|
// TODO(bob): Check for stack overflow.
|
2013-11-12 17:32:35 +01:00
|
|
|
#define PUSH(value) (fiber->stack[fiber->stackSize++] = value)
|
|
|
|
|
#define POP() (fiber->stack[--fiber->stackSize])
|
|
|
|
|
#define PEEK() (fiber->stack[fiber->stackSize - 1])
|
2013-11-17 23:20:15 +01:00
|
|
|
#define READ_ARG() (frame->ip++, bytecode[ip++])
|
|
|
|
|
|
|
|
|
|
// Hoist these into local variables. They are accessed frequently in the loop
|
|
|
|
|
// but change less frequently. Keeping them in locals and updating them when
|
|
|
|
|
// a call frame has been pushed or pop gives a large speed boost.
|
|
|
|
|
CallFrame* frame = &fiber->frames[fiber->numFrames - 1];
|
|
|
|
|
int ip = frame->ip;
|
|
|
|
|
unsigned char* bytecode = frame->fn->bytecode;
|
2013-11-10 20:46:13 +01:00
|
|
|
|
2013-11-20 16:20:16 +01:00
|
|
|
// Use this before a CallFrame is pushed to store the local variables back
|
|
|
|
|
// into the current one.
|
|
|
|
|
#define STORE_FRAME() frame->ip = ip
|
|
|
|
|
|
|
|
|
|
// Use this after a CallFrame has been pushed or popped to refresh the local
|
|
|
|
|
// variables.
|
|
|
|
|
#define LOAD_FRAME() \
|
|
|
|
|
frame = &fiber->frames[fiber->numFrames - 1]; \
|
|
|
|
|
ip = frame->ip; \
|
|
|
|
|
bytecode = frame->fn->bytecode \
|
|
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
#ifdef COMPUTED_GOTOS
|
|
|
|
|
|
|
|
|
|
static void* dispatchTable[] = {
|
|
|
|
|
&&code_CONSTANT,
|
|
|
|
|
&&code_NULL,
|
|
|
|
|
&&code_FALSE,
|
|
|
|
|
&&code_TRUE,
|
|
|
|
|
&&code_CLASS,
|
|
|
|
|
&&code_SUBCLASS,
|
|
|
|
|
&&code_METHOD_INSTANCE,
|
|
|
|
|
&&code_METHOD_STATIC,
|
|
|
|
|
&&code_METHOD_CTOR,
|
|
|
|
|
&&code_LIST,
|
|
|
|
|
&&code_LOAD_LOCAL,
|
|
|
|
|
&&code_STORE_LOCAL,
|
|
|
|
|
&&code_LOAD_GLOBAL,
|
|
|
|
|
&&code_STORE_GLOBAL,
|
|
|
|
|
&&code_LOAD_FIELD,
|
|
|
|
|
&&code_STORE_FIELD,
|
|
|
|
|
&&code_DUP,
|
|
|
|
|
&&code_POP,
|
|
|
|
|
&&code_CALL_0,
|
|
|
|
|
&&code_CALL_1,
|
|
|
|
|
&&code_CALL_2,
|
|
|
|
|
&&code_CALL_3,
|
|
|
|
|
&&code_CALL_4,
|
|
|
|
|
&&code_CALL_5,
|
|
|
|
|
&&code_CALL_6,
|
|
|
|
|
&&code_CALL_7,
|
|
|
|
|
&&code_CALL_8,
|
|
|
|
|
&&code_CALL_9,
|
|
|
|
|
&&code_CALL_10,
|
|
|
|
|
&&code_JUMP,
|
|
|
|
|
&&code_LOOP,
|
|
|
|
|
&&code_JUMP_IF,
|
|
|
|
|
&&code_AND,
|
|
|
|
|
&&code_OR,
|
|
|
|
|
&&code_IS,
|
|
|
|
|
&&code_END
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define INTERPRET_LOOP DISPATCH();
|
|
|
|
|
#define CASE_CODE(name) code_##name
|
|
|
|
|
#define DISPATCH() goto *dispatchTable[instruction = bytecode[ip++]]
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
#define INTERPRET_LOOP for (;;) switch (instruction = bytecode[ip++])
|
|
|
|
|
#define CASE_CODE(name) case CODE_##name
|
|
|
|
|
#define DISPATCH() break
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
Code instruction;
|
|
|
|
|
INTERPRET_LOOP
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(CONSTANT):
|
|
|
|
|
PUSH(frame->fn->constants[READ_ARG()]);
|
|
|
|
|
DISPATCH();
|
2013-11-04 06:38:58 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH();
|
|
|
|
|
CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH();
|
|
|
|
|
CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH();
|
|
|
|
|
|
|
|
|
|
CASE_CODE(CLASS):
|
|
|
|
|
CASE_CODE(SUBCLASS):
|
|
|
|
|
{
|
|
|
|
|
int isSubclass = instruction == CODE_SUBCLASS;
|
|
|
|
|
int numFields = READ_ARG();
|
2013-11-04 06:38:58 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
ObjClass* superclass;
|
|
|
|
|
if (isSubclass)
|
2013-10-25 06:32:17 +02:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
// TODO(bob): Handle the superclass not being a class object!
|
|
|
|
|
superclass = AS_CLASS(POP());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Implicit Object superclass.
|
|
|
|
|
superclass = vm->objectClass;
|
|
|
|
|
}
|
2013-11-13 20:05:03 +01:00
|
|
|
|
refactor: rename internal value creation functions to wren-prefixed public API names
Move `allocate`, `initObj`, `newClass`, and related static helpers from `vm.c` into `value.c` with consistent `wren` prefix, and update all call sites in `compiler.c`, `primitives.c`, and `vm.c` to use the new names (`wrenNewFunction`, `wrenNewString`, `wrenNewClass`, `wrenNewInstance`, `wrenNewList`). Remove the old declarations from `vm.h` and add comprehensive documentation comments to `value.h` explaining the NaN-tagging representation and type system.
2013-11-28 05:00:03 +01:00
|
|
|
ObjClass* classObj = wrenNewClass(vm, superclass, numFields);
|
2013-11-13 20:05:03 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// Assume the first class being defined is Object.
|
|
|
|
|
if (vm->objectClass == NULL)
|
|
|
|
|
{
|
|
|
|
|
vm->objectClass = classObj;
|
|
|
|
|
}
|
2013-10-25 06:32:17 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// Define a "new" method on the metaclass.
|
|
|
|
|
// TODO(bob): Can this be inherited?
|
|
|
|
|
int newSymbol = ensureSymbol(&vm->methods, "new", strlen("new"));
|
|
|
|
|
classObj->metaclass->methods[newSymbol].type = METHOD_CTOR;
|
|
|
|
|
classObj->metaclass->methods[newSymbol].fn = NULL;
|
2013-11-16 20:26:32 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
PUSH(OBJ_VAL(classObj));
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-10-25 06:32:17 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(LIST):
|
|
|
|
|
{
|
|
|
|
|
int numElements = READ_ARG();
|
refactor: rename internal value creation functions to wren-prefixed public API names
Move `allocate`, `initObj`, `newClass`, and related static helpers from `vm.c` into `value.c` with consistent `wren` prefix, and update all call sites in `compiler.c`, `primitives.c`, and `vm.c` to use the new names (`wrenNewFunction`, `wrenNewString`, `wrenNewClass`, `wrenNewInstance`, `wrenNewList`). Remove the old declarations from `vm.h` and add comprehensive documentation comments to `value.h` explaining the NaN-tagging representation and type system.
2013-11-28 05:00:03 +01:00
|
|
|
ObjList* list = wrenNewList(vm, numElements);
|
2013-11-25 06:48:27 +01:00
|
|
|
for (int i = 0; i < numElements; i++)
|
|
|
|
|
{
|
|
|
|
|
list->elements[i] = fiber->stack[fiber->stackSize - numElements + i];
|
2013-11-10 20:46:13 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// Discard the elements.
|
|
|
|
|
fiber->stackSize -= numElements;
|
2013-11-24 22:38:31 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
PUSH(OBJ_VAL(list));
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-11-24 22:38:31 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(METHOD_INSTANCE):
|
|
|
|
|
CASE_CODE(METHOD_STATIC):
|
|
|
|
|
CASE_CODE(METHOD_CTOR):
|
|
|
|
|
{
|
|
|
|
|
int type = instruction;
|
|
|
|
|
int symbol = READ_ARG();
|
|
|
|
|
int constant = READ_ARG();
|
|
|
|
|
ObjClass* classObj = AS_CLASS(PEEK());
|
2013-11-24 22:38:31 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
switch (type)
|
2013-10-25 06:32:17 +02:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
case CODE_METHOD_INSTANCE:
|
|
|
|
|
classObj->methods[symbol].type = METHOD_BLOCK;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CODE_METHOD_STATIC:
|
|
|
|
|
// Statics are defined on the metaclass.
|
|
|
|
|
classObj = classObj->metaclass;
|
|
|
|
|
classObj->methods[symbol].type = METHOD_BLOCK;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CODE_METHOD_CTOR:
|
|
|
|
|
// Constructors are like statics.
|
|
|
|
|
classObj = classObj->metaclass;
|
|
|
|
|
classObj->methods[symbol].type = METHOD_CTOR;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-10-25 06:32:17 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
ObjFn* body = AS_FN(frame->fn->constants[constant]);
|
|
|
|
|
classObj->methods[symbol].fn = body;
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-11-20 16:20:16 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(LOAD_LOCAL):
|
|
|
|
|
{
|
|
|
|
|
int local = READ_ARG();
|
|
|
|
|
PUSH(fiber->stack[frame->stackStart + local]);
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(STORE_LOCAL):
|
|
|
|
|
{
|
|
|
|
|
int local = READ_ARG();
|
|
|
|
|
fiber->stack[frame->stackStart + local] = PEEK();
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(LOAD_GLOBAL):
|
|
|
|
|
{
|
|
|
|
|
int global = READ_ARG();
|
|
|
|
|
PUSH(vm->globals[global]);
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(STORE_GLOBAL):
|
|
|
|
|
{
|
|
|
|
|
int global = READ_ARG();
|
|
|
|
|
vm->globals[global] = PEEK();
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-10-26 05:32:42 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(LOAD_FIELD):
|
|
|
|
|
{
|
|
|
|
|
int field = READ_ARG();
|
|
|
|
|
// TODO(bob): We'll have to do something better here to handle functions
|
|
|
|
|
// inside methods.
|
|
|
|
|
Value receiver = fiber->stack[frame->stackStart];
|
|
|
|
|
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
|
|
|
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
|
|
|
ASSERT(field < instance->classObj->numFields, "Out of bounds field.");
|
|
|
|
|
PUSH(instance->fields[field]);
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-10-26 05:32:42 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(STORE_FIELD):
|
|
|
|
|
{
|
|
|
|
|
int field = READ_ARG();
|
|
|
|
|
// TODO(bob): We'll have to do something better here to handle functions
|
|
|
|
|
// inside methods.
|
|
|
|
|
Value receiver = fiber->stack[frame->stackStart];
|
|
|
|
|
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
|
|
|
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
|
|
|
ASSERT(field < instance->classObj->numFields, "Out of bounds field.");
|
|
|
|
|
instance->fields[field] = PEEK();
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-11-23 23:55:05 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(DUP): PUSH(PEEK()); DISPATCH();
|
|
|
|
|
CASE_CODE(POP): POP(); DISPATCH();
|
|
|
|
|
|
|
|
|
|
CASE_CODE(CALL_0):
|
|
|
|
|
CASE_CODE(CALL_1):
|
|
|
|
|
CASE_CODE(CALL_2):
|
|
|
|
|
CASE_CODE(CALL_3):
|
|
|
|
|
CASE_CODE(CALL_4):
|
|
|
|
|
CASE_CODE(CALL_5):
|
|
|
|
|
CASE_CODE(CALL_6):
|
|
|
|
|
CASE_CODE(CALL_7):
|
|
|
|
|
CASE_CODE(CALL_8):
|
|
|
|
|
CASE_CODE(CALL_9):
|
|
|
|
|
CASE_CODE(CALL_10):
|
|
|
|
|
{
|
|
|
|
|
// Add one for the implicit receiver argument.
|
|
|
|
|
int numArgs = instruction - CODE_CALL_0 + 1;
|
|
|
|
|
int symbol = READ_ARG();
|
2013-11-23 23:55:05 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
Value receiver = fiber->stack[fiber->stackSize - numArgs];
|
2013-11-26 16:52:37 +01:00
|
|
|
ObjClass* classObj = wrenGetClass(vm, receiver);
|
2013-11-25 06:48:27 +01:00
|
|
|
Method* method = &classObj->methods[symbol];
|
|
|
|
|
switch (method->type)
|
2013-10-23 22:51:41 +02:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
case METHOD_NONE:
|
|
|
|
|
printf("Receiver ");
|
2013-11-27 03:02:10 +01:00
|
|
|
wrenPrintValue(receiver);
|
2013-11-25 06:48:27 +01:00
|
|
|
printf(" does not implement method \"%s\".\n",
|
|
|
|
|
vm->methods.names[symbol]);
|
|
|
|
|
// TODO(bob): Throw an exception or halt the fiber or something.
|
|
|
|
|
exit(1);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case METHOD_PRIMITIVE:
|
2013-10-24 00:32:59 +02:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
Value* args = &fiber->stack[fiber->stackSize - numArgs];
|
|
|
|
|
Value result = method->primitive(vm, args);
|
2013-11-17 18:02:57 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
fiber->stack[fiber->stackSize - numArgs] = result;
|
2013-11-17 18:02:57 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// Discard the stack slots for the arguments (but leave one for
|
|
|
|
|
// the result).
|
|
|
|
|
fiber->stackSize -= numArgs - 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case METHOD_FIBER:
|
|
|
|
|
{
|
|
|
|
|
STORE_FRAME();
|
|
|
|
|
Value* args = &fiber->stack[fiber->stackSize - numArgs];
|
|
|
|
|
method->fiberPrimitive(vm, fiber, args);
|
|
|
|
|
LOAD_FRAME();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case METHOD_BLOCK:
|
|
|
|
|
STORE_FRAME();
|
|
|
|
|
callFunction(fiber, method->fn, numArgs);
|
|
|
|
|
LOAD_FRAME();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case METHOD_CTOR:
|
|
|
|
|
{
|
refactor: rename internal value creation functions to wren-prefixed public API names
Move `allocate`, `initObj`, `newClass`, and related static helpers from `vm.c` into `value.c` with consistent `wren` prefix, and update all call sites in `compiler.c`, `primitives.c`, and `vm.c` to use the new names (`wrenNewFunction`, `wrenNewString`, `wrenNewClass`, `wrenNewInstance`, `wrenNewList`). Remove the old declarations from `vm.h` and add comprehensive documentation comments to `value.h` explaining the NaN-tagging representation and type system.
2013-11-28 05:00:03 +01:00
|
|
|
Value instance = wrenNewInstance(vm, AS_CLASS(receiver));
|
2013-11-25 06:48:27 +01:00
|
|
|
|
|
|
|
|
// Store the new instance in the receiver slot so that it can be
|
|
|
|
|
// "this" in the body of the constructor and returned by it.
|
|
|
|
|
fiber->stack[fiber->stackSize - numArgs] = instance;
|
2013-11-10 23:21:14 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
if (method->fn == NULL)
|
2013-11-17 18:02:57 +01:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
// Default constructor, so no body to call. Just discard the
|
|
|
|
|
// stack slots for the arguments (but leave one for the instance).
|
|
|
|
|
fiber->stackSize -= numArgs - 1;
|
2013-11-17 18:02:57 +01:00
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Invoke the constructor body.
|
2013-11-20 16:20:16 +01:00
|
|
|
STORE_FRAME();
|
2013-11-12 17:32:35 +01:00
|
|
|
callFunction(fiber, method->fn, numArgs);
|
2013-11-20 16:20:16 +01:00
|
|
|
LOAD_FRAME();
|
|
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
break;
|
2013-10-24 00:32:59 +02:00
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(JUMP):
|
|
|
|
|
{
|
|
|
|
|
int offset = READ_ARG();
|
|
|
|
|
ip += offset;
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CASE_CODE(LOOP):
|
|
|
|
|
{
|
|
|
|
|
// The loop body's result is on the top of the stack. Since we are
|
|
|
|
|
// looping and running the body again, discard it.
|
|
|
|
|
POP();
|
|
|
|
|
|
|
|
|
|
// Jump back to the top of the loop.
|
|
|
|
|
int offset = READ_ARG();
|
|
|
|
|
ip -= offset;
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CASE_CODE(JUMP_IF):
|
|
|
|
|
{
|
|
|
|
|
int offset = READ_ARG();
|
|
|
|
|
Value condition = POP();
|
|
|
|
|
|
|
|
|
|
// False is the only falsey value.
|
|
|
|
|
if (IS_FALSE(condition)) ip += offset;
|
|
|
|
|
DISPATCH();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CASE_CODE(AND):
|
|
|
|
|
{
|
|
|
|
|
int offset = READ_ARG();
|
|
|
|
|
Value condition = PEEK();
|
2013-11-18 07:38:59 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// False is the only falsey value.
|
|
|
|
|
if (!IS_FALSE(condition))
|
2013-11-18 07:38:59 +01:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
// Discard the condition and evaluate the right hand side.
|
2013-11-18 07:38:59 +01:00
|
|
|
POP();
|
2013-11-17 23:20:15 +01:00
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
else
|
2013-11-06 00:40:21 +01:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
// Short-circuit the right hand side.
|
|
|
|
|
ip += offset;
|
2013-11-19 16:35:25 +01:00
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-11-19 16:35:25 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(OR):
|
|
|
|
|
{
|
|
|
|
|
int offset = READ_ARG();
|
|
|
|
|
Value condition = PEEK();
|
2013-11-19 16:35:25 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// False is the only falsey value.
|
|
|
|
|
if (IS_FALSE(condition))
|
|
|
|
|
{
|
|
|
|
|
// Discard the condition and evaluate the right hand side.
|
|
|
|
|
POP();
|
2013-11-20 03:24:58 +01:00
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
else
|
2013-11-20 03:24:58 +01:00
|
|
|
{
|
2013-11-25 06:48:27 +01:00
|
|
|
// Short-circuit the right hand side.
|
|
|
|
|
ip += offset;
|
2013-11-06 00:40:21 +01:00
|
|
|
}
|
2013-11-25 06:48:27 +01:00
|
|
|
DISPATCH();
|
|
|
|
|
}
|
2013-11-06 00:40:21 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(IS):
|
|
|
|
|
{
|
2013-11-26 16:52:37 +01:00
|
|
|
// TODO(bob): What if classObj is not a class?
|
|
|
|
|
ObjClass* expected = AS_CLASS(POP());
|
2013-11-25 06:48:27 +01:00
|
|
|
Value obj = POP();
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
|
2013-11-26 16:52:37 +01:00
|
|
|
ObjClass* actual = wrenGetClass(vm, obj);
|
|
|
|
|
int isInstance = 0;
|
|
|
|
|
|
|
|
|
|
// Walk the superclass chain looking for the class.
|
|
|
|
|
while (actual != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (actual == expected)
|
|
|
|
|
{
|
|
|
|
|
isInstance = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
actual = actual->superclass;
|
|
|
|
|
}
|
|
|
|
|
PUSH(BOOL_VAL(isInstance));
|
2013-11-25 06:48:27 +01:00
|
|
|
DISPATCH();
|
|
|
|
|
}
|
feat: add `is` operator for type checking and refactor primitives registration
Add TOKEN_IS token and CODE_IS bytecode to support runtime type checking via the `is` keyword. Refactor primitive registration by replacing `registerPrimitives` with `loadCore`, which compiles a core library string to define built-in classes (Bool, Class, Function, Num, Null, String, IO) and then attaches primitive methods to them. Update Primitive typedef to remove unused numArgs parameter, and add findGlobal helper to retrieve globals by name. Include test files for class syntax, class instantiation, and the new `is` operator.
2013-11-08 02:07:32 +01:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
CASE_CODE(END):
|
|
|
|
|
{
|
|
|
|
|
Value result = POP();
|
|
|
|
|
fiber->numFrames--;
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// If we are returning from the top-level block, just return the value.
|
|
|
|
|
if (fiber->numFrames == 0) return result;
|
2013-10-24 00:32:59 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// Store the result of the block in the first slot, which is where the
|
|
|
|
|
// caller expects it.
|
|
|
|
|
fiber->stack[frame->stackStart] = result;
|
2013-10-25 06:32:17 +02:00
|
|
|
|
2013-11-25 06:48:27 +01:00
|
|
|
// Discard the stack slots for the call frame (leaving one slot for the
|
|
|
|
|
// result).
|
|
|
|
|
fiber->stackSize = frame->stackStart + 1;
|
|
|
|
|
LOAD_FRAME();
|
|
|
|
|
DISPATCH();
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 15:52:28 +01:00
|
|
|
void callFunction(Fiber* fiber, ObjFn* fn, int numArgs)
|
2013-11-05 16:56:59 +01:00
|
|
|
{
|
2013-11-06 15:52:28 +01:00
|
|
|
fiber->frames[fiber->numFrames].fn = fn;
|
2013-11-05 16:56:59 +01:00
|
|
|
fiber->frames[fiber->numFrames].ip = 0;
|
2013-11-09 20:18:27 +01:00
|
|
|
fiber->frames[fiber->numFrames].stackStart = fiber->stackSize - numArgs;
|
2013-11-05 16:56:59 +01:00
|
|
|
|
2013-11-06 15:52:28 +01:00
|
|
|
// TODO(bob): Check for stack overflow.
|
2013-11-05 16:56:59 +01:00
|
|
|
fiber->numFrames++;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-29 18:14:19 +01:00
|
|
|
void pinObj(WrenVM* vm, Obj* obj, PinnedObj* pinned)
|
2013-11-12 17:32:35 +01:00
|
|
|
{
|
2013-11-29 18:14:19 +01:00
|
|
|
pinned->obj = obj;
|
|
|
|
|
pinned->previous = vm->pinned;
|
|
|
|
|
vm->pinned = pinned;
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|
|
|
|
|
|
2013-11-29 18:14:19 +01:00
|
|
|
void unpinObj(WrenVM* vm)
|
2013-11-12 17:32:35 +01:00
|
|
|
{
|
2013-11-29 18:14:19 +01:00
|
|
|
vm->pinned = vm->pinned->previous;
|
2013-11-12 17:32:35 +01:00
|
|
|
}
|