diff --git a/py/builtintables.c b/py/builtintables.c index 8edc69987b..cbf885844a 100644 --- a/py/builtintables.c +++ b/py/builtintables.c @@ -31,6 +31,9 @@ STATIC const mp_map_elem_t mp_builtin_object_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_list), (mp_obj_t)&mp_type_list }, { MP_OBJ_NEW_QSTR(MP_QSTR_map), (mp_obj_t)&mp_type_map }, { MP_OBJ_NEW_QSTR(MP_QSTR_object), (mp_obj_t)&mp_type_object }, +#if MICROPY_ENABLE_PROPERTY + { MP_OBJ_NEW_QSTR(MP_QSTR_property), (mp_obj_t)&mp_type_property }, +#endif { MP_OBJ_NEW_QSTR(MP_QSTR_set), (mp_obj_t)&mp_type_set }, { MP_OBJ_NEW_QSTR(MP_QSTR_str), (mp_obj_t)&mp_type_str }, { MP_OBJ_NEW_QSTR(MP_QSTR_super), (mp_obj_t)&mp_type_super }, diff --git a/py/mpconfig.h b/py/mpconfig.h index cbe18b409b..f9c02a6f02 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -135,6 +135,11 @@ typedef double mp_float_t; #define MICROPY_ENABLE_SLICE (1) #endif +// Whether to support the property object +#ifndef MICROPY_ENABLE_PROPERTY +#define MICROPY_ENABLE_PROPERTY (0) +#endif + // Enable features which improve CPython compatibility // but may lead to more code size/memory usage. // TODO: Originally intended as generic category to not diff --git a/py/obj.h b/py/obj.h index 6e5796668d..9efec60de0 100644 --- a/py/obj.h +++ b/py/obj.h @@ -283,6 +283,7 @@ extern const mp_obj_type_t mp_type_fun_bc; extern const mp_obj_type_t mp_type_module; extern const mp_obj_type_t mp_type_staticmethod; extern const mp_obj_type_t mp_type_classmethod; +extern const mp_obj_type_t mp_type_property; // Exceptions extern const mp_obj_type_t mp_type_BaseException; @@ -518,6 +519,9 @@ typedef struct _mp_obj_static_class_method_t { mp_obj_t fun; } mp_obj_static_class_method_t; +// property +const mp_obj_t *mp_obj_property_get(mp_obj_t self_in); + // sequence helpers void mp_seq_multiply(const void *items, uint item_sz, uint len, uint times, void *dest); bool m_seq_get_fast_slice_indexes(machine_uint_t len, mp_obj_t slice, machine_uint_t *begin, machine_uint_t *end); diff --git a/py/objproperty.c b/py/objproperty.c new file mode 100644 index 0000000000..dd4eedebdb --- /dev/null +++ b/py/objproperty.c @@ -0,0 +1,95 @@ +#include +#include + +#include "nlr.h" +#include "misc.h" +#include "mpconfig.h" +#include "qstr.h" +#include "obj.h" +#include "runtime.h" + +#if MICROPY_ENABLE_PROPERTY + +typedef struct _mp_obj_property_t { + mp_obj_base_t base; + mp_obj_t proxy[3]; // getter, setter, deleter +} mp_obj_property_t; + +STATIC mp_obj_t property_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) { + // TODO check n_kw == 0 + + mp_obj_property_t *o = m_new_obj(mp_obj_property_t); + o->base.type = &mp_type_property; + if (n_args >= 5) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "property takes at most 4 arguments")); + } + if (n_args >= 4) { + // doc ignored + } + if (n_args >= 3) { + o->proxy[2] = args[2]; + } else { + o->proxy[2] = mp_const_none; + } + if (n_args >= 2) { + o->proxy[1] = args[1]; + } else { + o->proxy[1] = mp_const_none; + } + if (n_args >= 1) { + o->proxy[0] = args[0]; + } else { + o->proxy[0] = mp_const_none; + } + return o; +} + +STATIC mp_obj_t property_getter(mp_obj_t self_in, mp_obj_t getter) { + mp_obj_property_t *p2 = m_new_obj(mp_obj_property_t); + *p2 = *(mp_obj_property_t*)self_in; + p2->proxy[0] = getter; + return p2; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(property_getter_obj, property_getter); + +STATIC mp_obj_t property_setter(mp_obj_t self_in, mp_obj_t setter) { + mp_obj_property_t *p2 = m_new_obj(mp_obj_property_t); + *p2 = *(mp_obj_property_t*)self_in; + p2->proxy[1] = setter; + return p2; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(property_setter_obj, property_setter); + +STATIC mp_obj_t property_deleter(mp_obj_t self_in, mp_obj_t deleter) { + mp_obj_property_t *p2 = m_new_obj(mp_obj_property_t); + *p2 = *(mp_obj_property_t*)self_in; + p2->proxy[2] = deleter; + return p2; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(property_deleter_obj, property_deleter); + +STATIC const mp_map_elem_t property_locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_getter), (mp_obj_t)&property_getter_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_setter), (mp_obj_t)&property_setter_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_deleter), (mp_obj_t)&property_deleter_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(property_locals_dict, property_locals_dict_table); + +const mp_obj_type_t mp_type_property = { + { &mp_type_type }, + .name = MP_QSTR_property, + .make_new = property_make_new, + .locals_dict = (mp_obj_t)&property_locals_dict, +}; + +const mp_obj_t *mp_obj_property_get(mp_obj_t self_in) { + assert(MP_OBJ_IS_TYPE(self_in, &mp_type_property)); + mp_obj_property_t *self = self_in; + return self->proxy; +} + +#endif // MICROPY_ENABLE_PROPERTY diff --git a/py/objtype.c b/py/objtype.c index cb8cfc5325..c1749ff5f3 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -229,15 +229,35 @@ STATIC mp_obj_t class_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) { STATIC void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { // logic: look in obj members then class locals (TODO check this against CPython) mp_obj_class_t *self = self_in; + mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { // object member, always treated as a value + // TODO should we check for properties? dest[0] = elem->value; return; } + mp_obj_t member = mp_obj_class_lookup(self->base.type, attr); if (member != MP_OBJ_NULL) { - class_convert_return_attr(self_in, member, dest); + if (0) { +#if MICROPY_ENABLE_PROPERTY + } else if (MP_OBJ_IS_TYPE(member, &mp_type_property)) { + // object member is a property + // delegate the store to the property + // TODO should this be part of class_convert_return_attr? + const mp_obj_t *proxy = mp_obj_property_get(member); + if (proxy[0] == mp_const_none) { + // TODO + } else { + dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); + // TODO should we convert the returned value using class_convert_return_attr? + } +#endif + } else { + // not a property + class_convert_return_attr(self_in, member, dest); + } return; } @@ -257,10 +277,30 @@ 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) { mp_obj_class_t *self = self_in; + +#if MICROPY_ENABLE_PROPERTY + // for property, we need to do a lookup first in the class dict + // this makes all stores slow... how to fix? + mp_obj_t member = mp_obj_class_lookup(self->base.type, attr); + if (member != MP_OBJ_NULL && MP_OBJ_IS_TYPE(member, &mp_type_property)) { + // attribute already exists and is a property + // delegate the store to the property + const mp_obj_t *proxy = mp_obj_property_get(member); + if (proxy[1] == mp_const_none) { + // TODO better error message + return false; + } else { + mp_obj_t dest[2] = {self_in, value}; + mp_call_function_n_kw(proxy[1], 2, 0, dest); + return true; + } + } +#endif + if (value == MP_OBJ_NULL) { // delete attribute - mp_map_elem_t *el = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); - return el != NULL; + mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); + return elem != NULL; } else { // store attribute mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; diff --git a/py/py.mk b/py/py.mk index 741d4438f8..5d02c95faf 100644 --- a/py/py.mk +++ b/py/py.mk @@ -60,6 +60,7 @@ PY_O_BASENAME = \ objmap.o \ objmodule.o \ objobject.o \ + objproperty.o \ objnone.o \ objnamedtuple.o \ objrange.o \ diff --git a/py/qstrdefs.h b/py/qstrdefs.h index a08af5a5ed..df383b776c 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -281,3 +281,10 @@ Q(stdout) Q(stderr) Q(version_info) #endif + +#if MICROPY_ENABLE_PROPERTY +Q(property) +Q(getter) +Q(setter) +Q(deleter) +#endif diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h index 832bdb05fb..fe35e5b094 100644 --- a/unix/mpconfigport.h +++ b/unix/mpconfigport.h @@ -10,6 +10,7 @@ #define MICROPY_ENABLE_REPL_HELPERS (1) #define MICROPY_ENABLE_LEXER_UNIX (1) #define MICROPY_ENABLE_SOURCE_LINE (1) +#define MICROPY_ENABLE_PROPERTY (1) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_PATH_MAX (PATH_MAX)