From eae16445d5f6ca4bcd693422fc93ccf4fd7e215e Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 11 Jan 2014 19:22:29 +0000 Subject: [PATCH] py: Implement staticmethod and classmethod (internally). Still need to make built-ins by these names, and write tests. --- py/obj.c | 4 +- py/obj.h | 24 +++++++++++ py/objdict.c | 120 +++++++++++++++++++-------------------------------- py/objtype.c | 74 ++++++++++++++++++++++++------- py/runtime.c | 17 +++++++- 5 files changed, 144 insertions(+), 95 deletions(-) diff --git a/py/obj.c b/py/obj.c index dfb450fb8d..81b5c69f7a 100644 --- a/py/obj.c +++ b/py/obj.c @@ -231,7 +231,7 @@ uint mp_get_index(const mp_obj_type_t *type, machine_uint_t len, mp_obj_t index) } } -// may return NULL +// may return MP_OBJ_NULL mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) { mp_small_int_t len = 0; if (MP_OBJ_IS_TYPE(o_in, &str_type)) { @@ -249,7 +249,7 @@ mp_obj_t mp_obj_len_maybe(mp_obj_t o_in) { } else if (MP_OBJ_IS_TYPE(o_in, &dict_type)) { len = mp_obj_dict_len(o_in); } else { - return NULL; + return MP_OBJ_NULL; } return MP_OBJ_NEW_SMALL_INT(len); } diff --git a/py/obj.h b/py/obj.h index 8d236008ee..b92f1e2a7e 100644 --- a/py/obj.h +++ b/py/obj.h @@ -59,6 +59,15 @@ typedef struct _mp_obj_base_t mp_obj_base_t; #define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) MP_DEFINE_CONST_FUN_OBJ_VOID_PTR(obj_name, false, n_args_min, n_args_max, (mp_fun_var_t)fun_name) #define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, fun_name) MP_DEFINE_CONST_FUN_OBJ_VOID_PTR(obj_name, true, 0, (~((machine_uint_t)0)), (mp_fun_kw_t)fun_name) +// These macros are used to declare and define constant staticmethond and classmethod objects +// You can put "static" in front of the definitions to make them local + +#define MP_DECLARE_CONST_STATICMETHOD_OBJ(obj_name) extern const mp_obj_staticmethod_t obj_name +#define MP_DECLARE_CONST_CLASSMETHOD_OBJ(obj_name) extern const mp_obj_classmethod_t obj_name + +#define MP_DEFINE_CONST_STATICMETHOD_OBJ(obj_name, fun_name) const mp_obj_staticmethod_t obj_name = {{&mp_type_staticmethod}, fun_name} +#define MP_DEFINE_CONST_CLASSMETHOD_OBJ(obj_name, fun_name) const mp_obj_classmethod_t obj_name = {{&mp_type_classmethod}, fun_name} + // Need to declare this here so we are not dependent on map.h struct _mp_map_t; struct _mp_map_elem_t; @@ -316,3 +325,18 @@ extern const mp_obj_type_t gen_instance_type; extern const mp_obj_type_t module_type; mp_obj_t mp_obj_new_module(qstr module_name); struct _mp_map_t *mp_obj_module_get_globals(mp_obj_t self_in); + +// staticmethod and classmethod types; defined here so we can make const versions + +extern const mp_obj_type_t mp_type_staticmethod; +extern const mp_obj_type_t mp_type_classmethod; + +typedef struct _mp_obj_staticmethod_t { + mp_obj_base_t base; + mp_obj_t fun; +} mp_obj_staticmethod_t; + +typedef struct _mp_obj_classmethod_t { + mp_obj_base_t base; + mp_obj_t fun; +} mp_obj_classmethod_t; diff --git a/py/objdict.c b/py/objdict.c index 8902e1020c..6dbb1f316b 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -139,6 +139,35 @@ static mp_obj_t dict_copy(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, dict_copy); +// this is a classmethod +static mp_obj_t dict_fromkeys(int n_args, const mp_obj_t *args) { + assert(2 <= n_args && n_args <= 3); + mp_obj_t iter = rt_getiter(args[1]); + mp_obj_t len = mp_obj_len_maybe(iter); + mp_obj_t value = mp_const_none; + mp_obj_t next = NULL; + mp_obj_dict_t *self = NULL; + + if (n_args > 2) { + value = args[2]; + } + + if (len == MP_OBJ_NULL) { + /* object's type doesn't have a __len__ slot */ + self = mp_obj_new_dict(0); + } else { + self = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len)); + } + + while ((next = rt_iternext(iter)) != mp_const_stop_iteration) { + mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + } + + return self; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_fun_obj, 2, 3, dict_fromkeys); +static MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, (const mp_obj_t)&dict_fromkeys_fun_obj); + static mp_obj_t dict_get_helper(mp_map_t *self, mp_obj_t key, mp_obj_t deflt, mp_map_lookup_kind_t lookup_kind) { mp_map_elem_t *elem = mp_map_lookup(self, key, lookup_kind); mp_obj_t value; @@ -280,23 +309,18 @@ static mp_obj_t dict_view_it_iternext(mp_obj_t self_in) { if (next != NULL) { switch (self->kind) { - case MP_DICT_VIEW_ITEMS: - { - mp_obj_t items[] = {next->key, next->value}; - return mp_obj_new_tuple(2, items); - } - case MP_DICT_VIEW_KEYS: - { - return next->key; - } - case MP_DICT_VIEW_VALUES: - { - return next->value; - } - default: - { - assert(0); /* can't happen */ - } + case MP_DICT_VIEW_ITEMS: + { + mp_obj_t items[] = {next->key, next->value}; + return mp_obj_new_tuple(2, items); + } + case MP_DICT_VIEW_KEYS: + return next->key; + case MP_DICT_VIEW_VALUES: + return next->value; + default: + assert(0); /* can't happen */ + return mp_const_none; } } else { return mp_const_stop_iteration; @@ -320,7 +344,6 @@ static mp_obj_t dict_view_getiter(mp_obj_t view_in) { return o; } - static void dict_view_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) { assert(MP_OBJ_IS_TYPE(self_in, &dict_view_type)); mp_obj_dict_view_t *self = self_in; @@ -354,7 +377,6 @@ mp_obj_t mp_obj_new_dict_view(mp_obj_dict_t *dict, mp_dict_view_kind_t kind) { return o; } - static mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) { assert(MP_OBJ_IS_TYPE(self_in, &dict_type)); mp_obj_dict_t *self = self_in; @@ -376,67 +398,13 @@ static mp_obj_t dict_values(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values); - /******************************************************************************/ -/* dict metaclass */ - -static mp_obj_t dict_fromkeys(int n_args, const mp_obj_t *args) { - assert(2 <= n_args && n_args <= 3); - mp_obj_t iter = rt_getiter(args[1]); - mp_obj_t len = mp_obj_len_maybe(iter); - mp_obj_t value = mp_const_none; - mp_obj_t next = NULL; - mp_obj_dict_t *self = NULL; - - if (n_args > 2) { - value = args[2]; - } - - if (len == NULL) { - /* object's type doesn't have a __len__ slot */ - self = mp_obj_new_dict(0); - } else { - self = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len)); - } - - while ((next = rt_iternext(iter)) != mp_const_stop_iteration) { - mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; - } - - return self; -} -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_obj, 2, 3, dict_fromkeys); - -static const mp_method_t dict_class_methods[] = { - { "fromkeys", &dict_fromkeys_obj }, - { NULL, NULL }, // end-of-list sentinel -}; - -/* this should be unnecessary when inheritance works */ -static void dict_class_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) { - print(env, ""); -} - -/* this should be unnecessary when inheritance works */ -static mp_obj_t dict_class_call_n(mp_obj_t self_in, int n_args, const mp_obj_t *args) { - return rt_build_map(0); -} - -static const mp_obj_type_t dict_class = { - { &mp_const_type }, - "dict_class", - .print = dict_class_print, - .methods = dict_class_methods, - .call_n = dict_class_call_n, -}; - - -/******************************************************************************/ -/* dict constructors & etc */ +/* dict constructors & public C API */ static const mp_method_t dict_type_methods[] = { { "clear", &dict_clear_obj }, { "copy", &dict_copy_obj }, + { "fromkeys", &dict_fromkeys_obj }, { "get", &dict_get_obj }, { "items", &dict_items_obj }, { "keys", &dict_keys_obj }, @@ -449,7 +417,7 @@ static const mp_method_t dict_type_methods[] = { }; const mp_obj_type_t dict_type = { - { &dict_class }, + { &mp_const_type }, "dict", .print = dict_print, .make_new = dict_make_new, diff --git a/py/objtype.c b/py/objtype.c index 6c89c1ff2b..011ee43552 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -27,15 +27,13 @@ static mp_obj_t mp_obj_new_class(mp_obj_t class) { return o; } -static mp_map_elem_t *mp_obj_class_lookup(mp_obj_t self_in, qstr attr, mp_map_lookup_kind_t lookup_kind) { +static mp_map_elem_t *mp_obj_class_lookup(const mp_obj_type_t *type, qstr attr, mp_map_lookup_kind_t lookup_kind) { for (;;) { - assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type)); - mp_obj_type_t *self = self_in; - if (self->locals_dict == NULL) { + if (type->locals_dict == NULL) { return NULL; } - assert(MP_OBJ_IS_TYPE(self->locals_dict, &dict_type)); // Micro Python restriction, for now - mp_map_t *locals_map = ((void*)self->locals_dict + sizeof(mp_obj_base_t)); // XXX hack to get map object from dict object + assert(MP_OBJ_IS_TYPE(type->locals_dict, &dict_type)); // Micro Python restriction, for now + mp_map_t *locals_map = ((void*)type->locals_dict + sizeof(mp_obj_base_t)); // XXX hack to get map object from dict object mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), lookup_kind); if (elem != NULL) { return elem; @@ -44,25 +42,27 @@ static mp_map_elem_t *mp_obj_class_lookup(mp_obj_t self_in, qstr attr, mp_map_lo // attribute not found, keep searching base classes // for a const struct, this entry might be NULL - if (self->bases_tuple == MP_OBJ_NULL) { + if (type->bases_tuple == MP_OBJ_NULL) { return NULL; } uint len; mp_obj_t *items; - mp_obj_tuple_get(self->bases_tuple, &len, &items); + mp_obj_tuple_get(type->bases_tuple, &len, &items); if (len == 0) { return NULL; } for (uint i = 0; i < len - 1; i++) { - elem = mp_obj_class_lookup(items[i], attr, lookup_kind); + assert(MP_OBJ_IS_TYPE(items[i], &mp_const_type)); + elem = mp_obj_class_lookup((mp_obj_type_t*)items[i], attr, lookup_kind); if (elem != NULL) { return elem; } } // search last base (simple tail recursion elimination) - self_in = items[len - 1]; + assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_const_type)); + type = (mp_obj_type_t*)items[len - 1]; } } @@ -73,11 +73,12 @@ static void class_print(void (*print)(void *env, const char *fmt, ...), void *en // args are reverse in the array static mp_obj_t class_make_new(mp_obj_t self_in, int n_args, const mp_obj_t *args) { assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type)); + mp_obj_type_t *self = self_in; mp_obj_t o = mp_obj_new_class(self_in); // look for __init__ function - mp_map_elem_t *init_fn = mp_obj_class_lookup(self_in, MP_QSTR___init__, MP_MAP_LOOKUP); + mp_map_elem_t *init_fn = mp_obj_class_lookup(self, MP_QSTR___init__, MP_MAP_LOOKUP); if (init_fn != NULL) { // call __init__ function @@ -114,7 +115,7 @@ static void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[1] = elem->value; return; } - elem = mp_obj_class_lookup((mp_obj_t)self->base.type, attr, MP_MAP_LOOKUP); + elem = mp_obj_class_lookup(self->base.type, attr, MP_MAP_LOOKUP); if (elem != NULL) { if (mp_obj_is_callable(elem->value)) { // class member is callable so build a bound method @@ -132,7 +133,7 @@ static void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { static bool class_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { // logic: look in class locals (no add) then obj members (add) (TODO check this against CPython) mp_obj_class_t *self = self_in; - mp_map_elem_t *elem = mp_obj_class_lookup((mp_obj_t)self->base.type, attr, MP_MAP_LOOKUP); + mp_map_elem_t *elem = mp_obj_class_lookup(self->base.type, attr, MP_MAP_LOOKUP); if (elem != NULL) { elem->value = value; } else { @@ -188,17 +189,47 @@ static mp_obj_t type_call_n(mp_obj_t self_in, int n_args, const mp_obj_t *args) // for fail, do nothing; for attr, dest[1] = value; for method, dest[0] = self, dest[1] = method static void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - mp_map_elem_t *elem = mp_obj_class_lookup(self_in, attr, MP_MAP_LOOKUP); + assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type)); + mp_obj_type_t *self = self_in; + mp_map_elem_t *elem = mp_obj_class_lookup(self, attr, MP_MAP_LOOKUP); if (elem != NULL) { dest[1] = elem->value; return; } + + // generic method lookup + // this is a lookup in the class itself (ie not the classes type or instance) + const mp_method_t *meth = self->methods; + if (meth != NULL) { + for (; meth->name != NULL; meth++) { + if (strcmp(meth->name, qstr_str(attr)) == 0) { + // check if the methods are functions, static or class methods + // see http://docs.python.org/3.3/howto/descriptor.html + if (MP_OBJ_IS_TYPE(meth->fun, &mp_type_staticmethod)) { + // return just the function + dest[1] = ((mp_obj_staticmethod_t*)meth->fun)->fun; + } else if (MP_OBJ_IS_TYPE(meth->fun, &mp_type_classmethod)) { + // return a bound method, with self being this class + dest[1] = ((mp_obj_classmethod_t*)meth->fun)->fun; + dest[0] = self_in; + } else { + // return just the function + // TODO need to wrap in a type check for the first argument; eg list.append(1,1) needs to throw an exception + dest[1] = (mp_obj_t)meth->fun; + } + return; + } + } + } } static bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { + assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type)); + mp_obj_type_t *self = self_in; + // TODO CPython allows STORE_ATTR to a class, but is this the correct implementation? - mp_map_elem_t *elem = mp_obj_class_lookup(self_in, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + mp_map_elem_t *elem = mp_obj_class_lookup(self, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); if (elem != NULL) { elem->value = value; return true; @@ -284,3 +315,16 @@ static mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) { } MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance); + +/******************************************************************************/ +// staticmethod and classmethod types (probably should go in a different file) + +const mp_obj_type_t mp_type_staticmethod = { + { &mp_const_type }, + "staticmethod", +}; + +const mp_obj_type_t mp_type_classmethod = { + { &mp_const_type }, + "classmethod", +}; diff --git a/py/runtime.c b/py/runtime.c index 29571a44b8..409d1d8262 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -774,12 +774,25 @@ void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { dest[0] = base; } else { // generic method lookup + // this is a lookup in the object (ie not class or type) const mp_method_t *meth = type->methods; if (meth != NULL) { for (; meth->name != NULL; meth++) { if (strcmp(meth->name, qstr_str(attr)) == 0) { - dest[1] = (mp_obj_t)meth->fun; - dest[0] = base; + // check if the methods are functions, static or class methods + // see http://docs.python.org/3.3/howto/descriptor.html + if (MP_OBJ_IS_TYPE(meth->fun, &mp_type_staticmethod)) { + // return just the function + dest[1] = ((mp_obj_staticmethod_t*)meth->fun)->fun; + } else if (MP_OBJ_IS_TYPE(meth->fun, &mp_type_classmethod)) { + // return a bound method, with self being the type of this object + dest[1] = ((mp_obj_classmethod_t*)meth->fun)->fun; + dest[0] = mp_obj_get_type(base); + } else { + // return a bound method, with self being this object + dest[1] = (mp_obj_t)meth->fun; + dest[0] = base; + } break; } }