From 84b7a37fabfaeb4bfebc189e9e5c6b6dea27f01b Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Tue, 9 Mar 2021 22:35:40 +0900 Subject: [PATCH] Remove the distinction between a class's 'fields' and 'methods' and implement actual method resolution --- src/builtins.c | 76 ++++++++++------ src/chunk.h | 6 +- src/compiler.c | 101 +++++++++++----------- src/debug.c | 2 +- src/memory.c | 2 - src/obj_base.c | 4 +- src/obj_function.c | 22 ++++- src/obj_tuple.c | 3 + src/object.c | 12 --- src/object.h | 2 +- src/opcodes.h | 4 +- src/rline.c | 2 +- src/util.h | 2 +- src/vm.c | 124 ++++++++++++++++----------- src/vm.h | 24 ++++++ test/test.krk | 2 + test/test.krk.expect | 2 +- test/testAttributePacking.krk.expect | 2 +- test/testDel.krk.expect | 6 +- test/testGetattrDir.krk.expect | 4 +- tools/gendoc.krk | 2 +- 21 files changed, 241 insertions(+), 163 deletions(-) diff --git a/src/builtins.c b/src/builtins.c index 5f8b0d3..883ea52 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -8,6 +8,9 @@ static KrkClass * Helper; static KrkClass * LicenseReader; +FUNC_SIG(list,__init__); +FUNC_SIG(list,sort); + KrkValue krk_dirObject(int argc, KrkValue argv[], int hasKw) { if (argc != 1) return krk_runtimeError(vm.exceptions->argumentError, "wrong number of arguments or bad type, got %d\n", argc); @@ -18,51 +21,52 @@ KrkValue krk_dirObject(int argc, KrkValue argv[], int hasKw) { if (IS_INSTANCE(argv[0])) { /* Obtain self-reference */ KrkInstance * self = AS_INSTANCE(argv[0]); - - /* First add each method of the class */ - for (size_t i = 0; i < self->_class->methods.capacity; ++i) { - if (self->_class->methods.entries[i].key.type != VAL_KWARGS) { - krk_writeValueArray(AS_LIST(myList), - self->_class->methods.entries[i].key); - } - } - - /* Then add each field of the instance */ for (size_t i = 0; i < self->fields.capacity; ++i) { if (self->fields.entries[i].key.type != VAL_KWARGS) { + krk_writeValueArray(AS_LIST(myList), self->fields.entries[i].key); } } - } else { - if (IS_CLASS(argv[0])) { - KrkClass * _class = AS_CLASS(argv[0]); + } else if (IS_CLASS(argv[0])) { + KrkClass * _class = AS_CLASS(argv[0]); + while (_class) { for (size_t i = 0; i < _class->methods.capacity; ++i) { if (_class->methods.entries[i].key.type != VAL_KWARGS) { krk_writeValueArray(AS_LIST(myList), _class->methods.entries[i].key); } } - for (size_t i = 0; i < _class->fields.capacity; ++i) { - if (_class->fields.entries[i].key.type != VAL_KWARGS) { - krk_writeValueArray(AS_LIST(myList), - _class->fields.entries[i].key); - } - } + _class = _class->base; } - KrkClass * type = krk_getType(argv[0]); + } + KrkClass * type = krk_getType(argv[0]); + + while (type) { for (size_t i = 0; i < type->methods.capacity; ++i) { if (type->methods.entries[i].key.type != VAL_KWARGS) { krk_writeValueArray(AS_LIST(myList), type->methods.entries[i].key); } } + type = type->base; } - /* Prepare output value */ + /* Throw it at a set to get unique, unordered results */ + krk_push(krk_set_of(AS_LIST(myList)->count, AS_LIST(myList)->values, 0)); + krk_swap(1); krk_pop(); - return myList; + + /* Now build a fresh list */ + myList = krk_list_of(0,NULL,0); + krk_push(myList); + krk_swap(1); + FUNC_NAME(list,__init__)(2,(KrkValue[]){krk_peek(1), krk_peek(0)},0); + FUNC_NAME(list,sort)(1,(KrkValue[]){krk_peek(1)},0); + krk_pop(); + + return krk_pop(); } @@ -674,7 +678,21 @@ KRK_FUNC(getattr,{ FUNCTION_TAKES_AT_LEAST(2); KrkValue object = argv[0]; CHECK_ARG(1,str,KrkString*,property); - return krk_valueGetAttribute(object, property->chars); + KrkValue result = krk_valueGetAttribute(object, property->chars); + if (argc == 3 && krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION && + krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->attributeError)) { + krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION); + result = argv[2]; + } + return result; +}) + +KRK_FUNC(setattr,{ + FUNCTION_TAKES_EXACTLY(3); + KrkValue object = argv[0]; + CHECK_ARG(1,str,KrkString*,property); + KrkValue value = argv[2]; + return krk_valueSetAttribute(object, property->chars, value); }) @@ -727,6 +745,14 @@ KRK_METHOD(LicenseReader,__call__,{ return krk_runtimeError(vm.exceptions->typeError, "unexpected error"); }) +static KrkValue _property_init(int argc, KrkValue argv[], int hasKw) { + if (argc != 2) { + return krk_runtimeError(vm.exceptions->notImplementedError, "Additional arguments to @property() are not supported."); + } + + return OBJECT_VAL(krk_newProperty(argv[1])); +} + static KrkValue _property_repr(int argc, KrkValue argv[], int hasKw) { if (argc != 1 || !IS_PROPERTY(argv[0])) return krk_runtimeError(vm.exceptions->typeError, "?"); struct StringBuilder sb = {0}; @@ -828,7 +854,8 @@ void _createAndBind_builtins(void) { "attaching new properties to the @c \\__builtins__ instance." ); - krk_makeClass(vm.builtins, &vm.baseClasses->propertyClass, "Property", vm.baseClasses->objectClass); + krk_makeClass(vm.builtins, &vm.baseClasses->propertyClass, "property", vm.baseClasses->objectClass); + krk_defineNative(&vm.baseClasses->propertyClass->methods, ".__init__", _property_init); krk_defineNative(&vm.baseClasses->propertyClass->methods, ".__repr__", _property_repr); krk_defineNative(&vm.baseClasses->propertyClass->methods, ":__doc__", _property_doc); krk_defineNative(&vm.baseClasses->propertyClass->methods, ":__name__", _property_name); @@ -887,6 +914,7 @@ void _createAndBind_builtins(void) { BUILTIN_FUNCTION("any", _any, "Returns True if at least one element in the given iterable is truthy, False otherwise."); BUILTIN_FUNCTION("all", _all, "Returns True if every element in the given iterable is truthy, False otherwise."); BUILTIN_FUNCTION("getattr", FUNC_NAME(krk,getattr), "Obtain a property of an object as if it were accessed by the dot operator."); + BUILTIN_FUNCTION("setattr", FUNC_NAME(krk,setattr), "Set a property of an object as if it were accessed by the dot operator."); BUILTIN_FUNCTION("sum", _sum, "Add the elements of an iterable."); BUILTIN_FUNCTION("min", _min, "Return the lowest value in an iterable or the passed arguments."); BUILTIN_FUNCTION("max", _max, "Return the highest value in an iterable or the passed arguments."); diff --git a/src/chunk.h b/src/chunk.h index d563439..dec0041 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -32,7 +32,6 @@ typedef enum { OP_CALL_STACK, OP_CLEANUP_WITH, OP_CLOSE_UPVALUE, - OP_CREATE_PROPERTY, OP_DIVIDE, OP_DOCSTRING, OP_EQUAL, @@ -63,7 +62,6 @@ typedef enum { OP_SWAP, OP_TRUE, OP_FILTER_EXCEPT, - OP_CREATE_CLASSMETHOD, OP_INVOKE_ITER, OP_INVOKE_CONTAINS, OP_BREAKPOINT, /* NEVER output this instruction in the compiler or bad things can happen */ @@ -88,7 +86,7 @@ typedef enum { OP_IMPORT_FROM, OP_INC, OP_KWARGS, - OP_METHOD, + OP_CLASS_PROPERTY, OP_SET_GLOBAL, OP_SET_LOCAL, OP_SET_PROPERTY, @@ -121,7 +119,7 @@ typedef enum { OP_IMPORT_FROM_LONG, OP_INC_LONG, OP_KWARGS_LONG, - OP_METHOD_LONG, + OP_CLASS_PROPERTY_LONG, OP_SET_GLOBAL_LONG, OP_SET_LOCAL_LONG, OP_SET_PROPERTY_LONG, diff --git a/src/compiler.c b/src/compiler.c index df34906..1195aed 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -103,6 +103,11 @@ typedef enum { TYPE_CLASSMETHOD, } FunctionType; +struct IndexWithNext { + size_t ind; + struct IndexWithNext * next; +}; + typedef struct Compiler { struct Compiler * enclosing; KrkFunction * function; @@ -123,6 +128,8 @@ typedef struct Compiler { int * continues; size_t localNameCapacity; + + struct IndexWithNext * properties; } Compiler; typedef struct ClassCompiler { @@ -167,6 +174,7 @@ static void initCompiler(Compiler * compiler, FunctionType type) { compiler->continues = NULL; compiler->loopLocalCount = 0; compiler->localNameCapacity = 0; + compiler->properties = NULL; if (type != TYPE_MODULE) { current->function->name = krk_copyString(parser.previous.start, parser.previous.length); @@ -181,6 +189,14 @@ static void initCompiler(Compiler * compiler, FunctionType type) { } } +static void rememberClassProperty(size_t ind) { + struct IndexWithNext * me = malloc(sizeof(struct IndexWithNext)); + me->ind = ind; + me->next = current->properties; + current->properties = me; +} + + static void parsePrecedence(Precedence precedence); static ssize_t parseVariable(const char * errorMessage); static void variable(int canAssign); @@ -379,6 +395,12 @@ static void freeCompiler(Compiler * compiler) { FREE_ARRAY(Upvalue,compiler->upvalues, compiler->upvaluesSpace); FREE_ARRAY(int,compiler->breaks, compiler->breakSpace); FREE_ARRAY(int,compiler->continues, compiler->continueSpace); + + while (compiler->properties) { + void * tmp = compiler->properties; + compiler->properties = compiler->properties->next; + free(tmp); + } } static size_t emitConstant(KrkValue value) { @@ -989,12 +1011,11 @@ static void method(size_t blockWidth) { if (check(TOKEN_AT)) { decorator(0, TYPE_METHOD); } else if (match(TOKEN_IDENTIFIER)) { - emitBytes(OP_DUP, 0); /* SET_PROPERTY will pop class */ size_t ind = identifierConstant(&parser.previous); consume(TOKEN_EQUAL, "Class field must have value."); expression(); - EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind); - emitByte(OP_POP); /* Value of expression replaces dup of class*/ + rememberClassProperty(ind); + EMIT_CONSTANT_OP(OP_CLASS_PROPERTY, ind); if (!match(TOKEN_EOL) && !match(TOKEN_EOF)) { errorAtCurrent("Expected end of line after class attribut declaration"); } @@ -1012,7 +1033,8 @@ static void method(size_t blockWidth) { } function(type, blockWidth); - EMIT_CONSTANT_OP(OP_METHOD, ind); + rememberClassProperty(ind); + EMIT_CONSTANT_OP(OP_CLASS_PROPERTY, ind); } } @@ -1199,40 +1221,14 @@ static KrkToken decorator(size_t level, FunctionType type) { advance(); /* Collect the `@` */ KrkToken funcName = {0}; - int haveCallable = 0; - /* hol'up, let's special case some stuff */ KrkToken at_staticmethod = syntheticToken("staticmethod"); - KrkToken at_property = syntheticToken("property"); - KrkToken at_classmethod = syntheticToken("classmethod"); - if (identifiersEqual(&at_staticmethod, &parser.current)) { - if (level != 0 || type != TYPE_METHOD) { - error("Invalid use of @staticmethod, which must be the top decorator of a class method."); - return funcName; - } - advance(); - type = TYPE_STATIC; - emitBytes(OP_DUP, 0); /* SET_PROPERTY will pop class */ - } else if (identifiersEqual(&at_property, &parser.current)) { - if (level != 0 || type != TYPE_METHOD) { - error("Invalid use of @property, which must be the top decorator of a class method."); - return funcName; - } - advance(); - type = TYPE_PROPERTY; - emitBytes(OP_DUP, 0); - } else if (identifiersEqual(&at_classmethod, &parser.current)) { - if (level != 0 || type != TYPE_METHOD) { - error("Invalid use of @classmethod, which must be the top decorator of a class method."); - return funcName; - } - advance(); - type = TYPE_CLASSMETHOD; - } else { - /* Collect an identifier */ - expression(); - haveCallable = 1; - } + KrkToken at_classmethod = syntheticToken("classmethod"); + + if (identifiersEqual(&at_staticmethod, &parser.current)) type = TYPE_STATIC; + if (identifiersEqual(&at_classmethod, &parser.current)) type = TYPE_CLASSMETHOD; + + expression(); consume(TOKEN_EOL, "Expected line feed after decorator."); if (blockWidth) { @@ -1262,8 +1258,7 @@ static KrkToken decorator(size_t level, FunctionType type) { return funcName; } - if (haveCallable) - emitBytes(OP_CALL, 1); + emitBytes(OP_CALL, 1); if (level == 0) { if (type == TYPE_FUNCTION) { @@ -1271,22 +1266,10 @@ static KrkToken decorator(size_t level, FunctionType type) { declareVariable(); size_t ind = (current->scopeDepth > 0) ? 0 : identifierConstant(&funcName); defineVariable(ind); - } else if (type == TYPE_STATIC) { - size_t ind = identifierConstant(&funcName); - EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind); - emitByte(OP_POP); - } else if (type == TYPE_CLASSMETHOD) { - emitByte(OP_CREATE_CLASSMETHOD); - size_t ind = identifierConstant(&funcName); - EMIT_CONSTANT_OP(OP_METHOD, ind); - } else if (type == TYPE_PROPERTY) { - emitByte(OP_CREATE_PROPERTY); - size_t ind = identifierConstant(&funcName); - EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind); - emitByte(OP_POP); } else { size_t ind = identifierConstant(&funcName); - EMIT_CONSTANT_OP(OP_METHOD, ind); + rememberClassProperty(ind); + EMIT_CONSTANT_OP(OP_CLASS_PROPERTY, ind); } } @@ -2076,6 +2059,20 @@ static ssize_t resolveUpvalue(Compiler * compiler, KrkToken * name) { } } while (0) static void namedVariable(KrkToken name, int canAssign) { + if (current->type == TYPE_CLASS) { + /* Only at the class body level, see if this is a class property. */ + struct IndexWithNext * properties = current->properties; + while (properties) { + KrkString * constant = AS_STRING(currentChunk()->constants.values[properties->ind]); + if (constant->length == name.length && !memcmp(constant->chars, name.start, name.length)) { + ssize_t arg = properties->ind; + EMIT_CONSTANT_OP(OP_GET_LOCAL, 0); + DO_VARIABLE(OP_SET_PROPERTY, OP_GET_PROPERTY, OP_NONE); + return; + } + properties = properties->next; + } + } ssize_t arg = resolveLocal(current, &name); if (arg != -1) { DO_VARIABLE(OP_SET_LOCAL, OP_GET_LOCAL, OP_NONE); diff --git a/src/debug.c b/src/debug.c index 31fc90d..1f75a5c 100644 --- a/src/debug.c +++ b/src/debug.c @@ -576,7 +576,7 @@ KRK_FUNC(dis,{ } } else if (IS_CLASS(argv[0])) { KrkValue code; - if (krk_tableGet(&AS_CLASS(argv[0])->fields, OBJECT_VAL(S("__func__")), &code) && IS_CLOSURE(code)) { + if (krk_tableGet(&AS_CLASS(argv[0])->methods, OBJECT_VAL(S("__func__")), &code) && IS_CLOSURE(code)) { KrkFunction * func = AS_CLOSURE(code)->function; krk_disassembleCodeObject(stdout, func, AS_CLASS(argv[0])->name->chars); } diff --git a/src/memory.c b/src/memory.c index 22d6e89..8a8d430 100644 --- a/src/memory.c +++ b/src/memory.c @@ -63,7 +63,6 @@ static void freeObject(KrkObj * object) { case OBJ_CLASS: { KrkClass * _class = (KrkClass*)object; krk_freeTable(&_class->methods); - krk_freeTable(&_class->fields); FREE(KrkClass, object); break; } @@ -163,7 +162,6 @@ static void blackenObject(KrkObj * object) { krk_markObject((KrkObj*)_class->docstring); krk_markObject((KrkObj*)_class->base); krk_markTable(&_class->methods); - krk_markTable(&_class->fields); break; } case OBJ_INSTANCE: { diff --git a/src/obj_base.c b/src/obj_base.c index 7826a63..d40838b 100644 --- a/src/obj_base.c +++ b/src/obj_base.c @@ -39,10 +39,10 @@ static KrkValue _class_to_str(int argc, KrkValue argv[], int hasKw) { /* Determine if this class has a module */ KrkValue module = NONE_VAL(); - krk_tableGet(&AS_CLASS(argv[0])->fields, OBJECT_VAL(S("__module__")), &module); + krk_tableGet(&AS_CLASS(argv[0])->methods, OBJECT_VAL(S("__module__")), &module); KrkValue qualname = NONE_VAL(); - krk_tableGet(&AS_CLASS(argv[0])->fields, OBJECT_VAL(S("__qualname__")), &qualname); + krk_tableGet(&AS_CLASS(argv[0])->methods, OBJECT_VAL(S("__qualname__")), &qualname); KrkString * name = IS_STRING(qualname) ? AS_STRING(qualname) : AS_CLASS(argv[0])->name; int includeModule = !(IS_NONE(module) || (IS_STRING(module) && AS_STRING(module) == S("__builtins__"))); diff --git a/src/obj_function.c b/src/obj_function.c index 085c046..ea5b93b 100644 --- a/src/obj_function.c +++ b/src/obj_function.c @@ -98,13 +98,14 @@ static KrkValue _closure_str(int argc, KrkValue argv[], int hasKw) { /* method.__str__ / method.__repr__ */ static KrkValue _bound_str(int argc, KrkValue argv[], int hasKw) { KrkValue s = _bound_get_name(argc, argv, hasKw); + if (!IS_STRING(s)) return NONE_VAL(); krk_push(s); const char * typeName = krk_typeName(AS_BOUND_METHOD(argv[0])->receiver); - size_t len = AS_STRING(s)->length + sizeof("") + strlen(typeName) + 1; + size_t len = AS_STRING(s)->length + sizeof("") + strlen(typeName) + 1; char * tmp = malloc(len); - snprintf(tmp, len, "", typeName, AS_CSTRING(s)); + snprintf(tmp, len, "", typeName, AS_CSTRING(s)); s = OBJECT_VAL(krk_copyString(tmp,len-1)); free(tmp); krk_pop(); @@ -167,6 +168,20 @@ static KrkValue _bound_get_argnames(int argc, KrkValue argv[], int hasKw) { } +KRK_FUNC(staticmethod,{ + FUNCTION_TAKES_EXACTLY(1); + CHECK_ARG(0,CLOSURE,KrkClosure*,method); + method->function->isStaticMethod = 1; + return argv[0]; +}) + +KRK_FUNC(classmethod,{ + FUNCTION_TAKES_EXACTLY(1); + CHECK_ARG(0,CLOSURE,KrkClosure*,method); + method->function->isClassMethod = 1; + return argv[0]; +}) + _noexport void _createAndBind_functionClass(void) { ADD_BASE_CLASS(vm.baseClasses->codeobjectClass, "codeobject", vm.baseClasses->objectClass); @@ -195,4 +210,7 @@ void _createAndBind_functionClass(void) { krk_defineNative(&vm.baseClasses->methodClass->methods, ":__args__", _bound_get_argnames); krk_defineNative(&vm.baseClasses->methodClass->methods, "_ip_to_line", _bound_ip_to_line); krk_finalizeClass(vm.baseClasses->methodClass); + + BUILTIN_FUNCTION("staticmethod", FUNC_NAME(krk,staticmethod), "A static method does not take an implicit self or cls argument."); + BUILTIN_FUNCTION("classmethod", FUNC_NAME(krk,classmethod), "A class method takes an implicit cls argument, instead of self."); } diff --git a/src/obj_tuple.c b/src/obj_tuple.c index 9cfeeee..4c4800f 100644 --- a/src/obj_tuple.c +++ b/src/obj_tuple.c @@ -9,6 +9,9 @@ if (index < 0 || index >= (krk_integer_type)self->values.count) return krk_runtimeError(vm.exceptions->indexError, "tuple index out of range: " PRIkrk_int, index) static KrkValue _tuple_init(int argc, KrkValue argv[], int hasKw) { + if (argc == 1) { + return OBJECT_VAL(krk_newTuple(0)); + } return krk_runtimeError(vm.exceptions->typeError,"tuple() initializier unsupported"); } diff --git a/src/object.c b/src/object.c index f53e27e..8029b77 100644 --- a/src/object.c +++ b/src/object.c @@ -289,19 +289,12 @@ KrkClass * krk_newClass(KrkString * name, KrkClass * baseClass) { _class->name = name; _class->allocSize = sizeof(KrkInstance); krk_initTable(&_class->methods); - krk_initTable(&_class->fields); if (baseClass) { - krk_push(OBJECT_VAL(_class)); _class->base = baseClass; _class->allocSize = baseClass->allocSize; - _class->_ongcscan = baseClass->_ongcscan; _class->_ongcsweep = baseClass->_ongcsweep; - - krk_tableAddAll(&baseClass->methods, &_class->methods); - krk_tableAddAll(&baseClass->fields, &_class->fields); - krk_pop(); } return _class; @@ -311,11 +304,6 @@ KrkInstance * krk_newInstance(KrkClass * _class) { KrkInstance * instance = (KrkInstance*)allocateObject(_class->allocSize, OBJ_INSTANCE); instance->_class = _class; krk_initTable(&instance->fields); - if (_class) { - krk_push(OBJECT_VAL(instance)); - krk_tableAddAll(&_class->fields, &instance->fields); - krk_pop(); - } return instance; } diff --git a/src/object.h b/src/object.h index e5fca99..74beb7e 100644 --- a/src/object.h +++ b/src/object.h @@ -126,6 +126,7 @@ typedef struct { unsigned char collectsKeywords:1; unsigned char isClassMethod:1; unsigned char isGenerator:1; + unsigned char isStaticMethod:1; struct KrkInstance * globalsContext; } KrkFunction; @@ -158,7 +159,6 @@ typedef struct KrkClass { KrkString * docstring; struct KrkClass * base; KrkTable methods; - KrkTable fields; size_t allocSize; KrkCleanupCallback _ongcscan; KrkCleanupCallback _ongcsweep; diff --git a/src/opcodes.h b/src/opcodes.h index 6ff4f4c..9a50fc3 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -36,10 +36,8 @@ SIMPLE(OP_SWAP) SIMPLE(OP_FINALIZE) SIMPLE(OP_IS) SIMPLE(OP_POW) -SIMPLE(OP_CREATE_PROPERTY) SIMPLE(OP_CLEANUP_WITH) SIMPLE(OP_FILTER_EXCEPT) -SIMPLE(OP_CREATE_CLASSMETHOD) SIMPLE(OP_BREAKPOINT) SIMPLE(OP_YIELD) CONSTANT(OP_DEFINE_GLOBAL,(void)0) @@ -51,7 +49,7 @@ CONSTANT(OP_CLASS,(void)0) CONSTANT(OP_GET_PROPERTY, (void)0) CONSTANT(OP_SET_PROPERTY, (void)0) CONSTANT(OP_DEL_PROPERTY,(void)0) -CONSTANT(OP_METHOD, (void)0) +CONSTANT(OP_CLASS_PROPERTY, (void)0) CONSTANT(OP_CLOSURE, CLOSURE_MORE) CONSTANT(OP_IMPORT, (void)0) CONSTANT(OP_IMPORT_FROM, (void)0) diff --git a/src/rline.c b/src/rline.c index fe76590..9845fac 100644 --- a/src/rline.c +++ b/src/rline.c @@ -565,7 +565,7 @@ char * syn_krk_types[] = { "object","exception","isinstance","type","tuple","reversed", "print","set","any","all","bool","ord","chr","hex","oct","filter", "sorted","bytes","getattr","sum","min","max","id","hash","map","bin", - "enumerate","zip", + "enumerate","zip","setattr","property","staticmethod","classmethod", NULL }; diff --git a/src/util.h b/src/util.h index 0e7b17f..b402c7e 100644 --- a/src/util.h +++ b/src/util.h @@ -88,7 +88,7 @@ static inline const char * _method_name(const char * func) { #define MAKE_CLASS(klass) do { krk_makeClass(module,&klass,#klass,vm.baseClasses->objectClass); klass ->allocSize = sizeof(struct klass); } while (0) #define BIND_METHOD(klass,method) krk_defineNative(&klass->methods, "." #method, _ ## klass ## _ ## method) #define BIND_FIELD(klass,method) krk_defineNative(&klass->methods, ":" #method, _ ## klass ## _ ## method) -#define BIND_PROP(klass,method) krk_defineNativeProperty(&klass->fields, #method, _ ## klass ## _ ## method) +#define BIND_PROP(klass,method) krk_defineNativeProperty(&klass->methods, #method, _ ## klass ## _ ## method) #define BIND_FUNC(module,func) krk_defineNative(&module->fields, #func, _krk_ ## func) /** diff --git a/src/vm.c b/src/vm.c index d023264..e9b1345 100644 --- a/src/vm.c +++ b/src/vm.c @@ -371,7 +371,7 @@ KrkClass * krk_makeClass(KrkInstance * module, KrkClass ** _class, const char * /* Now give it a __module__ */ KrkValue moduleName = NONE_VAL(); krk_tableGet(&module->fields, OBJECT_VAL(S("__name__")), &moduleName); - krk_attachNamedValue(&(*_class)->fields,"__module__",moduleName); + krk_attachNamedValue(&(*_class)->methods,"__module__",moduleName); krk_pop(); } krk_pop(); @@ -414,7 +414,12 @@ void krk_finalizeClass(KrkClass * _class) { }; for (struct TypeMap * entry = specials; entry->method; ++entry) { - if (krk_tableGet(&_class->methods, vm.specialMethodNames[entry->index], &tmp)) { + KrkClass * _base = _class; + while (_base) { + if (krk_tableGet(&_base->methods, vm.specialMethodNames[entry->index], &tmp)) break; + _base = _base->base; + } + if (_base && (IS_CLOSURE(tmp) || IS_NATIVE(tmp))) { *entry->method = AS_OBJECT(tmp); } } @@ -894,13 +899,25 @@ KrkValue krk_callSimple(KrkValue value, int argCount, int isMethod) { */ int krk_bindMethod(KrkClass * _class, KrkString * name) { KrkValue method, out; - if (!krk_tableGet(&_class->methods, OBJECT_VAL(name), &method)) return 0; + while (_class) { + if (krk_tableGet(&_class->methods, OBJECT_VAL(name), &method)) break; + _class = _class->base; + } + if (!_class) return 0; if (IS_NATIVE(method) && ((KrkNative*)AS_OBJECT(method))->isMethod == 2) { out = AS_NATIVE(method)->function(1, (KrkValue[]){krk_peek(0)}, 0); + } else if (IS_PROPERTY(method)) { + /* Need to push for property object */ + krk_push(krk_peek(0)); + out = krk_callSimple(AS_PROPERTY(method)->method, 1, 0); } else if (IS_CLOSURE(method) && (AS_CLOSURE(method)->function->isClassMethod)) { out = OBJECT_VAL(krk_newBoundMethod(OBJECT_VAL(_class), AS_OBJECT(method))); - } else { + } else if (IS_CLOSURE(method) && (AS_CLOSURE(method)->function->isStaticMethod)) { + out = method; + } else if (IS_CLOSURE(method) || IS_NATIVE(method)) { out = OBJECT_VAL(krk_newBoundMethod(krk_peek(0), AS_OBJECT(method))); + } else { + out = method; } krk_pop(); krk_push(out); @@ -1039,8 +1056,7 @@ static KrkValue krk_getsize(int argc, KrkValue argv[], int hasKw) { } case OBJ_CLASS: { KrkClass * self = AS_CLASS(argv[0]); - mySize += sizeof(KrkClass) + sizeof(KrkTableEntry) * self->fields.capacity - + sizeof(KrkTableEntry) * self->methods.capacity; + mySize += sizeof(KrkClass) + sizeof(KrkTableEntry) * self->methods.capacity; break; } case OBJ_NATIVE: { @@ -1699,11 +1715,6 @@ static int valueGetProperty(KrkString * name) { if (IS_INSTANCE(krk_peek(0))) { KrkInstance * instance = AS_INSTANCE(krk_peek(0)); if (krk_tableGet(&instance->fields, OBJECT_VAL(name), &value)) { - if (IS_PROPERTY(value)) { - /* Properties retreived from instances are magic. */ - krk_push(krk_callSimple(AS_PROPERTY(value)->method, 1, 0)); - return 1; - } krk_pop(); krk_push(value); return 1; @@ -1711,10 +1722,13 @@ static int valueGetProperty(KrkString * name) { objectClass = instance->_class; } else if (IS_CLASS(krk_peek(0))) { KrkClass * _class = AS_CLASS(krk_peek(0)); - if (krk_tableGet(&_class->fields, OBJECT_VAL(name), &value) || - krk_tableGet(&_class->methods, OBJECT_VAL(name), &value)) { + while (_class) { + if (krk_tableGet(&_class->methods, OBJECT_VAL(name), &value)) break; + _class = _class->base; + } + if (_class) { if (IS_CLOSURE(value) && AS_CLOSURE(value)->function->isClassMethod) { - value = OBJECT_VAL(krk_newBoundMethod(OBJECT_VAL(_class), AS_OBJECT(value))); + value = OBJECT_VAL(krk_newBoundMethod(krk_peek(0), AS_OBJECT(value))); } krk_pop(); krk_push(value); @@ -1760,7 +1774,7 @@ static int valueDelProperty(KrkString * name) { return 1; } else if (IS_CLASS(krk_peek(0))) { KrkClass * _class = AS_CLASS(krk_peek(0)); - if (!krk_tableDelete(&_class->fields, OBJECT_VAL(name))) { + if (!krk_tableDelete(&_class->methods, OBJECT_VAL(name))) { return 0; } krk_pop(); /* the original value */ @@ -1770,6 +1784,46 @@ static int valueDelProperty(KrkString * name) { return 0; } +static int valueSetProperty(KrkString * name) { + KrkValue owner = krk_peek(1); + KrkValue value = krk_peek(0); + if (IS_INSTANCE(owner)) { + if (krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), value)) { + KrkClass * _class = AS_INSTANCE(owner)->_class; + KrkValue method; + while (_class) { + if (krk_tableGet(&_class->methods, OBJECT_VAL(name), &method)) break; + _class = _class->base; + } + if (_class && IS_PROPERTY(method)) { + krk_tableDelete(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name)); + krk_push(krk_callSimple(AS_PROPERTY(method)->method, 2, 0)); + return 1; + } + } + } else if (IS_CLASS(owner)) { + krk_tableSet(&AS_CLASS(owner)->methods, OBJECT_VAL(name), value); + } else { + /* TODO: Setters for other things */ + return 0; + } + krk_swap(1); + krk_pop(); + return 1; +} + +KrkValue krk_valueSetAttribute(KrkValue owner, char * name, KrkValue to) { + krk_push(OBJECT_VAL(krk_copyString(name,strlen(name)))); + krk_push(owner); + krk_push(to); + if (!valueSetProperty(AS_STRING(krk_peek(2)))) { + return krk_runtimeError(vm.exceptions->attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(1)), name); + } + krk_swap(1); + krk_pop(); /* String */ + return krk_pop(); +} + #define READ_BYTE() (*frame->ip++) #define BINARY_OP(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); krk_push(krk_operator_ ## op (a,b)); break; } #define BINARY_OP_CHECK_ZERO(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); \ @@ -2043,8 +2097,6 @@ _resumeHook: (void)0; } KrkClass * subclass = AS_CLASS(krk_peek(0)); subclass->base = AS_CLASS(superclass); - krk_tableAddAll(&AS_CLASS(superclass)->methods, &subclass->methods); - krk_tableAddAll(&AS_CLASS(superclass)->fields, &subclass->fields); subclass->allocSize = AS_CLASS(superclass)->allocSize; subclass->_ongcsweep = AS_CLASS(superclass)->_ongcsweep; subclass->_ongcscan = AS_CLASS(superclass)->_ongcscan; @@ -2059,21 +2111,6 @@ _resumeHook: (void)0; case OP_SWAP: krk_swap(1); break; - case OP_CREATE_PROPERTY: { - KrkProperty * newProperty = krk_newProperty(krk_peek(0)); - krk_pop(); - krk_push(OBJECT_VAL(newProperty)); - break; - } - case OP_CREATE_CLASSMETHOD: { - if (!IS_CLOSURE(krk_peek(0))) { - krk_runtimeError(vm.exceptions->typeError, "Classmethod must be a method, not '%s'", - krk_typeName(krk_peek(0))); - goto _finishException; - } - AS_CLOSURE(krk_peek(0))->function->isClassMethod = 1; - break; - } case OP_FILTER_EXCEPT: { int isMatch = 0; if (IS_CLASS(krk_peek(0)) && krk_isInstanceOf(krk_peek(1), AS_CLASS(krk_peek(0)))) { @@ -2275,7 +2312,7 @@ _resumeHook: (void)0; KrkClass * _class = krk_newClass(name, vm.baseClasses->objectClass); krk_push(OBJECT_VAL(_class)); _class->filename = frame->closure->function->chunk.filename; - krk_attachNamedObject(&_class->fields, "__func__", (KrkObj*)frame->closure); + krk_attachNamedObject(&_class->methods, "__func__", (KrkObj*)frame->closure); break; } case OP_IMPORT_FROM_LONG: @@ -2322,27 +2359,14 @@ _resumeHook: (void)0; case OP_SET_PROPERTY_LONG: case OP_SET_PROPERTY: { KrkString * name = READ_STRING(OPERAND); - KrkTable * table = NULL; - if (IS_INSTANCE(krk_peek(1))) table = &AS_INSTANCE(krk_peek(1))->fields; - else if (IS_CLASS(krk_peek(1))) table = &AS_CLASS(krk_peek(1))->fields; - if (table) { - KrkValue previous; - if (krk_tableGet(table, OBJECT_VAL(name), &previous) && IS_PROPERTY(previous)) { - krk_push(krk_callSimple(AS_PROPERTY(previous)->method, 2, 0)); - break; - } else { - krk_tableSet(table, OBJECT_VAL(name), krk_peek(0)); - } - } else { - krk_runtimeError(vm.exceptions->attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars); + if (unlikely(!valueSetProperty(name))) { + krk_runtimeError(vm.exceptions->attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(1)), name->chars); goto _finishException; } - krk_swap(1); - krk_pop(); break; } - case OP_METHOD_LONG: - case OP_METHOD: { + case OP_CLASS_PROPERTY_LONG: + case OP_CLASS_PROPERTY: { KrkValue method = krk_peek(0); KrkClass * _class = AS_CLASS(krk_peek(1)); KrkValue name = OBJECT_VAL(READ_STRING(OPERAND)); diff --git a/src/vm.h b/src/vm.h index da1b36a..27d9b85 100644 --- a/src/vm.h +++ b/src/vm.h @@ -593,6 +593,14 @@ extern KrkValue krk_dict_of(int argc, KrkValue argv[], int hasKw); */ extern KrkValue krk_tuple_of(int argc, KrkValue argv[], int hasKw); +/** + * @brief Create a set object. + * @memberof Set + * + * This is the native function bound to @c setOf + */ +extern KrkValue krk_set_of(int argc, KrkValue argv[], int hasKw); + /** * @brief Call a callable and manage VM state to obtain the return value. * @memberof KrkValue @@ -735,6 +743,22 @@ extern int krk_isFalsey(KrkValue value); */ extern KrkValue krk_valueGetAttribute(KrkValue value, char * name); +/** + * @brief Set a property of an object by name. + * @memberof KrkValue + * + * This is a convenience function that works in essentially the + * same way as the OP_SET_PROPERTY instruction. + * + * @param owner The owner of the property to modify. + * @param name C-string of the property name to modify. + * @param to The value to assign. + * @return The set value, or None with an @ref AttributeError + * exception set in the current thread if the object can + * not have a property set. + */ +extern KrkValue krk_valueSetAttribute(KrkValue owner, char * name, KrkValue to); + /** * @brief Concatenate two strings. * diff --git a/test/test.krk b/test/test.krk index 202ac41..3d17f59 100755 --- a/test/test.krk +++ b/test/test.krk @@ -123,6 +123,8 @@ class SuperClass(): print("This is a great " + self.a + "!") def __str__(self): return "(I am a " + self.a + ")" + def __repr__(self): + return "(I am a " + self.a + ")" def __get__(self, ind): return "(get[" + str(ind) + "])" def __set__(self, ind, val): diff --git a/test/test.krk.expect b/test/test.krk.expect index ce4b1c9..eba09d9 100644 --- a/test/test.krk.expect +++ b/test/test.krk.expect @@ -29,7 +29,7 @@ Let's do some classes. yay: bax bar - + yay: bar This is a great teapot! Subclass says: (I am a teapot) diff --git a/test/testAttributePacking.krk.expect b/test/testAttributePacking.krk.expect index 642dd32..acb657a 100644 --- a/test/testAttributePacking.krk.expect +++ b/test/testAttributePacking.krk.expect @@ -1,4 +1,4 @@ -['__class__', '__str__', '__dir__', '__repr__', '__hash__', 'thatWeWantToSet', 'longList', 'onThatObject', 'ofAttributes'] +['__class__', '__dir__', '__hash__', '__repr__', '__str__', 'longList', 'ofAttributes', 'onThatObject', 'thatWeWantToSet'] 1 2 3 diff --git a/test/testDel.krk.expect b/test/testDel.krk.expect index 2e1dde8..5ea13dc 100644 --- a/test/testDel.krk.expect +++ b/test/testDel.krk.expect @@ -7,10 +7,10 @@ False [1, 3, 4, 5] [1, 3, 4] list index out of range: 3 -['__class__', '__str__', '__dir__', '__repr__', '__hash__', 'baz', 'qux'] +['__class__', '__dir__', '__hash__', '__repr__', '__str__', 'baz', 'qux'] 42 -['__class__', '__str__', '__dir__', '__repr__', '__hash__', 'qux'] +['__class__', '__dir__', '__hash__', '__repr__', '__str__', 'qux'] hi 'object' object has no attribute 'baz' -['__class__', '__str__', '__dir__', '__repr__', '__hash__'] +['__class__', '__dir__', '__hash__', '__repr__', '__str__'] 'object' object has no attribute 'bar' diff --git a/test/testGetattrDir.krk.expect b/test/testGetattrDir.krk.expect index 27e7d93..6ccfbfc 100644 --- a/test/testGetattrDir.krk.expect +++ b/test/testGetattrDir.krk.expect @@ -1,3 +1,3 @@ -['__init__', '__str__', '__repr__', '__getattr__', '__class__', '__dir__', '__hash__', '__qualname__', '__func__', '__module__', '_dict'] +['__class__', '__dir__', '__func__', '__getattr__', '__hash__', '__init__', '__module__', '__qualname__', '__repr__', '__str__', '_dict'] 1 -['__class__', '__str__', '__dir__', '__repr__', '__hash__', '__qualname__', '__func__', '__module__', 'butts'] +['__class__', '__dir__', '__func__', '__hash__', '__module__', '__qualname__', '__repr__', '__str__', 'butts'] diff --git a/tools/gendoc.krk b/tools/gendoc.krk index 3d33a77..755d44e 100755 --- a/tools/gendoc.krk +++ b/tools/gendoc.krk @@ -205,7 +205,7 @@ def processModules(modules): if isinstance(obj, function) and member not in seen: seen.append(member) methods.append(Pair(member,obj)) - else if isinstance(obj, Property) and member not in seen: + else if isinstance(obj, property) and member not in seen: seen.append(member) properties.append(Pair(member,obj.__method__)) if methods: