From 5c2cf641ae97cf090a6f2af32d9bc1028986b47e Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Tue, 28 Jan 2014 15:31:11 -0800 Subject: [PATCH] Make classes know their name. --- src/wren_compiler.c | 13 ++++-- src/wren_core.c | 78 ++++++++++++++++++++++------------ src/wren_value.c | 86 +++++++++++++++++++++++--------------- src/wren_value.h | 12 ++++-- src/wren_vm.c | 27 +++++++----- src/wren_vm.h | 35 +++++++++++++--- test/class/name.wren | 9 ++++ test/class/type.wren | 5 +++ test/object/to_string.wren | 2 +- 9 files changed, 183 insertions(+), 84 deletions(-) create mode 100644 test/class/name.wren diff --git a/src/wren_compiler.c b/src/wren_compiler.c index f83735d1..93a13a9c 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -1214,8 +1214,7 @@ static ObjFn* endCompiler(Compiler* compiler, compiler->parser->sourcePath, debugName, debugNameLength, compiler->debugSourceLines.data); - PinnedObj pinned; - pinObj(compiler->parser->vm, (Obj*)fn, &pinned); + WREN_PIN(compiler->parser->vm, fn); // In the function that contains this one, load the resulting function object. if (compiler->parent != NULL) @@ -1247,7 +1246,7 @@ static ObjFn* endCompiler(Compiler* compiler, // Pop this compiler off the stack. wrenSetCompiler(compiler->parser->vm, compiler->parent); - unpinObj(compiler->parser->vm); + WREN_UNPIN(compiler->parser->vm); #if WREN_DUMP_COMPILED_CODE wrenDebugPrintCode(compiler->parser->vm, fn); @@ -2417,6 +2416,12 @@ static void classDefinition(Compiler* compiler) int symbol = declareNamedVariable(compiler); bool isGlobal = compiler->scopeDepth == -1; + // Make a string constant for the name. + int nameConstant = addConstant(compiler, wrenNewString(compiler->parser->vm, + compiler->parser->previous.start, compiler->parser->previous.length)); + + wrenByteBufferClear(compiler->parser->vm, &compiler->parser->string); + // Load the superclass (if there is one). if (match(compiler, TOKEN_IS)) { @@ -2428,7 +2433,7 @@ static void classDefinition(Compiler* compiler) emit(compiler, CODE_NULL); } - emit(compiler, CODE_CLASS); + emitShort(compiler, CODE_CLASS, nameConstant); // Store a placeholder for the number of fields argument. We don't know // the value until we've compiled all the methods to see which fields are diff --git a/src/wren_core.c b/src/wren_core.c index 0dd4d879..cb18900d 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -27,6 +27,7 @@ #define RETURN_VAL(value) do { args[0] = value; return PRIM_VALUE; } while (0) +#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj)) #define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value)) #define RETURN_FALSE RETURN_VAL(FALSE_VAL) #define RETURN_NULL RETURN_VAL(NULL_VAL) @@ -148,6 +149,12 @@ DEF_NATIVE(bool_toString) } } +DEF_NATIVE(class_name) +{ + ObjClass* classObj = AS_CLASS(args[0]); + RETURN_OBJ(classObj->name); +} + DEF_NATIVE(fiber_create) { if (!IS_FN(args[1]) && !IS_CLOSURE(args[1])) @@ -165,7 +172,7 @@ DEF_NATIVE(fiber_create) newFiber->stack[0] = NULL_VAL; newFiber->stackSize++; - RETURN_VAL(OBJ_VAL(newFiber)); + RETURN_OBJ(newFiber); } DEF_NATIVE(fiber_isDone) @@ -520,12 +527,23 @@ DEF_NATIVE(object_new) DEF_NATIVE(object_toString) { + if (IS_CLASS(args[0])) + { + RETURN_OBJ(AS_CLASS(args[0])->name); + } + else if (IS_INSTANCE(args[0])) + { + ObjInstance* instance = AS_INSTANCE(args[0]); + RETURN_OBJ(wrenStringConcat(vm, "instance of ", + instance->classObj->name->value)); + } + RETURN_VAL(wrenNewString(vm, "", 8)); } DEF_NATIVE(object_type) { - RETURN_VAL(OBJ_VAL(wrenGetClass(vm, args[0]))); + RETURN_OBJ(wrenGetClass(vm, args[0])); } DEF_NATIVE(range_from) @@ -633,19 +651,7 @@ DEF_NATIVE(string_plus) if (!IS_STRING(args[1])) RETURN_NULL; // TODO: Handle coercion to string of RHS. - const char* left = AS_CSTRING(args[0]); - const char* right = AS_CSTRING(args[1]); - - size_t leftLength = strlen(left); - size_t rightLength = strlen(right); - - Value value = wrenNewString(vm, NULL, leftLength + rightLength); - ObjString* string = AS_STRING(value); - strcpy(string->value, left); - strcpy(string->value + leftLength, right); - string->value[leftLength + rightLength] = '\0'; - - RETURN_VAL(value); + RETURN_OBJ(wrenStringConcat(vm, AS_CSTRING(args[0]), AS_CSTRING(args[1]))); } DEF_NATIVE(string_eqeq) @@ -695,12 +701,36 @@ DEF_NATIVE(os_clock) RETURN_NUM(time); } +static ObjClass* defineSingleClass(WrenVM* vm, const char* name) +{ + size_t length = strlen(name); + int symbol = wrenSymbolTableAdd(vm, &vm->globalSymbols, name, length); + + ObjString* nameString = AS_STRING(wrenNewString(vm, name, length)); + WREN_PIN(vm, nameString); + + ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); + vm->globals[symbol] = OBJ_VAL(classObj); + + WREN_UNPIN(vm); + + return classObj; +} + static ObjClass* defineClass(WrenVM* vm, const char* name) { - // Add the symbol first since it can trigger a GC. - int symbol = wrenSymbolTableAdd(vm, &vm->globalSymbols, name, strlen(name)); + size_t length = strlen(name); + + // Add the symbol first since it can trigger a GC. + int symbol = wrenSymbolTableAdd(vm, &vm->globalSymbols, name, length); + + ObjString* nameString = AS_STRING(wrenNewString(vm, name, length)); + WREN_PIN(vm, nameString); + + ObjClass* classObj = wrenNewClass(vm, vm->objectClass, 0, nameString); + + WREN_UNPIN(vm); - ObjClass* classObj = wrenNewClass(vm, vm->objectClass, 0); vm->globals[symbol] = OBJ_VAL(classObj); return classObj; } @@ -716,11 +746,7 @@ void wrenInitializeCore(WrenVM* vm) { // Define the root Object class. This has to be done a little specially // because it has no superclass and an unusual metaclass (Class). - int objectSymbol = wrenSymbolTableAdd(vm, &vm->globalSymbols, - "Object", strlen("Object")); - vm->objectClass = wrenNewSingleClass(vm, 0); - vm->globals[objectSymbol] = OBJ_VAL(vm->objectClass); - + vm->objectClass = defineSingleClass(vm, "Object"); NATIVE(vm->objectClass, "== ", object_eqeq); NATIVE(vm->objectClass, "!= ", object_bangeq); NATIVE(vm->objectClass, "new", object_new); @@ -729,10 +755,8 @@ void wrenInitializeCore(WrenVM* vm) // Now we can define Class, which is a subclass of Object, but Object's // metaclass. - int classSymbol = wrenSymbolTableAdd(vm, &vm->globalSymbols, - "Class", strlen("Class")); - vm->classClass = wrenNewSingleClass(vm, 0); - vm->globals[classSymbol] = OBJ_VAL(vm->classClass); + vm->classClass = defineSingleClass(vm, "Class"); + NATIVE(vm->classClass, "name", class_name); // Now that Object and Class are defined, we can wire them up to each other. wrenBindSuperclass(vm, vm->classClass, vm->objectClass); diff --git a/src/wren_value.c b/src/wren_value.c index d1ee0b0f..d3257d7b 100644 --- a/src/wren_value.c +++ b/src/wren_value.c @@ -1,6 +1,7 @@ #include #include +#include "wren.h" #include "wren_value.h" #include "wren_vm.h" @@ -29,18 +30,18 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type) vm->first = obj; } -ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields) +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) { ObjClass* classObj = allocate(vm, sizeof(ObjClass)); initObj(vm, &classObj->obj, OBJ_CLASS); classObj->metaclass = NULL; classObj->superclass = NULL; classObj->numFields = numFields; + classObj->name = name; - PinnedObj pinned; - pinObj(vm, (Obj*)classObj, &pinned); + WREN_PIN(vm, classObj); wrenMethodBufferInit(vm, &classObj->methods); - unpinObj(vm); + WREN_UNPIN(vm); return classObj; } @@ -61,32 +62,39 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) } } -ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields) +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, + ObjString* name) { + WREN_PIN(vm, name); + // Create the metaclass. - ObjClass* metaclass = wrenNewSingleClass(vm, 0); + ObjString* metaclassName = wrenStringConcat(vm, name->value, " metaclass"); + WREN_PIN(vm, metaclassName); + + ObjClass* metaclass = wrenNewSingleClass(vm, 0, metaclassName); metaclass->metaclass = vm->classClass; + WREN_UNPIN(vm); + // Make sure the metaclass isn't collected when we allocate the class. - PinnedObj pinned; - pinObj(vm, (Obj*)metaclass, &pinned); + WREN_PIN(vm, metaclass); // Metaclasses always inherit Class and do not parallel the non-metaclass // hierarchy. wrenBindSuperclass(vm, metaclass, vm->classClass); - ObjClass* classObj = wrenNewSingleClass(vm, numFields); + ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); // Make sure the class isn't collected while the inherited methods are being // bound. - PinnedObj pinned2; - pinObj(vm, (Obj*)classObj, &pinned2); + WREN_PIN(vm, classObj); classObj->metaclass = metaclass; wrenBindSuperclass(vm, classObj, superclass); - unpinObj(vm); - unpinObj(vm); + WREN_UNPIN(vm); + WREN_UNPIN(vm); + WREN_UNPIN(vm); return classObj; } @@ -244,25 +252,22 @@ static void ensureListCapacity(WrenVM* vm, ObjList* list, int count) void wrenListAdd(WrenVM* vm, ObjList* list, Value value) { - // TODO: Macro for pinning and unpinning. - PinnedObj pinned; - if (IS_OBJ(value)) pinObj(vm, AS_OBJ(value), &pinned); + if (IS_OBJ(value)) WREN_PIN(vm, AS_OBJ(value)); ensureListCapacity(vm, list, list->count + 1); - if (IS_OBJ(value)) unpinObj(vm); + if (IS_OBJ(value)) WREN_UNPIN(vm); list->elements[list->count++] = value; } void wrenListInsert(WrenVM* vm, ObjList* list, Value value, int index) { - PinnedObj pinned; - if (IS_OBJ(value)) pinObj(vm, AS_OBJ(value), &pinned); + if (IS_OBJ(value)) WREN_PIN(vm, AS_OBJ(value)); ensureListCapacity(vm, list, list->count + 1); - if (IS_OBJ(value)) unpinObj(vm); + if (IS_OBJ(value)) WREN_UNPIN(vm); // Shift items down. for (int i = list->count; i > index; i--) @@ -278,8 +283,7 @@ Value wrenListRemoveAt(WrenVM* vm, ObjList* list, int index) { Value removed = list->elements[index]; - PinnedObj pinned; - if (IS_OBJ(removed)) pinObj(vm, AS_OBJ(removed), &pinned); + if (IS_OBJ(removed)) WREN_PIN(vm, AS_OBJ(removed)); // Shift items up. for (int i = index; i < list->count - 1; i++) @@ -295,7 +299,7 @@ Value wrenListRemoveAt(WrenVM* vm, ObjList* list, int index) list->capacity /= LIST_GROW_FACTOR; } - if (IS_OBJ(removed)) unpinObj(vm); + if (IS_OBJ(removed)) WREN_UNPIN(vm); list->count--; return removed; @@ -332,6 +336,20 @@ Value wrenNewString(WrenVM* vm, const char* text, size_t length) return OBJ_VAL(string); } +ObjString* wrenStringConcat(WrenVM* vm, const char* left, const char* right) +{ + size_t leftLength = strlen(left); + size_t rightLength = strlen(right); + + Value value = wrenNewString(vm, NULL, leftLength + rightLength); + ObjString* string = AS_STRING(value); + strcpy(string->value, left); + strcpy(string->value + leftLength, right); + string->value[leftLength + rightLength] = '\0'; + + return string; +} + Upvalue* wrenNewUpvalue(WrenVM* vm, Value* value) { Upvalue* upvalue = allocate(vm, sizeof(Upvalue)); @@ -353,6 +371,16 @@ static bool setMarkedFlag(Obj* obj) return false; } +static void markString(WrenVM* vm, ObjString* string) +{ + if (setMarkedFlag(&string->obj)) return; + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjString); + // TODO: O(n) calculation here is lame! + vm->bytesAllocated += strlen(string->value); +} + static void markClass(WrenVM* vm, ObjClass* classObj) { if (setMarkedFlag(&classObj->obj)) return; @@ -372,6 +400,8 @@ static void markClass(WrenVM* vm, ObjClass* classObj) } } + if (classObj->name != NULL) markString(vm, classObj->name); + // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjClass); vm->bytesAllocated += classObj->methods.capacity * sizeof(Method); @@ -502,16 +532,6 @@ static void markClosure(WrenVM* vm, ObjClosure* closure) vm->bytesAllocated += sizeof(Upvalue*) * closure->fn->numUpvalues; } -static void markString(WrenVM* vm, ObjString* string) -{ - if (setMarkedFlag(&string->obj)) return; - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjString); - // TODO: O(n) calculation here is lame! - vm->bytesAllocated += strlen(string->value); -} - void wrenMarkObj(WrenVM* vm, Obj* obj) { #if WREN_TRACE_MEMORY diff --git a/src/wren_value.h b/src/wren_value.h index d8982126..054928d1 100644 --- a/src/wren_value.h +++ b/src/wren_value.h @@ -5,7 +5,6 @@ #include #include "wren_common.h" -#include "wren.h" #include "wren_utils.h" // This defines the built-in types and their core representations in memory. @@ -288,6 +287,9 @@ typedef struct sObjClass // really low load factor. Since methods are pretty small (just a type and a // pointer), this should be a worthwhile trade-off. MethodBuffer methods; + + // The name of the class. + ObjString* name; } ObjClass; typedef struct @@ -522,7 +524,7 @@ typedef struct // Creates a new "raw" class. It has no metaclass or superclass whatsoever. // This is only used for bootstrapping the initial Object and Class classes, // which are a little special. -ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields); +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); // Makes [superclass] the superclass of [subclass], and causes subclass to // inherit its methods. This should be called before any methods are defined @@ -530,7 +532,8 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields); 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, int numFields, + ObjString* name); void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); @@ -574,6 +577,9 @@ Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive); // Creates a new string object and copies [text] into it. Value wrenNewString(WrenVM* vm, const char* text, size_t length); +// Creates a new string that is the concatenation of [left] and [right]. +ObjString* wrenStringConcat(WrenVM* vm, const char* left, const char* right); + // Creates a new open upvalue pointing to [value] on the stack. Upvalue* wrenNewUpvalue(WrenVM* vm, Value* value); diff --git a/src/wren_vm.c b/src/wren_vm.c index 2f48b78d..fd200225 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -971,6 +971,8 @@ static bool runInterpreter(WrenVM* vm) CASE_CODE(CLASS): { + ObjString* name = AS_STRING(fn->constants[READ_SHORT()]); + int numFields = READ_BYTE(); ObjClass* superclass; @@ -985,7 +987,7 @@ static bool runInterpreter(WrenVM* vm) superclass = AS_CLASS(PEEK()); } - ObjClass* classObj = wrenNewClass(vm, superclass, numFields); + ObjClass* classObj = wrenNewClass(vm, superclass, numFields, name); // Now that we know the total number of fields, make sure we don't // overflow. @@ -1035,12 +1037,9 @@ int wrenInterpret(WrenVM* vm, const char* sourcePath, const char* source) ObjFn* fn = wrenCompile(vm, sourcePath, source); if (fn != NULL) { - PinnedObj pinned; - pinObj(vm, (Obj*)fn, &pinned); - + WREN_PIN(vm, fn); vm->fiber = wrenNewFiber(vm, (Obj*)fn); - - unpinObj(vm); + WREN_UNPIN(vm); if (!runInterpreter(vm)) result = 70; // EX_SOFTWARE. } @@ -1051,14 +1050,14 @@ int wrenInterpret(WrenVM* vm, const char* sourcePath, const char* source) return result; } -void pinObj(WrenVM* vm, Obj* obj, PinnedObj* pinned) +void wrenPinObj(WrenVM* vm, Obj* obj, PinnedObj* pinned) { pinned->obj = obj; pinned->previous = vm->pinned; vm->pinned = pinned; } -void unpinObj(WrenVM* vm) +void wrenUnpinObj(WrenVM* vm) { vm->pinned = vm->pinned->previous; } @@ -1091,10 +1090,18 @@ void wrenDefineMethod(WrenVM* vm, const char* className, else { // The class doesn't already exist, so create it. + size_t length = strlen(className); + ObjString* nameString = AS_STRING(wrenNewString(vm, className, length)); + + WREN_PIN(vm, nameString); + // TODO: Allow passing in name for superclass? - classObj = wrenNewClass(vm, vm->objectClass, 0); + classObj = wrenNewClass(vm, vm->objectClass, 0, nameString); classSymbol = wrenSymbolTableAdd(vm, &vm->globalSymbols, - className, strlen(className)); + className, length); + + WREN_UNPIN(vm); + vm->globals[classSymbol] = OBJ_VAL(classObj); } diff --git a/src/wren_vm.h b/src/wren_vm.h index ea7e9f83..3becfbe1 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -9,6 +9,26 @@ // TODO: Get rid of this. #define MAX_GLOBALS 256 +// In order to token paste __LINE__, you need two weird levels of indirection +// since __LINE__ isn't expanded when used in a token paste. +// See: http://stackoverflow.com/a/1597129/9457 +#define WREN_TOKEN_PASTE(a, b) a ## b +#define WREN_TOKEN_PASTE2(a, b) WREN_TOKEN_PASTE(a, b) + +// Mark [obj] as a GC root so that it doesn't get collected. Initializes +// [pinned], which must be then passed to [unpinObj]. +#define WREN_PIN(vm, obj) \ + do \ + { \ + PinnedObj WREN_TOKEN_PASTE2(wrenPinned, __LINE__); \ + wrenPinObj(vm, (Obj*)obj, &WREN_TOKEN_PASTE2(wrenPinned, __LINE__)); \ + } \ + while (false) + +// Remove the most recently pinned object from the list of pinned GC roots. +#define WREN_UNPIN(vm) \ + wrenUnpinObj(vm) + typedef enum { // Load the constant at index [arg]. @@ -148,7 +168,9 @@ typedef enum // Pushes the created closure. CODE_CLOSURE, - // Define a new empty class and push it. + // TODO: Doc. + // Define a new empty class and push it. Short [arg1] is a constant for the + // name of the class. Byte [arg2] is the number of fields in the class. CODE_CLASS, // Define a method for symbol [arg]. The class receiving the method is popped @@ -272,13 +294,14 @@ void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); // Sets the current Compiler being run to [compiler]. void wrenSetCompiler(WrenVM* vm, Compiler* compiler); -// TODO: Make these static or prefix their names. - // Mark [obj] as a GC root so that it doesn't get collected. Initializes -// [pinned], which must be then passed to [unpinObj]. -void pinObj(WrenVM* vm, Obj* obj, PinnedObj* pinned); +// [pinned], which must be then passed to [unpinObj]. This is not intended to be +// used directly. Instead, use the [WREN_PIN_OBJ] macro. +void wrenPinObj(WrenVM* vm, Obj* obj, PinnedObj* pinned); // Remove the most recently pinned object from the list of pinned GC roots. -void unpinObj(WrenVM* vm); +// This is not intended to be used directly. Instead, use the [WREN_UNPIN_OBJ] +// macro. +void wrenUnpinObj(WrenVM* vm); #endif diff --git a/test/class/name.wren b/test/class/name.wren new file mode 100644 index 00000000..7da35335 --- /dev/null +++ b/test/class/name.wren @@ -0,0 +1,9 @@ +class Foo {} + +IO.print(Foo.name) // expect: Foo +IO.print(Foo.type.name) // expect: Foo metaclass + +// Make sure the built-in classes have proper names too. +IO.print(Object.name) // expect: Object +IO.print(Class.name) // expect: Class +IO.print(Bool.name) // expect: Bool diff --git a/test/class/type.wren b/test/class/type.wren index 24239fcc..d8c1f1ca 100644 --- a/test/class/type.wren +++ b/test/class/type.wren @@ -8,3 +8,8 @@ IO.print(Foo.type is Class) // expect: true // The metatype's metatype is Class. IO.print(Foo.type.type == Class) // expect: true + +// And Class's metatype circles back onto itself. +IO.print(Foo.type.type.type == Class) // expect: true +IO.print(Foo.type.type.type.type == Class) // expect: true +IO.print(Foo.type.type.type.type.type == Class) // expect: true diff --git a/test/object/to_string.wren b/test/object/to_string.wren index f1b09165..41aa9824 100644 --- a/test/object/to_string.wren +++ b/test/object/to_string.wren @@ -1,2 +1,2 @@ class Foo {} -IO.print((new Foo).toString) // expect: +IO.print((new Foo).toString == "instance of Foo") // expect: true