First pass at implementing foreign classes.
Most of the pieces are there: - You can declare a foreign class. - It will call your C function to provide an allocator function. - Whenever a foreign object is created, it calls the allocator. - Foreign methods can access the foreign bytes of an object. - Most of the runtime checking is in place for things like subclassing foreign classes. There is still some loose ends to tie up: - Finalizers are not called. - Some of the error-handling could be better. - The GC doesn't track how much memory a marked foreign object uses.
This commit is contained in:
+36
-7
@@ -252,6 +252,9 @@ typedef struct
|
||||
// Symbol table for the fields of the class.
|
||||
SymbolTable* fields;
|
||||
|
||||
// True if the class being compiled is a foreign class.
|
||||
bool isForeign;
|
||||
|
||||
// True if the current method being compiled is static.
|
||||
bool inStatic;
|
||||
|
||||
@@ -1938,6 +1941,10 @@ static void field(Compiler* compiler, bool allowAssignment)
|
||||
{
|
||||
error(compiler, "Cannot reference a field outside of a class definition.");
|
||||
}
|
||||
else if (enclosingClass->isForeign)
|
||||
{
|
||||
error(compiler, "Cannot define fields in a foreign class.");
|
||||
}
|
||||
else if (enclosingClass->inStatic)
|
||||
{
|
||||
error(compiler, "Cannot use an instance field in a static method.");
|
||||
@@ -2591,6 +2598,8 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
||||
case CODE_LOAD_LOCAL_7:
|
||||
case CODE_LOAD_LOCAL_8:
|
||||
case CODE_CONSTRUCT:
|
||||
case CODE_FOREIGN_CONSTRUCT:
|
||||
case CODE_FOREIGN_CLASS:
|
||||
return 0;
|
||||
|
||||
case CODE_LOAD_LOCAL:
|
||||
@@ -2945,7 +2954,8 @@ static void createConstructor(Compiler* compiler, Signature* signature,
|
||||
initCompiler(&methodCompiler, compiler->parser, compiler, false);
|
||||
|
||||
// Allocate the instance.
|
||||
emit(&methodCompiler, CODE_CONSTRUCT);
|
||||
emit(&methodCompiler, compiler->enclosingClass->isForeign
|
||||
? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT);
|
||||
|
||||
// Run its initializer.
|
||||
emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity),
|
||||
@@ -3072,7 +3082,6 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler,
|
||||
static void createDefaultConstructor(Compiler* compiler, int classSlot)
|
||||
{
|
||||
Signature signature = { "new", 3, SIG_INITIALIZER, 0 };
|
||||
|
||||
int initializerSymbol = signatureSymbol(compiler, &signature);
|
||||
|
||||
signature.type = SIG_METHOD;
|
||||
@@ -3083,8 +3092,8 @@ static void createDefaultConstructor(Compiler* compiler, int classSlot)
|
||||
}
|
||||
|
||||
// Compiles a class definition. Assumes the "class" token has already been
|
||||
// consumed.
|
||||
static void classDefinition(Compiler* compiler)
|
||||
// consumed (along with a possibly preceding "foreign" token).
|
||||
static void classDefinition(Compiler* compiler, bool isForeign)
|
||||
{
|
||||
// Create a variable to store the class in.
|
||||
int slot = declareNamedVariable(compiler);
|
||||
@@ -3109,7 +3118,15 @@ static void classDefinition(Compiler* compiler)
|
||||
// Store a placeholder for the number of fields argument. We don't know
|
||||
// the value until we've compiled all the methods to see which fields are
|
||||
// used.
|
||||
int numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
|
||||
int numFieldsInstruction = -1;
|
||||
if (isForeign)
|
||||
{
|
||||
emit(compiler, CODE_FOREIGN_CLASS);
|
||||
}
|
||||
else
|
||||
{
|
||||
numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
|
||||
}
|
||||
|
||||
// Store it in its name.
|
||||
defineVariable(compiler, slot);
|
||||
@@ -3120,6 +3137,7 @@ static void classDefinition(Compiler* compiler)
|
||||
pushScope(compiler);
|
||||
|
||||
ClassCompiler classCompiler;
|
||||
classCompiler.isForeign = isForeign;
|
||||
|
||||
// Set up a symbol table for the class's fields. We'll initially compile
|
||||
// them to slots starting at zero. When the method is bound to the class, the
|
||||
@@ -3155,7 +3173,11 @@ static void classDefinition(Compiler* compiler)
|
||||
}
|
||||
|
||||
// Update the class with the number of fields.
|
||||
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
|
||||
if (!isForeign)
|
||||
{
|
||||
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
|
||||
}
|
||||
|
||||
wrenSymbolTableClear(compiler->parser->vm, &fields);
|
||||
|
||||
compiler->enclosingClass = NULL;
|
||||
@@ -3227,7 +3249,14 @@ void definition(Compiler* compiler)
|
||||
{
|
||||
if (match(compiler, TOKEN_CLASS))
|
||||
{
|
||||
classDefinition(compiler);
|
||||
classDefinition(compiler, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match(compiler, TOKEN_FOREIGN))
|
||||
{
|
||||
consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'.");
|
||||
classDefinition(compiler, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -32,10 +32,13 @@ static void dumpObject(Obj* obj)
|
||||
{
|
||||
switch (obj->type)
|
||||
{
|
||||
case OBJ_CLASS: printf("[class %p]", obj); break;
|
||||
case OBJ_CLASS:
|
||||
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
|
||||
break;
|
||||
case OBJ_CLOSURE: printf("[closure %p]", obj); break;
|
||||
case OBJ_FIBER: printf("[fiber %p]", obj); break;
|
||||
case OBJ_FN: printf("[fn %p]", obj); break;
|
||||
case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
|
||||
case OBJ_INSTANCE: printf("[instance %p]", obj); break;
|
||||
case OBJ_LIST: printf("[list %p]", obj); break;
|
||||
case OBJ_MAP: printf("[map %p]", obj); break;
|
||||
@@ -269,7 +272,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
|
||||
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
|
||||
case CODE_FOREIGN_CONSTRUCT: printf("CODE_FOREIGN_CONSTRUCT\n"); break;
|
||||
|
||||
case CODE_CLASS:
|
||||
{
|
||||
@@ -278,6 +282,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
|
||||
|
||||
case CODE_METHOD_INSTANCE:
|
||||
{
|
||||
int symbol = READ_SHORT();
|
||||
|
||||
@@ -157,11 +157,22 @@ OPCODE(CLOSURE)
|
||||
// compiler-generated constructor metaclass methods.
|
||||
OPCODE(CONSTRUCT)
|
||||
|
||||
// Creates a new instance of a foreign class.
|
||||
//
|
||||
// Assumes the class object is in slot zero, and replaces it with the new
|
||||
// uninitialized instance of that class. This opcode is only emitted by the
|
||||
// compiler-generated constructor metaclass methods.
|
||||
OPCODE(FOREIGN_CONSTRUCT)
|
||||
|
||||
// Creates a class. Top of stack is the superclass, or `null` if the class
|
||||
// inherits Object. Below that is a string for the name of the class. Byte
|
||||
// [arg] is the number of fields in the class.
|
||||
OPCODE(CLASS)
|
||||
|
||||
// Creates a foreign class. Top of stack is the superclass, or `null` if the
|
||||
// class inherits Object. Below that is a string for the name of the class.
|
||||
OPCODE(FOREIGN_CLASS)
|
||||
|
||||
// Define a method for symbol [arg]. The class receiving the method is popped
|
||||
// off the stack, then the function defining the body is popped.
|
||||
//
|
||||
|
||||
+33
-1
@@ -65,7 +65,15 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass)
|
||||
subclass->superclass = superclass;
|
||||
|
||||
// Include the superclass in the total number of fields.
|
||||
subclass->numFields += superclass->numFields;
|
||||
if (subclass->numFields != -1)
|
||||
{
|
||||
subclass->numFields += superclass->numFields;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(superclass->numFields == 0,
|
||||
"A foreign class cannot inherit from a class with fields.");
|
||||
}
|
||||
|
||||
// Inherit methods from its superclass.
|
||||
for (int i = 0; i < superclass->methods.count; i++)
|
||||
@@ -166,6 +174,16 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn)
|
||||
wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
|
||||
}
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
|
||||
{
|
||||
ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size);
|
||||
initObj(vm, &object->obj, OBJ_FOREIGN, classObj);
|
||||
|
||||
// Zero out the bytes.
|
||||
memset(object->data, 0, size);
|
||||
return object;
|
||||
}
|
||||
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
||||
const Value* constants, int numConstants,
|
||||
int numUpvalues, int arity,
|
||||
@@ -903,6 +921,15 @@ static void markFn(WrenVM* vm, ObjFn* fn)
|
||||
// TODO: What about the function name?
|
||||
}
|
||||
|
||||
static void markForeign(WrenVM* vm, ObjForeign* foreign)
|
||||
{
|
||||
// TODO: Keep track of how much memory the foreign object uses. We can store
|
||||
// this in each foreign object, but it will balloon the size. We may not want
|
||||
// that much overhead. One option would be to let the foreign class register
|
||||
// a C function that returns a size for the object. That way the VM doesn't
|
||||
// always have to explicitly store it.
|
||||
}
|
||||
|
||||
static void markInstance(WrenVM* vm, ObjInstance* instance)
|
||||
{
|
||||
wrenMarkObj(vm, (Obj*)instance->obj.classObj);
|
||||
@@ -1007,6 +1034,7 @@ void wrenMarkObj(WrenVM* vm, Obj* obj)
|
||||
case OBJ_CLOSURE: markClosure( vm, (ObjClosure*) obj); break;
|
||||
case OBJ_FIBER: markFiber( vm, (ObjFiber*) obj); break;
|
||||
case OBJ_FN: markFn( vm, (ObjFn*) obj); break;
|
||||
case OBJ_FOREIGN: markForeign( vm, (ObjForeign*) obj); break;
|
||||
case OBJ_INSTANCE: markInstance(vm, (ObjInstance*)obj); break;
|
||||
case OBJ_LIST: markList( vm, (ObjList*) obj); break;
|
||||
case OBJ_MAP: markMap( vm, (ObjMap*) obj); break;
|
||||
@@ -1059,6 +1087,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
||||
DEALLOCATE(vm, fn->debug);
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJ_FOREIGN:
|
||||
// TODO: Call finalizer.
|
||||
break;
|
||||
|
||||
case OBJ_LIST:
|
||||
wrenValueBufferClear(vm, &((ObjList*)obj)->elements);
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
|
||||
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
|
||||
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
|
||||
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
|
||||
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
|
||||
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
|
||||
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
|
||||
@@ -74,6 +75,7 @@
|
||||
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
|
||||
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
|
||||
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
|
||||
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
|
||||
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
|
||||
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
|
||||
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
|
||||
@@ -89,6 +91,7 @@ typedef enum {
|
||||
OBJ_CLOSURE,
|
||||
OBJ_FIBER,
|
||||
OBJ_FN,
|
||||
OBJ_FOREIGN,
|
||||
OBJ_INSTANCE,
|
||||
OBJ_LIST,
|
||||
OBJ_MAP,
|
||||
@@ -397,6 +400,12 @@ struct sObjClass
|
||||
ObjString* name;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
uint8_t data[FLEXIBLE_ARRAY];
|
||||
} ObjForeign;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
@@ -652,6 +661,8 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
||||
frame->ip = wrenGetFrameFunction(frame)->bytecode;
|
||||
}
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
||||
|
||||
// TODO: The argument list here is getting a bit gratuitous.
|
||||
// Creates a new function object with the given code and constants. The new
|
||||
// function will take over ownership of [bytecode] and [sourceLines]. It will
|
||||
|
||||
+194
-72
@@ -37,6 +37,17 @@ static void* defaultReallocate(void* ptr, size_t newSize)
|
||||
return realloc(ptr, newSize);
|
||||
}
|
||||
|
||||
void wrenInitConfiguration(WrenConfiguration* configuration)
|
||||
{
|
||||
configuration->reallocateFn = NULL;
|
||||
configuration->loadModuleFn = NULL;
|
||||
configuration->bindForeignMethodFn = NULL;
|
||||
configuration->bindForeignClassFn = NULL;
|
||||
configuration->initialHeapSize = 1024 * 1024 * 10;
|
||||
configuration->minHeapSize = 1024 * 1024;
|
||||
configuration->heapGrowthPercent = 50;
|
||||
}
|
||||
|
||||
WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
{
|
||||
WrenReallocateFn reallocate = defaultReallocate;
|
||||
@@ -49,32 +60,19 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
memset(vm, 0, sizeof(WrenVM));
|
||||
|
||||
vm->reallocate = reallocate;
|
||||
vm->bindForeign = configuration->bindForeignMethodFn;
|
||||
vm->bindForeignMethod = configuration->bindForeignMethodFn;
|
||||
vm->bindForeignClass = configuration->bindForeignClassFn;
|
||||
vm->loadModule = configuration->loadModuleFn;
|
||||
vm->nextGC = configuration->initialHeapSize;
|
||||
vm->minNextGC = configuration->minHeapSize;
|
||||
|
||||
// +100 here because the configuration gives us the *additional* size of
|
||||
// the heap relative to the in-use memory, while heapScalePercent is the
|
||||
// *total* size of the heap relative to in-use.
|
||||
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
|
||||
|
||||
wrenSymbolTableInit(&vm->methodNames);
|
||||
|
||||
vm->nextGC = 1024 * 1024 * 10;
|
||||
if (configuration->initialHeapSize != 0)
|
||||
{
|
||||
vm->nextGC = configuration->initialHeapSize;
|
||||
}
|
||||
|
||||
vm->minNextGC = 1024 * 1024;
|
||||
if (configuration->minHeapSize != 0)
|
||||
{
|
||||
vm->minNextGC = configuration->minHeapSize;
|
||||
}
|
||||
|
||||
vm->heapScalePercent = 150;
|
||||
if (configuration->heapGrowthPercent != 0)
|
||||
{
|
||||
// +100 here because the configuration gives us the *additional* size of
|
||||
// the heap relative to the in-use memory, while heapScalePercent is the
|
||||
// *total* size of the heap relative to in-use.
|
||||
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
|
||||
}
|
||||
|
||||
ObjString* name = AS_STRING(CONST_STRING(vm, "core"));
|
||||
wrenPushRoot(vm, (Obj*)name);
|
||||
|
||||
@@ -311,24 +309,21 @@ static WrenForeignMethodFn findForeignMethod(WrenVM* vm,
|
||||
{
|
||||
WrenForeignMethodFn fn;
|
||||
|
||||
// Let the host try to find it first.
|
||||
if (vm->bindForeign != NULL)
|
||||
{
|
||||
fn = vm->bindForeign(vm, moduleName, className, isStatic, signature);
|
||||
if (fn != NULL) return fn;
|
||||
}
|
||||
|
||||
// Otherwise, try the built-in libraries.
|
||||
// Bind foreign methods in the core module.
|
||||
if (strcmp(moduleName, "core") == 0)
|
||||
{
|
||||
#if WREN_USE_LIB_IO
|
||||
fn = wrenBindIOForeignMethod(vm, className, signature);
|
||||
if (fn != NULL) return fn;
|
||||
#endif
|
||||
|
||||
ASSERT(fn != NULL, "Failed to bind core module foreign method.");
|
||||
return fn;
|
||||
}
|
||||
|
||||
// TODO: Report a runtime error on failure to find it.
|
||||
return NULL;
|
||||
// For other modules, let the host bind it.
|
||||
if (vm->bindForeignMethod == NULL) return NULL;
|
||||
|
||||
return vm->bindForeignMethod(vm, moduleName, className, isStatic, signature);
|
||||
}
|
||||
|
||||
// Defines [methodValue] as a method on [classObj].
|
||||
@@ -546,18 +541,23 @@ static bool importVariable(WrenVM* vm, Value moduleName, Value variableName,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verifies that [superclass] is a valid object to inherit from. That means it
|
||||
// must be a class and cannot be the class of any built-in type.
|
||||
// Verifies that [superclassValue] is a valid object to inherit from. That
|
||||
// means it must be a class and cannot be the class of any built-in type.
|
||||
//
|
||||
// If successful, returns null. Otherwise, returns a string for the runtime
|
||||
// Also validates that it doesn't result in a class with too many fields and
|
||||
// the other limitations foreign classes have.
|
||||
//
|
||||
// If successful, returns `null`. Otherwise, returns a string for the runtime
|
||||
// error message.
|
||||
static Value validateSuperclass(WrenVM* vm, Value name,
|
||||
Value superclassValue)
|
||||
static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue,
|
||||
int numFields)
|
||||
{
|
||||
// Make sure the superclass is a class.
|
||||
if (!IS_CLASS(superclassValue))
|
||||
{
|
||||
return CONST_STRING(vm, "Must inherit from a class.");
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' cannot inherit from a non-class object.",
|
||||
name);
|
||||
}
|
||||
|
||||
// Make sure it doesn't inherit from a sealed built-in type. Primitive methods
|
||||
@@ -572,13 +572,125 @@ static Value validateSuperclass(WrenVM* vm, Value name,
|
||||
superclass == vm->rangeClass ||
|
||||
superclass == vm->stringClass)
|
||||
{
|
||||
return wrenStringFormat(vm, "@ cannot inherit from @.",
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' cannot inherit from built-in class '@'.",
|
||||
name, OBJ_VAL(superclass->name));
|
||||
}
|
||||
|
||||
if (superclass->numFields == -1)
|
||||
{
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' cannot inherit from foreign class '@'.",
|
||||
name, OBJ_VAL(superclass->name));
|
||||
}
|
||||
|
||||
if (numFields == -1 && superclass->numFields > 0)
|
||||
{
|
||||
return wrenStringFormat(vm,
|
||||
"Foreign class '@' may not inherit from a class with fields.",
|
||||
name);
|
||||
}
|
||||
|
||||
if (superclass->numFields + numFields > MAX_FIELDS)
|
||||
{
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' may not have more than 255 fields, including inherited "
|
||||
"ones.", name);
|
||||
}
|
||||
|
||||
return NULL_VAL;
|
||||
}
|
||||
|
||||
static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
|
||||
{
|
||||
// TODO: Make this a runtime error?
|
||||
ASSERT(vm->bindForeignClass != NULL,
|
||||
"Cannot declare foreign classes without a bindForeignClassFn.");
|
||||
|
||||
WrenForeignClassMethods methods = vm->bindForeignClass(
|
||||
vm, module->name->value, classObj->name->value);
|
||||
|
||||
Method method;
|
||||
method.type = METHOD_FOREIGN;
|
||||
method.fn.foreign = methods.allocate;
|
||||
|
||||
ASSERT(method.fn.foreign != NULL,
|
||||
"A foreign class must provide an allocate function.");
|
||||
|
||||
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>", 10);
|
||||
wrenBindMethod(vm, classObj, symbol, method);
|
||||
|
||||
if (methods.finalize != NULL)
|
||||
{
|
||||
method.fn.foreign = methods.finalize;
|
||||
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
|
||||
wrenBindMethod(vm, classObj, symbol, method);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new class.
|
||||
//
|
||||
// If [numFields] is -1, the class is a foreign class. The name and superclass
|
||||
// should be on top of the fiber's stack. After calling this, the top of the
|
||||
// stack will contain either the new class or a string if a runtime error
|
||||
// occurred.
|
||||
//
|
||||
// Returns false if the result is an error.
|
||||
static bool defineClass(WrenVM* vm, ObjFiber* fiber, int numFields,
|
||||
ObjModule* module)
|
||||
{
|
||||
// Pull the name and superclass off the stack.
|
||||
Value name = fiber->stackTop[-2];
|
||||
Value superclassValue = fiber->stackTop[-1];
|
||||
|
||||
// We have two values on the stack and we are going to leave one, so discard
|
||||
// the other slot.
|
||||
fiber->stackTop--;
|
||||
|
||||
// Use implicit Object superclass if none given.
|
||||
ObjClass* superclass = vm->objectClass;
|
||||
|
||||
if (!IS_NULL(superclassValue))
|
||||
{
|
||||
Value error = validateSuperclass(vm, name, superclassValue, numFields);
|
||||
if (!IS_NULL(error))
|
||||
{
|
||||
fiber->stackTop[-1] = error;
|
||||
return false;
|
||||
}
|
||||
superclass = AS_CLASS(superclassValue);
|
||||
}
|
||||
|
||||
ObjClass* classObj = wrenNewClass(vm, superclass, numFields, AS_STRING(name));
|
||||
fiber->stackTop[-1] = OBJ_VAL(classObj);
|
||||
|
||||
if (numFields == -1) bindForeignClass(vm, classObj, module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
|
||||
{
|
||||
ObjClass* classObj = AS_CLASS(stack[0]);
|
||||
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
|
||||
|
||||
// TODO: Don't look up every time.
|
||||
int symbol = wrenSymbolTableFind(&vm->methodNames, "<allocate>", 10);
|
||||
ASSERT(symbol != -1, "Should have defined <allocate> symbol.");
|
||||
|
||||
ASSERT(classObj->methods.count > symbol, "Class should have allocator.");
|
||||
Method* method = &classObj->methods.data[symbol];
|
||||
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
|
||||
|
||||
// Pass the constructor arguments to the allocator as well.
|
||||
vm->foreignCallSlot = stack;
|
||||
vm->foreignCallNumArgs = (int)(fiber->stackTop - stack);
|
||||
|
||||
method->fn.foreign(vm);
|
||||
|
||||
// TODO: Check that allocateForeign was called.
|
||||
}
|
||||
|
||||
// The main bytecode interpreter loop. This is where the magic happens. It is
|
||||
// also, as you can imagine, highly performance critical. Returns `true` if the
|
||||
// fiber completed without error.
|
||||
@@ -1022,6 +1134,11 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
|
||||
DISPATCH();
|
||||
|
||||
CASE_CODE(FOREIGN_CONSTRUCT):
|
||||
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
|
||||
createForeign(vm, fiber, stackStart);
|
||||
DISPATCH();
|
||||
|
||||
CASE_CODE(CLOSURE):
|
||||
{
|
||||
ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]);
|
||||
@@ -1057,40 +1174,16 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
|
||||
CASE_CODE(CLASS):
|
||||
{
|
||||
Value name = PEEK2();
|
||||
ObjClass* superclass = vm->objectClass;
|
||||
|
||||
// Use implicit Object superclass if none given.
|
||||
if (!IS_NULL(PEEK()))
|
||||
{
|
||||
Value error = validateSuperclass(vm, name, PEEK());
|
||||
if (!IS_NULL(error)) RUNTIME_ERROR(error);
|
||||
superclass = AS_CLASS(PEEK());
|
||||
}
|
||||
|
||||
int numFields = READ_BYTE();
|
||||
|
||||
Value classObj = OBJ_VAL(wrenNewClass(vm, superclass, numFields,
|
||||
AS_STRING(name)));
|
||||
|
||||
// Don't pop the superclass and name off the stack until the subclass is
|
||||
// done being created, to make sure it doesn't get collected.
|
||||
DROP();
|
||||
DROP();
|
||||
|
||||
// Now that we know the total number of fields, make sure we don't
|
||||
// overflow.
|
||||
if (superclass->numFields + numFields > MAX_FIELDS)
|
||||
{
|
||||
RUNTIME_ERROR(wrenStringFormat(vm,
|
||||
"Class '@' may not have more than 255 fields, including inherited "
|
||||
"ones.", name));
|
||||
}
|
||||
|
||||
PUSH(classObj);
|
||||
if (!defineClass(vm, fiber, READ_BYTE(), NULL)) RUNTIME_ERROR(PEEK());
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
||||
CASE_CODE(FOREIGN_CLASS):
|
||||
{
|
||||
if (!defineClass(vm, fiber, -1, fn->module)) RUNTIME_ERROR(PEEK());
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
CASE_CODE(METHOD_INSTANCE):
|
||||
CASE_CODE(METHOD_STATIC):
|
||||
{
|
||||
@@ -1323,6 +1416,20 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value)
|
||||
DEALLOCATE(vm, value);
|
||||
}
|
||||
|
||||
void* wrenAllocateForeign(WrenVM* vm, size_t size)
|
||||
{
|
||||
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
|
||||
|
||||
// TODO: Validate this. It can fail if the user calls this inside another
|
||||
// foreign method, or calls one of the return functions.
|
||||
ObjClass* classObj = AS_CLASS(vm->foreignCallSlot[0]);
|
||||
|
||||
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
|
||||
vm->foreignCallSlot[0] = OBJ_VAL(foreign);
|
||||
|
||||
return (void*)foreign->data;
|
||||
}
|
||||
|
||||
// Execute [source] in the context of the core module.
|
||||
static WrenInterpretResult loadIntoCore(WrenVM* vm, const char* source)
|
||||
{
|
||||
@@ -1460,6 +1567,12 @@ void wrenPopRoot(WrenVM* vm)
|
||||
vm->numTempRoots--;
|
||||
}
|
||||
|
||||
int wrenGetArgumentCount(WrenVM* vm)
|
||||
{
|
||||
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
|
||||
return vm->foreignCallNumArgs;
|
||||
}
|
||||
|
||||
static void validateForeignArgument(WrenVM* vm, int index)
|
||||
{
|
||||
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
|
||||
@@ -1485,6 +1598,15 @@ double wrenGetArgumentDouble(WrenVM* vm, int index)
|
||||
return AS_NUM(*(vm->foreignCallSlot + index));
|
||||
}
|
||||
|
||||
void* wrenGetArgumentForeign(WrenVM* vm, int index)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
|
||||
if (!IS_FOREIGN(*(vm->foreignCallSlot + index))) return NULL;
|
||||
|
||||
return AS_FOREIGN(*(vm->foreignCallSlot + index))->data;
|
||||
}
|
||||
|
||||
const char* wrenGetArgumentString(WrenVM* vm, int index)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
|
||||
+4
-1
@@ -125,7 +125,10 @@ struct WrenVM
|
||||
int foreignCallNumArgs;
|
||||
|
||||
// The function used to locate foreign functions.
|
||||
WrenBindForeignMethodFn bindForeign;
|
||||
WrenBindForeignMethodFn bindForeignMethod;
|
||||
|
||||
// The function used to locate foreign classes.
|
||||
WrenBindForeignClassFn bindForeignClass;
|
||||
|
||||
// The function used to load modules.
|
||||
WrenLoadModuleFn loadModule;
|
||||
|
||||
Reference in New Issue
Block a user