py/objslice: Add support for indices() method on slice objects.

Instances of the slice class are passed to __getitem__() on objects when
the user indexes them with a slice.  In practice the majority of the time
(other than passing it on untouched) is to work out what the slice means in
the context of an array dimension of a particular length.  Since Python 2.3
there has been a method on the slice class, indices(), that takes a
dimension length and returns the real start, stop and step, accounting for
missing or negative values in the slice spec.  This commit implements such
a indices() method on the slice class.

It is configurable at compile-time via MICROPY_PY_BUILTINS_SLICE_INDICES,
disabled by default, enabled on unix, stm32 and esp32 ports.

This commit also adds new tests for slice indices and for slicing unicode
strings.
This commit is contained in:
Nicko van Someren
2019-11-16 17:07:11 -07:00
committed by Damien George
parent 007a704d82
commit 4c93955b7b
9 changed files with 160 additions and 74 deletions

View File

@@ -46,78 +46,20 @@ void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times
#if MICROPY_PY_BUILTINS_SLICE
bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) {
mp_obj_t ostart, ostop, ostep;
mp_int_t start, stop;
mp_obj_slice_get(slice, &ostart, &ostop, &ostep);
mp_obj_slice_indices(slice, len, indexes);
if (ostep != mp_const_none && ostep != MP_OBJ_NEW_SMALL_INT(1)) {
indexes->step = mp_obj_get_int(ostep);
if (indexes->step == 0) {
mp_raise_ValueError("slice step cannot be zero");
}
} else {
indexes->step = 1;
}
if (ostart == mp_const_none) {
if (indexes->step > 0) {
start = 0;
} else {
start = len - 1;
}
} else {
start = mp_obj_get_int(ostart);
}
if (ostop == mp_const_none) {
if (indexes->step > 0) {
stop = len;
} else {
stop = 0;
}
} else {
stop = mp_obj_get_int(ostop);
if (stop >= 0 && indexes->step < 0) {
stop += 1;
}
}
// Unlike subscription, out-of-bounds slice indexes are never error
if (start < 0) {
start = len + start;
if (start < 0) {
if (indexes->step < 0) {
start = -1;
} else {
start = 0;
}
}
} else if (indexes->step > 0 && (mp_uint_t)start > len) {
start = len;
} else if (indexes->step < 0 && (mp_uint_t)start >= len) {
start = len - 1;
}
if (stop < 0) {
stop = len + stop;
if (stop < 0) {
stop = -1;
}
if (indexes->step < 0) {
stop += 1;
}
} else if ((mp_uint_t)stop > len) {
stop = len;
// If the index is negative then stop points to the last item, not after it
if (indexes->step < 0) {
indexes->stop++;
}
// CPython returns empty sequence in such case, or point for assignment is at start
if (indexes->step > 0 && start > stop) {
stop = start;
} else if (indexes->step < 0 && start < stop) {
stop = start + 1;
if (indexes->step > 0 && indexes->start > indexes->stop) {
indexes->stop = indexes->start;
} else if (indexes->step < 0 && indexes->start < indexes->stop) {
indexes->stop = indexes->start + 1;
}
indexes->start = start;
indexes->stop = stop;
return indexes->step == 1;
}