diff --git a/nac3core/irrt/irrt/error_context.hpp b/nac3core/irrt/irrt/error_context.hpp index 34de06d2..fee47d40 100644 --- a/nac3core/irrt/irrt/error_context.hpp +++ b/nac3core/irrt/irrt/error_context.hpp @@ -4,81 +4,81 @@ #include namespace { -// nac3core's "str" struct type definition -template -struct Str { - const char* content; - SizeT length; -}; - -// A limited set of errors IRRT could use. -typedef uint32_t ErrorId; -struct ErrorIds { - ErrorId index_error; - ErrorId value_error; - ErrorId assertion_error; - ErrorId runtime_error; -}; - -struct ErrorContext { - // Context - ErrorIds* error_ids; - - // Error thrown by IRRT - ErrorId error_id; - const char* message_template; // MUST BE `&'static` - uint64_t param1; - uint64_t param2; - uint64_t param3; - - void initialize(ErrorIds* error_ids) { - this->error_ids = error_ids; - clear_error(); - } - - void clear_error() { - // Point the message_template to an empty str. Don't set it to nullptr as a sentinel - this->message_template = ""; - } - - void set_error(ErrorId error_id, const char* message, uint64_t param1 = 0, uint64_t param2 = 0, uint64_t param3 = 0) { - this->error_id = error_id; - this->message_template = message; - this->param1 = param1; - this->param2 = param2; - this->param3 = param3; - } - - bool has_error() { - return !cstr_utils::is_empty(message_template); - } - + // nac3core's "str" struct type definition template - void get_error_str(Str *dst_str) { - dst_str->content = message_template; - dst_str->length = (SizeT) cstr_utils::length(message_template); - } -}; + struct Str { + const char* content; + SizeT length; + }; + + // A limited set of errors IRRT could use. + typedef uint32_t ErrorId; + struct ErrorIds { + ErrorId index_error; + ErrorId value_error; + ErrorId assertion_error; + ErrorId runtime_error; + }; + + struct ErrorContext { + // Context + const ErrorIds* error_ids; + + // Error thrown by IRRT + ErrorId error_id; + const char* message_template; // MUST BE `&'static` + int64_t param1; + int64_t param2; + int64_t param3; + + void initialize(const ErrorIds* error_ids) { + this->error_ids = error_ids; + clear_error(); + } + + void clear_error() { + // Point the message_template to an empty str. Don't set it to nullptr as a sentinel + this->message_template = ""; + } + + void set_error(ErrorId error_id, const char* message, int64_t param1 = 0, int64_t param2 = 0, int64_t param3 = 0) { + this->error_id = error_id; + this->message_template = message; + this->param1 = param1; + this->param2 = param2; + this->param3 = param3; + } + + bool has_error() { + return !cstr_utils::is_empty(message_template); + } + + template + void get_error_str(Str *dst_str) { + dst_str->content = message_template; + dst_str->length = (SizeT) cstr_utils::length(message_template); + } + }; } extern "C" { -void __nac3_error_context_initialize(ErrorContext* errctx, ErrorIds* error_ids) { - errctx->initialize(error_ids); -} + void __nac3_error_context_initialize(ErrorContext* errctx, const ErrorIds* error_ids) { + errctx->initialize(error_ids); + } -bool __nac3_error_context_has_no_error(ErrorContext* errctx) { - return !errctx->has_error(); -} + bool __nac3_error_context_has_no_error(ErrorContext* errctx) { + return !errctx->has_error(); + } -void __nac3_error_context_get_error_str(ErrorContext* errctx, Str *dst_str) { - errctx->get_error_str(dst_str); -} + void __nac3_error_context_get_error_str(ErrorContext* errctx, Str *dst_str) { + errctx->get_error_str(dst_str); + } -void __nac3_error_context_get_error_str64(ErrorContext* errctx, Str *dst_str) { - errctx->get_error_str(dst_str); -} + void __nac3_error_context_get_error_str64(ErrorContext* errctx, Str *dst_str) { + errctx->get_error_str(dst_str); + } -void __nac3_error_dummy_raise(ErrorContext* errctx) { - errctx->set_error(errctx->error_ids->runtime_error, "THROWN FROM __nac3_error_dummy_raise!!!!!!"); -} + void __nac3_error_dummy_raise(ErrorContext* errctx) { + errctx->set_error(errctx->error_ids->runtime_error, "THROWN FROM __nac3_error_dummy_raise!!!!!!"); + } } \ No newline at end of file diff --git a/nac3core/irrt/irrt/numpy/ndarray_basic.hpp b/nac3core/irrt/irrt/numpy/ndarray_basic.hpp index ff597dc3..f3511cb2 100644 --- a/nac3core/irrt/irrt/numpy/ndarray_basic.hpp +++ b/nac3core/irrt/irrt/numpy/ndarray_basic.hpp @@ -4,10 +4,9 @@ #include #include -namespace { -namespace ndarray { +namespace { namespace ndarray { namespace basic { namespace util { - // Throw an error if there is an axis with negative dimension + // throw an error if there is an axis with negative dimension template void assert_shape_no_negative(ErrorContext* errctx, SizeT ndims, const SizeT* shape) { for (SizeT axis = 0; axis < ndims; axis++) { @@ -22,7 +21,7 @@ namespace ndarray { } } - // Compute the size/# of elements of an ndarray given its shape + // compute the size/# of elements of an ndarray given its shape template SizeT calc_size_from_shape(SizeT ndims, const SizeT* shape) { SizeT size = 1; @@ -30,12 +29,12 @@ namespace ndarray { return size; } - // Compute the strides of an ndarray given an ndarray `shape` - // and assuming that the ndarray is *fully C-contagious*. + // compute the strides of an ndarray given an ndarray `shape` + // and assuming that the ndarray is *fully c-contagious*. // - // You might want to read up on https://ajcr.net/stride-guide-part-1/. + // you might want to read up on https://ajcr.net/stride-guide-part-1/. // - // This function might be used in isolation without an ndarray. That's + // this function might be used in isolation without an ndarray. that's // why it separated out into its own util function. template void set_strides_by_shape(SizeT itemsize, SizeT ndims, SizeT* dst_strides, const SizeT* shape) { @@ -59,24 +58,23 @@ namespace ndarray { } } - // Calculate the size/# of elements of an `ndarray`. - // This function corresponds to `np.size()` or `ndarray.size` + // calculate the size/# of elements of an `ndarray`. + // this function corresponds to `np.size()` or `ndarray.size` template SizeT size(NDArray* ndarray) { - return ndarray::util::calc_size_from_shape(ndarray->ndims, ndarray->shape); + return util::calc_size_from_shape(ndarray->ndims, ndarray->shape); } - // Calculate the number of bytes of its content of an `ndarray` *in its view*. - // This function corresponds to `ndarray.nbytes` + // calculate the number of bytes of its content of an `ndarray` *in its view*. + // this function corresponds to `ndarray.nbytes` template SizeT nbytes(NDArray* ndarray) { - return ndarray::size(ndarray) * ndarray->itemsize; + return size(ndarray) * ndarray->itemsize; } - // Set the strides of the ndarray with `ndarray_util::set_strides_by_shape` template void set_strides_by_shape(NDArray* ndarray) { - ndarray::util::set_strides_by_shape(ndarray->itemsize, ndarray->ndims, ndarray->strides, ndarray->shape); + util::set_strides_by_shape(ndarray->itemsize, ndarray->ndims, ndarray->strides, ndarray->shape); } template @@ -90,11 +88,11 @@ namespace ndarray { template uint8_t* get_nth_pelement(NDArray* ndarray, SizeT nth) { SizeT* indices = (SizeT*) __builtin_alloca(sizeof(SizeT) * ndarray->ndims); - ndarray::util::set_indices_by_nth(ndarray->ndims, ndarray->shape, indices, nth); - return ndarray::get_pelement_by_indices(ndarray, indices); + util::set_indices_by_nth(ndarray->ndims, ndarray->shape, indices, nth); + return get_pelement_by_indices(ndarray, indices); } - // Get the pointer to the nth element of the ndarray as if it were flattened. + // get the pointer to the nth element of the ndarray as if it were flattened. template uint8_t* checked_get_nth_pelement(NDArray* ndarray, ErrorContext* errctx, SizeT nth) { SizeT arr_size = ndarray->size(); @@ -106,46 +104,47 @@ namespace ndarray { ); return 0; } - return ndarray::get_nth_pelement(ndarray, nth); + return get_nth_pelement(ndarray, nth); } template void set_pelement_value(NDArray* ndarray, uint8_t* pelement, const uint8_t* pvalue) { __builtin_memcpy(pelement, pvalue, ndarray->itemsize); } -}; -} +} } } extern "C" { -uint32_t __nac3_ndarray_size(NDArray* ndarray) { - return ndarray::size(ndarray); -} + using namespace ndarray::basic; -uint64_t __nac3_ndarray_size64(NDArray* ndarray) { - return ndarray::size(ndarray); -} + uint32_t __nac3_ndarray_size(NDArray* ndarray) { + return size(ndarray); + } -uint32_t __nac3_ndarray_nbytes(NDArray* ndarray) { - return ndarray::nbytes(ndarray); -} + uint64_t __nac3_ndarray_size64(NDArray* ndarray) { + return size(ndarray); + } -uint64_t __nac3_ndarray_nbytes64(NDArray* ndarray) { - return ndarray::nbytes(ndarray); -} + uint32_t __nac3_ndarray_nbytes(NDArray* ndarray) { + return nbytes(ndarray); + } -void __nac3_ndarray_util_assert_shape_no_negative(ErrorContext* errctx, int32_t ndims, int32_t* shape) { - ndarray::util::assert_shape_no_negative(errctx, ndims, shape); -} + uint64_t __nac3_ndarray_nbytes64(NDArray* ndarray) { + return nbytes(ndarray); + } -void __nac3_ndarray_util_assert_shape_no_negative64(ErrorContext* errctx, int64_t ndims, int64_t* shape) { - ndarray::util::assert_shape_no_negative(errctx, ndims, shape); -} + void __nac3_ndarray_util_assert_shape_no_negative(ErrorContext* errctx, int32_t ndims, int32_t* shape) { + util::assert_shape_no_negative(errctx, ndims, shape); + } -void __nac3_ndarray_set_strides_by_shape(NDArray* ndarray) { - ndarray::set_strides_by_shape(ndarray); -} + void __nac3_ndarray_util_assert_shape_no_negative64(ErrorContext* errctx, int64_t ndims, int64_t* shape) { + util::assert_shape_no_negative(errctx, ndims, shape); + } -void __nac3_ndarray_set_strides_by_shape64(NDArray* ndarray) { - ndarray::set_strides_by_shape(ndarray); -} + void __nac3_ndarray_set_strides_by_shape(NDArray* ndarray) { + set_strides_by_shape(ndarray); + } + + void __nac3_ndarray_set_strides_by_shape64(NDArray* ndarray) { + set_strides_by_shape(ndarray); + } } \ No newline at end of file diff --git a/nac3core/irrt/irrt/numpy/ndarray_broadcast.hpp b/nac3core/irrt/irrt/numpy/ndarray_broadcast.hpp index 4de4976b..4b4d9d57 100644 --- a/nac3core/irrt/irrt/numpy/ndarray_broadcast.hpp +++ b/nac3core/irrt/irrt/numpy/ndarray_broadcast.hpp @@ -1,7 +1,6 @@ #include -namespace { -namespace ndarray { +namespace { namespace ndarray { namespace broadcast { namespace util { template bool can_broadcast_shape_to( @@ -112,5 +111,4 @@ namespace ndarray { } } } -} -} \ No newline at end of file +} } } \ No newline at end of file diff --git a/nac3core/irrt/irrt/numpy/ndarray_def.hpp b/nac3core/irrt/irrt/numpy/ndarray_def.hpp index f0b4469d..aaa91fe3 100644 --- a/nac3core/irrt/irrt/numpy/ndarray_def.hpp +++ b/nac3core/irrt/irrt/numpy/ndarray_def.hpp @@ -1,52 +1,55 @@ #pragma once namespace { -// The NDArray object. `SizeT` is the *signed* size type of this ndarray. -// -// NOTE: The order of fields is IMPORTANT. DON'T TOUCH IT -// -// Some resources you might find helpful: -// - The official numpy implementations: -// - https://github.com/numpy/numpy/blob/735a477f0bc2b5b84d0e72d92f224bde78d4e069/doc/source/reference/c-api/types-and-structures.rst -// - On strides (about reshaping, slicing, C-contagiousness, etc) -// - https://ajcr.net/stride-guide-part-1/. -// - https://ajcr.net/stride-guide-part-2/. -// - https://ajcr.net/stride-guide-part-3/. -template -struct NDArray { - // The underlying data this `ndarray` is pointing to. + // The NDArray object. `SizeT` is the *signed* size type of this ndarray. // - // NOTE: Formally this should be of type `void *`, but clang - // translates `void *` to `i8 *` when run with `-S -emit-llvm`, - // so we will put `uint8_t *` here for clarity. + // NOTE: The order of fields is IMPORTANT. DON'T TOUCH IT // - // This pointer should point to the first element of the ndarray directly - uint8_t *data; + // Some resources you might find helpful: + // - The official numpy implementations: + // - https://github.com/numpy/numpy/blob/735a477f0bc2b5b84d0e72d92f224bde78d4e069/doc/source/reference/c-api/types-and-structures.rst + // - On strides (about reshaping, slicing, C-contagiousness, etc) + // - https://ajcr.net/stride-guide-part-1/. + // - https://ajcr.net/stride-guide-part-2/. + // - https://ajcr.net/stride-guide-part-3/. + template + struct NDArray { + // The underlying data this `ndarray` is pointing to. + // + // NOTE: Formally this should be of type `void *`, but clang + // translates `void *` to `i8 *` when run with `-S -emit-llvm`, + // so we will put `uint8_t *` here for clarity. + // + // This pointer should point to the first element of the ndarray directly + uint8_t *data; - // The number of bytes of a single element in `data`. - // - // The `SizeT` is treated as `unsigned`. - SizeT itemsize; + // The number of bytes of a single element in `data`. + // + // The `SizeT` is treated as `unsigned`. + SizeT itemsize; - // The number of dimensions of this shape. - // - // The `SizeT` is treated as `unsigned`. - SizeT ndims; + // The number of dimensions of this shape. + // + // The `SizeT` is treated as `unsigned`. + SizeT ndims; - // Array shape, with length equal to `ndims`. - // - // The `SizeT` is treated as `unsigned`. - // - // NOTE: `shape` can contain 0. - // (those appear when the user makes an out of bounds slice into an ndarray, e.g., `np.zeros((3, 3))[400:].shape == (0, 3)`) - SizeT *shape; + // Array shape, with length equal to `ndims`. + // + // The `SizeT` is treated as `unsigned`. + // + // NOTE: `shape` can contain 0. + // (those appear when the user makes an out of bounds slice into an ndarray, e.g., `np.zeros((3, 3))[400:].shape == (0, 3)`) + SizeT *shape; - // Array strides (stride value is in number of bytes, NOT number of elements), with length equal to `ndims`. - // - // The `SizeT` is treated as `signed`. - // - // NOTE: `strides` can have negative numbers. - // (those appear when there is a slice with a negative step, e.g., `my_array[::-1]`) - SizeT *strides; -}; + // Array strides (stride value is in number of bytes, NOT number of elements), with length equal to `ndims`. + // + // The `SizeT` is treated as `signed`. + // + // NOTE: `strides` can have negative numbers. + // (those appear when there is a slice with a negative step, e.g., `my_array[::-1]`) + SizeT *strides; + }; + + // Because ndarray is so complicated, its functions are splitted into + // different files and namespaces. } \ No newline at end of file diff --git a/nac3core/irrt/irrt/numpy/ndarray_fill.hpp b/nac3core/irrt/irrt/numpy/ndarray_fill.hpp index 97130b49..1414b5ac 100644 --- a/nac3core/irrt/irrt/numpy/ndarray_fill.hpp +++ b/nac3core/irrt/irrt/numpy/ndarray_fill.hpp @@ -3,26 +3,26 @@ #include #include -namespace { -namespace ndarray { +namespace { namespace ndarray { namespace fill { // Fill the ndarray with a value template void fill_generic(NDArray* ndarray, const uint8_t* pvalue) { - const SizeT size = ndarray::size(ndarray); + const SizeT size = ndarray::basic::size(ndarray); for (SizeT i = 0; i < size; i++) { - uint8_t* pelement = ndarray::get_nth_pelement(ndarray, i); // No need for checked_get_nth_pelement - ndarray::set_pelement_value(ndarray, pelement, pvalue); + uint8_t* pelement = ndarray::basic::get_nth_pelement(ndarray, i); // No need for checked_get_nth_pelement + ndarray::basic::set_pelement_value(ndarray, pelement, pvalue); } } -} -} +} } } extern "C" { -void __nac3_ndarray_fill_generic(NDArray* ndarray, uint8_t* pvalue) { - ndarray::fill_generic(ndarray, pvalue); -} + using namespace ndarray::fill; -void __nac3_ndarray_fill_generic64(NDArray* ndarray, uint8_t* pvalue) { - ndarray::fill_generic(ndarray, pvalue); -} + void __nac3_ndarray_fill_generic(NDArray* ndarray, uint8_t* pvalue) { + fill_generic(ndarray, pvalue); + } + + void __nac3_ndarray_fill_generic64(NDArray* ndarray, uint8_t* pvalue) { + fill_generic(ndarray, pvalue); + } } \ No newline at end of file diff --git a/nac3core/irrt/irrt/numpy/ndarray_subscript.hpp b/nac3core/irrt/irrt/numpy/ndarray_subscript.hpp index 6e9fce9e..8d796ea1 100644 --- a/nac3core/irrt/irrt/numpy/ndarray_subscript.hpp +++ b/nac3core/irrt/irrt/numpy/ndarray_subscript.hpp @@ -3,31 +3,31 @@ #include #include #include +#include namespace { -typedef uint8_t NDSubscriptType; + typedef uint8_t NDSubscriptType; -extern "C" { -const NDSubscriptType INPUT_SUBSCRIPT_TYPE_INDEX = 0; -const NDSubscriptType INPUT_SUBSCRIPT_TYPE_SLICE = 1; + const NDSubscriptType INPUT_SUBSCRIPT_TYPE_INDEX = 0; + const NDSubscriptType INPUT_SUBSCRIPT_TYPE_SLICE = 1; + + struct NDSubscript { + // A poor-man's enum variant type + NDSubscriptType type; + + /* + if type == INPUT_SUBSCRIPT_TYPE_INDEX => `slice` points to a single `SliceIndex` + if type == INPUT_SUBSCRIPT_TYPE_SLICE => `slice` points to a single `UserRange` + + `SizeT` is controlled by the caller: `NDSubscript` only cares about where that + slice is (the pointer), `NDSubscript` does not care/know about the actual `sizeof()` + of the slice value. + */ + uint8_t* data; + }; } -struct NDSubscript { - // A poor-man's enum variant type - NDSubscriptType type; - - /* - if type == INPUT_SUBSCRIPT_TYPE_INDEX => `slice` points to a single `SizeT` - if type == INPUT_SUBSCRIPT_TYPE_SLICE => `slice` points to a single `UserRange` - - `SizeT` is controlled by the caller: `NDSubscript` only cares about where that - slice is (the pointer), `NDSubscript` does not care/know about the actual `sizeof()` - of the slice value. - */ - uint8_t* data; -}; - -namespace ndarray { +namespace { namespace ndarray { namespace subscript { namespace util { template SizeT deduce_ndims_after_slicing(SizeT ndims, SizeT num_subscripts, const NDSubscript* subscripts) { @@ -61,7 +61,7 @@ namespace ndarray { // - `dst_ndarray->itemsize` does not have to be set, it will be set to `src_ndarray->itemsize` // - `dst_ndarray->shape` and `dst_ndarray.strides` can contain empty values template - void subscript(SizeT num_subscripts, NDSubscript* subscripts, NDArray* src_ndarray, NDArray* dst_ndarray) { + void subscript(ErrorContext* errctx, SizeT num_subscripts, NDSubscript* subscripts, NDArray* src_ndarray, NDArray* dst_ndarray) { // REFERENCE CODE (check out `_index_helper` in `__getitem__`): // https://github.com/wadetb/tinynumpy/blob/0d23d22e07062ffab2afa287374c7b366eebdda1/tinynumpy/tinynumpy.py#L652 @@ -79,8 +79,19 @@ namespace ndarray { // Handle when the ndsubscript is just a single (possibly negative) integer // e.g., `my_array[::2, -5, ::-1]` // ^^------ like this - SizeT index_user = *((SizeT*) ndsubscript->data); - SizeT index = slice::resolve_index_in_length(src_ndarray->shape[src_axis], index_user); + SliceIndex input_index = *((SliceIndex*) ndsubscript->data); + + SliceIndex index = slice::resolve_index_in_length(src_ndarray->shape[src_axis], input_index); + if (index == slice::OUT_OF_BOUNDS) { + // Error message copied from numpy by doing `np.zeros((3, 4))[100]` + errctx->set_error( + errctx->error_ids->index_error, + "index {0} is out of bounds for axis {1} with size {2}", + input_index, src_axis, src_ndarray->shape[src_axis] + ); + return; // Terminate + } + dst_ndarray->data += index * src_ndarray->strides[src_axis]; // Add offset // Next @@ -89,11 +100,14 @@ namespace ndarray { // Handle when the ndsubscript is a slice (represented by UserSlice in IRRT) // e.g., `my_array[::2, -5, ::-1]` // ^^^------^^^^----- like these - UserSlice* user_slice = (UserSlice*) ndsubscript->data; + UserSlice* input_user_slice = (UserSlice*) ndsubscript->data; // TODO: use checked indices Slice slice; - user_slice->indices(src_ndarray->shape[src_axis], &slice); // To resolve negative indices and other funny stuff written by the user + input_user_slice->indices_checked(errctx, src_ndarray->shape[src_axis], &slice); // To resolve negative indices and other funny stuff written by the user + if (errctx->has_error()) { + return; // Propagate error + } // NOTE: There is no need to write special code to handle negative steps/strides. // This simple implementation meticulously handles both positive and negative steps/strides. @@ -123,15 +137,16 @@ namespace ndarray { dst_ndarray->strides[dst_axis] = src_ndarray->strides[src_axis]; } } -} -} +} } } extern "C" { -void __nac3_ndarray_subscript(int32_t num_subscripts, NDSubscript* subscripts, NDArray* src_ndarray, NDArray *dst_ndarray) { - ndarray::subscript(num_subscripts, subscripts, src_ndarray, dst_ndarray); -} + using namespace ndarray::subscript; -void __nac3_ndarray_subscript64(int64_t num_subscripts, NDSubscript* subscripts, NDArray* src_ndarray, NDArray *dst_ndarray) { - ndarray::subscript(num_subscripts, subscripts, src_ndarray, dst_ndarray); -} + void __nac3_ndarray_subscript(ErrorContext* errctx, int32_t num_subscripts, NDSubscript* subscripts, NDArray* src_ndarray, NDArray *dst_ndarray) { + subscript(errctx, num_subscripts, subscripts, src_ndarray, dst_ndarray); + } + + void __nac3_ndarray_subscript64(ErrorContext* errctx, int64_t num_subscripts, NDSubscript* subscripts, NDArray* src_ndarray, NDArray *dst_ndarray) { + subscript(errctx, num_subscripts, subscripts, src_ndarray, dst_ndarray); + } } \ No newline at end of file diff --git a/nac3core/irrt/irrt/slice.hpp b/nac3core/irrt/irrt/slice.hpp index 826c9f7e..d7f23c99 100644 --- a/nac3core/irrt/irrt/slice.hpp +++ b/nac3core/irrt/irrt/slice.hpp @@ -4,129 +4,140 @@ #include namespace { -struct Slice { - SliceIndex start; - SliceIndex stop; - SliceIndex step; + struct Slice { + SliceIndex start; + SliceIndex stop; + SliceIndex step; - // The length/The number of elements of the slice if it were a range, - // i.e., the value of `len(range(this->start, this->stop, this->end))` - SliceIndex len() { - SliceIndex diff = stop - start; - if (diff > 0 && step > 0) { - return ((diff - 1) / step) + 1; - } else if (diff < 0 && step < 0) { - return ((diff + 1) / step) + 1; - } else { - return 0; + // The length/The number of elements of the slice if it were a range, + // i.e., the value of `len(range(this->start, this->stop, this->end))` + SliceIndex len() { + SliceIndex diff = stop - start; + if (diff > 0 && step > 0) { + return ((diff - 1) / step) + 1; + } else if (diff < 0 && step < 0) { + return ((diff + 1) / step) + 1; + } else { + return 0; + } + } + }; + + namespace slice { + // "Resolve" an index value under a length in Python lists. + // If you have a `list` of length 100, `list[-1]` would resolve to `list[100-1] == list[99]`. + // + // If length == 0, this function returns 0 + // + // If index is out of bounds, this function clamps the value + // (to `list[0]` or `list[-1]` in the context of a list and depending on if index is + or -) + SliceIndex resolve_index_in_length_clamped(SliceIndex length, SliceIndex index) { + if (index < 0) { + // Remember that index is negative, so do a plus here + return max(length + index, 0); + } else { + return min(length, index); + } + } + + const SliceIndex OUT_OF_BOUNDS = -1; + + // Like `resolve_index_in_length`. + // But also checks if the resolved index is in + // bounds (function returns true) or out of bounds + // (function returns false); `0 <= resolved index < length` is false). + SliceIndex resolve_index_in_length(SliceIndex length, SliceIndex index) { + SliceIndex resolved = index < 0 ? length + index : index; + + bool in_bounds = 0 <= resolved && resolved < length; + return in_bounds ? resolved : OUT_OF_BOUNDS; } } -}; -namespace slice { - // "Resolve" an index value under a length in Python lists. - // If you have a `list` of length 100, `list[-1]` would resolve to `list[100-1] == list[99]`. + // A user-written Python-like slice. // - // If length == 0, this function returns 0 + // i.e., this slice is a triple of either an int or nothing. (e.g., `my_array[:10:2]`, `start` is None) // - // If index is out of bounds, this function clamps the value - // (to `list[0]` or `list[-1]` in the context of a list and depending on if index is + or -) - SliceIndex resolve_index_in_length(SliceIndex length, SliceIndex index) { - if (index < 0) { - // Remember that index is negative, so do a plus here - return max(length + index, 0); - } else { - return min(length, index); - } - } -} + // You can "resolve" a `UserSlice` by using `user_slice.indices()` + struct UserSlice { + // Did the user specify `start`? If 0, `start` is undefined (and contains an empty value) + bool start_defined; + SliceIndex start; -// A user-written Python-like slice. -// -// i.e., this slice is a triple of either an int or nothing. (e.g., `my_array[:10:2]`, `start` is None) -// -// You can "resolve" a `UserSlice` by using `UserSlice::indices()` -struct UserSlice { - // Did the user specify `start`? If 0, `start` is undefined (and contains an empty value) - bool start_defined; - SliceIndex start; + // Similar to `start_defined` + bool stop_defined; + SliceIndex stop; - // Similar to `start_defined` - bool stop_defined; - SliceIndex stop; + // Similar to `start_defined` + bool step_defined; + SliceIndex step; - // Similar to `start_defined` - bool step_defined; - SliceIndex step; - - // Constructor faithfully follows Python's `slice()`. - explicit UserSlice(SliceIndex stop) { - start_defined = false; - stop_defined = true; - step_defined = false; - - this->stop = stop; - } - - explicit UserSlice(SliceIndex start, SliceIndex stop) { - start_defined = true; - stop_defined = true; - step_defined = false; - - this->start = start; - this->stop = stop; - } - - explicit UserSlice(SliceIndex start, SliceIndex stop, SliceIndex step) { - start_defined = true; - stop_defined = true; - step_defined = true; - - this->start = start; - this->stop = stop; - this->step = step; - } - - // Like Python's `slice(start, stop, step).indices(length)` - void indices(SliceIndex length, Slice* result) { - // NOTE: This function implements Python's `slice.indices` *FAITHFULLY*. - // SEE: https://github.com/python/cpython/blob/f62161837e68c1c77961435f1b954412dd5c2b65/Objects/sliceobject.c#L546 - result->step = step_defined ? step : 1; - bool step_is_negative = result->step < 0; - - if (start_defined) { - result->start = slice::resolve_index_in_length(length, start); - } else { - result->start = step_is_negative ? length - 1 : 0; - } - - if (stop_defined) { - result->stop = slice::resolve_index_in_length(length, stop); - } else { - result->stop = step_is_negative ? -1 : length; - } - } - - // `indices()` but asserts `this->step != 0` and `this->length >= 0` - void checked_indices(ErrorContext* errctx, SliceIndex length, Slice* result) { - if (!(length >= 0)) { - errctx->set_error( - errctx->error_ids->value_error, - "length should not be negative, got {0}", // Edited. Error message copied from python by doing `slice(0, 0, 0).indices(100)` - length - ); - return; + // Convenient constructor for C++ internal use only (say testing) + UserSlice() { + this->reset(); } - if (!(this->step_defined && this->step != 0)) { - // Error message - errctx->set_error( - errctx->error_ids->value_error, - "slice step cannot be zero" // Error message copied from python by doing `slice(0, 0, 0).indices(100)` - ); - return; + void reset() { + this->start_defined = false; + this->stop_defined = false; + this->step_defined = false; } - this->indices(length, result); - } -}; + + void set_start(SliceIndex start) { + this->start_defined = true; + this->start = start; + } + + void set_stop(SliceIndex stop) { + this->stop_defined = true; + this->stop = stop; + } + + void set_step(SliceIndex step) { + this->step_defined = true; + this->step = step; + } + + // Like Python's `slice(start, stop, step).indices(length)` + void indices(SliceIndex length, Slice* result) { + // NOTE: This function implements Python's `slice.indices` *FAITHFULLY*. + // SEE: https://github.com/python/cpython/blob/f62161837e68c1c77961435f1b954412dd5c2b65/Objects/sliceobject.c#L546 + result->step = step_defined ? step : 1; + bool step_is_negative = result->step < 0; + + if (start_defined) { + result->start = slice::resolve_index_in_length_clamped(length, start); + } else { + result->start = step_is_negative ? length - 1 : 0; + } + + if (stop_defined) { + result->stop = slice::resolve_index_in_length_clamped(length, stop); + } else { + result->stop = step_is_negative ? -1 : length; + } + } + + // `indices()` but asserts `this->step != 0` and `this->length >= 0` + void indices_checked(ErrorContext* errctx, SliceIndex length, Slice* result) { + if (length < 0) { + errctx->set_error( + errctx->error_ids->value_error, + "length should not be negative, got {0}", // Edited. Error message copied from python by doing `slice(0, 0, 0).indices(100)` + length + ); + return; + } + + if (this->step_defined && this->step == 0) { + // Error message + errctx->set_error( + errctx->error_ids->value_error, + "slice step cannot be zero" // Error message copied from python by doing `slice(0, 0, 0).indices(100)` + ); + return; + } + this->indices(length, result); + } + }; } \ No newline at end of file diff --git a/nac3core/irrt/irrt/utils.hpp b/nac3core/irrt/irrt/utils.hpp index fdf14fba..b871e3f3 100644 --- a/nac3core/irrt/irrt/utils.hpp +++ b/nac3core/irrt/irrt/utils.hpp @@ -3,76 +3,76 @@ #include namespace { -template -const T& max(const T& a, const T& b) { - return a > b ? a : b; -} - -template -const T& min(const T& a, const T& b) { - return a > b ? b : a; -} - -template -bool arrays_match(int len, T* as, T* bs) { - for (int i = 0; i < len; i++) { - if (as[i] != bs[i]) return false; - } - return true; -} - -namespace cstr_utils { - bool is_empty(const char* str) { - return str[0] == '\0'; + template + const T& max(const T& a, const T& b) { + return a > b ? a : b; } - int8_t compare(const char* a, const char* b) { - uint32_t i = 0; - while (true) { - if (a[i] < b[i]) { - return -1; - } else if (a[i] > b[i]) { - return 1; - } else { // a[i] == b[i] - if (a[i] == '\0') { - return 0; - } else { - i++; + template + const T& min(const T& a, const T& b) { + return a > b ? b : a; + } + + template + bool arrays_match(int len, T* as, T* bs) { + for (int i = 0; i < len; i++) { + if (as[i] != bs[i]) return false; + } + return true; + } + + namespace cstr_utils { + bool is_empty(const char* str) { + return str[0] == '\0'; + } + + int8_t compare(const char* a, const char* b) { + uint32_t i = 0; + while (true) { + if (a[i] < b[i]) { + return -1; + } else if (a[i] > b[i]) { + return 1; + } else { // a[i] == b[i] + if (a[i] == '\0') { + return 0; + } else { + i++; + } } } } - } - int8_t equal(const char* a, const char* b) { - return compare(a, b) == 0; - } - - uint32_t length(const char* str) { - uint32_t length = 0; - while (*str != '\0') { - length++; - str++; - } - return length; - } - - bool copy(const char* src, char* dst, uint32_t dst_max_size) { - for (uint32_t i = 0; i < dst_max_size; i++) { - bool is_last = i + 1 == dst_max_size; - if (is_last && src[i] != '\0') { - dst[i] = '\0'; - return false; - } - - if (src[i] == '\0') { - dst[i] = '\0'; - return true; - } - - dst[i] = src[i]; + int8_t equal(const char* a, const char* b) { + return compare(a, b) == 0; } - __builtin_unreachable(); + uint32_t length(const char* str) { + uint32_t length = 0; + while (*str != '\0') { + length++; + str++; + } + return length; + } + + bool copy(const char* src, char* dst, uint32_t dst_max_size) { + for (uint32_t i = 0; i < dst_max_size; i++) { + bool is_last = i + 1 == dst_max_size; + if (is_last && src[i] != '\0') { + dst[i] = '\0'; + return false; + } + + if (src[i] == '\0') { + dst[i] = '\0'; + return true; + } + + dst[i] = src[i]; + } + + __builtin_unreachable(); + } } -} } \ No newline at end of file diff --git a/nac3core/irrt/irrt_test.cpp b/nac3core/irrt/irrt_test.cpp index 30ee1c65..407e5d4c 100644 --- a/nac3core/irrt/irrt_test.cpp +++ b/nac3core/irrt/irrt_test.cpp @@ -9,12 +9,15 @@ #include #include -#include +#include +#include #include int main() { - test_int_exp(); - run_all_tests_ndarray(); - run_all_tests_ndarray_slice(); + // Be wise about the order of suites!! + test::core::run(); + test::slice::run(); + test::ndarray_basic::run(); + test::ndarray_subscript::run(); return 0; } \ No newline at end of file diff --git a/nac3core/irrt/test/core.hpp b/nac3core/irrt/test/core.hpp index 5230bbf6..1fcb9d55 100644 --- a/nac3core/irrt/test/core.hpp +++ b/nac3core/irrt/test/core.hpp @@ -85,4 +85,69 @@ void __assert_values_match(const char* file, int line, T expected, T got) { } } -#define assert_values_match(expected, got) __assert_values_match(__FILE__, __LINE__, expected, got) \ No newline at end of file +#define assert_values_match(expected, got) __assert_values_match(__FILE__, __LINE__, expected, got) + +// A fake set of ErrorIds for testing only +const ErrorIds TEST_ERROR_IDS = { + .index_error = 0, + .value_error = 1, + .assertion_error = 2, + .runtime_error = 3, +}; + +ErrorContext create_testing_errctx() { + // Everything is global so it is fine to directly return a struct ErrorContext + ErrorContext errctx; + errctx.initialize(&TEST_ERROR_IDS); + return errctx; +} + +void debug_print_errctx_content(ErrorContext* errctx) { + if (errctx->has_error()) { + printf( + "(Error ID %d): %s ... where param1 = %ld, param2 = %ld, param3 = %ld\n", + errctx->error_id, + errctx->message_template, + errctx->param1, + errctx->param2, + errctx->param3 + ); + } else { + printf("\n"); + } +} + +void __assert_errctx_no_error(const char* file, int line, ErrorContext* errctx) { + if (errctx->has_error()) { + print_assertion_failed(file, line); + printf("Expecting no error but caught the following:\n\n"); + debug_print_errctx_content(errctx); + test_fail(); + } +} + +#define assert_errctx_no_error(errctx) __assert_errctx_no_error(__FILE__, __LINE__, errctx) + +void __assert_errctx_has_error(const char* file, int line, ErrorContext *errctx, ErrorId expected_error_id) { + if (errctx->has_error()) { + if (errctx->error_id == expected_error_id) { + // OK + } else { + // Otherwise it got the wrong kind of error + print_assertion_failed(file, line); + printf( + "Expecting error id %d but got error id %d. Error caught:\n\n", + expected_error_id, + errctx->error_id + ); + debug_print_errctx_content(errctx); + test_fail(); + } + } else { + print_assertion_failed(file, line); + printf("Expecting an error, but there is none."); + test_fail(); + } +} + +#define assert_errctx_has_error(errctx, expected_error_id) __assert_errctx_has_error(__FILE__, __LINE__, errctx, expected_error_id) \ No newline at end of file diff --git a/nac3core/irrt/test/print.hpp b/nac3core/irrt/test/print.hpp index bd0ec746..cccb2c2b 100644 --- a/nac3core/irrt/test/print.hpp +++ b/nac3core/irrt/test/print.hpp @@ -6,6 +6,11 @@ template void print_value(const T& value) {} +template <> +void print_value(const bool& value) { + printf("%s", value ? "true" : "false"); +} + template <> void print_value(const int8_t& value) { printf("%d", value); diff --git a/nac3core/irrt/test/test_core.hpp b/nac3core/irrt/test/test_core.hpp index 661ca2b7..57a35093 100644 --- a/nac3core/irrt/test/test_core.hpp +++ b/nac3core/irrt/test/test_core.hpp @@ -1,11 +1,19 @@ #pragma once #include -#include +#include +namespace test { +namespace core { void test_int_exp() { BEGIN_TEST(); assert_values_match(125, __nac3_int_exp_impl(5, 3)); assert_values_match(3125, __nac3_int_exp_impl(5, 5)); } + +void run() { + test_int_exp(); +} +} +} \ No newline at end of file diff --git a/nac3core/irrt/test/test_ndarray.hpp b/nac3core/irrt/test/test_ndarray_basic.hpp similarity index 71% rename from nac3core/irrt/test/test_ndarray.hpp rename to nac3core/irrt/test/test_ndarray_basic.hpp index d1073fe3..2a03f882 100644 --- a/nac3core/irrt/test/test_ndarray.hpp +++ b/nac3core/irrt/test/test_ndarray_basic.hpp @@ -3,12 +3,14 @@ #include #include +namespace test { +namespace ndarray_basic { void test_calc_size_from_shape_normal() { // Test shapes with normal values BEGIN_TEST(); int32_t shape[4] = { 2, 3, 5, 7 }; - assert_values_match(210, ndarray::util::calc_size_from_shape(4, shape)); + assert_values_match(210, ndarray::basic::util::calc_size_from_shape(4, shape)); } void test_calc_size_from_shape_has_zero() { @@ -16,7 +18,7 @@ void test_calc_size_from_shape_has_zero() { BEGIN_TEST(); int32_t shape[4] = { 2, 0, 5, 7 }; - assert_values_match(0, ndarray::util::calc_size_from_shape(4, shape)); + assert_values_match(0, ndarray::basic::util::calc_size_from_shape(4, shape)); } void test_set_strides_by_shape() { @@ -25,7 +27,7 @@ void test_set_strides_by_shape() { int32_t shape[4] = { 99, 3, 5, 7 }; int32_t strides[4] = { 0 }; - ndarray::util::set_strides_by_shape((int32_t) sizeof(int32_t), 4, strides, shape); + ndarray::basic::util::set_strides_by_shape((int32_t) sizeof(int32_t), 4, strides, shape); int32_t expected_strides[4] = { 105 * sizeof(int32_t), @@ -36,8 +38,10 @@ void test_set_strides_by_shape() { assert_arrays_match(4, expected_strides, strides); } -void run_all_tests_ndarray() { +void run() { test_calc_size_from_shape_normal(); test_calc_size_from_shape_has_zero(); test_set_strides_by_shape(); +} +} } \ No newline at end of file diff --git a/nac3core/irrt/test/test_ndarray_subscript.hpp b/nac3core/irrt/test/test_ndarray_subscript.hpp new file mode 100644 index 00000000..7bd68755 --- /dev/null +++ b/nac3core/irrt/test/test_ndarray_subscript.hpp @@ -0,0 +1,182 @@ +#pragma once + +#include +#include + +namespace test { namespace ndarray_subscript { + void test_ndsubscript_normal_1() { + /* + Reference Python code: + ```python + ndarray = np.arange(12, dtype=np.float64).reshape((3, 4)); + # array([[ 0., 1., 2., 3.], + # [ 4., 5., 6., 7.], + # [ 8., 9., 10., 11.]]) + + dst_ndarray = ndarray[-2:, 1::2] + # array([[ 5., 7.], + # [ 9., 11.]]) + + assert dst_ndarray.shape == (2, 2) + assert dst_ndarray.strides == (32, 16) + assert dst_ndarray[0, 0] == 5.0 + assert dst_ndarray[0, 1] == 7.0 + assert dst_ndarray[1, 0] == 9.0 + assert dst_ndarray[1, 1] == 11.0 + ``` + */ + BEGIN_TEST(); + + double src_data[12] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0 }; + int32_t src_itemsize = sizeof(double); + const int32_t src_ndims = 2; + int32_t src_shape[src_ndims] = { 3, 4 }; + int32_t src_strides[src_ndims] = {}; + NDArray src_ndarray = { + .data = (uint8_t*) src_data, + .itemsize = src_itemsize, + .ndims = src_ndims, + .shape = src_shape, + .strides = src_strides + }; + ndarray::basic::set_strides_by_shape(&src_ndarray); + + // Destination ndarray + // As documented, ndims and shape & strides must be allocated and determined by the caller. + const int32_t dst_ndims = 2; + int32_t dst_shape[dst_ndims] = {999, 999}; // Empty values + int32_t dst_strides[dst_ndims] = {999, 999}; // Empty values + NDArray dst_ndarray = { + .data = nullptr, + .ndims = dst_ndims, + .shape = dst_shape, + .strides = dst_strides + }; + + // Create the slice in `ndarray[-2::, 1::2]` + UserSlice subscript_1; + subscript_1.set_start(-2); + + UserSlice subscript_2; + subscript_2.set_start(1); + subscript_2.set_step(2); + + const int32_t num_ndsubscripts = 2; + NDSubscript ndsubscripts[num_ndsubscripts] = { + { .type = INPUT_SUBSCRIPT_TYPE_SLICE, .data = (uint8_t*) &subscript_1 }, + { .type = INPUT_SUBSCRIPT_TYPE_SLICE, .data = (uint8_t*) &subscript_2 } + }; + + ErrorContext errctx = create_testing_errctx(); + ndarray::subscript::subscript(&errctx, num_ndsubscripts, ndsubscripts, &src_ndarray, &dst_ndarray); + assert_errctx_no_error(&errctx); + + int32_t expected_shape[dst_ndims] = { 2, 2 }; + int32_t expected_strides[dst_ndims] = { 32, 16 }; + + assert_arrays_match(dst_ndims, expected_shape, dst_ndarray.shape); + assert_arrays_match(dst_ndims, expected_strides, dst_ndarray.strides); + + // dst_ndarray[0, 0] + assert_values_match( + 5.0, + *((double *) ndarray::basic::get_pelement_by_indices(&dst_ndarray, (int32_t[dst_ndims]) { 0, 0 })) + ); + // dst_ndarray[0, 1] + assert_values_match( + 7.0, + *((double *) ndarray::basic::get_pelement_by_indices(&dst_ndarray, (int32_t[dst_ndims]) { 0, 1 })) + ); + // dst_ndarray[1, 0] + assert_values_match( + 9.0, + *((double *) ndarray::basic::get_pelement_by_indices(&dst_ndarray, (int32_t[dst_ndims]) { 1, 0 })) + ); + // dst_ndarray[1, 1] + assert_values_match( + 11.0, + *((double *) ndarray::basic::get_pelement_by_indices(&dst_ndarray, (int32_t[dst_ndims]) { 1, 1 })) + ); + } + + void test_ndsubscript_normal_2() { + /* + ```python + ndarray = np.arange(12, dtype=np.float64).reshape((3, 4)) + # array([[ 0., 1., 2., 3.], + # [ 4., 5., 6., 7.], + # [ 8., 9., 10., 11.]]) + + dst_ndarray = ndarray[2, ::-2] + # array([11., 9.]) + + assert dst_ndarray.shape == (2,) + assert dst_ndarray.strides == (-16,) + assert dst_ndarray[0] == 11.0 + assert dst_ndarray[1] == 9.0 + ``` + */ + BEGIN_TEST(); + + double src_data[12] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0 }; + int32_t src_itemsize = sizeof(double); + const int32_t src_ndims = 2; + int32_t src_shape[src_ndims] = { 3, 4 }; + int32_t src_strides[src_ndims] = {}; + NDArray src_ndarray = { + .data = (uint8_t*) src_data, + .itemsize = src_itemsize, + .ndims = src_ndims, + .shape = src_shape, + .strides = src_strides + }; + ndarray::basic::set_strides_by_shape(&src_ndarray); + + // Destination ndarray + // As documented, ndims and shape & strides must be allocated and determined by the caller. + const int32_t dst_ndims = 1; + int32_t dst_shape[dst_ndims] = {999}; // Empty values + int32_t dst_strides[dst_ndims] = {999}; // Empty values + NDArray dst_ndarray = { + .data = nullptr, + .ndims = dst_ndims, + .shape = dst_shape, + .strides = dst_strides + }; + + // Create the slice in `ndarray[2, ::-2]` + int32_t subscript_1 = 2; + + UserSlice subscript_2; + subscript_2.set_step(-2); + + const int32_t num_ndsubscripts = 2; + NDSubscript ndsubscripts[num_ndsubscripts] = { + { .type = INPUT_SUBSCRIPT_TYPE_INDEX, .data = (uint8_t*) &subscript_1 }, + { .type = INPUT_SUBSCRIPT_TYPE_SLICE, .data = (uint8_t*) &subscript_2 } + }; + + ErrorContext errctx = create_testing_errctx(); + ndarray::subscript::subscript(&errctx, num_ndsubscripts, ndsubscripts, &src_ndarray, &dst_ndarray); + assert_errctx_no_error(&errctx); + + int32_t expected_shape[dst_ndims] = { 2 }; + int32_t expected_strides[dst_ndims] = { -16 }; + assert_arrays_match(dst_ndims, expected_shape, dst_ndarray.shape); + assert_arrays_match(dst_ndims, expected_strides, dst_ndarray.strides); + + assert_values_match( + 11.0, + *((double *) ndarray::basic::get_pelement_by_indices(&dst_ndarray, (int32_t[dst_ndims]) { 0 })) + ); + assert_values_match( + 9.0, + *((double *) ndarray::basic::get_pelement_by_indices(&dst_ndarray, (int32_t[dst_ndims]) { 1 })) + ); + } + + void run() { + test_ndsubscript_normal_1(); + test_ndsubscript_normal_2(); + } +} } \ No newline at end of file diff --git a/nac3core/irrt/test/test_slice.hpp b/nac3core/irrt/test/test_slice.hpp index 4ba72019..28669497 100644 --- a/nac3core/irrt/test/test_slice.hpp +++ b/nac3core/irrt/test/test_slice.hpp @@ -3,18 +3,94 @@ #include #include -void test_slice_1() { +namespace test { +namespace slice { +void test_slice_normal() { + // Normal situation BEGIN_TEST(); - UserSlice user_slice(5); + UserSlice user_slice; + user_slice.set_stop(5); + Slice slice; user_slice.indices(100, &slice); + printf("%d, %d, %d\n", slice.start, slice.stop, slice.step); + assert_values_match(0, slice.start); assert_values_match(5, slice.stop); assert_values_match(1, slice.step); } -void run_all_tests_ndarray_slice() { - test_slice_1(); +void test_slice_start_too_large() { + // Start is too large and should be clamped to length + BEGIN_TEST(); + + UserSlice user_slice; + user_slice.set_start(400); + + Slice slice; + user_slice.indices(100, &slice); + + assert_values_match(100, slice.start); + assert_values_match(100, slice.stop); + assert_values_match(1, slice.step); +} + +void test_slice_negative_start_stop() { + // Negative start/stop should be resolved + BEGIN_TEST(); + + UserSlice user_slice; + user_slice.set_start(-10); + user_slice.set_stop(-5); + + Slice slice; + user_slice.indices(100, &slice); + + assert_values_match(90, slice.start); + assert_values_match(95, slice.stop); + assert_values_match(1, slice.step); +} + +void test_slice_only_negative_step() { + // Things like `[::-5]` should be handled correctly + BEGIN_TEST(); + + UserSlice user_slice; + user_slice.set_step(-5); + + Slice slice; + user_slice.indices(100, &slice); + + assert_values_match(99, slice.start); + assert_values_match(-1, slice.stop); + assert_values_match(-5, slice.step); +} + +void test_slice_step_zero() { + // Step = 0 is a value error + BEGIN_TEST(); + + ErrorContext errctx = create_testing_errctx(); + + UserSlice user_slice; + user_slice.set_start(2); + user_slice.set_stop(12); + user_slice.set_step(0); + + Slice slice; + user_slice.indices_checked(&errctx, 100, &slice); + + assert_errctx_has_error(&errctx, errctx.error_ids->value_error); +} + +void run() { + test_slice_normal(); + test_slice_start_too_large(); + test_slice_negative_start_stop(); + test_slice_only_negative_step(); + test_slice_step_zero(); +} +} } \ No newline at end of file