diff --git a/py/builtin.h b/py/builtin.h index e49e839a62..85ceeafdf4 100644 --- a/py/builtin.h +++ b/py/builtin.h @@ -32,4 +32,6 @@ MP_DECLARE_CONST_FUN_OBJ(mp_builtin_sorted_obj); MP_DECLARE_CONST_FUN_OBJ(mp_builtin_sum_obj); MP_DECLARE_CONST_FUN_OBJ(mp_builtin_str_obj); +MP_DECLARE_CONST_FUN_OBJ(mp_namedtuple_obj); + void mp_module_micropython_init(void); diff --git a/py/obj.h b/py/obj.h index c7ffb4619c..855b151506 100644 --- a/py/obj.h +++ b/py/obj.h @@ -256,6 +256,8 @@ mp_obj_t mp_obj_new_module(qstr module_name); mp_obj_type_t *mp_obj_get_type(mp_obj_t o_in); const char *mp_obj_get_type_str(mp_obj_t o_in); bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo); +// Without supefluous arg checking +bool mp_obj_is_subclass_fast(mp_obj_t object, mp_obj_t classinfo); void mp_obj_print_helper(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind); void mp_obj_print(mp_obj_t o, mp_print_kind_t kind); diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c new file mode 100644 index 0000000000..3485301113 --- /dev/null +++ b/py/objnamedtuple.c @@ -0,0 +1,144 @@ +#include + +#include "misc.h" +#include "mpconfig.h" +#include "nlr.h" +#include "qstr.h" +#include "obj.h" +#include "objtuple.h" + +typedef struct _mp_obj_namedtuple_type_t { + mp_obj_type_t base; + const char *fields; +} mp_obj_namedtuple_type_t; + +typedef struct _mp_obj_namedtuple_t { + mp_obj_tuple_t tuple; +} mp_obj_namedtuple_t; + +static inline bool is_end_tok(char c) { + return c == ' ' || c == ','; +} + +static inline const char *skip_to_next(const char *p) { + while (!is_end_tok(*p)) { + if (*p == 0) { + return NULL; + } + p++; + } + while (is_end_tok(*p)) { + if (*p == 0) { + return NULL; + } + p++; + } + return p; +} + +STATIC uint namedtuple_count_fields(const char *namedef) { + uint cnt = 0; + while (*namedef != 0) { + cnt++; + while (!is_end_tok(*namedef) && *namedef != 0) { + namedef++; + } + while (is_end_tok(*namedef) && *namedef != 0) { + namedef++; + } + } + return cnt; +} + +STATIC int namedtuple_find_field(const char *name, const char *namedef) { + int id = 0; + int len = strlen(name); + while (namedef) { + if (memcmp(name, namedef, len) == 0) { + namedef += len; + if (*namedef == 0 || is_end_tok(*namedef)) { + return id; + } + } + namedef = skip_to_next(namedef); + id++; + } + + return -1; +} + +STATIC void namedtuple_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) { + mp_obj_namedtuple_t *o = o_in; + print(env, "%s(", qstr_str(o->tuple.base.type->name)); + const char *fields = ((mp_obj_namedtuple_type_t*)o->tuple.base.type)->fields; + + for (int i = 0; i < o->tuple.len; i++) { + if (i > 0) { + print(env, ", "); + } + const char *next = fields; + + while (!is_end_tok(*next) && *next != 0) { + next++; + } + print(env, "%.*s=", next - fields, fields); + mp_obj_print_helper(print, env, o->tuple.items[i], PRINT_REPR); + while (is_end_tok(*next) && *next != 0) { + next++; + } + fields = next; + } + print(env, ")"); +} + +STATIC void namedtuple_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_namedtuple_t *self = self_in; + const char *fields = ((mp_obj_namedtuple_type_t*)self->tuple.base.type)->fields; + int id = namedtuple_find_field(qstr_str(attr), fields); + if (id < 0) { + return; + } + dest[0] = self->tuple.items[id]; +} + +STATIC bool namedtuple_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { + nlr_jump(mp_obj_new_exception_msg(&mp_type_AttributeError, "can't set attribute")); +} + +STATIC mp_obj_t namedtuple_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) { + mp_obj_namedtuple_type_t *type = type_in; + uint num_fields = namedtuple_count_fields(type->fields); + if (n_args != num_fields) { + // Counts include implicit "self" + nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "__new__() takes %d positional arguments but %d were given", + num_fields + 1, n_args + 1)); + } + mp_obj_tuple_t *tuple = mp_obj_new_tuple(n_args, args); + tuple->base.type = type_in; + return tuple; +} + +STATIC const mp_obj_tuple_t namedtuple_base_tuple = {{&tuple_type}, 1, {(mp_obj_t)&tuple_type}}; + +mp_obj_t mp_obj_new_namedtuple_type(qstr name, const char *fields) { + mp_obj_namedtuple_type_t *o = m_new0(mp_obj_namedtuple_type_t, 1); + o->base.base.type = &mp_type_type; + o->base.name = name; + o->base.print = namedtuple_print; + o->base.make_new = namedtuple_make_new; + o->base.unary_op = tuple_unary_op; + o->base.binary_op = tuple_binary_op; + o->base.load_attr = namedtuple_load_attr; + o->base.store_attr = namedtuple_store_attr; + o->base.bases_tuple = (mp_obj_t)&namedtuple_base_tuple; + o->fields = fields; + return o; +} + +STATIC mp_obj_t new_namedtuple_type(mp_obj_t name_in, mp_obj_t fields_in) { + qstr name = MP_OBJ_QSTR_VALUE(name_in); + const char *fields = mp_obj_str_get_str(fields_in); + return mp_obj_new_namedtuple_type(name, fields); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_namedtuple_obj, new_namedtuple_type); diff --git a/py/objtuple.c b/py/objtuple.c index 41ad8a5d58..d39b36d9fa 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -86,7 +86,7 @@ STATIC bool tuple_cmp_helper(int op, mp_obj_t self_in, mp_obj_t another_in) { return mp_seq_cmp_objs(op, self->items, self->len, another->items, another->len); } -STATIC mp_obj_t tuple_unary_op(int op, mp_obj_t self_in) { +mp_obj_t tuple_unary_op(int op, mp_obj_t self_in) { mp_obj_tuple_t *self = self_in; switch (op) { case RT_UNARY_OP_BOOL: return MP_BOOL(self->len != 0); @@ -95,7 +95,7 @@ STATIC mp_obj_t tuple_unary_op(int op, mp_obj_t self_in) { } } -STATIC mp_obj_t tuple_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { +mp_obj_t tuple_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { mp_obj_tuple_t *o = lhs; switch (op) { case RT_BINARY_OP_SUBSCR: @@ -116,7 +116,7 @@ STATIC mp_obj_t tuple_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { } case RT_BINARY_OP_ADD: { - if (!MP_OBJ_IS_TYPE(rhs, &tuple_type)) { + if (!mp_obj_is_subclass_fast(mp_obj_get_type(rhs), (mp_obj_t)&tuple_type)) { return NULL; } mp_obj_tuple_t *p = rhs; diff --git a/py/objtuple.h b/py/objtuple.h index 6ff38c10eb..c310bfb7d5 100644 --- a/py/objtuple.h +++ b/py/objtuple.h @@ -5,3 +5,5 @@ typedef struct _mp_obj_tuple_t { } mp_obj_tuple_t; void tuple_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind); +mp_obj_t tuple_unary_op(int op, mp_obj_t self_in); +mp_obj_t tuple_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs); diff --git a/py/objtype.c b/py/objtype.c index 46b96c7314..c814e9c75e 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -454,16 +454,7 @@ mp_obj_t mp_obj_new_super(mp_obj_t type, mp_obj_t obj) { /******************************************************************************/ // subclassing and built-ins specific to types -bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { - if (!MP_OBJ_IS_TYPE(object, &mp_type_type)) { - nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 1 must be a class")); - } - - // TODO support a tuple of classes for second argument - if (!MP_OBJ_IS_TYPE(classinfo, &mp_type_type)) { - nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 2 must be a class")); - } - +bool mp_obj_is_subclass_fast(mp_obj_t object, mp_obj_t classinfo) { for (;;) { if (object == classinfo) { return true; @@ -496,6 +487,19 @@ bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { } } +bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { + if (!MP_OBJ_IS_TYPE(object, &mp_type_type)) { + nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 1 must be a class")); + } + + // TODO support a tuple of classes for second argument + if (!MP_OBJ_IS_TYPE(classinfo, &mp_type_type)) { + nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 2 must be a class")); + } + + return mp_obj_is_subclass_fast(object, classinfo); +} + STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) { return MP_BOOL(mp_obj_is_subclass(object, classinfo)); } diff --git a/py/py.mk b/py/py.mk index a12e44d4d5..72ac785b56 100644 --- a/py/py.mk +++ b/py/py.mk @@ -58,6 +58,7 @@ PY_O_BASENAME = \ objmap.o \ objmodule.o \ objnone.o \ + objnamedtuple.o \ objrange.o \ objset.o \ objslice.o \ diff --git a/py/qstrdefs.h b/py/qstrdefs.h index 9f002777bb..f1ba14ae60 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -57,6 +57,7 @@ Q(bytes) Q(callable) Q(chr) Q(classmethod) +Q(collections) Q(complex) Q(dict) Q(dir) @@ -77,6 +78,7 @@ Q(list) Q(map) Q(max) Q(min) +Q(namedtuple) Q(next) Q(ord) Q(path) diff --git a/py/runtime.c b/py/runtime.c index ca08ce3d1f..0de161b08b 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -189,6 +189,9 @@ void rt_init(void) { mp_obj_t m_array = mp_obj_new_module(MP_QSTR_array); rt_store_attr(m_array, MP_QSTR_array, (mp_obj_t)&array_type); + mp_obj_t m_collections = mp_obj_new_module(MP_QSTR_collections); + rt_store_attr(m_collections, MP_QSTR_namedtuple, (mp_obj_t)&mp_namedtuple_obj); + #if MICROPY_CPYTHON_COMPAT // Precreate sys module, so "import sys" didn't throw exceptions. mp_obj_t m_sys = mp_obj_new_module(MP_QSTR_sys); diff --git a/tests/basics/namedtuple1.py b/tests/basics/namedtuple1.py new file mode 100644 index 0000000000..1e194472fb --- /dev/null +++ b/tests/basics/namedtuple1.py @@ -0,0 +1,46 @@ +from collections import namedtuple + +T = namedtuple("Tup", "foo bar") +# CPython prints fully qualified name, what we don't bother to do so far +#print(T) +t = T(1, 2) +print(t) +print(t[0], t[1]) +print(t.foo, t.bar) + +print(len(t)) +print(bool(t)) +print(t + t) +print(t * 3) + +print(isinstance(t, tuple)) + +try: + t[0] = 200 +except TypeError: + print("TypeError") +try: + t.bar = 200 +except AttributeError: + print("AttribiteError") + +try: + t = T(1) +except TypeError: + print("TypeError") + +try: + t = T(1, 2, 3) +except TypeError: + print("TypeError") + +# Try comma field separator +T2 = namedtuple("TupComma", "foo,bar") +t = T2(1, 2) +print(t) +print(t.foo, t.bar) + +# Try list of fields +# Not implemented so far +#T3 = namedtuple("TupComma", ["foo", "bar"]) +#t = T3(1, 2)