[pygccxml-commit] SF.net SVN: pygccxml: [507] pyplusplus_dev
Brought to you by:
mbaas,
roman_yakovenko
From: <mb...@us...> - 2006-09-03 09:32:00
|
Revision: 507 http://svn.sourceforge.net/pygccxml/?rev=507&view=rev Author: mbaas Date: 2006-09-03 02:31:45 -0700 (Sun, 03 Sep 2006) Log Message: ----------- Added the 'function_transformers' sub-package. Modified Paths: -------------- pyplusplus_dev/setup.py Added Paths: ----------- pyplusplus_dev/pyplusplus/function_transformers/ pyplusplus_dev/pyplusplus/function_transformers/__init__.py pyplusplus_dev/pyplusplus/function_transformers/arg_policies.py pyplusplus_dev/pyplusplus/function_transformers/code_manager.py pyplusplus_dev/pyplusplus/function_transformers/function_transformer.py pyplusplus_dev/pyplusplus/function_transformers/subst.py pyplusplus_dev/pyplusplus/function_transformers/substitution_manager.py Added: pyplusplus_dev/pyplusplus/function_transformers/__init__.py =================================================================== --- pyplusplus_dev/pyplusplus/function_transformers/__init__.py (rev 0) +++ pyplusplus_dev/pyplusplus/function_transformers/__init__.py 2006-09-03 09:31:45 UTC (rev 507) @@ -0,0 +1,21 @@ +# Helper classes for wrapper function creation + +"""This sub-package provides text substitution services for creating C++ functions. + +The helper classes in this package are meant to be used by the actual +calldef code creators (that are not part of this sub-package). They +implement the core of the "arg policy" mechanism which can be used by +a user to modify the source code for a function. + +The main class of this sub-package is the class L{substitution_manager_t}. This +class maintains two sets of special variables, one for the wrapper function +and one for the virtual function, and provides text substitution services. +The variables contain parts of source code that can be inserted into the +function source code template which is generated by the user of the class. + + +""" + +from substitution_manager import substitution_manager_t +from function_transformer import function_transformer_t + Property changes on: pyplusplus_dev/pyplusplus/function_transformers/__init__.py ___________________________________________________________________ Name: svn:eol-style + native Added: pyplusplus_dev/pyplusplus/function_transformers/arg_policies.py =================================================================== --- pyplusplus_dev/pyplusplus/function_transformers/arg_policies.py (rev 0) +++ pyplusplus_dev/pyplusplus/function_transformers/arg_policies.py 2006-09-03 09:31:45 UTC (rev 507) @@ -0,0 +1,70 @@ +# Copyright 2006 Roman Yakovenko. +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# Initial author: Matthias Baas + +"""This module contains standard argument policy objects. +""" + +from pygccxml import declarations + +# Output +class Output: + """Handles a single output variable. + + The specified variable is removed from the argument list and is turned + into a return value. + + void getValue(int& v) -> v = getValue() + """ + + def __init__(self, idx): + """Constructor. + + The specified argument must be a reference or a pointer. + + @param idx: Index of the argument that is an output value (the first arg has index 1). + @type idx: int + """ + self.idx = idx + self.localvar = "<not initialized>" + + def __str__(self): + return "Output(%d)"%(self.idx) + + def init_funcs(self, sm): + # Remove the specified output argument from the wrapper function + arg = sm.remove_arg(self.idx) + + # Do some sanity checking (whether the argument can actually be + # an output argument, i.e. it has to be a reference or a pointer) + reftype = arg.type + if not (isinstance(reftype, declarations.reference_t) or + isinstance(reftype, declarations.pointer_t)): + raise ValueError, 'Output variable %d ("%s") must be a reference or a pointer (got %s)'%(self.idx, arg.name, arg.type) + + # Declare a local variable that will receive the output value + self.localvar = sm.wrapperfunc.declare_local(arg.name, str(reftype.base)) + # Append the output to the result tuple + sm.wrapperfunc.resultexprs.append(self.localvar) + + # Replace the expression in the C++ function call + if isinstance(reftype, declarations.pointer_t): + sm.wrapperfunc.inputparams[self.idx-1] = "&%s"%self.localvar + else: + sm.wrapperfunc.inputparams[self.idx-1] = self.localvar + + + def virtual_post_call(self, sm): + """Extract the C++ value after the call to the Python function. + """ + arg = sm.virtualfunc.arglist[self.idx-1] + res = "// Extract the C++ value for output argument '%s' (index: %d)\n"%(arg.name, self.idx) + if isinstance(arg.type, declarations.pointer_t): + res += "*" + res += "%s = boost::python::extract<%s>(%s[%d]);"%(arg.name, arg.type.base, sm.virtualfunc.resultvar, sm.wrapperfunc.resultexprs.index(self.localvar)) + return res + + Property changes on: pyplusplus_dev/pyplusplus/function_transformers/arg_policies.py ___________________________________________________________________ Name: svn:eol-style + native Added: pyplusplus_dev/pyplusplus/function_transformers/code_manager.py =================================================================== --- pyplusplus_dev/pyplusplus/function_transformers/code_manager.py (rev 0) +++ pyplusplus_dev/pyplusplus/function_transformers/code_manager.py 2006-09-03 09:31:45 UTC (rev 507) @@ -0,0 +1,267 @@ +# Copyright 2006 Roman Yakovenko. +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# Initial author: Matthias Baas + +"""This module contains the L{code_manager_t} and L{wrapper_code_manager_t} classes. +""" + +import types +from subst import subst_t + +# code_manager_t +class code_manager_t(subst_t): + """This class manages pieces of source code for a C++ function. + + The class mainly provides the interface for the code blocks to + manipulate a function and stores the actual substitution variables. + Each function has its own code manager instance. + + A code block can declare a local variable using L{declare_local()} + and it can manipulate one of the attributes that are used to + initialize the final variables (see the documentation of the + instance variables below). The final variables (such as RETTYPE, + FUNCNAME, ARGLIST, etc.) are stored as regular attributes of the + object. + + The functionality to perform a text substitution (using the + substitution() method) is inherited + from the class L{subst_t}. + + @ivar classname: The name of the class of which the generated function is a member. A value of None or an empty string is used for free functions. This attribute is used for creating the CLASSSPEC variable. + @type classname: str + @ivar rettype: Return type. The value may be any object where str(obj) is valid C++ code. The value None corresponds to void. This will be the value of the variable RETTYPE. + @type rettype: str + @ivar arglist: The argument list. The items are pygccxml argument_t objects. This list will appear in the variables ARGLIST, ARGLISTDEF and ARGLISTTYPES. + @type arglist: list of argument_t + @ivar inputparams: A list of strings that contain the input parameter for the function call. This list is used for the INPUTPARAMS variable. + @type inputparams: list of str + @ivar resultvar: The name of the variable that will receive the result of the function call. If None, the return value is ignored. This attribute will be used for the variable RESULTVARASSIGNMENT. + @type resultvar: str + @ivar resultexpr: A string containing the expression that will be put after the "return" statement. This expression is used for the variable RETURNSTMT. + @type resultexpr: str + + @author: Matthias Baas + """ + + def __init__(self): + """Constructor. + """ + subst_t.__init__(self, blockvars=["DECLARATIONS", + "PRECALL", + "POSTCALL"]) + + # The name of the class of which the generated function is a member + # (pass None or an empty string if the function is a free function) + self.classname = None + + # Return type (the value may be any object where str(obj) is valid + # C++ code. The value None corresponds to "void"). + self.rettype = None + # The argument list. The items are argument_t objects. + self.arglist = [] + + # A list of strings that contain the input parameter for the + # function call + self.inputparams = [] + + # The name of the variable that will receive the result of the + # function call. If None, the return value is ignored. + self.resultvar = None + + # A string containing the expression that will be put after + # the "return" statement. + self.resultexpr = None + + # Key:Variable name / Value:(type,size,default) + self._declared_vars = {} + # A list with variable tuples: (name, type, size, default) + self._local_var_list = [] + + # declare_local + def declare_local(self, name, type, size=None, default=None): + """Declare a local variable and return its final name. + + @param name: The desired variable name + @type name: str + @param type: The type of the variable (must be valid C++ code) + @type type: str + @param size: The array length or None + @type size: int + @param default: The default value (must be valid C++ code) or None + @type default: str + @return: The assigned variable name (which is guaranteed to be unique) + @rtype: str + """ + name = self._make_name_unique(name) + self._declared_vars[name] = (type,size,default) + self._local_var_list.append((name, type, size, default)) + return name + + def is_defined(self, name): + """Check if a variable name is already in use. + + The method returns True if name is the name of an argument or of + a local variable. + + @rtype: bool + """ + if name in self._declared_vars: + return True + if filter(lambda a: a.name==name, self.arglist): + return True + return False + + def local_type_str(self, name): + """Return the type of a local variable. + + An exception is raised if a variable called name does not exist. + + @return: Returns the type of the specified local variable. + @rtype: str + """ + if name not in self._declared_vars: + raise ValueError, 'Local variable "%s" not found.'%name + + type,size,default = self._declared_vars[name] + + if size==None: + return type + else: + return "*%s"%type + + def init_variables(self): + """Initialize the substitution variables. + + Based on the (lowercase) attributes, the final (uppercase) + variables are created. Those variables are regular string + attributes. + """ + + # CLASSSPEC + if (self.classname in [None, ""]): + self.CLASSSPEC = "" + else: + self.CLASSSPEC = "%s::"%self.classname + + # RETTYPE + if self.rettype==None: + self.RETTYPE = "void" + else: + self.RETTYPE = str(self.rettype) + + # ARGLISTDEF + args = map(lambda a: str(a), self.arglist) + self.ARGLISTDEF = ", ".join(args) + + # ARGLIST + args = map(lambda s: s.split("=")[0], args) + self.ARGLIST = ", ".join(args) + + # ARGLISTTYPES + args = map(lambda a: str(a.type), self.arglist) + self.ARGLISTTYPES = ", ".join(args) + + # Create the declaration block -> DECLARATIONS + vardecls = [] + for varname,type,size,default in self._local_var_list: + if default==None: + vd = "%s %s"%(type, varname) + else: + vd = "%s %s = %s"%(type, varname, default) + if size!=None: + vd += "[%d]"%size + vardecls.append(vd+";") + self.DECLARATIONS = "\n".join(vardecls) + + # RESULTVARASSIGNMENT + if self.resultvar!=None: + self.RESULTVARASSIGNMENT = "%s = "%self.resultvar + + # INPUTPARAMS + self.INPUTPARAMS = ", ".join(self.inputparams) + + # RETURNSTMT + if self.resultexpr!=None: + self.RETURNSTMT = "return %s;"%self.resultexpr + + # _make_name_unique + def _make_name_unique(self, name): + """Make a variable name unique so that there's no clash with other names. + + @param name: The variable name that should be unique + @type name: str + @return: A unique name based on the input name + @rtype: str + """ + if not self.is_defined(name): + return name + + n = 2 + while 1: + newname = "%s_%d"%(name, n) + if not self.is_defined(newname): + return newname + n += 1 + + +# wrapper_code_manager_t +class wrapper_code_manager_t(code_manager_t): + """The CodeManager class for the wrapper function. + + In contrast to a regular C++ function a Python function can return + several values, so this class provides the extra attribute "resultexprs" + which is a list of individual expressions. Apart from that this + class is identical to L{code_manager_t}. + + @ivar resultexprs: Similar to resultexpr but this list variable can contain more than just one result. The items can be either strings containing the variable names (or expressions) that should be returned or it can be an argument_t object (usually from the argument list of the virtual function) whose name attribute will be used. This attribute only exists on the code manager for the wrapper function (the virtual function cannot return several values, use resultexpr instead). + @type resultexprs: list of str or argument_t + """ + + def __init__(self): + """Constructor. + """ + code_manager_t.__init__(self) + + # Similar to resultexpr but now there can be more than just one result + # The items can be either strings containing the variable names (or + # expressions) that should be returned or it can be an argument_t + # object (usually from the argument list of the virtual function) + # whose name attribute will be used. + self.resultexprs = [] + + def init_variables(self): + """Initialize the substitution variables. + """ + + # Prepare the variables for RETTYPE and RETURNSTMT... + + # Convert all items into strings + resultexprs = [] + for re in self.resultexprs: + # String? + if isinstance(re, types.StringTypes): + resultexprs.append(re) + # argument_t + else: + resultexprs.append(re.name) + + # No output values? + if len(resultexprs)==0: + self.rettype = None + self.resultexpr = None + # Exactly one output value? + elif len(resultexprs)==1: + self.rettype = "boost::python::object" + self.resultexpr = "boost::python::object(%s)"%resultexprs[0] + # More than one output value... + else: + self.rettype = "boost::python::object" + self.resultexpr = "boost::python::make_tuple(%s)"%(", ".join(resultexprs)) + + # Invoke the inherited method that sets the actual variables + code_manager_t.init_variables(self) + + Property changes on: pyplusplus_dev/pyplusplus/function_transformers/code_manager.py ___________________________________________________________________ Name: svn:eol-style + native Added: pyplusplus_dev/pyplusplus/function_transformers/function_transformer.py =================================================================== --- pyplusplus_dev/pyplusplus/function_transformers/function_transformer.py (rev 0) +++ pyplusplus_dev/pyplusplus/function_transformers/function_transformer.py 2006-09-03 09:31:45 UTC (rev 507) @@ -0,0 +1,109 @@ +# Copyright 2006 Roman Yakovenko. +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# Initial author: Matthias Baas + +"""This module contains the class L{function_transformer_t}. +""" + +import sys, os.path, copy, re, types +from pygccxml import declarations, parser + + +# function_transformer_t +class function_transformer_t: + """Base class for a function transformer. + + This class specifies the interface that a user written transformer + has to implement. It doesn't contain any actual functionality so + a user doesn't have to derive from this class. Methods that are not + implemented are treated as if they would do nothing and return None. + + @author: Matthias Baas + """ + + def __init__(self): + """Constructor. + """ + pass + + def init_funcs(self, sm): + """Wrapper initialization. + + This method is called before the actual wrapper source code is + generated. This is the place where you can modify the signature + of the C++ wrapper function or allocate local variables. + + @param sm: Substitution manager instance + @type sm: substitution_manager_t + """ + pass + + def wrapper_pre_call(self, sm): + """Generate the C++ code that should be executed before the actual function call. + + The code from this method will be put into the wrapper function. + + @param sm: Substitution manager instance + @type sm: substitution_manager_t + @return: C++ code or None + @rtype: str + """ + pass + + def wrapper_post_call(self, sm): + """Generate the C++ code that should be executed after the actual function call. + + The code from this method will be put into the wrapper function. + + @param sm: Substitution manager instance + @type sm: substitution_manager_t + @return: C++ code or None + @rtype: str + """ + pass + + def wrapper_cleanup(self, sm): + """Generate code that should be executed in the case of an error. + + This method has to assume that the preCall code was executed but + the postCall code won't be executed because something went wrong. + + <not used yet> + + @param sm: Substitution manager instance + @type sm: substitution_manager_t + @return: C++ code or None + @rtype: str + """ + pass + + def virtual_pre_call(self, sm): + """Generate the C++ code that should be executed before the actual function call. + + The code from this method will be put into the virtual function. + + @param sm: Substitution manager instance + @type sm: substitution_manager_t + @return: C++ code or None + @rtype: str + """ + pass + + def virtual_post_call(self, sm): + """Generate the C++ code that should be executed after the actual function call. + + The code from this method will be put into the virtual function. + + @param sm: Substitution manager instance + @type sm: substitution_manager_t + @return: C++ code or None + @rtype: str + """ + pass + + def virtual_cleanup(self, sm): + pass + Property changes on: pyplusplus_dev/pyplusplus/function_transformers/function_transformer.py ___________________________________________________________________ Name: svn:eol-style + native Added: pyplusplus_dev/pyplusplus/function_transformers/subst.py =================================================================== --- pyplusplus_dev/pyplusplus/function_transformers/subst.py (rev 0) +++ pyplusplus_dev/pyplusplus/function_transformers/subst.py 2006-09-03 09:31:45 UTC (rev 507) @@ -0,0 +1,149 @@ +# Copyright 2006 Roman Yakovenko. +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# Initial author: Matthias Baas + +"""This module contains the base class L{subst_t}. +""" + +import re + +# subst_t +class subst_t: + """Perform text substitutions. + + This class performs text substitutions on a template string. The + variables are simply stored as attributes inside the class and may + be of any type that can be converted to a string. An empty string + is used if a template string references a variable that does not + exist. + + Example:: + + sm = subst_t(blockvars=['BODY']) + sm.RETURNTYPE = 'int' + sm.FUNCNAME = 'foo' + sm.ARGLIST = 'int n' + sm.BODY = '''int res=0; + for(int i=0; i<n; i++) + { + res += n; + } + return res;''' + + template = ''' + $RETURNTYPE $FUNCNAME($ARGLIST) + { + $BODY + } + ''' + + print sm.substitute(template) + + The result of the substitution will be:: + + int foo(int n) + { + int res=0; + for(int i=0; i<n; i++) + { + res += n; + } + return res; + } + + The variable BODY is a block variable which means it may contain + an entire block of text where each line should be indented with + the same depth as the variable in the template string. The value + of BODY should not contain an already indented block. + + @author: Matthias Baas + """ + + def __init__(self, blockvars): + """Constructor. + + The argument blockvars is used to declare the names of those + variables that may contain a block of code (i.e. multiple lines). + + @param blockvars: A list of block variable names. + @type blockvars: list of str + """ + self._blockvars = dict(map(lambda x: (x,0), blockvars)) + + def substitute(self, template): + """Substitute the variables in template and return the result. + + All variables of the form "$<varname>" are replaced with the + corresponding attribute <varname>. Block variables must appear + in one single line. The indendation of the variable determines + the indendation of the entire block. + Unknown variables will be substituted with an empty string. + + @param template: The template string + @type template: str + @return: Returns the input string where all variables have been substituted. + @rtype: str + """ + + lines = [] + # Replace the block variables... + for line in template.split("\n"): + s = line.lstrip() + # Determine the indendation depth + depth = len(line)-len(s) + key = s.rstrip() + if key!="" and key[0]=="$" and key[1:] in self._blockvars: + block = getattr(self, key[1:], None) + if block==None or block=="": + line = None + else: + line = self._indent(depth, block) + else: + line = line.rstrip() + if line!=None and (line!="" or (lines!=[] and lines[-1]!="")): + lines.append(line) + code = "\n".join(lines) + + # Replace the non-block variables... + varexpr = re.compile("\$[a-zA-Z_]+") + while 1: + m = varexpr.search(code) + if m==None: + break + s = m.start() + e = m.end() + key = code[s+1:e] + code = "%s%s%s"%(code[:s], getattr(self, key, ""), code[e:]) + + # Replace trailing blanks on each line... + expr = re.compile("[ ]+$", re.MULTILINE) + code = expr.sub("", code) + + # Replace two subsequent empty lines with one single line... + expr = re.compile("^\n^\n", re.MULTILINE) + n1 = len(code) + while 1: + code = expr.sub("\n", code) + n2 = len(code) + if n2==n1: + break + n1 = n2 + + # Remove blank lines right after '{' or before '}' + code = code.replace("{\n\n", "{\n") + code = code.replace("\n\n}", "\n}") + + return code + + # _indent + def _indent(self, n, code): + """Indent source code. + """ + if code=="": + return "" + return "\n".join(map(lambda s: ((n*" ")+s).rstrip(), code.split("\n"))) + + Property changes on: pyplusplus_dev/pyplusplus/function_transformers/subst.py ___________________________________________________________________ Name: svn:eol-style + native Added: pyplusplus_dev/pyplusplus/function_transformers/substitution_manager.py =================================================================== --- pyplusplus_dev/pyplusplus/function_transformers/substitution_manager.py (rev 0) +++ pyplusplus_dev/pyplusplus/function_transformers/substitution_manager.py 2006-09-03 09:31:45 UTC (rev 507) @@ -0,0 +1,554 @@ +# Copyright 2006 Roman Yakovenko. +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# Initial author: Matthias Baas + +"""This module contains the L{substitution_manager_t} class. +""" + +from pygccxml import declarations +from code_manager import code_manager_t, wrapper_code_manager_t +from function_transformer import function_transformer_t + +# substitution_manager_t +class substitution_manager_t: + """Helper class for creating C++ source code for wrapper functions. + + The class does not create the entire function source code itself + but it maintains the individual parts that can be composed by the + user of the class. Those individual parts are stored inside + variables which can be used to perform text substitutions. + + Doing substitutions + =================== + + Here is an example that demonstrates the usage of the class. The + user creates a template string that contains the layout of the + entire wrapper function. Such a template string may look like + this:: + + $RETTYPE $CLASSSPEC$FUNCNAME($ARGLIST) + { + $DECLARATIONS + + $PRECALL + + $RESULTVARASSIGNMENT$CALLFUNCNAME($INPUTPARAMS); + + $POSTCALL + + $RETURNSTMT + } + + Any part of the function that is not fixed, i.e. that can be + modified by argument policies, is specified via a variable. The + substitution manager can now be used to substitute the variables with + their actual value. There are actually two sets of identical + variables, one for the wrapper function and one for the virtual + function. You choose a set either by using the L{subst_wrapper()} or + L{subst_virtual()} method for doing the substitution. For example, + performing a "wrapper" substitution on the above template string + might result in the following code:: + + boost::python::object Spam_wrapper::foo_wrapper(Spam& self, int mode) + { + int result; + int w; + int h; + + result = self.foo(w, &h, mode); + + return boost::python::make_tuple(result, w, h); + } + + In this example, the individual variables have the following values: + + - RETTYPE = C{boost::python::object} + - CLASSSPEC = C{Spam_wrapper::} + - FUNCNAME = C{foo_wrapper} + - ARGLIST = C{Spam& self, int mode} + - DECLARATIONS = C{int result;\\nint w;\\nint h;} + - PRECALL = <empty> + - RESULTVARASSIGNMENT = C{result =} + - CALLFUNCNAME = C{self.foo} + - INPUTPARAMS = C{w, &h, mode} + - POSTCALL = <empty> + - RETURNSTMT = C{return boost::python::make_tuple(result, w, h);} + + + Modifying the variables + ======================= + + In addition to the actual user of the class (who wants to do text + substitutions), the class is also used by the arg policies (code blocks) + to modify the variables. + There are two attributes L{wrapperfunc} and L{virtualfunc} that are + used to modify either the wrapper or the virtual function. If the + signature of the wrapper needs modification this should be done via + the methods L{remove_arg()} and L{insert_arg()} and not via the + wrapperfunc or virtualfunc attributes because this affects the + virtual function as well (because the virtual function makes a call + to the Python function). + + Variables + ========= + + + - RETTYPE: The return type (e.g. "void", "int", "boost::python::object") + + - CLASSSPEC: "<classname>::" or empty + + - FUNCNAME: The name of the wrapper or virtual function. + + - ARGLIST: The parameters for $FUNCNAME (including self if required) + + - ARGLISTDEF: Like ARGLIST, but including default values (if there are any) + + - ARGLISTTYPES: Like ARGLIST but the variable names are left out and only the types are listed (this can identify a particular signature). + + - DECLARATIONS: The declaration block + + - PRECALL:: + + +--------------------------+ + | [try {] | + +--------------------------+ + | Pre-call code block 1 | + +--------------------------+ + | Pre-call code block 2 | + +--------------------------+ + | ... | + +--------------------------+ + | Pre-call code block n | + +--------------------------+ + + - RESULTVARASSIGNMENT: "<varname> = " or empty + + - CALLFUNCNAME: The name of the function that should be invoked (self?). + + - INPUTPARAMS: The values or variables that will be passed to $FUNCNAME, + e.g. "a, b" or "0.5, 1.5" etc + + - POSTCALL:: + + +--------------------------+ + | Post-call code block n | + +--------------------------+ + | ... | + +--------------------------+ + | Post-call code block 2 | + +--------------------------+ + | Post-call code block 1 | + +--------------------------+ + | [} catch(...) {...}] | + +--------------------------+ + + - RETURNSTMT: "return <varname>" or "return boost::python::make_tuple(...)" + + + @ivar wrapperfunc: The L{code manager<code_manager_t>} object that manages the wrapper function. This is used by the arg policies to modify the wrapper function. + @type wrapperfunc: L{wrapper_code_manager_t} + @ivar virtualfunc: The L{code manager<code_manager_t>} object that manages the virtual function. This is used by the arg policies to modify the virtual function. + @type virtualfunc: L{code_manager_t} + + @group Methods called by the user of the class: append_code_block, subst_wrapper, subst_virtual, get_includes + @group Methods called by the arg policies: remove_arg, insert_arg, require_include + + @author: Matthias Baas + """ + + def __init__(self, decl, wrapperclass=None, transformers=None): + """Constructor. + + @param decl: calldef declaration + @type decl: calldef_t + @param wrapperclass: The name of the class the generated function should belong to (or None if the generated function should be a free function) + @type wrapperclass: str + @param transformers: Function transformer objects + @type transformers: list of function_transformer_t + """ + + # Code manager for the virtual function + self.virtualfunc = code_manager_t() + # Code manager for the wrapper function + self.wrapperfunc = wrapper_code_manager_t() + + # The declaration that represents the original C++ function + self.decl = decl + + # The function transformers + if transformers==None: + transformers = [] + self.transformers = transformers + + self.wrapperclass = wrapperclass + + # A list of required include files + self._virtual_includes = [] + self._wrapper_includes = [] + + # Initialize the code managers... + + if str(decl.return_type)=="void": + rettype = None + else: + rettype = decl.return_type + self.wrapperfunc.resultvar = self.wrapperfunc.declare_local("result", str(rettype)) + self.wrapperfunc.resultexprs = [self.wrapperfunc.resultvar] + + self.virtualfunc.rettype = rettype + self.virtualfunc.arglist = decl.arguments[:] + self.virtualfunc.classname = wrapperclass + self.virtualfunc.FUNCNAME = decl.name + self.virtualfunc.CALLFUNCNAME = decl.name + self.virtualfunc.inputparams = map(lambda a: a.name, decl.arguments) + + self.wrapperfunc.rettype = rettype + self.wrapperfunc.arglist = decl.arguments[:] + self.wrapperfunc.classname = wrapperclass + self.wrapperfunc.FUNCNAME = "%s_wrapper"%decl.alias + self.wrapperfunc.CALLFUNCNAME = decl.name + self.wrapperfunc.inputparams = map(lambda a: a.name, decl.arguments) + + # Check if we're dealing with a member function... + clsdecl = self._classDecl(decl) + if clsdecl!=None: + selfname = self.wrapperfunc._make_name_unique("self") + selfarg = declarations.argument_t(selfname, "%s&"%clsdecl.name) + self.wrapperfunc.arglist.insert(0, selfarg) + self.wrapperfunc.CALLFUNCNAME = "%s.%s"%(selfname, self.wrapperfunc.CALLFUNCNAME) + + # Argument index map + # Original argument index ---> Input arg index (indices are 0-based!) + # Initial state is the identity: f(x) = x + # The argument index map represents a function that maps the argument + # index of the original C++ function to the index of the corresponding + # parameter in the input parameter list for the Python call. + self.argidxmap = range(len(decl.arguments)) + + + # Flag that is set after the functions were initialized + self._funcs_initialized = False + + + def append_transformer(self, transformer): + """not yet implemented""" + pass + + # init_funcs + def init_funcs(self): + """Initialize the virtual function and the wrapper function. + + After this method has been called, the substitution variables + are ready for usage. + + It is not necessary to call this method manually, it is + automatically called at the time a substitution is requested. + """ + + # Append the default return_virtual_result_t code modifier + transformers = self.transformers+[return_virtual_result_t()] + + for cb in transformers: + if hasattr(cb, "init_funcs"): + cb.init_funcs(self) + + # Create a variable that will hold the result of the Python call + # inside the virtual function. + if len(self.wrapperfunc.resultexprs)>0: + self.virtualfunc.resultvar = self.virtualfunc.declare_local("pyresult", "boost::python::object") +# self.virtualfunc.resultexpr = self.virtualfunc.resultvar + + self.wrapperfunc.init_variables() + self.virtualfunc.init_variables() + + self._funcs_initialized = True + + # The default method which is used when a particular method from + # the code_base_t interface is not implemented + defmeth = lambda x: None + # Create the wrapper function pre-call block... + src = map(lambda cb: getattr(cb, "wrapper_pre_call", defmeth)(self), transformers) + src = filter(lambda x: x!=None, src) + precall = "\n\n".join(src) + self.wrapperfunc.PRECALL = precall + + # Create the wrapper function post-call block... + src = map(lambda cb: getattr(cb, "wrapper_post_call", defmeth)(self), transformers) + src = filter(lambda x: x!=None, src) + src.reverse() + postcall = "\n\n".join(src) + self.wrapperfunc.POSTCALL = postcall + + # Create the virtual function pre-call block... + src = map(lambda cb: getattr(cb, "virtual_pre_call", defmeth)(self), transformers) + src = filter(lambda x: x!=None, src) + precall = "\n\n".join(src) + self.virtualfunc.PRECALL = precall + + # Create the virtual function post-call block... + src = map(lambda cb: getattr(cb, "virtual_post_call", defmeth)(self), transformers) + src = filter(lambda x: x!=None, src) + src.reverse() + postcall = "\n\n".join(src) + self.virtualfunc.POSTCALL = postcall + + + # remove_arg + def remove_arg(self, idx): + """Remove an argument from the wrapper function. + + This function can also be used to remove the original return value + (idx=0). + + The function is supposed to be called by function transformer + objects. + + @param idx: Argument index of the original function (may be negative) + @type idx: int + @returns: Returns the argument_t object that was removed (or None + if idx is 0 and the function has no return type). You must not + modify this object as it may still be in use on the virtual + function. + @rtype: argument_t + """ + if self._funcs_initialized: + raise ValueError, "remove_arg() may only be called before function initialization." + if idx<0: + idx += len(self.virtualfunc.arglist)+1 + if idx>=len(self.virtualfunc.arglist)+1: + raise IndexError, "Index (%d) out of range."%idx + + # Remove original return type? + if idx==0: + if id(self.wrapperfunc.rettype)==id(self.wrapperfunc.rettype): + self.wrapperfunc.rettype = None + else: + raise ValueError, 'Argument %d not found on the wrapper function'%(idx) + # Remove argument... + else: + # Get the original argument... + arg = self.virtualfunc.arglist[idx-1] + # ...and remove it from the wrapper + try: + self.wrapperfunc.arglist.remove(arg) + except ValueError: + raise ValueError, 'Argument %d ("%s") not found on the wrapper function'%(idx, arg.name) + + # Remove the input parameter on the Python call in the + # virtual function. + paramidx = self.argidxmap[idx-1] + if paramidx==None: + raise ValueError, "Argument was already removed" + del self.virtualfunc.inputparams[paramidx] + self.argidxmap[idx-1] = None + for i in range(idx,len(self.argidxmap)): + if self.argidxmap[i]!=None: + self.argidxmap[i] -= 1 + + return arg + + # insert_arg + def insert_arg(self, idx, arg): + """Insert a new argument into the argument list of the wrapper function. + + This function is supposed to be called by function transformer + objects. + + @param idx: New argument index (may be negative) + @type idx: int + @param arg: New argument object + @type arg: argument_t + """ + if self._funcs_initialized: + raise ValueError, "insert_arg() may only be called before function initialization." + if idx==0: + pass + else: + if idx<0: + idx += len(self.wrapperfunc.arglist)+2 + self.wrapperfunc.arglist.insert(idx-1, arg) + + # What to insert? + self.virtualfunc.inputparams.insert(idx-1, "???") + # Adjust the argument index map + for i in range(idx-1,len(self.argidxmap)): + if self.argidxmap[i]!=None: + self.argidxmap[i] += 1 + + # require_include + def require_include(self, include, where=None): + """Declare an include file that is required for the code to compile. + + This function is supposed to be called by function transformer + objects to tell the substitution manager that they create code + that requires a particular header file. + + include is the name of the include file which may contain <> or "" + characters around the name. + + @param include: The name of the include file (may contain <> or "") + @type include: str + @param where: "wrapper", "virtual" or None (for both) + @type where: str + """ + if where not in ["wrapper", "virtual", None]: + raise ValueError, "Invalid 'where' argument: %s"%where + + if include=="": + return + + # Add apostrophes if there aren't any already + if include[0] not in '"<': + include = '"%s"'%include + + if where=="wrapper" or where==None: + if include not in self._wrapper_includes: + self._wrapper_includes.append(include) + + if where=="virtual" or where==None: + if include not in self._virtual_includes: + self._virtual_includes.append(include) + + # get_includes + def get_includes(self, where=None): + """Return a list of include files required for the wrapper and/or the virtual function. + + @param where: "wrapper", "virtual" or None (for a combined list) + @type where: str + @return: A list of include file names (all names contain <> or "") + @rtype: list of str + """ + if where not in ["wrapper", "virtual", None]: + raise ValueError, "Invalid 'where' argument: %s"%where + + if where=="wrapper": + return self._wrapper_includes[:] + + if where=="virtual": + return self._virtual_includes[:] + + # Merge both lists (without duplicating names) + res = self._virtual_includes[:] + for inc in self._wrapper_includes: + if inc not in res: + res.append(inc) + + return res + + # subst_virtual + def subst_virtual(self, template): + """Perform a text substitution using the "virtual" variable set. + + @return: Returns the input string that has all variables substituted. + @rtype: str + """ + if not self._funcs_initialized: + self.init_funcs() + return self.virtualfunc.substitute(template) + + # subst_wrapper + def subst_wrapper(self, template): + """Perform a text substitution using the "wrapper" variable set. + + @return: Returns the input string that has all variables substituted. + @rtype: str + """ + if not self._funcs_initialized: + self.init_funcs() + return self.wrapperfunc.substitute(template) + + # _classDecl + def _classDecl(self, decl): + """Return the class declaration that belongs to a member declaration. + """ + while decl.parent!=None: + parent = decl.parent + if isinstance(parent, declarations.class_t): + return parent + decl = parent + return None + + +# return_virtual_result_t +class return_virtual_result_t(function_transformer_t): + """Extract and return the result value of the virtual function. + + This is an internal code block object that is automatically appended + to the list of code blocks inside the substitution_manager_t class. + """ + + def __init__(self): + function_transformer_t.__init__(self) + self.resultvar = "<not initialized>" + + def __str__(self): + return "ReturnVirtualResult()"%(self.idx) + + def init_funcs(self, sm): + if sm.virtualfunc.rettype==None: + return + + # Declare the local variable that will hold the return value + # for the virtual function + self.resultvar = sm.virtualfunc.declare_local("result", sm.virtualfunc.rettype) + # Replace the result expression if there is still the default + # result expression (which will not work anyway) + if sm.virtualfunc.resultexpr==sm.virtualfunc.resultvar: + sm.virtualfunc.resultexpr = self.resultvar + + def virtual_post_call(self, sm): + # Search the result tuple of the wrapper function for the return + # value of the C++ function call. If the value exists it is extracted + # from the Python result tuple, converted to C++ and returned from + # the virtual function. If it does not exist, do nothing. + try: + resultidx = sm.wrapperfunc.resultexprs.index(sm.wrapperfunc.resultvar) + except ValueError: + return + + res = "// Extract the C++ return value\n" + res += "%s = boost::python::extract<%s>(%s[%d]);"%(self.resultvar, sm.virtualfunc.rettype, sm.virtualfunc.resultvar, resultidx) + return res + + +###################################################################### +if __name__=="__main__": + import pyplusplus + from pygccxml import parser + from arg_policies import Output + cpp = """ + class Spam + { + public: + int foo(int& w, int* h, int mode=0); + }; + """ + parser = parser.project_reader_t(parser.config.config_t(), + decl_factory=pyplusplus.decl_wrappers.dwfactory_t()) + root = parser.read_string(cpp) + spam = root[0].class_("Spam") + foo = spam.member_function("foo") + + wm = substitution_manager_t(foo, transformers=[Output(1), Output(2)], wrapperclass="Spam_wrapper") + + template = '''$RETTYPE $CLASSSPEC$FUNCNAME($ARGLIST) +{ + $DECLARATIONS + + $PRECALL + + $RESULTVARASSIGNMENT$CALLFUNCNAME($INPUTPARAMS); + + $POSTCALL + + $RETURNSTMT +} +''' + print wm.subst_virtual(template) + print wm.subst_wrapper(template) + print wm.get_includes() + print wm.get_includes("virtual") + print wm.get_includes("wrapper") Property changes on: pyplusplus_dev/pyplusplus/function_transformers/substitution_manager.py ___________________________________________________________________ Name: svn:eol-style + native Modified: pyplusplus_dev/setup.py =================================================================== --- pyplusplus_dev/setup.py 2006-09-03 07:59:17 UTC (rev 506) +++ pyplusplus_dev/setup.py 2006-09-03 09:31:45 UTC (rev 507) @@ -107,6 +107,7 @@ 'pyplusplus.decl_wrappers', 'pyplusplus.module_builder', 'pyplusplus.utils', + 'pyplusplus.function_transformers', 'pyplusplus._logging_'], cmdclass = {"doc" : doc_cmd} ) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |