diff options
Diffstat (limited to 'xbmc/interfaces/python/PythonSwig.cpp.template')
-rw-r--r-- | xbmc/interfaces/python/PythonSwig.cpp.template | 942 |
1 files changed, 942 insertions, 0 deletions
diff --git a/xbmc/interfaces/python/PythonSwig.cpp.template b/xbmc/interfaces/python/PythonSwig.cpp.template new file mode 100644 index 0000000..24756ea --- /dev/null +++ b/xbmc/interfaces/python/PythonSwig.cpp.template @@ -0,0 +1,942 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +%> +<% +import Helper +import SwigTypeParser +import PythonTools + +import groovy.xml.XmlUtil +import groovy.text.SimpleTemplateEngine +import java.util.regex.Pattern + +/** + * All of the method nodes and all of the class nodes are used several + * times over, so they are pulled out once here. + */ + +// --------------------------------------------------------- +// initialize the SwigTypeParser with the module's typetables +module.findAll( { it.name() == 'typetab' } ).each { SwigTypeParser.appendTypeTable(it) } +// --------------------------------------------------------- + +// --------------------------------------------------------- +// Flatten out all of the method/function nodes, whether inside +// classes or not, into 'methods' +List methods = module.depthFirst().findAll { it.name() == 'function' || it.name() == 'constructor' || it.name() == 'destructor' } +// --------------------------------------------------------- + +// --------------------------------------------------------- +// Flatten out all of the class nodes into 'classes' +List classes = module.depthFirst().findAll { it.name() == 'class' } +// --------------------------------------------------------- + +// --------------------------------------------------------- +// Initialize the Helper with the type conversions +Helper.setup(this,classes, + /** + * This is meant to contain mini-templates for converting the return type + * of the native call to be returned to the python caller. + */ + [ 'void' : 'Py_INCREF(Py_None);\n ${result} = Py_None;', + 'long': '${result} = PyLong_FromLong(${api});', + 'unsigned long': '${result} = PyLong_FromLong(${api});', + 'bool': '${result} = ${api} ? Py_True : Py_False; Py_INCREF(${result});', + 'long long': '${result} = Py_BuildValue("L", ${api});', + 'int': '${result} = Py_BuildValue("i", ${api});', + 'unsigned int': '${result} = Py_BuildValue("I", ${api});', + 'double': '${result} = PyFloat_FromDouble(${api});', + 'float': '${result} = Py_BuildValue("f", static_cast<double>(${api}));', + 'std::string' : new File('typemaps/python.string.outtm'), + 'p.q(const).char' : '${result} = PyUnicode_FromString(${api});', + (Pattern.compile('''(p.){0,1}XbmcCommons::Buffer''')) : new File('typemaps/python.buffer.outtm'), + (Pattern.compile('''std::shared_ptr<\\(.*\\)>''')) : new File('typemaps/python.smart_ptr.outtm'), + (Pattern.compile('''std::unique_ptr<\\(.*\\)>''')) : new File('typemaps/python.smart_ptr.outtm'), + (Pattern.compile('''(p.){0,1}std::vector<\\(.*\\)>''')) : new File('typemaps/python.vector.outtm'), + (Pattern.compile('''(p.){0,1}Tuple<\\(.*\\)>''')) : new File('typemaps/python.Tuple.outtm'), + (Pattern.compile('''(p.){0,1}Alternative<\\(.*\\)>''')) : new File('typemaps/python.Alternative.outtm') + ], '${result} = makePythonInstance(${api},true);', + /** + * This is meant to contain mini-templates for converting the parameter types + * of the native call to be converted from the python types provided by the caller. + * + * Note: if the type can be handled by PythonTools.ltypeToFormatChar then it wont + * appear here as it gets converted directly within the PyArg_ParseTupleAndKeywords + * call. + */ + [ + 'std::string' : 'if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},false,"${api}","${method.@name}");', + (Pattern.compile('''(p.){0,1}std::vector<\\(.*\\)>''')) : new File('typemaps/python.vector.intm'), + (Pattern.compile('''(p.){0,1}Tuple(3){0,1}<\\(.*\\)>''')) : new File('typemaps/python.Tuple.intm'), + (Pattern.compile('''(p.){0,1}Alternative<\\(.*\\)>''')) : new File('typemaps/python.Alternative.intm'), + (Pattern.compile('''(r.){0,1}XbmcCommons::Buffer''')) : new File('typemaps/python.buffer.intm'), + (Pattern.compile('''(p.){0,1}std::map<\\(.*\\)>''')) : new File('typemaps/python.map.intm'), + (Pattern.compile('''(r.){0,1}XBMCAddon::Dictionary<\\(.*\\)>''')) : new File('typemaps/python.dict.intm'), + (Pattern.compile('''p.void''')) : '${api} = (void*)${slarg};', + 'bool' : '${api} = (PyLong_AsLong(${slarg}) == 0L ? false : true);', + 'long' : '${api} = PyLong_AsLong(${slarg});', + 'unsigned long' : '${api} = PyLong_AsUnsignedLong(${slarg});', + 'long long' : '${api} = PyLong_AsLongLong(${slarg});', + 'unsigned long long' : '${api} = PyLong_AsUnsignedLongLong(${slarg});', + 'int' : '${api} = (int)PyLong_AsLong(${slarg});', + 'double' : '${api} = PyFloat_AsDouble(${slarg});', + 'float' : '${api} = (float)PyFloat_AsDouble(${slarg});', + 'XBMCAddon::StringOrInt' : 'if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},PyLong_Check(${slarg}) || PyFloat_Check(${slarg}),"${api}","${method.@name}");' + ], '${api} = (${swigTypeParser.SwigType_str(ltype)})retrieveApiInstance(${slarg},"${ltype}","${helper.findNamespace(method)}","${helper.callingName(method)}");') +// --------------------------------------------------------- + +/*******************************************************************************/ +/** + * The doMethod will actually write out the CPython method call for + * the method/function represented by the provided Node ('method'). + */ +void doMethod(Node method, MethodType methodType) +{ + boolean isOperator = method.@name.startsWith("operator ") + boolean doAsMappingIndex = false + boolean doAsCallable = false + + if (isOperator) + { + if("[]" == method.@name.substring(9)) + doAsMappingIndex = true + else if("()" == method.@name.substring(9)) + doAsCallable = true + else + return; + } + + boolean constructor = methodType == MethodType.constructor + + // if we're a constructor, but we're private, then we're outta here + if (constructor && method.@access != null && method.@access != "public") + return + + boolean destructor = methodType == MethodType.destructor + List params = method?.parm + int numParams = params?.size() + String clazz = Helper.findFullClassName(method) + String returns = constructor ? 'p.' + clazz : (destructor ? 'void' : Helper.getReturnSwigType(method)) + Node classnode = Helper.findClassNode(method) + String classNameAsVariable = clazz == null ? null : PythonTools.getClassNameAsVariable(classnode) + boolean useKeywordParsing = !('true' == classnode?.@feature_python_nokwds || 'true' == method?.@feature_python_nokwds) + + // do the docs + if (!constructor && !destructor) + { + if (Helper.hasDoc(method)) + { +%> + PyDoc_STRVAR(${PythonTools.getPyMethodName(method,methodType)}__doc__, + ${PythonTools.makeDocString(method.doc[0])}); +<% } + } +%> + static <% if(methodType == MethodType.destructor) { %>void<% } else { %>PyObject*<% } %> ${module.@name}_${PythonTools.getPyMethodName(method,methodType)} (<%= ((clazz == null) ? "PyObject" : + (constructor ? "PyTypeObject" : 'PyHolder')) %>* ${constructor ? 'pytype' : 'self'} <% + if (doAsMappingIndex) { %>, PyObject* py${params[0].@name}<% } + else if (methodType != MethodType.destructor) { %> , PyObject *args, PyObject *kwds <%} %> ) + { + XBMC_TRACE; +<% if (numParams > 0) + { + if (useKeywordParsing && !doAsMappingIndex) + { %> + static const char *keywords[] = {<% + params.each { %> + "${it.@name}",<% } %> + NULL}; +<% } + params.each { +%> + ${SwigTypeParser.SwigType_str(SwigTypeParser.convertTypeToLTypeForParam(it.@type))} ${it.@name} ${it.@value != null ? ' = ' + it.@value : SwigTypeParser.SwigType_ispointer(it.@type) ? ' = nullptr' : ''};<% + if (!PythonTools.parameterCanBeUsedDirectly(it) && !doAsMappingIndex) + { %> + PyObject* py${it.@name} = NULL;<% + } + } + if (!doAsMappingIndex) + { %> + if (!${useKeywordParsing ? 'PyArg_ParseTupleAndKeywords' : 'PyArg_ParseTuple'}( + args, + <% if (useKeywordParsing) { %>kwds,<% } %> + "<%= PythonTools.makeFormatStringFromParameters(method) %>", + <% if (useKeywordParsing) { %>const_cast<char**>(keywords),<% } %><% params.eachWithIndex { param,i -> %> + &${PythonTools.parameterCanBeUsedDirectly(param) ? '' : 'py'}${param.@name}${i < params.size() - 1 ? "," : ""}<% } %> + )) + { + return NULL; + } + +<% } + } + // now actually invoke the method + if (returns != "void") { %> ${SwigTypeParser.SwigType_str(returns)} apiResult;<% } +%> + try + { +<% + // now do the input conversion if any are necessary + params.findAll({ !PythonTools.parameterCanBeUsedDirectly(it) || doAsMappingIndex }).each { %> ${Helper.getInConversion(it.@type, it.@name, 'py' + it.@name, method)} <% println() } +%> +<% + // check to see if this method is a call to a virtual function on a director class. + boolean isDirectorCall = Helper.isDirector(method) + if (isDirectorCall) + { +%> // This is a director call coming from python so it explicitly calls the base class method. +<% + } + // now do the method call itself + if (!destructor) { + if (constructor || !clazz) { %> XBMCAddon::SetLanguageHookGuard slhg(XBMCAddon::Python::PythonLanguageHook::GetIfExists(PyThreadState_Get()->interp).get());<% println() } +%> <% + if (returns != "void") { %>apiResult = <% } + if (clazz && !constructor) { + %>((${clazz}*)retrieveApiInstance((PyObject*)self,&Ty${classNameAsVariable}_Type,"${Helper.callingName(method)}","${clazz}"))-> <% + } + if (constructor && classnode.@feature_director) { + %>(&(Ty${classNameAsVariable}_Type.pythonType) != pytype) ? new ${classNameAsVariable}_Director(<% params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %>) : <% } + + // Here is the actual call ... if this is a Director we need to do an upCall (from Python) + if (isDirectorCall){ %>${clazz}::<% } + %>${Helper.callingName(method)}( <% params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %> ); +<% + if (constructor) { %> prepareForReturn(apiResult);<% } + } // close the 'if method is not a destructor' + else { // it is a destructor +%> + ${clazz}* theObj = (${clazz}*)retrieveApiInstance((PyObject*)self,&Ty${classNameAsVariable}_Type,"~${Helper.callingName(method)}","${clazz}"); + cleanForDealloc(theObj); +<% + } +%> + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); <% + if (!destructor) { %> + return NULL; <% + } %> + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); <% + if (!destructor) { %> + return NULL; <% + } %> + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${Helper.callingName(method)}\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${Helper.callingName(method)}\""); <% + if (!destructor) { %> + return NULL; <% + } %> + } +<% + if (!destructor) { %> + PyObject* result = Py_None; + + // transform the result +<% + if (constructor) { + %> result = makePythonInstance(apiResult,pytype,false);<% + } + else { +%> ${Helper.getOutConversion(returns,'result',method)}<% + } + if (constructor && method.@feature_director) { %> + if (&(Ty${classNameAsVariable}_Type.pythonType) != pytype) + ((${classNameAsVariable}_Director*)apiResult)->setPyObjectForDirector(result);<% + } + %> + + return result; <% } + else { %> + (((PyObject*)(self))->ob_type)->tp_free((PyObject*)self); + <% + } + %> + } <% +} +/*******************************************************************************/ + +/** + * This method writes out the instance of a TypeInfo (which includes + * The PyTypeObject as a member) for the class node provided. + * + * If classNameAsVariable is not null then the class name as a + * variable will be appended to it. + */ +void doClassTypeInfo(Node clazz, List classNameAsVariables = null) +{ + String classNameAsVariable = PythonTools.getClassNameAsVariable(clazz) + String fullClassName = Helper.findFullClassName(clazz) + classNameAsVariables?.add(classNameAsVariable) +%> + //========================================================================= + // These variables will hold the Python Type information for ${fullClassName} + TypeInfo Ty${classNameAsVariable}_Type(typeid(${fullClassName}));<% +%> + //========================================================================= +<% +} + +/** + * This method will take the name of an API class from another module and + * create an external reference to its TypeInfo instance. + */ +void doExternClassTypeInfo(String knownType) +{ + String classNameAsVariable = knownType.replaceAll('::','_') +%> + //========================================================================= + // These variables define the type ${knownType} from another module + extern TypeInfo Ty${classNameAsVariable}_Type; + //========================================================================= +<% +} + +/*******************************************************************************/ +/** + * This method takes the class node and outputs all of the python meta-data + * and class oddities (like comparators, as_mapping, etc.). These include: + * + * 1) comparator *_cmp python method as long as there's an operator==, an + * operator>, AND an operator<. + * 2) it will create a python "as_mapping" method as long as there's both + * an operator[], AND a .size() method on the class. + * 3) it will handle the explicitly defined rich compare (_rcmp) if the + * feature is included in the .i file using %feature("python:rcmp") + * 4) The array of PyMethodDefs for the class definition + * 5) It will handle public fields as if the were python properties by: + * a) Creating a get/set_member if there are read/write properties. + * b) Creating only a get if there are only read-only properties. + * 6) It will write the init[Classname] method for the class which will + * initialize the TypeInfo and PyTypeObject structs. + * + * If initTypeCalls is not null then the method name for the generated init + * method (see #6 above) will be appended to it. + */ +void doClassMethodInfo(Node clazz, List initTypeCalls) +{ + String classNameAsVariable = PythonTools.getClassNameAsVariable(clazz) + String fullClassName = Helper.findFullClassName(clazz) + String initTypeCall = "initPy${classNameAsVariable}_Type" + initTypeCalls?.add(initTypeCall) + + // see if we have any valid (or invalid) operators + boolean doComparator = false + boolean doAsMapping = false + boolean hasEquivalenceOp = false + boolean hasLtOp = false + boolean hasGtOp = false + Node indexOp = null + Node callableOp = null + Node sizeNode = null + + List normalMethods = clazz.function.findAll { !it.@name.startsWith("operator ") } + List operators = clazz.function.findAll { it.@name.startsWith("operator ") } + List properties = clazz.variable.findAll { it.@access != null && it.@access == "public" } + List properties_set = properties.findAll { it.@feature_immutable == null || it.@feature_immutable == 0 } + + operators.each { + // we have an operator. The only one we can handle is == + if (it.@name.substring(9).startsWith("==")) + hasEquivalenceOp = true + else if (it.@name.substring(9) == "<") + hasLtOp = true + else if (it.@name.substring(9) == ">") + hasGtOp = true + else if (it.@name.substring(9) == "[]") + indexOp = it + else if (it.@name.substring(9) == "()") + callableOp = it + else + System.err.println ("Warning: class ${fullClassName} has an operator \"${it.@name}\" that is being ignored."); + } + + if (hasGtOp || hasLtOp || hasEquivalenceOp) + { + if (!(hasLtOp && hasGtOp && hasEquivalenceOp)) + System.err.println ("Warning: class ${fullClassName} has an inconsistent operator set. To get a comparator you must implement all 3 operators >,<,==.") + else + doComparator = true + } + + if (indexOp) + { + sizeNode = clazz.function.find { it.@name == "size" } + if (sizeNode) + doAsMapping = true + else + System.err.println ("Warning: class ${fullClassName} has an inconsistent operator set. To get a as_mapping you must implement 'size' as well as operator[]") + } + + if (doAsMapping) + { +%> + static Py_ssize_t ${module.@name}_${classNameAsVariable}_size_(PyObject* self) + { + return (Py_ssize_t)((${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${Helper.callingName(indexOp)}","${fullClassName}"))-> size(); + } + + //========================================================================= + // tp_as_mapping struct for ${fullClassName} + //========================================================================= + PyMappingMethods ${module.@name}_${classNameAsVariable}_as_mapping = { + ${module.@name}_${classNameAsVariable}_size_, /* inquiry mp_length; __len__ */ + (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(indexOp,MethodType.method)}, /* binaryfunc mp_subscript __getitem__ */ + 0, /* objargproc mp_ass_subscript; __setitem__ */ + }; +<% + } + + if (clazz.@feature_python_rcmp) + { %> + static PyObject* ${module.@name}_${classNameAsVariable}_rcmp(PyObject* obj1, PyObject *obj2, int method) + ${Helper.unescape(clazz.@feature_python_rcmp)} +<% + } +%> + //========================================================================= + // This section contains the initialization for the + // Python extension for the Api class ${fullClassName} + //========================================================================= + // All of the methods on this class + static PyMethodDef ${classNameAsVariable}_methods[] = { <% + normalMethods.each { %> + {"${it.@sym_name}", (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(it,MethodType.method)}, METH_VARARGS|METH_KEYWORDS, ${Helper.hasDoc(it) ? PythonTools.getPyMethodName(it,MethodType.method) + '__doc__' : 'NULL'} }, <% } + + // now do all of the explicit feature:python:method's that may be in this class + List tmpl = [] + tmpl.addAll(clazz.attributes().keySet()) + List newMethodKeys = tmpl.findAll { it.startsWith('feature_python_method_') } + newMethodKeys.each { key -> + String featureEntry = clazz.attribute(key) + String methodName = key.substring('feature_python_method_'.length()) %> + {"${methodName}", (PyCFunction)${module.@name}_${PythonTools.getClassNameAsVariable(clazz)}_${methodName}, METH_VARARGS|METH_KEYWORDS, NULL}, +<% + } +%> + {NULL, NULL, 0, NULL} + }; + +<% + if (properties.size() > 0) { +%> static PyObject* ${classNameAsVariable}_getMember(PyHolder *self, void *name) + { + if (self == NULL) + return NULL; +<% + String clazzName = Helper.findFullClassName(properties[0]) +%> + try + { + ${clazzName}* theObj = (${clazzName}*)retrieveApiInstance((PyObject*)self, &Ty${classNameAsVariable}_Type, "${classNameAsVariable}_getMember()", "${clazzName}"); + + PyObject* result = NULL; + <% + properties.each { + String returns = Helper.getPropertyReturnSwigType(it); +%> if (strcmp((char*)name, "${it.@sym_name}") == 0) + { + ${SwigTypeParser.SwigType_lstr(returns)} apiResult = theObj->${it.@sym_name}; + ${Helper.getOutConversion(returns, 'result', it)} + } + else<% + } %> + { + Py_INCREF(Py_None); + return Py_None; + } + + return result; + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return NULL; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return NULL; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + return NULL; + } + + return NULL; + } + +<% + if (properties_set.size() > 0) { +%> int ${classNameAsVariable}_setMember(PyHolder *self, PyObject *value, void *name) + { + if (self == NULL) + return -1; + + ${clazzName}* theObj = NULL; + try + { + theObj = (${clazzName}*)retrieveApiInstance((PyObject*)self, &Ty${classNameAsVariable}_Type, "${classNameAsVariable}_getMember()", "${clazzName}"); + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return -1; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return -1; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + return -1; + } + +<% + properties_set.each { + String returns = Helper.getPropertyReturnSwigType(it); +%> if (strcmp((char*)name, "${it.@sym_name}") == 0) + { + ${SwigTypeParser.SwigType_lstr(returns)} tmp; + ${Helper.getInConversion(returns, 'tmp', 'value', it)} + if (PyErr_Occurred()) + throw PythonBindings::PythonToCppException(); + + theObj->${it.@sym_name} = tmp; + } + else<% + } %> + return -1; + + return 0; + } <% + } +%> + + // All of the methods on this class + static PyGetSetDef ${classNameAsVariable}_getsets[] = { <% + properties.each { %> + {(char*)"${it.@sym_name}", (getter)${classNameAsVariable}_getMember, ${(it.@feature_immutable == null || it.@feature_immutable == 0) ? '(setter)' + classNameAsVariable + '_setMember' : 'NULL'}, (char*)${Helper.hasDoc(it) ? PythonTools.makeDocString(it.doc[0]) : 'NULL'}, (char*)"${it.@sym_name}" }, <% } +%> + {NULL} + }; +<% + } + + if ((clazz.@feature_iterator && clazz.@feature_iterator != '') || + (clazz.@feature_iterable && clazz.@feature_iterable != '')) { %> + static PyObject* ${module.@name}_${classNameAsVariable}_iter(PyObject* self) + { <% + if (clazz.@feature_iterator) { %> + return self; <% + } + else { %> + PyObject* result = NULL; + try + { + ${clazz.@feature_iterable}* apiResult = ((${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${module.@name}_${classNameAsVariable}_iternext","${fullClassName}"))->begin(); + + ${Helper.getOutConversion('p.' + clazz.@feature_iterable,'result',clazz)} + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return NULL; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return NULL; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + return NULL; + } + + return result; <% + } %> + } +<% + + if (clazz.@feature_iterator) { %> + static PyObject* ${module.@name}_${classNameAsVariable}_iternext(PyObject* self) + { + PyObject* result = NULL; + try + { + ${fullClassName}* iter = (${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${module.@name}_${classNameAsVariable}_iternext","${fullClassName}"); + + // check if we have reached the end + if (!iter->end()) + { + ++(*iter); + + ${clazz.@feature_iterator} apiResult = **iter; + ${Helper.getOutConversion(clazz.@feature_iterator,'result',clazz)} + } + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return NULL; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return NULL; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + return NULL; + } + + return result; + } +<% + } + } +%> + + // This method initializes the above mentioned Python Type structure + static void ${initTypeCall}() + { +<% + if (Helper.hasDoc(clazz)) + { +%> + PyDoc_STRVAR(${classNameAsVariable}__doc__, + ${PythonTools.makeDocString(clazz.doc[0])} + ); +<% } %> + + PyTypeObject& pythonType = Ty${classNameAsVariable}_Type.pythonType; + pythonType.tp_name = "${module.@name}.${clazz.@sym_name}"; + pythonType.tp_basicsize = sizeof(PyHolder); + pythonType.tp_dealloc = (destructor)${module.@name}_${classNameAsVariable}_Dealloc; <% + + if (clazz.@feature_python_rcmp) { %> + pythonType.tp_richcompare=(richcmpfunc)${module.@name}_${classNameAsVariable}_rcmp;<% + } %> + + pythonType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + + pythonType.tp_doc = ${Helper.hasDoc(clazz) ? (classNameAsVariable + '__doc__') : 'NULL' }; + pythonType.tp_methods = ${classNameAsVariable}_methods; <% + if (properties.size() > 0) { %> + pythonType.tp_getset = ${classNameAsVariable}_getsets; +<% + } + if (callableOp) { %> + pythonType.tp_call = (ternaryfunc)${module.@name}_${PythonTools.getPyMethodName(callableOp,MethodType.method)}; +<% + } + if (doAsMapping) { %> + pythonType.tp_as_mapping = &${module.@name}_${classNameAsVariable}_as_mapping; +<% + } + + if (clazz.@feature_iterator) { %> + pythonType.tp_iter = (getiterfunc)${module.@name}_${classNameAsVariable}_iter; + pythonType.tp_iternext = (iternextfunc)${module.@name}_${classNameAsVariable}_iternext; +<% + } + else if (clazz.@feature_iterable && clazz.@feature_iterable != '') { %> + pythonType.tp_iter = (getiterfunc)${module.@name}_${classNameAsVariable}_iter; +<% + } + + Node baseclass = PythonTools.findValidBaseClass(clazz, module) +%> + + pythonType.tp_base = ${baseclass ? ('&(Ty' + PythonTools.getClassNameAsVariable(baseclass) + '_Type.pythonType)') : "NULL"}; + pythonType.tp_new = <% !Helper.hasDefinedConstructor(clazz) || Helper.hasHiddenConstructor(clazz) ? print('NULL') : print("${module.@name}_${classNameAsVariable}_New") %>; + pythonType.tp_init = dummy_tp_init; + + Ty${classNameAsVariable}_Type.swigType="p.${fullClassName}";<% + if (baseclass) { %> + Ty${classNameAsVariable}_Type.parentType=&Ty${PythonTools.getClassNameAsVariable(baseclass)}_Type; +<%} + + if (!Helper.hasHiddenConstructor(clazz)) { %> + registerAddonClassTypeInformation(&Ty${classNameAsVariable}_Type); +<%} %> + } + //========================================================================= +<% +} +/*******************************************************************************/ + + +List getAllVirtualMethods(Node clazz) +{ + List ret = [] + ret.addAll(clazz.findAll({ it.name() == 'function' && it.@storage && it.@storage == 'virtual' })) + if (clazz.baselist) { + if (clazz.baselist[0].base) clazz.baselist[0].base.each { + Node baseclassnode = Helper.findClassNodeByName(module,it.@name,clazz) + if (baseclassnode && baseclassnode.@feature_director) ret.addAll(getAllVirtualMethods(baseclassnode)) + } + } + return ret; +} + +%> +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +// ************************************************************************ +// This file was generated by xbmc compile process. DO NOT EDIT!! +// It was created by running the code generator on the spec file for +// the module "${module.@name}" on the template file PythonSwig.template.cpp +// ************************************************************************ + +<% +Helper.getInsertNodes(module, 'begin').each { %>${Helper.unescape(it)}<% } +%> + +#include <Python.h> +#include <string> +#include "CompileInfo.h" +#include "interfaces/python/LanguageHook.h" +#include "interfaces/python/swig.h" +#include "interfaces/python/PyContext.h" + +<% +Helper.getInsertNodes(module, 'header').each { %>${Helper.unescape(it)}<% } +%> + +namespace PythonBindings +{ +<% + // initTypeCalls is the + List initTypeCalls = [] + List classNameAsVariables = [] + + classes.each { clazz -> doClassTypeInfo(clazz, classNameAsVariables) } + + // make sure known api types are declared as externs + + // first, find all of the declared known api types + Set<String> knownApiTypes = new HashSet<String>() + module.depthFirst().each + { + String attr = it.attribute('feature_knownapitypes') + if (attr != null) + { + attr.trim().split(',').each { knownApiTypes.add(it) } + } + } + + // now declare an extern for each one + knownApiTypes.each { doExternClassTypeInfo(it) } + +%> + +<% +//========================================================================= +// Do the directors. For every class that can be extended in python, we +// need to create a Director instance with bridging calls. This chunk of +// code will generate those classes. + classes.findAll({ it.@feature_director != null }).each { clazz -> + // find the constructor for this class + constructor = clazz.constructor[0] +%> + //========================================================================= + // This class is the Director for ${Helper.findFullClassName(clazz)}. + // It provides the "reverse bridge" from C++ to Python to support + // cross-language polymorphism. + //========================================================================= + class ${PythonTools.getClassNameAsVariable(clazz)}_Director : public Director, public ${clazz.@name} + { + public: +<% + if (constructor) + {%> + inline ${PythonTools.getClassNameAsVariable(clazz)}_Director(<% + List params = constructor?.parm + params.eachWithIndex { param, i -> %>${SwigTypeParser.SwigType_str(param.@type)} ${param.@name}${i < params.size() - 1 ? "," : ""} <% } + %>) : ${Helper.findFullClassName(constructor)}(<% + params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %>) { } <% + } +%> +<% + getAllVirtualMethods(clazz).each + { %> + virtual ${SwigTypeParser.SwigType_str(Helper.getReturnSwigType(it))} ${Helper.callingName(it)}( <% + List params = it?.parm + String paramFormatStr = '' + params.each { paramFormatStr += 'O' } + params.eachWithIndex { param, i -> %> ${SwigTypeParser.SwigType_str(param.@type)} ${param.@name}${i < params.size() - 1 ? "," : ""} <% } + %> ) + { <% + params.each + { param -> + %> + PyObject* py${param.@name} = NULL; + ${Helper.getOutConversion(param.@type,'result',it,['result' : 'py' + param.@name, 'api' : param.@name])}<% + } +%> + XBMCAddon::Python::PyContext pyContext; + PyObject_CallMethod(self,"${Helper.callingName(it)}","(${paramFormatStr})"<% + params.each { + %>, py${it.@name} <% + } + %>); + if (PyErr_Occurred()) + throw PythonBindings::PythonToCppException(); + } +<% } + +%> + }; +<% + } +//========================================================================= + + // types used as method parameter or return values need to be declared + // as extern if they are unknown types. + methods.each { if (it.name() != 'destructor') { doMethod(it, (it.name() == 'constructor' ? MethodType.constructor : MethodType.method)); println(); } } + classes.each { clazz -> doMethod(clazz, MethodType.destructor) } + + // now find any methods that have been added explicitly + classes.each { node -> + List tmpl = [] + tmpl.addAll(node.attributes().keySet()) + List newMethodKeys = tmpl.findAll { it.startsWith('feature_python_method_') } + newMethodKeys.each { key -> + String featureEntry = node.attribute(key) + String methodName = key.substring('feature_python_method_'.length()) %> + static PyObject* ${module.@name}_${PythonTools.getClassNameAsVariable(node)}_${methodName}(PyObject* self, PyObject *args, PyObject *kwds) + ${Helper.unescape(featureEntry)} +<% + } + } + + classes.each { clazz -> doClassMethodInfo(clazz, initTypeCalls) } + +%> + + static PyMethodDef ${module.@name}_methods[] = { <% + module.depthFirst().findAll({ it.name() == 'function' && Helper.parents(it, { Node lnode -> lnode.name() == 'class'}).size() == 0 }).each { %> + {"${it.@sym_name}", (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(it,MethodType.method)}, METH_VARARGS|METH_KEYWORDS, ${Helper.hasDoc(it) ? PythonTools.getPyMethodName(it,MethodType.method) + '__doc__' : 'NULL'} }, <% } +%> + {NULL, NULL, 0, NULL} + }; + + // This is the call that will call all of the other initializes + // for all of the classes in this module + static void initTypes() + { + static bool typesAlreadyInitialized = false; + if (!typesAlreadyInitialized) + { + typesAlreadyInitialized = true; +<% + initTypeCalls.each { %> + ${it}();<% + } + + classNameAsVariables.each { %> + if (PyType_Ready(&(Ty${it}_Type.pythonType)) < 0) + return;<% + }%> + } + } + + static struct PyModuleDef createModule + { + PyModuleDef_HEAD_INIT, + "${module.@name}", + "", + -1, + ${module.@name}_methods, + nullptr, + nullptr, + nullptr, + nullptr, + }; + + PyObject *PyInit_Module_${module.@name}() + { + initTypes(); + + // init general ${module.@name} modules + PyObject* module; + +<% classNameAsVariables.each { %> + Py_INCREF(&(Ty${it}_Type.pythonType));<% + }%> + + module = PyModule_Create(&createModule); + if (module == NULL) return NULL; + +<% classes.each { clazz -> %> + PyModule_AddObject(module, "${clazz.@sym_name}", (PyObject*)(&(Ty${PythonTools.getClassNameAsVariable(clazz)}_Type.pythonType)));<% + }%> + + // constants + PyModule_AddStringConstant(module, "__author__", "Team Kodi <http://kodi.tv>"); + PyModule_AddStringConstant(module, "__date__", CCompileInfo::GetBuildDate().c_str()); + PyModule_AddStringConstant(module, "__version__", "3.0.1"); + PyModule_AddStringConstant(module, "__credits__", "Team Kodi"); + PyModule_AddStringConstant(module, "__platform__", "ALL"); + + // need to handle constants + // #define constants +<% module.depthFirst().findAll( { it.name() == 'constant'} ).each { + String pyCall = + (it.@type == 'int' || it.@type == 'long' || it.@type == 'unsigned int' || it.@type == 'unsigned long' || it.@type == 'bool') ? + 'PyModule_AddIntConstant' : 'PyModule_AddStringConstant' %> + ${pyCall}(module,"${it.@sym_name}",${it.@value}); <% + } %> + // constexpr constants +<% module.depthFirst().findAll( { it.name() == 'variable' && it.@storage && it.@storage == "constexpr" && !it.@error } ).each { + String pyCall = + ( it.@type == 'q(const).int' || it.@type == 'q(const).long' || it.@type == 'q(const).unsigned int' || it.@type == 'q(const).unsigned long' || it.@type == 'q(const).bool' ) ? + 'PyModule_AddIntConstant' : 'PyModule_AddStringConstant' %> + ${pyCall}(module,"${it.@sym_name}",${it.@value}); <% + } %> + return module; + } + +} // end PythonBindings namespace for python type definitions + +<% +Helper.getInsertNodes(module, 'footer').each { %>${Helper.unescape(it)}<% } +%> |