Skip to content

Commit 7c9c667

Browse files
committed
py: Implement iterator support for object that has __getitem__.
Addresses Issue adafruit#203.
1 parent fcd4ae8 commit 7c9c667

File tree

8 files changed

+117
-19
lines changed

8 files changed

+117
-19
lines changed

py/obj.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ typedef struct _mp_obj_base_t mp_obj_base_t;
3939
#define MP_OBJ_IS_SMALL_INT(o) ((((mp_small_int_t)(o)) & 1) != 0)
4040
#define MP_OBJ_IS_QSTR(o) ((((mp_small_int_t)(o)) & 3) == 2)
4141
#define MP_OBJ_IS_OBJ(o) ((((mp_small_int_t)(o)) & 3) == 0)
42-
#define MP_OBJ_IS_TYPE(o, t) (MP_OBJ_IS_OBJ(o) && (((mp_obj_base_t*)(o))->type == (t)))
42+
#define MP_OBJ_IS_TYPE(o, t) (MP_OBJ_IS_OBJ(o) && (((mp_obj_base_t*)(o))->type == (t))) // this does not work for checking a string, use below macro for that
4343
#define MP_OBJ_IS_STR(o) (MP_OBJ_IS_QSTR(o) || MP_OBJ_IS_TYPE(o, &str_type))
4444

4545
#define MP_OBJ_SMALL_INT_VALUE(o) (((mp_small_int_t)(o)) >> 1)
@@ -231,6 +231,7 @@ mp_obj_t mp_obj_new_dict(int n_args);
231231
mp_obj_t mp_obj_new_set(int n_args, mp_obj_t *items);
232232
mp_obj_t mp_obj_new_slice(mp_obj_t start, mp_obj_t stop, mp_obj_t step);
233233
mp_obj_t mp_obj_new_bound_meth(mp_obj_t meth, mp_obj_t self);
234+
mp_obj_t mp_obj_new_getitem_iter(mp_obj_t *args);
234235
mp_obj_t mp_obj_new_module(qstr module_name);
235236

236237
mp_obj_type_t *mp_obj_get_type(mp_obj_t o_in);

py/objgetitemiter.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <stdlib.h>
2+
#include <stdint.h>
3+
4+
#include "nlr.h"
5+
#include "misc.h"
6+
#include "mpconfig.h"
7+
#include "qstr.h"
8+
#include "obj.h"
9+
#include "runtime.h"
10+
11+
// this is a wrapper object that is turns something that has a __getitem__ method into an iterator
12+
13+
typedef struct _mp_obj_getitem_iter_t {
14+
mp_obj_base_t base;
15+
mp_obj_t args[3];
16+
} mp_obj_getitem_iter_t;
17+
18+
static mp_obj_t it_iternext(mp_obj_t self_in) {
19+
mp_obj_getitem_iter_t *self = self_in;
20+
nlr_buf_t nlr;
21+
if (nlr_push(&nlr) == 0) {
22+
// try to get next item
23+
mp_obj_t value = rt_call_method_n_kw(1, 0, self->args);
24+
self->args[2] = MP_OBJ_NEW_SMALL_INT(MP_OBJ_SMALL_INT_VALUE(self->args[2]) + 1);
25+
nlr_pop();
26+
return value;
27+
} else {
28+
// an exception was raised
29+
if (MP_OBJ_IS_TYPE(nlr.ret_val, &exception_type) && mp_obj_exception_get_type(nlr.ret_val) == MP_QSTR_StopIteration) {
30+
// return mp_const_stop_iteration instead of raising StopIteration
31+
return mp_const_stop_iteration;
32+
} else {
33+
// re-raise exception
34+
nlr_jump(nlr.ret_val);
35+
}
36+
}
37+
}
38+
39+
static const mp_obj_type_t it_type = {
40+
{ &mp_const_type },
41+
"iterator",
42+
.iternext = it_iternext
43+
};
44+
45+
// args are those returned from rt_load_method_maybe (ie either an attribute or a method)
46+
mp_obj_t mp_obj_new_getitem_iter(mp_obj_t *args) {
47+
mp_obj_getitem_iter_t *o = m_new_obj(mp_obj_getitem_iter_t);
48+
o->base.type = &it_type;
49+
o->args[0] = args[0];
50+
o->args[1] = args[1];
51+
o->args[2] = MP_OBJ_NEW_SMALL_INT(0);
52+
return o;
53+
}

