/////////////////////////////////////////////////////////////////////////////////////////////// // handling for making C++ types act as python lists/tuples, to allow a limited amount of // by-reference access from python // // We do this mostly by adding named methods and slots that modify or access the list in place. %header %{ // hacky macro to check if two variables are the same by looking at their length & second letter. // Used to identify if we're dealing with 'self' in a typemap or not #define STR_EQ(a, b) (sizeof(#a) == sizeof(#b) && (#a[1] == #b[1])) %} // a typemap that allows us to modify 'self' in place, but convert any inputs by value. %define LIST_MODIFY_IN_PLACE_TYPEMAP(ContainerType) %typemap(in) const ContainerType & (unsigned char tempmem[32], bool wasSelf = false) { using array_type = std::remove_pointer::type; { // convert the sequence by value using ConvertFromPy static_assert(sizeof(tempmem) >= sizeof(array_type), "not enough temp space for $1_basetype"); tempalloc($1, tempmem); int failIdx = 0; int res = TypeConversion::ConvertFromPy($input, indirect($1), &failIdx); if(!SWIG_IsOK(res)) { if(res == SWIG_TypeError) { SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'"); } else { snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx); SWIG_exception_fail(SWIG_ArgError(res), convert_error); } } } } %typemap(in) ContainerType * (unsigned char tempmem[32], bool wasSelf = false) { using array_type = std::remove_pointer::type; // don't convert 'self', leave as-is so we can modify by reference. Everything else needs to be // converted/copied to allow passing lists (or any sequence) python objects to a function // expecting a particular C++ array type constexpr bool isSelf = STR_EQ($input, self); wasSelf = isSelf; if(isSelf) { // we use an indirect dispatch class here (see self_dispatch) to avoid the need to instantiate // the conversion template for types we don't care about $1 = self_dispatch::getthis(self); } else { // convert the sequence by value using ConvertFromPy static_assert(sizeof(tempmem) >= sizeof(array_type), "not enough temp space for $1_basetype"); tempalloc($1, tempmem); int failIdx = 0; int res = TypeConversion::ConvertFromPy($input, indirect($1), &failIdx); if(!SWIG_IsOK(res)) { if(res == SWIG_TypeError) { SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'"); } else { snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx); SWIG_exception_fail(SWIG_ArgError(res), convert_error); } } } } %typemap(freearg) ContainerType * { // if we converted the sequence by-value, then destroy it again if(!wasSelf$argnum) tempdealloc($1); } %enddef // %define LIST_MODIFY_IN_PLACE_TYPEMAP // this macro defines the list array class named methods, forwarding to templated implementations %define EXTEND_ARRAY_CLASS_METHODS(Container) %extend Container { // functions with optional parameters need to use kwargs otherwise SWIG wraps them in multiple // overloads %feature("kwargs") pop; %feature("kwargs") sort; %feature("kwargs") index; PyObject *append(PyObject *value) { return array_append($self, value); } PyObject *clear() { return array_clear($self); } PyObject *insert(PyObject *index, PyObject *value) { return array_insert($self, index, value); } PyObject *pop(PyObject *index = NULL) { return array_pop($self, index); } PyObject *sort(PyObject *key = NULL, bool reverse = false) { return array_sort($self, key, reverse); } PyObject *copy() { return array_copy($self); } PyObject *reverse() { return array_reverse($self); } PyObject *index(PyObject *item, PyObject *start = NULL, PyObject *end = NULL) { return array_indexOf($self, item, start, end); } PyObject *count(PyObject *item) { return array_countOf($self, item); } PyObject *extend(PyObject *items) { return array_selfconcat($self, items); } PyObject *remove(PyObject *item) { return array_removeOne($self, item); } } // %extend Container %enddef // define EXTEND_ARRAY_CLASS_METHODS(Container) // Declare the slots that we're going to add - see ARRAY_DEFINE_SLOTS for the definitions %define ARRAY_ADD_SLOTS(array_type, unique_name) // define slots %feature("python:tp_repr") array_type STRINGIZE(repr_##unique_name); %feature("python:tp_str") array_type STRINGIZE(repr_##unique_name); %feature("python:sq_item") array_type STRINGIZE(getitem_##unique_name); %feature("python:sq_ass_item") array_type STRINGIZE(setitem_##unique_name); %feature("python:sq_length") array_type STRINGIZE(length_##unique_name); %feature("python:sq_concat") array_type STRINGIZE(concat_##unique_name); %feature("python:sq_repeat") array_type STRINGIZE(repeat_##unique_name); %feature("python:sq_inplace_concat") array_type STRINGIZE(selfconcat_##unique_name); %feature("python:sq_inplace_repeat") array_type STRINGIZE(selfrepeat_##unique_name); %feature("python:mp_subscript") array_type STRINGIZE(getsubscript_##unique_name); %feature("python:mp_ass_subscript") array_type STRINGIZE(setsubscript_##unique_name); // https://docs.python.org/3/library/collections.abc.html // https://docs.python.org/3/library/stdtypes.html#typesseq-common // https://docs.python.org/3/library/stdtypes.html#typesseq-mutable // https://docs.python.org/3/library/stdtypes.html#list %enddef // define C exported wrappers to bind to the slots that forward to templated implementations %define ARRAY_DEFINE_SLOTS(array_type, unique_name) // define C-exported wrappers that forward to templated implementations %wrapper %{ PyObject *repr_##unique_name(PyObject *self) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; return array_repr(thisptr); } PyObject *getitem_##unique_name(PyObject *self, Py_ssize_t idx) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; return array_getitem(thisptr, idx); } int setitem_##unique_name(PyObject *self, Py_ssize_t idx, PyObject *val) { array_type *thisptr = array_thisptr(self); if(!thisptr) return -1; return array_setitem(thisptr, idx, val); } Py_ssize_t length_##unique_name(PyObject *self, Py_ssize_t idx, PyObject *val) { array_type *thisptr = array_thisptr(self); if(!thisptr) return -1; return array_len(thisptr); } PyObject *getsubscript_##unique_name(PyObject *self, PyObject *idx) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; return array_getsubscript(thisptr, idx); } int setsubscript_##unique_name(PyObject *self, PyObject *idx, PyObject *val) { array_type *thisptr = array_thisptr(self); if(!thisptr) return -1; return array_setsubscript(thisptr, idx, val); } PyObject *concat_##unique_name(PyObject *self, PyObject *vals) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; return array_concat(thisptr, vals); } PyObject *repeat_##unique_name(PyObject *self, Py_ssize_t count) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; return array_repeat(thisptr, count); } PyObject *selfconcat_##unique_name(PyObject *self, PyObject *vals) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; PyObject *ret = array_selfconcat(thisptr, vals); if(ret) { Py_DECREF(ret); return Py_NewRef(self); } return NULL; } PyObject *selfrepeat_##unique_name(PyObject *self, Py_ssize_t count) { array_type *thisptr = array_thisptr(self); if(!thisptr) return NULL; PyObject *ret = array_selfrepeat(thisptr, count); if(ret) { Py_DECREF(ret); return Py_NewRef(self); } return NULL; } %} %enddef %define ARRAY_INSTANTIATION_CHECK_NAME(typeName) add_your_use_of_##typeName##_to_swig_interface %enddef // these pair of macros (TEMPLATE_ARRAY_DECLARE / NON_TEMPLATE_ARRAY_INSTANTIATE) are defined before // including the header that defines a class, to set up typemaps and other things that must be // available before the definition is parsed. // // Afterwards, you must call EXTEND_ARRAY_CLASS_METHODS to add the necessary named methods. %define TEMPLATE_ARRAY_DECLARE(typeName) LIST_MODIFY_IN_PLACE_TYPEMAP(typeName) // add a check to make sure that we explicitly instantiate all uses of this template (as a reference // type, not as a purely in parameter to a function - those are converted by value to C++ to allow // passing pure lists that aren't C++ side at all). %header %{ template void ARRAY_INSTANTIATION_CHECK_NAME(typeName)(typeName *); %} // override these typemaps to instantiate a checking template. %typemap(check) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(check) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(check) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(ret) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(ret) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(ret) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)(&$1); } %enddef %define NON_TEMPLATE_ARRAY_INSTANTIATE(typeName) ARRAY_ADD_SLOTS(typeName, typeName) ARRAY_DEFINE_SLOTS(typeName, typeName) LIST_MODIFY_IN_PLACE_TYPEMAP(typeName) %enddef // this instantiates a templated array for a particular type and sets it up to act like a python // sequence. %define TEMPLATE_ARRAY_INSTANTIATE(arrayType, innerType) ARRAY_ADD_SLOTS(arrayType, arrayType##_of_##innerType) // instantiate template %rename(arrayType##_of_##innerType) arrayType; %template(arrayType##_of_##innerType) arrayType; ARRAY_DEFINE_SLOTS(arrayType, arrayType##_of_##innerType) %header %{ template<> void ARRAY_INSTANTIATION_CHECK_NAME(arrayType)(arrayType *) { } %} %enddef // variation of the above to handle pointer'd inner type %define TEMPLATE_ARRAY_INSTANTIATE_PTR(arrayType, innerType) ARRAY_ADD_SLOTS(arrayType, arrayType##_of_ptr_##innerType) // instantiate template %rename(arrayType##_of_ptr_##innerType) arrayType; %template(arrayType##_of_ptr_##innerType) arrayType; ARRAY_DEFINE_SLOTS(arrayType, arrayType##_of_ptr_##innerType) %header %{ template<> void ARRAY_INSTANTIATION_CHECK_NAME(arrayType)(arrayType *) { } %} %enddef %define TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(arrayType, nspace, innerType) ARRAY_ADD_SLOTS(arrayType, arrayType##_of_##nspace##_##innerType) // instantiate template %rename(arrayType##_of_##nspace##_##innerType) arrayType; %template(arrayType##_of_##nspace##_##innerType) arrayType; ARRAY_DEFINE_SLOTS(arrayType, arrayType##_of_##nspace##_##innerType) %header %{ template<> void ARRAY_INSTANTIATION_CHECK_NAME(arrayType)(arrayType *) { } %} %enddef ///////////////////////////////////////////////////////////////////////// // Similar to above for handling templated container, but for fixed array // it's simpler because we map it to a tuple. Since tuples are immutable // we only need conversion in and out %define TEMPLATE_FIXEDARRAY_DECLARE(typeName) %typemap(in) const typeName & (unsigned char tempmem[16*8]) { using array_type = std::remove_pointer::type; { tempalloc($1, tempmem); int failIdx = 0; int res = TypeConversion::ConvertFromPy($input, indirect($1), &failIdx); if(!SWIG_IsOK(res)) { if(res == SWIG_TypeError) { SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'"); } else { snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx); SWIG_exception_fail(SWIG_ArgError(res), convert_error); } } } } %typemap(in) typeName { static_assert(false, "Error! Should not use this typemap"); } %typemap(in) typeName * (unsigned char tempmem[16*8]) { using array_type = std::remove_pointer::type; { tempalloc($1, tempmem); int failIdx = 0; int res = TypeConversion::ConvertFromPy($input, indirect($1), &failIdx); if(!SWIG_IsOK(res)) { if(res == SWIG_TypeError) { SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'"); } else { snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx); SWIG_exception_fail(SWIG_ArgError(res), convert_error); } } } } %typemap(out) typeName { $result = ConvertToPy(indirect($1)); } %typemap(out) typeName * { $result = ConvertToPy(indirect($1)); } // add a check to make sure that we explicitly instantiate all uses of this template (as a reference // type, not as a purely in parameter to a function - those are converted by value to C++ to allow // passing pure lists that aren't C++ side at all). %header %{ template void ARRAY_INSTANTIATION_CHECK_NAME(typeName)(typeName *); %} // override these typemaps to instantiate a checking template. %typemap(check) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(check) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(check) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(ret) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(ret) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } %typemap(ret) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)(&$1); } %enddef %define TEMPLATE_FIXEDARRAY_INSTANTIATE(arrayType, innerType, size) // instantiate template %rename(arrayType##_of_##size##_##innerType) arrayType; %template(arrayType##_of_##size##_##innerType) arrayType; %header %{ template<> void ARRAY_INSTANTIATION_CHECK_NAME(arrayType)(arrayType *) { } %} %enddef