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.
diff --git a/py/objslice.c b/py/objslice.c
index cfc819e..d17dbf6 100644
--- a/py/objslice.c
+++ b/py/objslice.c
@@ -27,7 +27,7 @@
 #include <stdlib.h>
 #include <assert.h>
 
-#include "py/obj.h"
+#include "py/runtime.h"
 
 /******************************************************************************/
 /* slice object                                                               */
@@ -53,6 +53,22 @@
     mp_print_str(print, ")");
 }
 
+#if MICROPY_PY_BUILTINS_SLICE_INDICES
+STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) {
+    mp_int_t length = mp_obj_int_get_checked(length_obj);
+    mp_bound_slice_t bound_indices;
+    mp_obj_slice_indices(self_in, length, &bound_indices);
+
+    mp_obj_t results[3] = {
+        MP_OBJ_NEW_SMALL_INT(bound_indices.start),
+        MP_OBJ_NEW_SMALL_INT(bound_indices.stop),
+        MP_OBJ_NEW_SMALL_INT(bound_indices.step),
+    };
+    return mp_obj_new_tuple(3, results);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(slice_indices_obj, slice_indices);
+#endif
+
 #if MICROPY_PY_BUILTINS_SLICE_ATTRS
 STATIC void slice_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
     if (dest[0] != MP_OBJ_NULL) {
@@ -60,22 +76,37 @@
         return;
     }
     mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in);
+
     if (attr == MP_QSTR_start) {
         dest[0] = self->start;
     } else if (attr == MP_QSTR_stop) {
         dest[0] = self->stop;
     } else if (attr == MP_QSTR_step) {
         dest[0] = self->step;
+    #if MICROPY_PY_BUILTINS_SLICE_INDICES
+    } else if (attr == MP_QSTR_indices) {
+        dest[0] = MP_OBJ_FROM_PTR(&slice_indices_obj);
+        dest[1] = self_in;
+    #endif
     }
 }
 #endif
 
+#if MICROPY_PY_BUILTINS_SLICE_INDICES && !MICROPY_PY_BUILTINS_SLICE_ATTRS
+STATIC const mp_rom_map_elem_t slice_locals_dict_table[] = {
+    { MP_ROM_QSTR(MP_QSTR_indices), MP_ROM_PTR(&slice_indices_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(slice_locals_dict, slice_locals_dict_table);
+#endif
+
 const mp_obj_type_t mp_type_slice = {
     { &mp_type_type },
     .name = MP_QSTR_slice,
     .print = slice_print,
 #if MICROPY_PY_BUILTINS_SLICE_ATTRS
     .attr = slice_attr,
+#elif MICROPY_PY_BUILTINS_SLICE_INDICES
+    .locals_dict = (mp_obj_dict_t*)&slice_locals_dict,
 #endif
 };
 
@@ -96,4 +127,69 @@
     *step = self->step;
 }
 
+// Return the real index and step values for a slice when applied to a sequence of
+// the given length, resolving missing components, negative values and values off
+// the end of the sequence.
+void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result) {
+    mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in);
+    mp_int_t start, stop, step;
+
+    if (self->step == mp_const_none) {
+        step = 1;
+    } else {
+        step = mp_obj_get_int(self->step);
+        if (step == 0) {
+            mp_raise_ValueError("slice step cannot be zero");
+        }
+    }
+
+    if (step > 0) {
+        // Positive step
+        if (self->start == mp_const_none) {
+            start = 0;
+        } else {
+            start = mp_obj_get_int(self->start);
+            if (start < 0) {
+                start += length;
+            }
+            start = MIN(length, MAX(start, 0));
+        }
+
+        if (self->stop == mp_const_none) {
+            stop = length;
+        } else {
+            stop = mp_obj_get_int(self->stop);
+            if (stop < 0) {
+                stop += length;
+            }
+            stop = MIN(length, MAX(stop, 0));
+        }
+    } else {
+        // Negative step
+        if (self->start == mp_const_none) {
+            start = length - 1;
+        } else {
+            start = mp_obj_get_int(self->start);
+            if (start < 0) {
+                start += length;
+            }
+            start = MIN(length - 1, MAX(start, -1));
+        }
+
+        if (self->stop == mp_const_none) {
+            stop = -1;
+        } else {
+            stop = mp_obj_get_int(self->stop);
+            if (stop < 0) {
+                stop += length;
+            }
+            stop = MIN(length - 1, MAX(stop, -1));
+        }
+    }
+
+    result->start = start;
+    result->stop = stop;
+    result->step = step;
+}
+
 #endif