diff --git a/src/primitives.c b/src/primitives.c index 7107e396..528950be 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -4,7 +4,6 @@ #include #include -#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); } diff --git a/src/value.c b/src/value.c index b9cc5a0a..b75d3e39 100644 --- a/src/value.c +++ b/src/value.c @@ -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) { diff --git a/src/value.h b/src/value.h index ed9991e6..bd1965b6 100644 --- a/src/value.h +++ b/src/value.h @@ -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); diff --git a/src/vm.c b/src/vm.c index 03049e28..71dda85b 100644 --- a/src/vm.c +++ b/src/vm.c @@ -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(); } diff --git a/test/bool_type.wren b/test/bool_type.wren new file mode 100644 index 00000000..e7b1cda5 --- /dev/null +++ b/test/bool_type.wren @@ -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 diff --git a/test/class_equality.wren b/test/class_equality.wren new file mode 100644 index 00000000..c0574a37 --- /dev/null +++ b/test/class_equality.wren @@ -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 diff --git a/test/fn_type.wren b/test/fn_type.wren new file mode 100644 index 00000000..7ef2a2ea --- /dev/null +++ b/test/fn_type.wren @@ -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 diff --git a/test/is.wren b/test/is.wren index d511441b..6fd0afeb 100644 --- a/test/is.wren +++ b/test/is.wren @@ -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"). diff --git a/test/list_type.wren b/test/list_type.wren new file mode 100644 index 00000000..ea7a2728 --- /dev/null +++ b/test/list_type.wren @@ -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 diff --git a/test/null_type.wren b/test/null_type.wren new file mode 100644 index 00000000..9a695ac0 --- /dev/null +++ b/test/null_type.wren @@ -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 diff --git a/test/num_type.wren b/test/num_type.wren new file mode 100644 index 00000000..128fc075 --- /dev/null +++ b/test/num_type.wren @@ -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 diff --git a/test/string_type.wren b/test/string_type.wren new file mode 100644 index 00000000..763be18f --- /dev/null +++ b/test/string_type.wren @@ -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