From edbdf71f5c810b0fb2d00c05c752fe25ecb3e832 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 00:54:06 +0200 Subject: [PATCH 1/9] rt_unpack_sequence(): Support generic iterables. --- py/runtime.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/py/runtime.c b/py/runtime.c index 6f6e3c903e..a3970fe3dc 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -769,8 +769,8 @@ mp_obj_t rt_store_set(mp_obj_t set, mp_obj_t item) { // unpacked items are stored in reverse order into the array pointed to by items void rt_unpack_sequence(mp_obj_t seq_in, uint num, mp_obj_t *items) { + uint seq_len; if (MP_OBJ_IS_TYPE(seq_in, &tuple_type) || MP_OBJ_IS_TYPE(seq_in, &list_type)) { - uint seq_len; mp_obj_t *seq_items; if (MP_OBJ_IS_TYPE(seq_in, &tuple_type)) { mp_obj_tuple_get(seq_in, &seq_len, &seq_items); @@ -778,17 +778,33 @@ void rt_unpack_sequence(mp_obj_t seq_in, uint num, mp_obj_t *items) { mp_obj_list_get(seq_in, &seq_len, &seq_items); } if (seq_len < num) { - nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "need more than %d values to unpack", (void*)(machine_uint_t)seq_len)); + goto too_short; } else if (seq_len > num) { - nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "too many values to unpack (expected %d)", (void*)(machine_uint_t)num)); + goto too_long; } for (uint i = 0; i < num; i++) { items[i] = seq_items[num - 1 - i]; } } else { - // TODO call rt_getiter and extract via rt_iternext - nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not iterable", mp_obj_get_type_str(seq_in))); + mp_obj_t iterable = rt_getiter(seq_in); + + for (seq_len = 0; seq_len < num; seq_len++) { + mp_obj_t el = rt_iternext(iterable); + if (el == mp_const_stop_iteration) { + goto too_short; + } + items[num - 1 - seq_len] = el; + } + if (rt_iternext(iterable) != mp_const_stop_iteration) { + goto too_long; + } } + return; + +too_short: + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "need more than %d values to unpack", seq_len)); +too_long: + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "too many values to unpack (expected %d)", num)); } mp_obj_t rt_build_map(int n_args) { From 513e6567b15adf2354f0b05f486d66ee0cbe2c94 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 00:54:36 +0200 Subject: [PATCH 2/9] Add testcase for sequence unpacking. --- tests/basics/seq-unpack.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/basics/seq-unpack.py diff --git a/tests/basics/seq-unpack.py b/tests/basics/seq-unpack.py new file mode 100644 index 0000000000..af8b998d05 --- /dev/null +++ b/tests/basics/seq-unpack.py @@ -0,0 +1,36 @@ +# Basics +a, b = 1, 2 +print(a, b) +a, b = (1, 2) +print(a, b) +(a, b) = 1, 2 +print(a, b) +(a, b) = (1, 2) +print(a, b) + +# Tuples/lists are optimized +a, b = [1, 2] +print(a, b) +[a, b] = 100, 200 +print(a, b) + +try: + a, b, c = (1, 2) +except ValueError: + print("ValueError") +try: + a, b, c = [1, 2, 3, 4] +except ValueError: + print("ValueError") + +# Generic iterable object +a, b, c = range(3) +print(a, b, c) +try: + a, b, c = range(2) +except ValueError: + print("ValueError") +try: + a, b, c = range(4) +except ValueError: + print("ValueError") From a9459bc7233e92a6516c3fbc8a18a9d33966e244 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 00:57:06 +0200 Subject: [PATCH 3/9] unix: Add basic time module (with time() and clock() functions). Both return int so far (single-precision float doesn't have enough bits to represent int32 precisely). --- unix/Makefile | 4 ++++ unix/main.c | 4 ++++ unix/mpconfigport.mk | 3 +++ unix/time.c | 25 +++++++++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 unix/time.c diff --git a/unix/Makefile b/unix/Makefile index 00e7e94602..a1a6a7102c 100644 --- a/unix/Makefile +++ b/unix/Makefile @@ -14,6 +14,10 @@ include ../py/py.mk CFLAGS = -I. -I$(PY_SRC) -Wall -Werror -ansi -std=gnu99 -DUNIX $(CFLAGS_MOD) LDFLAGS = $(LDFLAGS_MOD) -lm +ifeq ($(MICROPY_MOD_TIME),1) +CFLAGS_MOD += -DMICROPY_MOD_TIME=1 +SRC_MOD += time.c +endif ifeq ($(MICROPY_MOD_FFI),1) CFLAGS_MOD += `pkg-config --cflags libffi` -DMICROPY_MOD_FFI=1 LDFLAGS_MOD += -ldl -lffi diff --git a/unix/main.c b/unix/main.c index 5ca8115368..cc942163f9 100644 --- a/unix/main.c +++ b/unix/main.c @@ -24,6 +24,7 @@ extern const mp_obj_fun_native_t mp_builtin_open_obj; void file_init(); void rawsocket_init(); +void time_init(); void ffi_init(); static void execute_from_lexer(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, bool is_repl) { @@ -242,6 +243,9 @@ int main(int argc, char **argv) { file_init(); rawsocket_init(); +#if MICROPY_MOD_TIME + time_init(); +#endif #if MICROPY_MOD_FFI ffi_init(); #endif diff --git a/unix/mpconfigport.mk b/unix/mpconfigport.mk index d220339397..6c7be237bc 100644 --- a/unix/mpconfigport.mk +++ b/unix/mpconfigport.mk @@ -1,4 +1,7 @@ # Enable/disable modules to be included in interpreter +# Subset of CPython time module +MICROPY_MOD_TIME = 1 + # ffi module requires libffi (libffi-dev Debian package) MICROPY_MOD_FFI = 0 diff --git a/unix/time.c b/unix/time.c new file mode 100644 index 0000000000..acea69178a --- /dev/null +++ b/unix/time.c @@ -0,0 +1,25 @@ +#include +#include + +#include "misc.h" +#include "mpconfig.h" +#include "qstr.h" +#include "obj.h" +#include "runtime.h" + +static mp_obj_t mod_time_time() { + return mp_obj_new_int((machine_int_t)time(NULL)); +} +static MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_obj, mod_time_time); + +// Note: this is deprecated since CPy3.3, but pystone still uses it. +static mp_obj_t mod_time_clock() { + return mp_obj_new_int((machine_int_t)clock()); +} +static MP_DEFINE_CONST_FUN_OBJ_0(mod_time_clock_obj, mod_time_clock); + +void time_init() { + mp_obj_t m = mp_obj_new_module(QSTR_FROM_STR_STATIC("time")); + rt_store_attr(m, QSTR_FROM_STR_STATIC("time"), (mp_obj_t)&mod_time_time_obj); + rt_store_attr(m, QSTR_FROM_STR_STATIC("clock"), (mp_obj_t)&mod_time_clock_obj); +} From 7364af2d8cd72467bbb3bf135b29fae47105b232 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 02:38:22 +0200 Subject: [PATCH 4/9] Factor out m_seq_get_fast_slice_indexes() fucntions as sequence helper. Takes slice object and sequence length and computes subsequence indexes for case of slice step=1. --- py/obj.h | 1 + py/objstr.c | 22 ++-------------------- py/sequence.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/py/obj.h b/py/obj.h index 0680e6fb1c..45430d4a1c 100644 --- a/py/obj.h +++ b/py/obj.h @@ -395,3 +395,4 @@ typedef struct _mp_obj_classmethod_t { // 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/objstr.c b/py/objstr.c index 3f6aa483e2..92bd71f3de 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -115,26 +115,8 @@ mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) { } #if MICROPY_ENABLE_SLICE } else if (MP_OBJ_IS_TYPE(rhs_in, &slice_type)) { - machine_int_t start, stop, step; - mp_obj_slice_get(rhs_in, &start, &stop, &step); - assert(step == 1); - if (start < 0) { - start = lhs_len + start; - if (start < 0) { - start = 0; - } - } else if (start > lhs_len) { - start = lhs_len; - } - if (stop <= 0) { - stop = lhs_len + stop; - // CPython returns empty string in such case - if (stop < 0) { - stop = start; - } - } else if (stop > lhs_len) { - stop = lhs_len; - } + machine_uint_t start, stop; + assert(m_seq_get_fast_slice_indexes(lhs_len, rhs_in, &start, &stop)); return mp_obj_new_str(lhs_data + start, stop - start, false); #endif } else { diff --git a/py/sequence.c b/py/sequence.c index 56718c6f85..1e851a9f80 100644 --- a/py/sequence.c +++ b/py/sequence.c @@ -23,3 +23,33 @@ void mp_seq_multiply(const void *items, uint item_sz, uint len, uint times, void dest = (char*)dest + copy_sz; } } + +bool m_seq_get_fast_slice_indexes(machine_uint_t len, mp_obj_t slice, machine_uint_t *begin, machine_uint_t *end) { + machine_int_t start, stop, step; + mp_obj_slice_get(slice, &start, &stop, &step); + if (step != 1) { + return false; + } + + // Unlike subscription, out-of-bounds slice indexes are never error + if (start < 0) { + start = len + start; + if (start < 0) { + start = 0; + } + } else if (start > len) { + start = len; + } + if (stop <= 0) { + stop = len + stop; + // CPython returns empty sequence in such case + if (stop < 0) { + stop = start; + } + } else if (stop > len) { + stop = len; + } + *begin = start; + *end = stop; + return true; +} From 13cfabd1b265d974e53e03d1d77eac7dc1d000e5 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 03:32:55 +0200 Subject: [PATCH 5/9] Implement slicing for lists. --- py/obj.h | 1 + py/objlist.c | 10 +++++++++- tests/basics/list1.py | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/py/obj.h b/py/obj.h index 45430d4a1c..bed5103dbd 100644 --- a/py/obj.h +++ b/py/obj.h @@ -396,3 +396,4 @@ typedef struct _mp_obj_classmethod_t { // 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); +#define m_seq_copy(dest, src, len, item_sz) memcpy(dest, src, len * sizeof(item_sz)) diff --git a/py/objlist.c b/py/objlist.c index b28ca81279..f3db99a637 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -136,7 +136,15 @@ static mp_obj_t list_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { switch (op) { case RT_BINARY_OP_SUBSCR: { - // list load +#if MICROPY_ENABLE_SLICE + if (MP_OBJ_IS_TYPE(rhs, &slice_type)) { + machine_uint_t start, stop; + assert(m_seq_get_fast_slice_indexes(o->len, rhs, &start, &stop)); + mp_obj_list_t *res = list_new(stop - start); + m_seq_copy(res->items, o->items + start, res->len, mp_obj_t); + return res; + } +#endif uint index = mp_get_index(o->base.type, o->len, rhs); return o->items[index]; } diff --git a/tests/basics/list1.py b/tests/basics/list1.py index 250a12b704..8dc3939dd6 100644 --- a/tests/basics/list1.py +++ b/tests/basics/list1.py @@ -16,3 +16,7 @@ print(x) x += [2, 1] print(x) + +print(x[1:]) +print(x[:-1]) +print(x[2:3]) From 9ed5435061cc6ae85cd9d8556d934c0e638ffadd Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 03:42:07 +0200 Subject: [PATCH 6/9] Implement slicing for tuples. --- py/objtuple.c | 11 ++++++++++- tests/basics/tuple1.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/basics/tuple1.py diff --git a/py/objtuple.c b/py/objtuple.c index 5f1744ea30..da714e08a3 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -87,7 +88,15 @@ static mp_obj_t tuple_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { switch (op) { case RT_BINARY_OP_SUBSCR: { - // tuple load +#if MICROPY_ENABLE_SLICE + if (MP_OBJ_IS_TYPE(rhs, &slice_type)) { + machine_uint_t start, stop; + assert(m_seq_get_fast_slice_indexes(o->len, rhs, &start, &stop)); + mp_obj_tuple_t *res = mp_obj_new_tuple(stop - start, NULL); + m_seq_copy(res->items, o->items + start, res->len, mp_obj_t); + return res; + } +#endif uint index = mp_get_index(o->base.type, o->len, rhs); return o->items[index]; } diff --git a/tests/basics/tuple1.py b/tests/basics/tuple1.py new file mode 100644 index 0000000000..b64720b3eb --- /dev/null +++ b/tests/basics/tuple1.py @@ -0,0 +1,16 @@ +# basic tuple functionality +x = (1, 2, 3 * 4) +print(x) +try: + x[0] = 4 +except TypeError: + print("TypeError") +print(x) +try: + x.append(5) +except AttributeError: + print("AttributeError") + +print(x[1:]) +print(x[:-1]) +print(x[2:3]) From 87e85b7dc7753bde510ec37db321574a1cc0cc47 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 08:24:07 +0200 Subject: [PATCH 7/9] Implement str/bytes rich comparisons. --- py/obj.h | 1 + py/objstr.c | 12 ++++++++ py/sequence.c | 38 ++++++++++++++++++++++++++ tests/basics/string-compare.py | 50 ++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 tests/basics/string-compare.py diff --git a/py/obj.h b/py/obj.h index bed5103dbd..ca3ab1af6a 100644 --- a/py/obj.h +++ b/py/obj.h @@ -397,3 +397,4 @@ typedef struct _mp_obj_classmethod_t { 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); #define m_seq_copy(dest, src, len, item_sz) memcpy(dest, src, len * sizeof(item_sz)) +bool mp_seq_cmp_bytes(int op, const byte *data1, uint len1, const byte *data2, uint len2); diff --git a/py/objstr.c b/py/objstr.c index 92bd71f3de..50cd31d542 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -169,6 +169,18 @@ mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) { mp_seq_multiply(lhs_data, sizeof(*lhs_data), lhs_len, n, data); return mp_obj_str_builder_end(s); } + + // These 2 are never passed here, dealt with as a special case in rt_binary_op(). + //case RT_BINARY_OP_EQUAL: + //case RT_BINARY_OP_NOT_EQUAL: + case RT_BINARY_OP_LESS: + case RT_BINARY_OP_LESS_EQUAL: + case RT_BINARY_OP_MORE: + case RT_BINARY_OP_MORE_EQUAL: + if (MP_OBJ_IS_STR(rhs_in)) { + GET_STR_DATA_LEN(rhs_in, rhs_data, rhs_len); + return MP_BOOL(mp_seq_cmp_bytes(op, lhs_data, lhs_len, rhs_data, rhs_len)); + } } return MP_OBJ_NULL; // op not supported diff --git a/py/sequence.c b/py/sequence.c index 1e851a9f80..74b4fcfdf8 100644 --- a/py/sequence.c +++ b/py/sequence.c @@ -14,6 +14,8 @@ // Helpers for sequence types +#define SWAP(type, var1, var2) { type t = var2; var2 = var1; var1 = t; } + // Implements backend of sequence * integer operation. Assumes elements are // memory-adjacent in sequence. void mp_seq_multiply(const void *items, uint item_sz, uint len, uint times, void *dest) { @@ -53,3 +55,39 @@ bool m_seq_get_fast_slice_indexes(machine_uint_t len, mp_obj_t slice, machine_ui *end = stop; return true; } + +// Special-case comparison function for sequences of bytes +// Don't pass RT_BINARY_OP_NOT_EQUAL here +bool mp_seq_cmp_bytes(int op, const byte *data1, uint len1, const byte *data2, uint len2) { + // Let's deal only with > & >= + if (op == RT_BINARY_OP_LESS || op == RT_BINARY_OP_LESS_EQUAL) { + SWAP(const byte*, data1, data2); + SWAP(uint, len1, len2); + if (op == RT_BINARY_OP_LESS) { + op = RT_BINARY_OP_MORE; + } else { + op = RT_BINARY_OP_MORE_EQUAL; + } + } + uint min_len = len1 < len2 ? len1 : len2; + int res = memcmp(data1, data2, min_len); + if (res < 0) { + return false; + } + if (res > 0) { + return true; + } + + // If we had tie in the last element... + // ... and we have lists of different lengths... + if (len1 != len2) { + if (len1 < len2) { + // ... then longer list length wins (we deal only with >) + return false; + } + } else if (op == RT_BINARY_OP_MORE) { + // Otherwise, if we have strict relation, equality means failure + return false; + } + return true; +} diff --git a/tests/basics/string-compare.py b/tests/basics/string-compare.py new file mode 100644 index 0000000000..740e1959c8 --- /dev/null +++ b/tests/basics/string-compare.py @@ -0,0 +1,50 @@ +print("" == "") +print("" > "") +print("" < "") +print("" == "1") +print("1" == "") +print("" > "1") +print("1" > "") +print("" < "1") +print("1" < "") +print("" >= "1") +print("1" >= "") +print("" <= "1") +print("1" <= "") + +print("1" == "1") +print("1" != "1") +print("1" == "2") +print("1" == "10") + +print("1" > "1") +print("1" > "2") +print("2" > "1") +print("10" > "1") +print("1/" > "1") +print("1" > "10") +print("1" > "1/") + +print("1" < "1") +print("2" < "1") +print("1" < "2") +print("1" < "10") +print("1" < "1/") +print("10" < "1") +print("1/" < "1") + +print("1" >= "1") +print("1" >= "2") +print("2" >= "1") +print("10" >= "1") +print("1/" >= "1") +print("1" >= "10") +print("1" >= "1/") + +print("1" <= "1") +print("2" <= "1") +print("1" <= "2") +print("1" <= "10") +print("1" <= "1/") +print("10" <= "1") +print("1/" <= "1") From 6964422cf426164b39abdc66f260be4f582309e0 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 08:56:20 +0200 Subject: [PATCH 8/9] unix time.clock(): Actually return float value. --- unix/time.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unix/time.c b/unix/time.c index acea69178a..9d8cf497c4 100644 --- a/unix/time.c +++ b/unix/time.c @@ -14,7 +14,12 @@ static MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_obj, mod_time_time); // Note: this is deprecated since CPy3.3, but pystone still uses it. static mp_obj_t mod_time_clock() { - return mp_obj_new_int((machine_int_t)clock()); +// return mp_obj_new_int((machine_int_t)clock()); + // POSIX requires CLOCKS_PER_SEC equals 1000000, so that's what we assume + // float cannot represent full range of int32 precisely, so we pre-divide + // int to reduce resolution, and then actually do float division hoping + // to preserve integer part resolution. + return mp_obj_new_float((float)(clock() / 1000) / 1000.0); } static MP_DEFINE_CONST_FUN_OBJ_0(mod_time_clock_obj, mod_time_clock); From ea2509d92cbb222854ceb0b323b616b807dd221b Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 2 Feb 2014 08:57:05 +0200 Subject: [PATCH 9/9] Fix assert() usage. --- py/objlist.c | 4 +++- py/objstr.c | 4 +++- py/objtuple.c | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/py/objlist.c b/py/objlist.c index f3db99a637..59a4ad6b12 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -139,7 +139,9 @@ static mp_obj_t list_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { #if MICROPY_ENABLE_SLICE if (MP_OBJ_IS_TYPE(rhs, &slice_type)) { machine_uint_t start, stop; - assert(m_seq_get_fast_slice_indexes(o->len, rhs, &start, &stop)); + if (!m_seq_get_fast_slice_indexes(o->len, rhs, &start, &stop)) { + assert(0); + } mp_obj_list_t *res = list_new(stop - start); m_seq_copy(res->items, o->items + start, res->len, mp_obj_t); return res; diff --git a/py/objstr.c b/py/objstr.c index 50cd31d542..03602b6ec7 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -116,7 +116,9 @@ mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) { #if MICROPY_ENABLE_SLICE } else if (MP_OBJ_IS_TYPE(rhs_in, &slice_type)) { machine_uint_t start, stop; - assert(m_seq_get_fast_slice_indexes(lhs_len, rhs_in, &start, &stop)); + if (!m_seq_get_fast_slice_indexes(lhs_len, rhs_in, &start, &stop)) { + assert(0); + } return mp_obj_new_str(lhs_data + start, stop - start, false); #endif } else { diff --git a/py/objtuple.c b/py/objtuple.c index da714e08a3..3e5041c9dd 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -91,7 +91,9 @@ static mp_obj_t tuple_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) { #if MICROPY_ENABLE_SLICE if (MP_OBJ_IS_TYPE(rhs, &slice_type)) { machine_uint_t start, stop; - assert(m_seq_get_fast_slice_indexes(o->len, rhs, &start, &stop)); + if (!m_seq_get_fast_slice_indexes(o->len, rhs, &start, &stop)) { + assert(0); + } mp_obj_tuple_t *res = mp_obj_new_tuple(stop - start, NULL); m_seq_copy(res->items, o->items + start, res->len, mp_obj_t); return res;