forked from Mirror/wren
Handle inherited fields correctly.
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
#ifndef wren_common_h
|
||||
#define wren_common_h
|
||||
|
||||
// TODO(bob): Use stdbool.h and `bool` for bools instead of int/1/0.
|
||||
|
||||
// This header contains macros and defines used across the entire Wren
|
||||
// implementation. In particular, it contains "configuration" defines that
|
||||
// control how Wren works. Some of these are only used while hacking on
|
||||
@ -8,6 +10,9 @@
|
||||
//
|
||||
// This header is *not* intended to be included by code outside of Wren itself.
|
||||
|
||||
// TODO(bob): Prefix these with Wren and use #if instead of #ifdef. That way
|
||||
// the flags can be set externally.
|
||||
|
||||
// Define this to stress test the GC. It will perform a collection before every
|
||||
// allocation.
|
||||
//#define DEBUG_GC_STRESS
|
||||
|
||||
@ -1821,3 +1821,89 @@ ObjFn* wrenCompile(WrenVM* vm, const char* source)
|
||||
|
||||
return parser.hasError ? NULL : compiler.fn;
|
||||
}
|
||||
|
||||
void wrenBindMethod(ObjClass* classObj, ObjFn* fn)
|
||||
{
|
||||
int ip = 0;
|
||||
for (;;)
|
||||
{
|
||||
Code instruction = fn->bytecode[ip++];
|
||||
switch (instruction)
|
||||
{
|
||||
// Instructions with no arguments:
|
||||
case CODE_NULL:
|
||||
case CODE_FALSE:
|
||||
case CODE_TRUE:
|
||||
case CODE_DUP:
|
||||
case CODE_POP:
|
||||
case CODE_IS:
|
||||
case CODE_CLOSE_UPVALUE:
|
||||
case CODE_RETURN:
|
||||
break;
|
||||
|
||||
// Instructions with one argument:
|
||||
case CODE_CONSTANT:
|
||||
case CODE_CLASS:
|
||||
case CODE_SUBCLASS:
|
||||
case CODE_LIST:
|
||||
case CODE_LOAD_LOCAL:
|
||||
case CODE_STORE_LOCAL:
|
||||
case CODE_LOAD_UPVALUE:
|
||||
case CODE_STORE_UPVALUE:
|
||||
case CODE_LOAD_GLOBAL:
|
||||
case CODE_STORE_GLOBAL:
|
||||
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:
|
||||
case CODE_CALL_11:
|
||||
case CODE_CALL_12:
|
||||
case CODE_CALL_13:
|
||||
case CODE_CALL_14:
|
||||
case CODE_CALL_15:
|
||||
case CODE_CALL_16:
|
||||
case CODE_JUMP:
|
||||
case CODE_LOOP:
|
||||
case CODE_JUMP_IF:
|
||||
case CODE_AND:
|
||||
case CODE_OR:
|
||||
ip++;
|
||||
break;
|
||||
|
||||
// Instructions with two arguments:
|
||||
case CODE_METHOD_INSTANCE:
|
||||
case CODE_METHOD_STATIC:
|
||||
case CODE_METHOD_CTOR:
|
||||
ip += 2;
|
||||
break;
|
||||
|
||||
case CODE_CLOSURE:
|
||||
{
|
||||
int constant = fn->bytecode[ip++];
|
||||
ObjFn* loadedFn = AS_FN(fn->constants[constant]);
|
||||
ip += loadedFn->numUpvalues;
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOAD_FIELD:
|
||||
case CODE_STORE_FIELD:
|
||||
// Shift this class's fields down past the inherited ones.
|
||||
fn->bytecode[ip++] += classObj->superclass->numFields;
|
||||
break;
|
||||
|
||||
case CODE_END:
|
||||
return;
|
||||
|
||||
default:
|
||||
ASSERT(0, "Unknown instruction.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,4 +23,19 @@
|
||||
// execute that code when invoked.
|
||||
ObjFn* wrenCompile(WrenVM* vm, const char* source);
|
||||
|
||||
// When a class is defined, its superclass is not known until runtime since
|
||||
// class definitions are just imperative statements. Most of the bytecode for a
|
||||
// a method doesn't care, but there are two places where it matters:
|
||||
//
|
||||
// - To load or store a field, we need to know its index of the field in the
|
||||
// instance's field array. We need to adjust this so that subclass fields
|
||||
// are positioned after superclass fields, and we don't know this until the
|
||||
// superclass is known.
|
||||
//
|
||||
// - Superclass calls need to know which superclass to dispatch to.
|
||||
//
|
||||
// We could handle this dynamically, but that adds overhead. Instead, when a
|
||||
// method is bound, we walk the bytecode for the function and patch it up.
|
||||
void wrenBindMethod(ObjClass* classObj, ObjFn* fn);
|
||||
|
||||
#endif
|
||||
|
||||
@ -24,11 +24,12 @@ static ObjClass* newClass(WrenVM* vm, ObjClass* metaclass,
|
||||
initObj(vm, &obj->obj, OBJ_CLASS);
|
||||
obj->metaclass = metaclass;
|
||||
obj->superclass = superclass;
|
||||
obj->numFields = numFields;
|
||||
|
||||
// Inherit methods from its superclass (unless it's Object, which has none).
|
||||
if (superclass != NULL)
|
||||
{
|
||||
obj->numFields = superclass->numFields + numFields;
|
||||
|
||||
// Inherit methods from its superclass.
|
||||
for (int i = 0; i < MAX_SYMBOLS; i++)
|
||||
{
|
||||
obj->methods[i] = superclass->methods[i];
|
||||
@ -36,6 +37,9 @@ static ObjClass* newClass(WrenVM* vm, ObjClass* metaclass,
|
||||
}
|
||||
else
|
||||
{
|
||||
obj->numFields = numFields;
|
||||
|
||||
// Object has no superclass, so just clear these out.
|
||||
for (int i = 0; i < MAX_SYMBOLS; i++)
|
||||
{
|
||||
obj->methods[i].type = METHOD_NONE;
|
||||
@ -57,7 +61,6 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields)
|
||||
pinObj(vm, (Obj*)metaclass, &pinned);
|
||||
|
||||
ObjClass* classObj = newClass(vm, metaclass, superclass, numFields);
|
||||
classObj->numFields = numFields;
|
||||
|
||||
unpinObj(vm);
|
||||
|
||||
|
||||
@ -177,6 +177,9 @@ typedef enum
|
||||
typedef struct
|
||||
{
|
||||
MethodType type;
|
||||
|
||||
// The method function itself. The [type] determines which field of the union
|
||||
// is used.
|
||||
union
|
||||
{
|
||||
Primitive primitive;
|
||||
@ -192,7 +195,11 @@ typedef struct sObjClass
|
||||
Obj obj;
|
||||
struct sObjClass* metaclass;
|
||||
struct sObjClass* superclass;
|
||||
|
||||
// The number of fields needed for an instance of this class, including all
|
||||
// of its superclass fields.
|
||||
int numFields;
|
||||
|
||||
// TODO(bob): Hack. Probably don't want to use this much space.
|
||||
Method methods[MAX_SYMBOLS];
|
||||
} ObjClass;
|
||||
|
||||
@ -710,6 +710,9 @@ Value interpret(WrenVM* vm, Value function)
|
||||
break;
|
||||
}
|
||||
|
||||
ObjFn* methodFn = IS_FN(method) ? AS_FN(method) : AS_CLOSURE(method)->fn;
|
||||
wrenBindMethod(classObj, methodFn);
|
||||
|
||||
classObj->methods[symbol].fn = method;
|
||||
DISPATCH();
|
||||
}
|
||||
@ -1057,11 +1060,11 @@ Value interpret(WrenVM* vm, Value function)
|
||||
|
||||
void wrenCallFunction(Fiber* fiber, Value function, int numArgs)
|
||||
{
|
||||
// TODO(bob): Check for stack overflow.
|
||||
fiber->frames[fiber->numFrames].fn = function;
|
||||
fiber->frames[fiber->numFrames].ip = 0;
|
||||
fiber->frames[fiber->numFrames].stackStart = fiber->stackSize - numArgs;
|
||||
|
||||
// TODO(bob): Check for stack overflow.
|
||||
fiber->numFrames++;
|
||||
}
|
||||
|
||||
|
||||
35
test/inheritance/inherit_fields.wren
Normal file
35
test/inheritance/inherit_fields.wren
Normal file
@ -0,0 +1,35 @@
|
||||
class Foo {
|
||||
foo(a, b) {
|
||||
_field1 = a
|
||||
_field2 = b
|
||||
}
|
||||
|
||||
fooPrint {
|
||||
io.write(_field1)
|
||||
io.write(_field2)
|
||||
}
|
||||
}
|
||||
|
||||
class Bar is Foo {
|
||||
bar(a, b) {
|
||||
_field1 = a
|
||||
_field2 = b
|
||||
}
|
||||
|
||||
barPrint {
|
||||
io.write(_field1)
|
||||
io.write(_field2)
|
||||
}
|
||||
}
|
||||
|
||||
var bar = Bar.new
|
||||
bar.foo("foo 1", "foo 2")
|
||||
bar.bar("bar 1", "bar 2")
|
||||
|
||||
bar.fooPrint
|
||||
// expect: foo 1
|
||||
// expect: foo 2
|
||||
|
||||
bar.barPrint
|
||||
// expect: bar 1
|
||||
// expect: bar 2
|
||||
16
test/inheritance/is.wren
Normal file
16
test/inheritance/is.wren
Normal file
@ -0,0 +1,16 @@
|
||||
class A {}
|
||||
class B is A {}
|
||||
class C is B {}
|
||||
var a = A.new
|
||||
var b = B.new
|
||||
var c = C.new
|
||||
|
||||
io.write(a is A) // expect: true
|
||||
io.write(a is B) // expect: false
|
||||
io.write(a is C) // expect: false
|
||||
io.write(b is A) // expect: true
|
||||
io.write(b is B) // expect: true
|
||||
io.write(b is C) // expect: false
|
||||
io.write(c is A) // expect: true
|
||||
io.write(c is B) // expect: true
|
||||
io.write(c is C) // expect: true
|
||||
18
test/is.wren
18
test/is.wren
@ -20,24 +20,6 @@ io.write((fn 1) is Object) // expect: true
|
||||
io.write("s" is Object) // expect: true
|
||||
io.write(123 is Object) // expect: true
|
||||
|
||||
// Inheritance.
|
||||
class A {}
|
||||
class B is A {}
|
||||
class C is B {}
|
||||
var a = A.new
|
||||
var b = B.new
|
||||
var c = C.new
|
||||
|
||||
io.write(a is A) // expect: true
|
||||
io.write(a is B) // expect: false
|
||||
io.write(a is C) // expect: false
|
||||
io.write(b is A) // expect: true
|
||||
io.write(b is B) // expect: true
|
||||
io.write(b is C) // expect: false
|
||||
io.write(c is A) // expect: true
|
||||
io.write(c is B) // expect: true
|
||||
io.write(c is C) // expect: true
|
||||
|
||||
// TODO(bob): Non-class on RHS.
|
||||
// TODO(bob): Precedence and associativity.
|
||||
// TODO(bob): Metaclasses ("Num is Class").
|
||||
|
||||
Reference in New Issue
Block a user