Get inheritance and core classes more correctly wired up.

This commit is contained in:
Bob Nystrom
2013-11-26 07:52:37 -08:00
parent af694053a5
commit eaeec01dc6
12 changed files with 210 additions and 124 deletions

View File

@ -4,7 +4,6 @@
#include <string.h>
#include <time.h>
#include "compiler.h"
#include "primitives.h"
#include "value.h"
@ -33,18 +32,6 @@ DEF_PRIMITIVE(bool_not)
return BOOL_VAL(!AS_BOOL(args[0]));
}
DEF_PRIMITIVE(bool_eqeq)
{
if (!(IS_BOOL(args[1]))) return FALSE_VAL;
return BOOL_VAL(AS_BOOL(args[0]) == AS_BOOL(args[1]));
}
DEF_PRIMITIVE(bool_bangeq)
{
if (!(IS_BOOL(args[1]))) return TRUE_VAL;
return BOOL_VAL(AS_BOOL(args[0]) != AS_BOOL(args[1]));
}
DEF_PRIMITIVE(bool_toString)
{
// TODO(bob): Intern these strings or something.
@ -73,18 +60,6 @@ DEF_FIBER_PRIMITIVE(fn_call6) { callFunction(fiber, AS_FN(args[0]), 7); }
DEF_FIBER_PRIMITIVE(fn_call7) { callFunction(fiber, AS_FN(args[0]), 8); }
DEF_FIBER_PRIMITIVE(fn_call8) { callFunction(fiber, AS_FN(args[0]), 9); }
DEF_PRIMITIVE(fn_eqeq)
{
if (!IS_FN(args[1])) return FALSE_VAL;
return BOOL_VAL(AS_FN(args[0]) == AS_FN(args[1]));
}
DEF_PRIMITIVE(fn_bangeq)
{
if (!IS_FN(args[1])) return TRUE_VAL;
return BOOL_VAL(AS_FN(args[0]) != AS_FN(args[1]));
}
DEF_PRIMITIVE(list_count)
{
ObjList* list = AS_LIST(args[0]);
@ -198,6 +173,21 @@ DEF_PRIMITIVE(num_bangeq)
return BOOL_VAL(AS_NUM(args[0]) != AS_NUM(args[1]));
}
DEF_PRIMITIVE(object_eqeq)
{
return BOOL_VAL(valuesEqual(args[0], args[1]));
}
DEF_PRIMITIVE(object_bangeq)
{
return BOOL_VAL(!valuesEqual(args[0], args[1]));
}
DEF_PRIMITIVE(object_type)
{
return OBJ_VAL(wrenGetClass(vm, args[0]));
}
DEF_PRIMITIVE(string_contains)
{
const char* string = AS_CSTRING(args[0]);
@ -300,33 +290,29 @@ DEF_PRIMITIVE(os_clock)
return NUM_VAL(time);
}
static const char* CORE_LIB =
"class Object {}\n"
"class Bool {}\n"
"class Class {}\n"
"class Function {}\n"
"class List {}\n"
"class Num {}\n"
"class Null {}\n"
"class String {}\n"
"class IO {}\n"
"var io = IO.new\n"
"class OS {}\n";
static ObjClass* defineClass(WrenVM* vm, const char* name, ObjClass* superclass)
{
ObjClass* classObj = newClass(vm, superclass, 0);
int symbol = addSymbol(&vm->globalSymbols, name, strlen(name));
vm->globals[symbol] = OBJ_VAL(classObj);
return classObj;
}
void wrenLoadCore(WrenVM* vm)
{
ObjFn* core = wrenCompile(vm, CORE_LIB);
interpret(vm, core);
vm->objectClass = defineClass(vm, "Object", NULL);
PRIMITIVE(vm->objectClass, "== ", object_eqeq);
PRIMITIVE(vm->objectClass, "!= ", object_bangeq);
PRIMITIVE(vm->objectClass, "type", object_type);
vm->boolClass = AS_CLASS(findGlobal(vm, "Bool"));
// The "Class" class is the superclass of all metaclasses.
vm->classClass = defineClass(vm, "Class", vm->objectClass);
vm->boolClass = defineClass(vm, "Bool", vm->objectClass);
PRIMITIVE(vm->boolClass, "toString", bool_toString);
PRIMITIVE(vm->boolClass, "!", bool_not);
PRIMITIVE(vm->boolClass, "== ", bool_eqeq);
PRIMITIVE(vm->boolClass, "!= ", bool_bangeq);
vm->classClass = AS_CLASS(findGlobal(vm, "Class"));
vm->fnClass = AS_CLASS(findGlobal(vm, "Function"));
vm->fnClass = defineClass(vm, "Function", vm->objectClass);
FIBER_PRIMITIVE(vm->fnClass, "call", fn_call0);
FIBER_PRIMITIVE(vm->fnClass, "call ", fn_call1);
FIBER_PRIMITIVE(vm->fnClass, "call ", fn_call2);
@ -336,16 +322,14 @@ void wrenLoadCore(WrenVM* vm)
FIBER_PRIMITIVE(vm->fnClass, "call ", fn_call6);
FIBER_PRIMITIVE(vm->fnClass, "call ", fn_call7);
FIBER_PRIMITIVE(vm->fnClass, "call ", fn_call8);
PRIMITIVE(vm->fnClass, "== ", fn_eqeq);
PRIMITIVE(vm->fnClass, "!= ", fn_bangeq);
vm->listClass = AS_CLASS(findGlobal(vm, "List"));
vm->listClass = defineClass(vm, "List", vm->objectClass);
PRIMITIVE(vm->listClass, "count", list_count);
PRIMITIVE(vm->listClass, "[ ]", list_subscript);
vm->nullClass = AS_CLASS(findGlobal(vm, "Null"));
vm->nullClass = defineClass(vm, "Null", vm->objectClass);
vm->numClass = AS_CLASS(findGlobal(vm, "Num"));
vm->numClass = defineClass(vm, "Num", vm->objectClass);
PRIMITIVE(vm->numClass, "abs", num_abs);
PRIMITIVE(vm->numClass, "toString", num_toString)
PRIMITIVE(vm->numClass, "-", num_negate);
@ -358,10 +342,12 @@ void wrenLoadCore(WrenVM* vm)
PRIMITIVE(vm->numClass, "> ", num_gt);
PRIMITIVE(vm->numClass, "<= ", num_lte);
PRIMITIVE(vm->numClass, ">= ", num_gte);
// TODO(bob): The only reason there are here is so that 0 != -0. Is that what
// we want?
PRIMITIVE(vm->numClass, "== ", num_eqeq);
PRIMITIVE(vm->numClass, "!= ", num_bangeq);
vm->stringClass = AS_CLASS(findGlobal(vm, "String"));
vm->stringClass = defineClass(vm, "String", vm->objectClass);
PRIMITIVE(vm->stringClass, "contains ", string_contains);
PRIMITIVE(vm->stringClass, "count", string_count);
PRIMITIVE(vm->stringClass, "toString", string_toString)
@ -370,14 +356,18 @@ void wrenLoadCore(WrenVM* vm)
PRIMITIVE(vm->stringClass, "!= ", string_bangeq);
PRIMITIVE(vm->stringClass, "[ ]", string_subscript);
ObjClass* ioClass = AS_CLASS(findGlobal(vm, "IO"));
ObjClass* ioClass = defineClass(vm, "IO", vm->objectClass);
PRIMITIVE(ioClass, "write ", io_write);
ObjClass* osClass = AS_CLASS(findGlobal(vm, "OS"));
// TODO(bob): Making this an instance is lame. The only reason we're doing it
// is because "IO.write()" looks ugly. Maybe just get used to that?
Value ioObject = newInstance(vm, ioClass);
vm->globals[addSymbol(&vm->globalSymbols, "io", 2)] = ioObject;
ObjClass* osClass = defineClass(vm, "OS", vm->objectClass);
PRIMITIVE(osClass->metaclass, "clock", os_clock);
ObjClass* unsupportedClass = newClass(vm, vm->objectClass, 0);
// TODO(bob): Make this a distinct object type.
ObjClass* unsupportedClass = newClass(vm, vm->objectClass, 0);
vm->unsupported = (Value)newInstance(vm, unsupportedClass);
}

View File

@ -1,4 +1,66 @@
#include "value.h"
#include "vm.h"
int valuesEqual(Value a, Value b)
{
#ifdef NAN_TAGGING
return a.bits == b.bits;
#else
if (a.type != b.type) return 0;
if (a.type == VAL_NUM) return a.num == b.num;
return a.obj == b.obj;
#endif
}
// Returns the class of [object].
ObjClass* wrenGetClass(WrenVM* vm, Value value)
{
#ifdef NAN_TAGGING
if (IS_NUM(value)) return vm->numClass;
if (IS_OBJ(value))
{
Obj* obj = AS_OBJ(value);
switch (obj->type)
{
case OBJ_CLASS: return AS_CLASS(value)->metaclass;
case OBJ_FN: return vm->fnClass;
case OBJ_INSTANCE: return AS_INSTANCE(value)->classObj;
case OBJ_LIST: return vm->listClass;
case OBJ_STRING: return vm->stringClass;
}
}
switch (GET_TAG(value))
{
case TAG_FALSE: return vm->boolClass;
case TAG_NAN: return vm->numClass;
case TAG_NULL: return vm->nullClass;
case TAG_TRUE: return vm->boolClass;
}
return NULL;
#else
switch (value.type)
{
case VAL_FALSE: return vm->boolClass;
case VAL_NULL: return vm->nullClass;
case VAL_NUM: return vm->numClass;
case VAL_TRUE: return vm->boolClass;
case VAL_OBJ:
{
switch (value.obj->type)
{
case OBJ_CLASS: return AS_CLASS(value)->metaclass;
case OBJ_FN: return vm->fnClass;
case OBJ_LIST: return vm->listClass;
case OBJ_STRING: return vm->stringClass;
case OBJ_INSTANCE: return AS_INSTANCE(value)->classObj;
}
}
}
#endif
}
int valueIsFn(Value value)
{

View File

@ -313,6 +313,13 @@ typedef struct
#endif
// Returns non-zero if [a] and [b] are strictly equal using built-in equality
// semantics. This is identity for object values, and value equality for others.
int valuesEqual(Value a, Value b);
// Returns the class of [value].
ObjClass* wrenGetClass(WrenVM* vm, Value value);
int valueIsBool(Value value);
int valueIsFn(Value value);
int valueIsInstance(Value value);

101
src/vm.c
View File

@ -345,9 +345,20 @@ static ObjClass* newSingleClass(WrenVM* vm, ObjClass* metaclass,
obj->superclass = superclass;
obj->numFields = numFields;
for (int i = 0; i < MAX_SYMBOLS; i++)
// Inherit methods from its superclass (unless it's Object, which has none).
if (superclass != NULL)
{
obj->methods[i].type = METHOD_NONE;
for (int i = 0; i < MAX_SYMBOLS; i++)
{
obj->methods[i] = superclass->methods[i];
}
}
else
{
for (int i = 0; i < MAX_SYMBOLS; i++)
{
obj->methods[i].type = METHOD_NONE;
}
}
return obj;
@ -356,9 +367,9 @@ static ObjClass* newSingleClass(WrenVM* vm, ObjClass* metaclass,
ObjClass* newClass(WrenVM* vm, ObjClass* superclass, int numFields)
{
// Make the metaclass.
// TODO(bob): What is the metaclass's metaclass and superclass?
// TODO(bob): What is the metaclass's metaclass?
// TODO(bob): Handle static fields.
ObjClass* metaclass = newSingleClass(vm, NULL, NULL, 0);
ObjClass* metaclass = newSingleClass(vm, NULL, vm->classClass, 0);
// Make sure it isn't collected when we allocate the metaclass.
pinObj(vm, (Obj*)metaclass);
@ -368,17 +379,6 @@ ObjClass* newClass(WrenVM* vm, ObjClass* superclass, int numFields)
unpinObj(vm, (Obj*)metaclass);
// Inherit methods from its superclass (unless it's Object, which has none).
// TODO(bob): If we want BETA-style inheritance, we'll need to do this after
// the subclass has defined its methods.
if (superclass != NULL)
{
for (int i = 0; i < MAX_SYMBOLS; i++)
{
classObj->methods[i] = superclass->methods[i];
}
}
return classObj;
}
@ -763,55 +763,6 @@ void dumpCode(WrenVM* vm, ObjFn* fn)
}
}
// Returns the class of [object].
static ObjClass* getClass(WrenVM* vm, Value value)
{ // TODO(bob): Unify these.
#ifdef NAN_TAGGING
if (IS_NUM(value)) return vm->numClass;
if (IS_OBJ(value))
{
Obj* obj = AS_OBJ(value);
switch (obj->type)
{
case OBJ_CLASS: return AS_CLASS(value)->metaclass;
case OBJ_FN: return vm->fnClass;
case OBJ_INSTANCE: return AS_INSTANCE(value)->classObj;
case OBJ_LIST: return vm->listClass;
case OBJ_STRING: return vm->stringClass;
}
}
switch (GET_TAG(value))
{
case TAG_FALSE: return vm->boolClass;
case TAG_NAN: return vm->numClass;
case TAG_NULL: return vm->nullClass;
case TAG_TRUE: return vm->boolClass;
}
return NULL;
#else
switch (value.type)
{
case VAL_FALSE: return vm->boolClass;
case VAL_NULL: return vm->nullClass;
case VAL_NUM: return vm->numClass;
case VAL_TRUE: return vm->boolClass;
case VAL_OBJ:
{
switch (value.obj->type)
{
case OBJ_CLASS: return AS_CLASS(value)->metaclass;
case OBJ_FN: return vm->fnClass;
case OBJ_LIST: return vm->listClass;
case OBJ_STRING: return vm->stringClass;
case OBJ_INSTANCE: return AS_INSTANCE(value)->classObj;
}
}
}
#endif
}
void dumpStack(Fiber* fiber)
{
printf(":: ");
@ -1076,7 +1027,7 @@ Value interpret(WrenVM* vm, ObjFn* fn)
int symbol = READ_ARG();
Value receiver = fiber->stack[fiber->stackSize - numArgs];
ObjClass* classObj = getClass(vm, receiver);
ObjClass* classObj = wrenGetClass(vm, receiver);
Method* method = &classObj->methods[symbol];
switch (method->type)
{
@ -1213,12 +1164,24 @@ Value interpret(WrenVM* vm, ObjFn* fn)
CASE_CODE(IS):
{
Value classObj = POP();
// TODO(bob): What if classObj is not a class?
ObjClass* expected = AS_CLASS(POP());
Value obj = POP();
// TODO(bob): What if classObj is not a class?
ObjClass* actual = getClass(vm, obj);
PUSH(BOOL_VAL(actual == AS_CLASS(classObj)));
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));
DISPATCH();
}

4
test/bool_type.wren Normal file
View File

@ -0,0 +1,4 @@
io.write(true is Bool) // expect: true
io.write(true is Object) // expect: true
io.write(true is Num) // expect: false
io.write(true.type == Bool) // expect: true

13
test/class_equality.wren Normal file
View File

@ -0,0 +1,13 @@
io.write(Num == Num) // expect: true
io.write(Num == Bool) // expect: false
// Not equal to other types.
io.write(Num == 123) // expect: false
io.write(Num == true) // expect: false
io.write(Num != Num) // expect: false
io.write(Num != Bool) // expect: true
// Not equal to other types.
io.write(Num != 123) // expect: true
io.write(Num != true) // expect: true

4
test/fn_type.wren Normal file
View File

@ -0,0 +1,4 @@
io.write((fn 0) is Function) // expect: true
io.write((fn 0) is Object) // expect: true
io.write((fn 0) is String) // expect: false
io.write((fn 0).type == Function) // expect: true

View File

@ -1,3 +1,4 @@
io.write(Num is Class) // expect: true
io.write(true is Bool) // expect: true
io.write((fn 1) is Function) // expect: true
io.write(123 is Num) // expect: true
@ -11,6 +12,32 @@ io.write((fn 1) is Num) // expect: false
io.write("s" is Null) // expect: false
io.write(123 is String) // expect: false
// Everything extends Object.
io.write(Num is Object) // expect: true
io.write(null is Object) // expect: true
io.write(true is Object) // expect: true
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").

4
test/list_type.wren Normal file
View File

@ -0,0 +1,4 @@
io.write([] is List) // expect: true
io.write([] is Object) // expect: true
io.write([] is Bool) // expect: false
io.write([].type == List) // expect: true

4
test/null_type.wren Normal file
View File

@ -0,0 +1,4 @@
io.write(null is Null) // expect: true
io.write(null is Object) // expect: true
io.write(null is Bool) // expect: false
io.write(null.type == Null) // expect: true

4
test/num_type.wren Normal file
View File

@ -0,0 +1,4 @@
io.write(123 is Num) // expect: true
io.write(123 is Object) // expect: true
io.write(123 is String) // expect: false
io.write(123.type == Num) // expect: true

4
test/string_type.wren Normal file
View File

@ -0,0 +1,4 @@
io.write("s" is String) // expect: true
io.write("s" is Object) // expect: true
io.write("s" is Num) // expect: false
io.write("s".type == String) // expect: true