Inherit methods from enclosing classes.

Like NewtonScript, the inner classes superclasses take precedence, but
if not found there then the enclosing classes are searched.

This code is still a bit hacky in some corners, but it's a step in the
right direction.
This commit is contained in:
Bob Nystrom
2015-12-21 17:05:46 -08:00
parent 8ff11a3c2c
commit c472a61bff
5 changed files with 103 additions and 10 deletions

View File

@ -48,6 +48,7 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name)
ObjClass* classObj = ALLOCATE(vm, ObjClass);
initObj(vm, &classObj->obj, OBJ_CLASS, NULL);
classObj->superclass = NULL;
classObj->enclosingClass = NULL;
classObj->numFields = numFields;
classObj->name = name;
@ -82,7 +83,8 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass)
}
}
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass,
ObjClass* enclosingClass, int numFields,
ObjString* name)
{
// Create the metaclass.
@ -92,6 +94,9 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName));
metaclass->obj.classObj = vm->classClass;
// TODO: Is this what we want? Test.
metaclass->enclosingClass = enclosingClass;
wrenPopRoot(vm);
// Make sure the metaclass isn't collected when we allocate the class.
@ -102,6 +107,8 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
wrenBindSuperclass(vm, metaclass, vm->classClass);
ObjClass* classObj = wrenNewSingleClass(vm, numFields, name);
classObj->enclosingClass = enclosingClass;
// Make sure the class isn't collected while the inherited methods are being
// bound.

View File

@ -375,6 +375,8 @@ struct sObjClass
{
Obj obj;
ObjClass* superclass;
ObjClass* enclosingClass;
// The number of fields needed for an instance of this class, including all
// of its superclass fields.
@ -615,7 +617,8 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name);
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass);
// Creates a new class object as well as its associated metaclass.
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass,
ObjClass* enclosingClass, int numFields,
ObjString* name);
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method);

View File

@ -391,10 +391,36 @@ static void runtimeError(WrenVM* vm)
// Aborts the current fiber with an appropriate method not found error for a
// method with [symbol] on [classObj].
static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
static Method* methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
{
ObjClass* originalClass = classObj;
classObj = originalClass->enclosingClass;
while (classObj != NULL)
{
if (symbol < classObj->methods.count)
{
Method* method = &classObj->methods.data[symbol];
if (method->type != METHOD_NONE)
{
// TODO: Doing this lazily is a drag. The problem is we don't know all
// of the enclosing class's methods at the point in time when the inner
// class is created. If we change the grammar to only allow inner
// classes to appear after all method definitions, we could do this
// eagerly. Worth doing?
// Copy it down now so we don't have to walk the enclosing classes
// every time.
originalClass->methods.data[symbol] = *method;
return method;
}
}
classObj = classObj->enclosingClass;
}
vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.",
OBJ_VAL(classObj->name), vm->methodNames.data[symbol].buffer);
OBJ_VAL(originalClass->name), vm->methodNames.data[symbol].buffer);
return NULL;
}
// Checks that [value], which must be a function or closure, does not require
@ -681,15 +707,22 @@ static void createClass(WrenVM* vm, int numFields, ObjModule* module)
Value name = vm->fiber->stackTop[-2];
Value superclass = vm->fiber->stackTop[-1];
// TODO: This does the wrong thing when there is no enclosing method (i.e.
// top level code). If we make the top level a class definition then this
// should work provided we make sure that class is in the first slot of the
// first call frame.
ObjClass* enclosingClass = wrenGetClass(vm,
vm->fiber->frames[vm->fiber->numFrames - 1].stackStart[0]);
// We have two values on the stack and we are going to leave one, so discard
// the other slot.
vm->fiber->stackTop--;
vm->fiber->error = validateSuperclass(vm, name, superclass, numFields);
if (!IS_NULL(vm->fiber->error)) return;
ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields,
AS_STRING(name));
ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), enclosingClass,
numFields, AS_STRING(name));
vm->fiber->stackTop[-1] = OBJ_VAL(classObj);
if (numFields == -1) bindForeignClass(vm, classObj, module);
@ -960,12 +993,13 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
goto completeCall;
completeCall:
// If the class's method table doesn't include the symbol, bail.
// If the class's method table doesn't include the symbol, look in the
// enclosing classes, or fail if not found there.
if (symbol >= classObj->methods.count ||
(method = &classObj->methods.data[symbol])->type == METHOD_NONE)
{
methodNotFound(vm, classObj, symbol);
RUNTIME_ERROR();
method = methodNotFound(vm, classObj, symbol);
if (method == NULL) RUNTIME_ERROR();
}
switch (method->type)

View File

@ -0,0 +1,34 @@
class Outer {
construct new() {}
def outerBefore() { System.print("Outer before") }
def shadowOuter() { System.print("bad") }
class Inner {
construct new() {}
def innerBefore() { System.print("Inner before") }
def shadowInner() { System.print("bad") }
class MoreInner {
construct new() {}
def shadowOuter() { System.print("ok") }
def shadowInner() { System.print("ok") }
}
def innerAfter() { System.print("Inner after") }
}
def outerAfter() { System.print("Outer after") }
}
var moreInner = Outer.new().Inner.new().MoreInner.new()
moreInner.outerBefore() // expect: Outer before
moreInner.outerAfter() // expect: Outer after
moreInner.innerBefore() // expect: Inner before
moreInner.innerAfter() // expect: Inner after
moreInner.shadowOuter() // expect: ok
moreInner.shadowInner() // expect: ok
// TODO: Test how super interacts with enclosing classes.

View File

@ -0,0 +1,15 @@
class Base {
def method() { System.print("Base") }
}
class Outer {
construct new() {}
def method() { System.print("Outer") }
class Inner is Base {
construct new() {}
}
}
Outer.new().Inner.new().method() // expect: Base