py/objstr.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
7777
if (MP_OBJ_IS_SMALL_INT(rhs_in)) {
7878
uint index = mp_get_index(mp_obj_get_type(lhs_in), lhs_len, rhs_in);
7979
if (MP_OBJ_IS_TYPE(lhs_in, &bytes_type)) {
80-
return MP_OBJ_NEW_SMALL_INT(lhs_data[index]);
80+
return MP_OBJ_NEW_SMALL_INT((mp_small_int_t)lhs_data[index]);
8181
} else {
8282
return mp_obj_new_str(lhs_data + index, 1, true);
8383
}
@@ -549,7 +549,7 @@ mp_obj_t bytes_it_iternext(mp_obj_t self_in) {
549549
mp_obj_str_it_t *self = self_in;
550550
GET_STR_DATA_LEN(self->str, str, len);
551551
if (self->cur < len) {
552-
mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT(str[self->cur]);
552+
mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT((mp_small_int_t)str[self->cur]);
553553
self->cur += 1;
554554
return o_out;
555555
} else {

py/objtype.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,17 @@ static mp_obj_t class_make_new(mp_obj_t self_in, uint n_args, uint n_kw, const m
117117
}
118118

119119
// TODO somehow replace const char * with a qstr
120-
static const char *binary_op_method_name[] = {
121-
[RT_BINARY_OP_SUBSCR] = "__getitem__",
120+
static const qstr binary_op_method_name[] = {
121+
[RT_BINARY_OP_SUBSCR] = MP_QSTR___getitem__,
122122
/*
123123
RT_BINARY_OP_OR,
124124
RT_BINARY_OP_XOR,
125125
RT_BINARY_OP_AND,
126126
RT_BINARY_OP_LSHIFT,
127127
RT_BINARY_OP_RSHIFT,
128128
*/
129-
[RT_BINARY_OP_ADD] = "__add__",
130-
[RT_BINARY_OP_SUBTRACT] = "__sub__",
129+
[RT_BINARY_OP_ADD] = MP_QSTR___add__,
130+
[RT_BINARY_OP_SUBTRACT] = MP_QSTR___sub__,
131131
/*
132132
RT_BINARY_OP_MULTIPLY,
133133
RT_BINARY_OP_FLOOR_DIVIDE,
@@ -157,16 +157,16 @@ static const char *binary_op_method_name[] = {
157157
RT_COMPARE_OP_IS,
158158
RT_COMPARE_OP_IS_NOT,
159159
*/
160-
[RT_COMPARE_OP_EXCEPTION_MATCH] = "__not_implemented__",
160+
[RT_COMPARE_OP_EXCEPTION_MATCH] = MP_QSTR_, // not implemented, used to make sure array has full size
161161
};
162162

163163
static mp_obj_t class_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
164164
mp_obj_class_t *lhs = lhs_in;
165-
const char *op_name = binary_op_method_name[op];
166-
if (op_name == NULL) {
165+
qstr op_name = binary_op_method_name[op];
166+
if (op_name == 0) {
167167
return MP_OBJ_NULL;
168168
}
169-
mp_obj_t member = mp_obj_class_lookup(lhs->base.type, QSTR_FROM_STR_STATIC(op_name));
169+
mp_obj_t member = mp_obj_class_lookup(lhs->base.type, op_name);
170170
if (member != MP_OBJ_NULL) {
171171
return rt_call_function_2(member, lhs_in, rhs_in);
172172
} else {

py/py.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ PY_O_BASENAME = \
4747
objfloat.o \
4848
objfun.o \
4949
objgenerator.o \
50+
objgetitemiter.o \
5051
objint.o \
5152
objint_longlong.o \
5253
objlist.o \

py/qstrdefs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Q(__next__)
1313
Q(__qualname__)
1414
Q(__repl_print__)
1515

16+
Q(__getitem__)
17+
Q(__add__)
18+
Q(__sub__)
19+
1620
Q(micropython)
1721
Q(byte_code)
1822
Q(native)

py/runtime.c

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ void rt_init(void) {
8787
// init loaded modules table
8888
mp_map_init(&map_loaded_modules, 3);
8989

90-
// built-in exceptions (TODO, make these proper classes)
90+
// built-in exceptions (TODO, make these proper classes, and const if possible)
9191
mp_map_add_qstr(&map_builtins, MP_QSTR_AttributeError, mp_obj_new_exception(MP_QSTR_AttributeError));
9292
mp_map_add_qstr(&map_builtins, MP_QSTR_IndexError, mp_obj_new_exception(MP_QSTR_IndexError));
9393
mp_map_add_qstr(&map_builtins, MP_QSTR_KeyError, mp_obj_new_exception(MP_QSTR_KeyError));
@@ -100,6 +100,7 @@ void rt_init(void) {
100100
mp_map_add_qstr(&map_builtins, MP_QSTR_OverflowError, mp_obj_new_exception(MP_QSTR_OverflowError));
101101
mp_map_add_qstr(&map_builtins, MP_QSTR_OSError, mp_obj_new_exception(MP_QSTR_OSError));
102102
mp_map_add_qstr(&map_builtins, MP_QSTR_AssertionError, mp_obj_new_exception(MP_QSTR_AssertionError));
103+
mp_map_add_qstr(&map_builtins, MP_QSTR_StopIteration, mp_obj_new_exception(MP_QSTR_StopIteration));
103104

104105
// built-in objects
105106
mp_map_add_qstr(&map_builtins, MP_QSTR_Ellipsis, mp_const_ellipsis);
@@ -805,7 +806,7 @@ mp_obj_t rt_load_attr(mp_obj_t base, qstr attr) {
805806
// use load_method
806807
mp_obj_t dest[2];
807808
rt_load_method(base, attr, dest);
808-
if (dest[1] == NULL) {
809+
if (dest[1] == MP_OBJ_NULL) {
809810
// load_method returned just a normal attribute
810811
return dest[0];
811812
} else {
@@ -814,9 +815,10 @@ mp_obj_t rt_load_attr(mp_obj_t base, qstr attr) {
814815
}
815816
}
816817

817-
void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
818-
DEBUG_OP_printf("load method %p.%s\n", base, qstr_str(attr));
819-
818+
// no attribute found, returns: dest[0] == MP_OBJ_NULL, dest[1] == MP_OBJ_NULL
819+
// normal attribute found, returns: dest[0] == <attribute>, dest[1] == MP_OBJ_NULL
820+
// method attribute found, returns: dest[0] == <method>, dest[1] == <self>
821+
static void rt_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest) {
820822
// clear output to indicate no attribute/method found yet
821823
dest[0] = MP_OBJ_NULL;
822824
dest[1] = MP_OBJ_NULL;
@@ -830,7 +832,7 @@ void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
830832
}
831833

832834
// if nothing found yet, look for built-in and generic names
833-
if (dest[0] == NULL) {
835+
if (dest[0] == MP_OBJ_NULL) {
834836
if (attr == MP_QSTR___next__ && type->iternext != NULL) {
835837
dest[0] = (mp_obj_t)&mp_builtin_next_obj;
836838
dest[1] = base;
@@ -861,8 +863,14 @@ void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
861863
}
862864
}
863865
}
866+
}
867+
868+
void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
869+
DEBUG_OP_printf("load method %p.%s\n", base, qstr_str(attr));
870+
871+
rt_load_method_maybe(base, attr, dest);
864872

865-
if (dest[0] == NULL) {
873+
if (dest[0] == MP_OBJ_NULL) {
866874
// no attribute/method called attr
867875
// following CPython, we give a more detailed error message for type objects
868876
if (MP_OBJ_IS_TYPE(base, &mp_const_type)) {
@@ -910,7 +918,16 @@ mp_obj_t rt_getiter(mp_obj_t o_in) {
910918
if (type->getiter != NULL) {
911919
return type->getiter(o_in);
912920
} else {
913-
nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not iterable", type->name));
921+
// check for __getitem__ method
922+
mp_obj_t dest[2];
923+
rt_load_method_maybe(o_in, qstr_from_str("__getitem__"), dest);
924+
if (dest[0] != MP_OBJ_NULL) {
925+
// __getitem__ exists, create an iterator
926+
return mp_obj_new_getitem_iter(dest);
927+
} else {
928+
// object not iterable
929+
nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not iterable", type->name));
930+
}
914931
}
915932
}
916933

tests/basics/getitem.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# create a class that has a __getitem__ method
2+
class A:
3+
def __getitem__(self, index):
4+
print('getitem', index)
5+
if index > 10:
6+
raise StopIteration
7+
8+
# test __getitem__
9+
A()[0]
10+
A()[1]
11+
12+
# iterate using a for loop
13+
for i in A():
14+
pass
15+
16+
# iterate manually
17+
it = iter(A())
18+
try:
19+
while True:
20+
next(it)
21+
except StopIteration:
22+
pass

0 commit comments

Comments
 (0)