mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 14:18:42 +01:00
Get inheritance and core classes more correctly wired up.
This commit is contained in:
100
src/primitives.c
100
src/primitives.c
@ -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);
|
||||
}
|
||||
|
||||
62
src/value.c
62
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)
|
||||
{
|
||||
|
||||
@ -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
101
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();
|
||||
}
|
||||
|
||||
|
||||
4
test/bool_type.wren
Normal file
4
test/bool_type.wren
Normal 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
13
test/class_equality.wren
Normal 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
4
test/fn_type.wren
Normal 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
|
||||
27
test/is.wren
27
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").
|
||||
|
||||
4
test/list_type.wren
Normal file
4
test/list_type.wren
Normal 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
4
test/null_type.wren
Normal 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
4
test/num_type.wren
Normal 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
4
test/string_type.wren
Normal 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
|
||||
Reference in New Issue
Block a user