mirror of
https://github.com/triqs/dft_tools
synced 2024-12-27 23:03:51 +01:00
ed6379ce63
wrapper generator: add treatment of parameter class - methods taking a parameter class are called by **kw - the dict is passed to the C++ - the converters for the parameters are generated.
802 lines
41 KiB
Python
802 lines
41 KiB
Python
import sys
|
|
import re
|
|
import os
|
|
from mako.template import Template
|
|
import importlib
|
|
|
|
# the xxx_desc.py will always be called by cmake command, with the proper arguments
|
|
# analyse sys.argv right now : we need it early for the use_module
|
|
# The mako files for the wrapper and the header, and the file to write (_target)
|
|
wrapper_mako, wrapper_target, header_mako, header_target = sys.argv[1:5]
|
|
# Directories where to find the headers generated by other modules
|
|
module_path_list = sys.argv[5:]
|
|
|
|
# the correspondance c type -> py_type
|
|
c_to_py_type = {'void' : 'None', 'int' : 'int', 'long' : 'int', 'double' : "float", "std::string" : "str"}
|
|
|
|
# Translation for formatting of parsing converter.
|
|
basic_types_formatting = {'double' : 'd', 'int' : 'i'}
|
|
|
|
# Translate the name of the c++ type to the python type.
|
|
# for doc signatures.
|
|
def translate_c_type_to_py_type(t) :
|
|
if t in c_to_py_type : return c_to_py_type[t]
|
|
m = re.match('std::vector<(.*)>',t)
|
|
if m: return "list[%s]"%translate_c_type_to_py_type(m.group(1))
|
|
# numpy, etc...
|
|
return t
|
|
|
|
class cfunction :
|
|
"""
|
|
Representation of one overload of a C++ function or method.
|
|
"""
|
|
def __init__(self, signature, calling_pattern = None, no_self_c = False, is_constructor = False,
|
|
is_method = False, is_static = False, release_GIL_and_enable_signal = False, c_name = None, doc = '') :
|
|
"""
|
|
- signature :
|
|
signature of the function, with types, parameter names and defaut value
|
|
rtype( arg1 name1, arg2 name2 = default2, ....)
|
|
it can be :
|
|
- a string :
|
|
rtype (arg1 name1, arg2 name2 = default2, ....)
|
|
- a string :
|
|
rtype c_name ( arg1 name1, arg2 name2 = default2, ....)
|
|
- a dict [expert only] : rtype -> string , args -> list of tuples [ (c_type, variable_name, default_value)]
|
|
where rtype is the C++ type returned by the function.
|
|
|
|
- calling_pattern :
|
|
A string containing a piece of C++ code to call the C++ function.
|
|
This code can use :
|
|
- self_c : a reference to the C++ object (except for constructor, and static method).
|
|
- self_class : the name of the class of the C++ object (only for static method)
|
|
- the name of the parameters.
|
|
It should define a "result" variable :
|
|
- unless for a constructor or if the C++ return type is void.
|
|
- result shall be of any type from which the C++ return type is (move) constructible.
|
|
If calling_pattern is None, a default one is synthesized by the generator,
|
|
assuming the C++ function has exactly the signature given by the signature parameter of this function
|
|
including the c_name in it (which is then mandatory).
|
|
|
|
- no_self_c : boolean. do not generate self_c reference in C++ code, in some rare calling_pattern. Avoid a compiler warning.
|
|
- is_constructor : boolean
|
|
- is_method : boolean
|
|
- is_static : boolean. If True, it is a static method
|
|
- release_GIL_and_enable_signal [expert only] :
|
|
- For long functions in pure C++.
|
|
- If True, the GIL is released in the call of the C++ function and restored after the call.
|
|
- It also saves the signal handler of python and restores it after the call,
|
|
and enables the C++ triqs signal_handler.
|
|
- This allows e.g. to intercept Ctrl-C during the long C++ function.
|
|
- **Requirement** :
|
|
The function wrapped must be pure C++, i.e. no call whatsoever to the python C API, directly or indirectly.
|
|
otherwise the behaviour is undefined.
|
|
- doc : the doc string.
|
|
- c_name : Internal use only.
|
|
"""
|
|
self._calling_pattern = calling_pattern
|
|
self.is_constructor = is_constructor
|
|
self.no_self_c = no_self_c
|
|
self.doc = doc
|
|
self.is_method = is_method
|
|
self.is_static = is_static
|
|
self._dict_call = None
|
|
if is_static : assert is_method, "is_static only works with method"
|
|
self.release_GIL_and_enable_signal = release_GIL_and_enable_signal
|
|
assert isinstance(signature, str) or isinstance(signature, dict), "Signature must be a string of a dict: cf doc"
|
|
self.c_name = c_name # Not none for internal call only
|
|
## Analyse signature.
|
|
self.args = []
|
|
if isinstance(signature, str) : # it is a string, we analyse it to get the rtype, and args
|
|
signature = re.sub('operator\(\s*\)','__operator_call',signature) # temp. replacement, to make the regex easier
|
|
m = re.match(r"\s*(.*?)\s*\((.*)\)",signature)
|
|
self.rtype, args = m.group(1).strip() or None, m.group(2).strip()
|
|
# extract the c_name if present
|
|
if self.rtype :
|
|
spl = self.rtype.strip().rsplit(' ',1)
|
|
if not is_constructor and len(spl)> 1 and '>' not in spl[-1] :
|
|
self.rtype, self.c_name = spl
|
|
if self.c_name == '__operator_call' : self.c_name = "operator()"
|
|
if args.strip().startswith("**") : # special case : dict call
|
|
assert calling_pattern is None, "When ** is given as argument, no calling pattern can be provided"
|
|
self._dict_call = args[2:]
|
|
args, self.args = '','' # no argument
|
|
def f(): # analyse the argument, be careful that , can also be in type, like A<B,C>, so we count the < >
|
|
acc = ''
|
|
for s in args.split(',') :
|
|
acc += (',' if acc else '') + s.strip()
|
|
if acc.count('<') == acc.count('>') :
|
|
r, acc = acc,''
|
|
yield r
|
|
def g(a) :
|
|
if '=' in a :
|
|
l,r = a.split('=')
|
|
return l.strip().rsplit(' ') + [r]
|
|
else :
|
|
return a.rsplit(' ',1)
|
|
#args = [ re.sub('=',' ',x).split() for x in f() if x] # list of (type, name, default) or (type, name)
|
|
args = [ g(x) for x in f() if x] # list of (type, name, default) or (type, name)
|
|
else :
|
|
self.rtype = signature.pop("rtype", None)
|
|
args = signature.pop('args',())
|
|
self.c_name = signature.pop("c_name", '')
|
|
for a in args: # put back the default if there is none
|
|
# treat the case when the type is const T *, or T* (e.g. const char *).
|
|
# Need to regroup the first pieces.
|
|
if a[0] == 'const' : a = [' '.join(a[:2])] + list(a[2:])
|
|
if a[1] == '*' : a = [' '.join(a[:2])] + list(a[2:])
|
|
if len(a) == 2 : (t,n),d = a,None
|
|
elif len(a) == 3 : t,n,d = a
|
|
else : raise RuntimeError, "Syntax error in overload: args = %s"%args
|
|
self.args.append([t.strip(),n.strip(),d])
|
|
# end analyze signature
|
|
assert self.c_name or self._calling_pattern or self.is_constructor, "You must specify a calling_pattern or the signature must contain the name of the function"
|
|
if self.is_constructor :
|
|
assert self.rtype == None, "Constructor must not have a return type"
|
|
self.is_method = False
|
|
|
|
def _get_calling_pattern(self) :
|
|
"""Generation only: gets the calling_pattern or synthesize the default"""
|
|
if self._calling_pattern : return self._calling_pattern
|
|
s1 = '' if self._dict_call is None else "if (!convertible_from_python<%s>(keywds,true)) goto error_return;\n"%self._dict_call
|
|
s = "%s result = "%self.rtype if self.rtype != "void" else ""
|
|
self_c = ""
|
|
if self.is_method:
|
|
self_c = "self_c." if not self.is_static else "self_class::"
|
|
# the wrapped types are called by pointer
|
|
args = ",".join([ ('*' if t in module_._wrapped_types else '') + n for t,n,d in self.args])
|
|
args = args if self._dict_call is None else "convert_from_python<%s>(keywds)"%self._dict_call # call with the keywds argument
|
|
return "%s %s %s%s(%s)"%(s1, s,self_c, self.c_name , args)
|
|
|
|
def _get_signature (self):
|
|
"""Signature for the python doc"""
|
|
rtype = translate_c_type_to_py_type(self.rtype) if self.rtype else ''
|
|
args_rep = ", ".join(["%s %s%s"%(translate_c_type_to_py_type(t),n,' = ' + str(d) if d else '') for t,n,d in self.args])
|
|
return "({args_rep}) -> {rtype}".format(**locals())
|
|
|
|
def _get_c_signature (self):
|
|
"""Signature for the C++ calling errors"""
|
|
name = self.c_name if self.c_name else "(no C++ name)"
|
|
rtype = self.rtype if self.rtype else ''
|
|
args_rep = ", ".join(["%s %s"%(t,n) for t,n,d in self.args])
|
|
return "{name}({args_rep}) -> {rtype}".format(**locals())
|
|
|
|
def __repr__(self):
|
|
return "C++ function of signature : %s"%(self._get_signature())
|
|
|
|
def _parsing_format(self) :
|
|
"""Generation only: the formatting for the PyParse_xxx calls"""
|
|
def f(t) :
|
|
return basic_types_formatting[t] if t in basic_types_formatting else 'O&'
|
|
l1 = [ f(t) for t,n,d in self.args if d==None]
|
|
l2 = [ f(t) for t,n,d in self.args if d!=None]
|
|
if l2 : l2.insert(0,'|') # starts the default arguments, cf python doc
|
|
return ''.join(l1 + l2)
|
|
|
|
def _generate_doc(self) :
|
|
doc = "\n".join([ " " + x.strip() for x in self.doc.split('\n')])
|
|
doc = doc.replace('"',"'") # the " are replaced by \"r.
|
|
#doc = doc.replace('"',r'\"') # the " are replaced by \"r. Does not work, makes \\"
|
|
if self._dict_call is not None : return doc
|
|
return "Signature : %s\n%s"%( self._get_signature(),doc)
|
|
|
|
class pure_pyfunction_from_module :
|
|
"""
|
|
Representation of one python function defined in Python code in an external module.
|
|
Will be use to make a pure python method of an object, or or a module.
|
|
Data :
|
|
- name : name given in Python
|
|
- doc : the doc string.
|
|
- module : module path to the function [pure python only]
|
|
"""
|
|
def __init__(self, name, module) :
|
|
""" """
|
|
self.py_name, self.module = name, module
|
|
try :
|
|
m = __import__(module.rsplit('.')[-1])
|
|
f = getattr(m,name)
|
|
self.doc = f.__doc__ # get the doc and check the function can be loaded.
|
|
except :
|
|
print " I cannot import the function %s from the module %s"%(name,module)
|
|
raise
|
|
|
|
def _generate_doc(self) :
|
|
return self.doc
|
|
|
|
class python_function:
|
|
"""
|
|
A python function, given as a function.
|
|
Its code gets analysed and will be put into the C++ wrapper, to avoid import.
|
|
"""
|
|
def __init__(self, name, f) :
|
|
""" """
|
|
self.name, self.f = name,f
|
|
import inspect as ins
|
|
self.code = "\n".join(['"%s\\n"'%line.rstrip().replace('"', '\\"') for line in ins.getsourcelines(self.f)[0]])
|
|
self.doc = f.__doc__ # UNUSED AT THE MOMENT ??? REALLY ???
|
|
|
|
class pyfunction :
|
|
"""
|
|
Representation of one python function of the extension
|
|
It is basically :
|
|
- a python name
|
|
- a list of overload
|
|
- possibly some preprocessing/postprocessing python code.
|
|
"""
|
|
def __init__(self, name, arity = None, is_method = False, is_static = False, doc = '', python_precall = None, python_postcall = None):
|
|
"""
|
|
- name : name given in Python
|
|
- arity : arity of the function
|
|
- is_method : boolean
|
|
- is_static : boolean. Is is a static method
|
|
- doc : the doc string.
|
|
- overloads : a list of cfunction objects representing the various C++ overloads of the function
|
|
- python_precall : a python function_ to be called before the call of the C++ function
|
|
The function must take F(*args, **kw) and return (args, kw)
|
|
- python_postcall : a python function_ to be called after the call of the C++ function
|
|
The function must take a python object, and return one...
|
|
"""
|
|
self.py_name =name # name given in python
|
|
self.arity = arity
|
|
self.is_method = is_method # can be a method, a function...
|
|
self.is_static = is_static #
|
|
self.doc = doc
|
|
def analyse(f):
|
|
return python_function(f.__name__, f) if callable(f) else f
|
|
self.python_precall, self.python_postcall = analyse(python_precall), analyse(python_postcall)
|
|
self.overloads = [] # List of all C++ overloads
|
|
self.do_implement = True # in some cases, we do not want to implement it automatically, (special methods).
|
|
self.is_constructor = False
|
|
|
|
def add_overload(self, **kw) :
|
|
self.overloads.append(cfunction(**kw))
|
|
|
|
def _generate_doc(self) :
|
|
if len(self.overloads) == 1 : #only one overload
|
|
s = "\n".join([f._generate_doc() for f in self.overloads])
|
|
else :
|
|
s = "\n".join([self.doc, "\n"] + [f._generate_doc() for f in self.overloads])
|
|
return repr(s)[1:-1] # remove the ' ' made by repr
|
|
|
|
def _is_type_a_view(c_type) :
|
|
return c_type.split('<', 1)[0].endswith("_view") # A bit basic ?
|
|
|
|
def _regular_type_if_view_else_type(c_type) :
|
|
return "typename %s::regular_type"%c_type if _is_type_a_view(c_type) else c_type
|
|
|
|
class class_ :
|
|
"""
|
|
Representation of a wrapped type
|
|
"""
|
|
hidden_python_function = {} # global dict of the python function to add to the module, hidden for the user, for precompute and so on
|
|
def __init__(self, py_type, c_type, c_type_absolute = None, hdf5 = False, arithmetic = None, serializable = None, is_printable = False, doc = '' ) :
|
|
"""
|
|
- py_type : Name given in Python
|
|
- c_type : C++ type to be wrapped.
|
|
- c_type_absolute : full path of c_type, no using, no alias (need for the py_converter hpp file)
|
|
- hdf5 : generate the hdf5 write/read function from C++ triqs hdf5 protocol and register them in the hdf_archive
|
|
- arithmetic : determines the operations to be implemented.
|
|
- The idea is to give an abstract description of the mathematical structure to be implemented :
|
|
an algebra, a group, a vector space, and so on.
|
|
The generator will then implement all necessary functions, by calling their C++ counterparts.
|
|
- Possible values :
|
|
- ("abelian_group") : implements + and -
|
|
- ("vector_space", Scalar) : implements a vector_space, with scalar Scalar
|
|
- ("algebra", Scalar) : implements an algebra, with scalar Scalar
|
|
- ("algebra_with_unit", with_options..., Scalars...) :
|
|
implements an algebra, with:
|
|
- scalars Scalar... : the scalars
|
|
- with_options is (possibly empty) list of options :
|
|
- with_unit : +/- of an element with a scalar (injection of the scalar with the unit)
|
|
- with_unary_minus : implement unary minus
|
|
- "add_only" : implements only +
|
|
- with_inplace_operators : option to deduce the +=, -=, ...
|
|
operators from +,-, .. It deduces the possibles terms to put at the rhs, looking at the
|
|
case of the +,- operators where the lhs is of the type of self.
|
|
NB : The operator is mapped to the corresponding C++ operators (for some objects, this may be faster)
|
|
so it has to be defined in C++ as well....
|
|
- .... more to be defined.
|
|
- serializable : Whether and how the object is to be serialized. Possible values are :
|
|
- "tuple" : reduce it to a tuple of smaller objects, using the
|
|
boost serialization compatible template in C++, and the converters of the smaller objects.
|
|
- "via_string" : serialize via a string, made by
|
|
triqs::serialize/triqs::deserialize
|
|
On modern hdf5 (>1.8.9) it uses hdf5 to make the string, on older version it will use boost serialization
|
|
(which generates a very heavy code, sometimes can x2 the code size of the wrapper, just for this function !).
|
|
- is_printable = If true, generate the str, repr from the C++ << stream operator
|
|
- doc : the doc string.
|
|
"""
|
|
self.c_type = c_type
|
|
self.c_type_absolute = c_type_absolute or c_type
|
|
self.c_type_is_view = _is_type_a_view(c_type)
|
|
self.implement_regular_type_converter = self.c_type_is_view # by default, it will also make the converter of the associated regular type
|
|
if self.c_type_is_view :
|
|
self.regular_type = 'typename ' + self.c_type + '::regular_type'
|
|
self.regular_type_absolute = 'typename ' + self.c_type_absolute + '::regular_type'
|
|
self.py_type = py_type
|
|
c_to_py_type[self.c_type] = self.py_type # register the name translation for the doc generation
|
|
self.hdf5 = hdf5
|
|
assert serializable in [None, "via_string", "tuple"]
|
|
self.serializable = serializable
|
|
self.is_printable = is_printable
|
|
self.iterator = None
|
|
self.doc = doc
|
|
self.methods = {} # a dict : string -> pyfunction for each method name
|
|
self.pure_python_methods= {}
|
|
self.constructor = None # a pyfunction for the constructors.
|
|
self.members= [] # a list of _member
|
|
self.properties= [] # a list of _property
|
|
|
|
# Init arithmetic
|
|
# expect a tuple : "algebra", "scalar1", "scalar2", etc...
|
|
self.number_protocol = {}
|
|
if arithmetic :
|
|
if not isinstance(arithmetic, tuple) : arithmetic = (arithmetic,)
|
|
# read the with_... option and clean them for the list
|
|
with_unary_minus = 'with_unary_minus' in arithmetic
|
|
with_unit = 'with_unit' in arithmetic
|
|
with_inplace_operators = 'with_inplace_operators' in arithmetic
|
|
arithmetic = [x for x in arithmetic if not x.startswith("with_")]
|
|
add = arithmetic[0] in ("algebra", "abelian_group", "vector_space", "only_add")
|
|
abelian_group = arithmetic[0] in ("algebra", "abelian_group", "vector_space")
|
|
vector_space = arithmetic[0] in ("algebra", "vector_space")
|
|
algebra = arithmetic[0] in ("algebra",)
|
|
|
|
if add :
|
|
# add
|
|
add = pyfunction(name ="__add__",arity = 2)
|
|
add.add_overload (calling_pattern = "+", signature = {'args' : [(self.c_type,'x'), (self.c_type,'y')], 'rtype' :self.c_type})
|
|
self.number_protocol['add'] = add
|
|
|
|
if abelian_group :
|
|
#sub
|
|
sub = pyfunction(name ="__sub__",arity = 2)
|
|
sub.add_overload (calling_pattern = "-", signature = {'args' :[(self.c_type,'x'), (self.c_type,'y')], 'rtype' : self.c_type})
|
|
self.number_protocol['subtract'] = sub
|
|
|
|
if vector_space :
|
|
# mul
|
|
mul = pyfunction(name ="__mul__", arity = 2)
|
|
for scalar in arithmetic[1:] :
|
|
mul.add_overload (calling_pattern = "*", signature = {'args' :[(self.c_type,'x'), (scalar,'y')], 'rtype' : self.c_type})
|
|
mul.add_overload (calling_pattern = "*", signature = {'args' :[(scalar,'x'), (self.c_type,'y')], 'rtype' : self.c_type})
|
|
self.number_protocol['multiply'] = mul
|
|
# div
|
|
div = pyfunction(name ="__div__", arity = 2)
|
|
for scalar in arithmetic[1:] :
|
|
div.add_overload (calling_pattern = "/", signature = {'args' :[(self.c_type,'x'), (scalar,'y')], 'rtype' : self.c_type})
|
|
self.number_protocol['divide'] = div
|
|
|
|
if algebra :
|
|
mul.add_overload (calling_pattern = "*", signature = {'args' :[(self.c_type,'x'), (self.c_type,'y')], 'rtype' : self.c_type})
|
|
|
|
if with_unit: # Allow + and - between scalar and operator
|
|
assert algebra, "The with_unit option only makes sense for algebra"
|
|
for scal in arithmetic[1:] :
|
|
add = self.number_protocol['add']
|
|
add.add_overload (calling_pattern = "+", signature = {'args' :[(self.c_type,'x'), (scal,'y')], 'rtype' : self.c_type})
|
|
add.add_overload (calling_pattern = "+", signature = {'args' :[(scal,'x'), (self.c_type,'y')], 'rtype' : self.c_type})
|
|
sub = self.number_protocol['subtract']
|
|
sub.add_overload (calling_pattern = "-", signature = {'args' :[(self.c_type,'x'), (scal,'y')], 'rtype' : self.c_type})
|
|
sub.add_overload (calling_pattern = "-", signature = {'args' :[(scal,'x'), (self.c_type,'y')], 'rtype' : self.c_type})
|
|
|
|
if with_unary_minus :
|
|
# Allow unary - on an operator
|
|
neg = pyfunction(name = "__neg__", arity = 1)
|
|
neg.add_overload (calling_pattern = "-", signature = {'args' :[(self.c_type,'x')], 'rtype' : self.c_type})
|
|
self.number_protocol['negative'] = neg
|
|
|
|
if with_inplace_operators : self.deduce_inplace_arithmetic()
|
|
|
|
def deduce_inplace_arithmetic(self) :
|
|
"""Deduce all the +=, -=, *=, /= operators from the +, -, *, / operators"""
|
|
def one_op(op, name, iname) :
|
|
if name not in self.number_protocol : return
|
|
impl = pyfunction(name = iname, arity = 2)
|
|
for overload in self.number_protocol[name].overloads :
|
|
x_t,y_t = overload.args[0][0], overload.args[1][0]
|
|
if x_t == self.c_type : # only when first the object
|
|
impl.add_overload (calling_pattern = op+"=", signature = {'args' : [(x_t,'x'), (y_t,'y')], 'rtype' :overload.rtype})
|
|
self.number_protocol['inplace_'+name] = impl
|
|
one_op('+',"add","__iadd__")
|
|
one_op('-',"subtract","__isub__")
|
|
one_op('*',"multiply","__imul__")
|
|
one_op('/',"divide","__idiv__")
|
|
|
|
def add_constructor(self, signature, calling_pattern = None, python_precall = None, python_postcall = None, build_from_regular_type_if_view = True, doc = ''):
|
|
"""
|
|
- signature : signature of the function, with types, parameter names and defaut value
|
|
rtype( arg1 name1, arg2 name2 = default2, ....)
|
|
signature can be :
|
|
- a string of 2 possible forms (i.e. c_name can be omitted) :
|
|
- rtype (arg1 name1, arg2 name2 = default2, ....)
|
|
- rtype c_name ( arg1 name1, arg2 name2 = default2, ....)
|
|
- a dict : rtype -> string , args -> list of tuples [ (c_type, variable_name, default_value)]
|
|
- rtype : the C++ type returned by the function. None for constructor
|
|
default_value is None when there is no default.
|
|
- calling_pattern [expert only]:
|
|
- Pattern to rewrite the call of the c++ constructor.
|
|
- It is a string, argument name and defining a result of the c_type
|
|
e.g., the default pattern is ::
|
|
auto result = c_type (a,b,c)
|
|
- build_from_regular_type_if_view : boolean.
|
|
- If True, and the type is a view, the wrapper calls the C++ constructor *of the corresponding regular type*.
|
|
- If False, it simply calls the constructor the C++ type.
|
|
- This allows to construct object which are wrapped by view (like gf e.g.), by calling the
|
|
constructor of the regular type, much simpler, than the view.
|
|
- python_precall :
|
|
- A string of the type "module.function_name"
|
|
where function_name is a python function to be called before the call of the C++ function.
|
|
- It must take F(*args, **kw) and return (args, kw)
|
|
- python_postcall :
|
|
- A string of the type "module.function_name"
|
|
where function_name is a python function to be called after the call of the C++ function.
|
|
- The function must take a python object, and return one...
|
|
- doc : the doc string.
|
|
"""
|
|
f = cfunction(signature, calling_pattern = calling_pattern, is_constructor = True, is_method = True, doc = doc)
|
|
all_args = ",".join([ ('*' if t in module_._wrapped_types else '') + n for t,n,d in f.args])
|
|
f._calling_pattern = ''
|
|
if calling_pattern is not None :
|
|
f._calling_pattern, all_args = calling_pattern + ';\n', "std::move(result)"
|
|
if self.c_type_is_view and build_from_regular_type_if_view :
|
|
f._calling_pattern += "((%s *)self)->_c = new %s(%s (%s));"%(self.py_type, self.c_type,_regular_type_if_view_else_type(self.c_type),all_args)
|
|
else :
|
|
f._calling_pattern += "((%s *)self)->_c = new %s (%s);"%(self.py_type, self.c_type,all_args)
|
|
|
|
if not self.constructor :
|
|
self.constructor = pyfunction(name = "__init__", is_method = True, doc = doc, python_precall = python_precall, python_postcall = python_postcall)
|
|
self.constructor.is_constructor = True
|
|
self.constructor.overloads.append(f)
|
|
|
|
def add_method(self, signature, name =None, calling_pattern = None, no_self_c = False, is_method = False, is_static = False,
|
|
python_precall = None, python_postcall = None, doc = '', release_GIL_and_enable_signal = False, c_name = None):
|
|
"""
|
|
Add a C++ overload to a method of name name.
|
|
|
|
- signature : signature of the function, with types, parameter names and defaut value
|
|
rtype( arg1 name1, arg2 name2 = default2, ....)
|
|
signature can be :
|
|
- a string of 2 possible forms (i.e. c_name can be omitted) :
|
|
- rtype (arg1 name1, arg2 name2 = default2, ....)
|
|
- rtype c_name ( arg1 name1, arg2 name2 = default2, ....)
|
|
- a dict : rtype -> string , args -> list of tuples [ (c_type, variable_name, default_value)]
|
|
- rtype : the C++ type returned by the function. None for constructor
|
|
default_value is None when there is no default.
|
|
- name : name given in Python
|
|
If None, the C++ name extracted from the signature is used.
|
|
- calling_pattern :
|
|
- Pattern to rewrite the call of the c++ function,
|
|
- It is a string, using self_c, argument name and defining result at the end if rtype != void
|
|
e.g., the default pattern is :
|
|
auto result = self_c.method_name(a,b,c).
|
|
- If None, the signature must contain c_name
|
|
- no_self_c : boolean. do not generate self_c reference in C++ code, in
|
|
some rare calling_pattern. Avoid a compiler warning.
|
|
- is_method : boolean
|
|
- is_static : boolean. Is is a static method
|
|
- python_precall :
|
|
- A string of the type "module.function_name"
|
|
where function_name is a python function to be called before the call of the C++ function.
|
|
- It must take F(*args, **kw) and return (args, kw)
|
|
- python_postcall :
|
|
- A string of the type "module.function_name"
|
|
where function_name is a python function to be called after the call of the C++ function.
|
|
- The function must take a python object, and return one...
|
|
- doc : the doc string.
|
|
- release_GIL_and_enable_signal [expert only] :
|
|
- For long functions in pure C++.
|
|
- If True, the GIL is released in the call of the C++ function and restored after the call.
|
|
- It also saves the signal handler of python and restores it after the call,
|
|
and enables the C++ triqs signal_handler.
|
|
- This allows e.g. to intercept Ctrl-C during the long C++ function.
|
|
- **Requirement** :
|
|
The function wrapped must be pure C++, i.e. no call whatsoever to the python C API, directly or indirectly.
|
|
otherwise the behaviour is undefined.
|
|
"""
|
|
f = cfunction(signature, calling_pattern = calling_pattern, no_self_c = no_self_c, is_constructor = False,
|
|
is_method = True, is_static = is_static, release_GIL_and_enable_signal = release_GIL_and_enable_signal, doc = doc, c_name = c_name or name)
|
|
name = name or f.c_name
|
|
if name not in self.methods :
|
|
self.methods[name] = pyfunction(name = name, is_method = True, is_static = is_static, doc = doc, python_precall = python_precall, python_postcall = python_postcall)
|
|
self.methods[name].overloads.append(f)
|
|
|
|
def add_call(self, **kw) :
|
|
"""
|
|
Add the __call__ operator.
|
|
|
|
It just call add_method, for the operator(), with name = "__call__"
|
|
|
|
Cf add_method documentation.
|
|
"""
|
|
if 'c_name' not in kw and 'calling_pattern' not in kw : kw['c_name']= "operator()"
|
|
self.add_method(name = "__call__", **kw)
|
|
|
|
class _iterator :
|
|
def __init__(self,c_type, c_cast_type, begin, end) :
|
|
self.c_type, self.c_cast_type, self.begin, self.end = c_type, c_cast_type, begin, end
|
|
|
|
def add_iterator(self, c_type = "const_iterator", c_cast_type = None, begin = "std::begin", end = "std::end") :
|
|
"""
|
|
Add an iterator, wrapping a C++ iterator.
|
|
Parameters :
|
|
- c_type : type of the C++ variable
|
|
- c_cast_type : If not None, the result of the C++ iterator dereference if converted to the cast_type.
|
|
- begin, end :
|
|
"""
|
|
self.iterator = self._iterator(c_type, c_cast_type, begin, end)
|
|
|
|
def add_pure_python_method(self, f, rename = None):
|
|
"""
|
|
Add a method name (or an overload of method name).
|
|
f can be :
|
|
- a string module1.module2.fnt_name
|
|
- a function in python
|
|
"""
|
|
def process_doc(doc) :
|
|
return doc.replace('\n','\\n') if doc else ''
|
|
if type(f) ==type('') :
|
|
module, name = f.rsplit('.',1)
|
|
try :
|
|
m = __import__(module.rsplit('.')[-1])
|
|
doc = m.__dict__[name].__doc__
|
|
except :
|
|
raise
|
|
self.pure_python_methods[rename or name] = pure_pyfunction_from_module(name = name, module = module), 'module', process_doc(doc)
|
|
elif callable(f) :
|
|
assert rename == None
|
|
self.hidden_python_function[f.__name__] = f
|
|
self.pure_python_methods[f.__name__] = f.__name__, 'inline', process_doc(f.__doc__)
|
|
else : raise ValueError, "argument f must be callable or a string"
|
|
|
|
class _member :
|
|
def __init__(self, c_name, c_type, py_name, read_only, doc) :
|
|
self.c_name, self.c_type, self.py_name, self.doc, self.read_only = c_name, c_type, py_name or c_name, doc, read_only
|
|
|
|
def add_member(self, c_name, c_type, py_name = None, read_only = False, doc = ''):
|
|
"""
|
|
Add a class member
|
|
Parameters :
|
|
- c_name : name of the variable in C++
|
|
- c_type : type of the C++ variable
|
|
- py_name : name of the variable in python. If None, use c_name.
|
|
- read_only : bool
|
|
- doc : the doc string.
|
|
"""
|
|
self.members.append( self._member(c_name, c_type, py_name, read_only, doc))
|
|
|
|
class _property :
|
|
def __init__(self, name, getter, setter, doc) :
|
|
self.name, self.getter, self.setter, self.doc = name, getter, setter, doc
|
|
|
|
def add_property(self, getter, setter = None, name = None, doc = ''):
|
|
"""
|
|
Add a property
|
|
Parameters :
|
|
- getter : the cfunction representing the get part
|
|
- setter : the cfunction representing the set part or None if the property if read only
|
|
- name : name in python. If None, try to use the C++ name of the getter.
|
|
- doc : the doc string.
|
|
"""
|
|
if not isinstance(getter, str) : getter.is_method = True
|
|
self.properties.append( self._property(name or getter.c_name, getter, setter, doc) )
|
|
|
|
def add_len(self, c_name = None, calling_pattern = None, doc = "Length") :
|
|
"""
|
|
Add the len operator
|
|
"""
|
|
if not c_name and not calling_pattern : c_name = "size"
|
|
self.add_method(name = "__len__impl", calling_pattern = calling_pattern, signature="int %s()"%c_name, doc= doc)
|
|
self.methods['__len__impl'].do_implement = False # do not implement automatically, the signature is special
|
|
|
|
def add_getitem(self, signature, calling_pattern = None, doc = "operator[]" ) :
|
|
"""
|
|
Add a the __getitem__ operator
|
|
"""
|
|
self.add_method(name = "__getitem__impl", calling_pattern = calling_pattern, doc = doc, signature = signature, c_name = "operator[]")
|
|
|
|
def add_setitem(self, signature, calling_pattern = None, doc = "operator[]", **d ) :
|
|
"""
|
|
Add a the __setitem__ operator
|
|
"""
|
|
self.add_method(name = "__setitem__impl", calling_pattern = calling_pattern or "self_c[i] = v", doc = doc, signature = signature, **d)
|
|
|
|
def add_method_copy(self) :
|
|
"""Add a method copy, that make a **deep** copy, using triqs::make_clone"""
|
|
self.add_method(name = "copy", calling_pattern = self.c_type + " result = triqs::make_clone(self_c)", signature = self.c_type +"()", doc = "Make a copy (clone) of self")
|
|
|
|
def add_method_copy_from(self) :
|
|
"""Add a copy_from, using C++ assignment"""
|
|
# other by pointer, it is necessarly a wrapped type
|
|
self.add_method(name = "copy_from", calling_pattern = " self_c = *other", signature = 'void(' + self.c_type +" other)", doc = "Assignment")
|
|
|
|
def _prepare_for_generation(self) :
|
|
"""Internal : Called just before the code generation"""
|
|
self.has_mapping_protocol = '__getitem__impl' in self.methods or '__len__impl' in self.methods
|
|
if '__setitem__impl' in self.methods and not '__getitem__impl' in self.methods : raise RuntimeError, "Cannot generate a class with a setter and no getter"
|
|
|
|
class module_ :
|
|
"""
|
|
Representation of a module
|
|
"""
|
|
_wrapped_types = []
|
|
|
|
def __init__(self, full_name, doc = '') :
|
|
"""
|
|
- full_name = complete name of the module (after install, e.g. pytriqs.gf.local.gf
|
|
- doc : doc string
|
|
"""
|
|
self.full_name = full_name
|
|
self.name = full_name.rsplit('.',1)[-1]
|
|
self.doc = doc
|
|
self.classes = {} # dict : string -> class_. Key is the Python type
|
|
self.functions = {} # functions : dict : string -> function_. Modules functions. Key is the python name.
|
|
self.include_list = []
|
|
self.enums = []
|
|
self.using =[]
|
|
self.python_functions = {}
|
|
self.hidden_python_functions = {}
|
|
self.module_path_list = []
|
|
self._preamble = ''
|
|
|
|
def add_class(self, cls):
|
|
"""
|
|
Add a class into the module.
|
|
It should not exist in the module already.
|
|
"""
|
|
if cls.py_type in self.classes : raise IndexError, "The class %s already exists"%cls.py_type
|
|
self.classes[cls.py_type] = cls
|
|
self._wrapped_types += [cls.c_type, cls.c_type_absolute] # we can call is by its name or its absolute name
|
|
|
|
def add_function(self, signature, name =None, calling_pattern = None, python_precall = None, python_postcall = None, doc = '', release_GIL_and_enable_signal = False):
|
|
"""
|
|
Add a C++ overload to function of the module
|
|
|
|
- signature : signature of the function, with types, parameter names and defaut value
|
|
rtype( arg1 name1, arg2 name2 = default2, ....)
|
|
signature can be :
|
|
- a string of 2 possible forms (i.e. c_name can be omitted) :
|
|
- rtype (arg1 name1, arg2 name2 = default2, ....)
|
|
- rtype c_name ( arg1 name1, arg2 name2 = default2, ....)
|
|
- a dict : rtype -> string , args -> list of tuples [ (c_type, variable_name, default_value)]
|
|
- rtype : the C++ type returned by the function. None for constructor
|
|
default_value is None when there is no default.
|
|
- name : name given in Python
|
|
If None, the C++ name extracted from the signature is used.
|
|
- calling_pattern :
|
|
- Pattern to rewrite the call of the c++ function,
|
|
- It is a string, using self_c, argument name and defining result at the end if rtype != void
|
|
e.g., the default pattern is :
|
|
auto result = self_c.method_name(a,b,c).
|
|
- If None, the signature must contain c_name
|
|
- python_precall :
|
|
- A string of the type "module.function_name"
|
|
where function_name is a python function to be called before the call of the C++ function.
|
|
- It must take F(*args, **kw) and return (args, kw)
|
|
- python_postcall :
|
|
- A string of the type "module.function_name"
|
|
where function_name is a python function to be called after the call of the C++ function.
|
|
- The function must take a python object, and return one...
|
|
- doc : the doc string.
|
|
- release_GIL_and_enable_signal [expert only] :
|
|
- For long functions in pure C++.
|
|
- If True, the GIL is released in the call of the C++ function and restored after the call.
|
|
- It also saves the signal handler of python and restores it after the call,
|
|
and enables the C++ triqs signal_handler.
|
|
- This allows e.g. to intercept Ctrl-C during the long C++ function.
|
|
- **Requirement** :
|
|
The function wrapped must be pure C++, i.e. no call whatsoever to the python C API, directly or indirectly.
|
|
otherwise the behaviour is undefined.
|
|
"""
|
|
f = cfunction(signature, calling_pattern = calling_pattern, release_GIL_and_enable_signal = release_GIL_and_enable_signal, doc = doc,c_name = name)
|
|
name = name or f.c_name
|
|
if name not in self.functions :
|
|
self.functions[name] = pyfunction(name = name, doc = doc, python_precall = python_precall, python_postcall = python_postcall)
|
|
self.functions[name].overloads.append(f)
|
|
|
|
def add_python_function(self, f, name = None, hidden = False) :
|
|
assert callable(f)
|
|
if not hidden :
|
|
self.python_functions[name or f.__name__] = python_function(name or f.__name__, f)
|
|
else :
|
|
self.hidden_python_functions[name or f.__name__] = python_function(name or f.__name__, f)
|
|
|
|
def add_include(self, *filenames) :
|
|
"""
|
|
Add the filenames as C++ include in the generated wrapper and header.
|
|
"""
|
|
self.include_list.extend(filenames)
|
|
|
|
def add_using(self,ns) :
|
|
"""
|
|
Add the using statement into the generated wrapper (and NOT the header).
|
|
"""
|
|
self.using.append(ns)
|
|
|
|
def add_preamble(self, preamble) :
|
|
"""
|
|
Add the using statement into the generated wrapper (and NOT the header).
|
|
"""
|
|
self._preamble += preamble + '\n'
|
|
|
|
def use_module(self, modulename) :
|
|
"""
|
|
From the name of the module :
|
|
- add the header file generated for this module to the C++ include list
|
|
- read this file, extract the list of _wrapped_types, and add it to the wrapped_type list.
|
|
"""
|
|
f = None
|
|
for path in module_path_list :
|
|
hppfile = path + '/' + modulename + '.hpp'
|
|
if os.path.exists(hppfile) :
|
|
f = open(hppfile ,'r')
|
|
break
|
|
if not f : raise RuntimeError, "Cannot find the module %s.\n ... module_path_list = %s"%(modulename, self.module_path_list)
|
|
|
|
while f.readline().strip() != "// WrappedTypeList" :
|
|
pass
|
|
l = f.readline()[3:] # // strip "// "
|
|
self._wrapped_types += eval(l)
|
|
self.add_include(hppfile)
|
|
#print "Loading triqs wrapped module %s"%modulename
|
|
#print " ... found C++ header file %s"%hppfile
|
|
#print " ... found wrapped types %s"%l
|
|
|
|
class _enum :
|
|
def __init__(self, c_name, values, c_namespace, doc) :
|
|
self.c_name, self.c_namespace, self.values, self.doc = c_name, c_namespace + "::", values, doc
|
|
self.c_name_absolute = self.c_namespace + self.c_name
|
|
|
|
def add_enum(self, c_name, values, c_namespace ="", doc = '') :
|
|
"""
|
|
Add an enum into the module.
|
|
Parameters :
|
|
- c_name : name in C++
|
|
- c_namespace: namespace of the enum
|
|
- values : list of string representing the C++ enum values
|
|
- doc : the doc string.
|
|
"""
|
|
self.enums.append( self._enum(c_name, values, c_namespace, doc))
|
|
|
|
def _get_proper_converter(self, t) :
|
|
if t in basic_types_formatting : return ''
|
|
if t in self._wrapped_types : return ',converter_for_parser_wrapped_type<'+t+'>'
|
|
if t.split('<',1)[0].endswith("_view") : return ',converter_for_parser_view_type<'+t+'>'
|
|
return ',converter_for_parser_non_wrapped_type<'+t+'>'
|
|
|
|
def _all_args_kw_functions(self) :
|
|
l = [ (f, self.name, None) for f in self.functions.values()]
|
|
for c in self.classes.values() :
|
|
l += [(m,c.py_type, c.c_type) for m in c.methods.values() if m.do_implement]
|
|
if c.constructor :
|
|
l.append( (c.constructor,c.py_type, c.c_type))
|
|
return l
|
|
|
|
def _prepare_for_generation(self) :
|
|
for c in self.classes.values() : c._prepare_for_generation()
|
|
for n,f in class_.hidden_python_function.items() :
|
|
self.add_python_function(f,name = n, hidden=True)
|
|
|
|
def _generate_wrapper_code(self, mako_template, wrap_file) :
|
|
self._prepare_for_generation()
|
|
tpl = Template(filename=mako_template)
|
|
rendered = tpl.render(module=self, regular_type_if_view_else_type= _regular_type_if_view_else_type, is_type_a_view = _is_type_a_view, python_function = python_function)
|
|
with open(wrap_file,'w') as f:
|
|
f.write(rendered)
|
|
|
|
def _generate_py_converter_header(self, mako_template, wrap_file) :
|
|
self._prepare_for_generation()
|
|
tpl = Template(filename=mako_template)
|
|
rendered = tpl.render(module=self)
|
|
with open(wrap_file,'w') as f:
|
|
f.write(rendered)
|
|
|
|
def generate_code(self) :
|
|
"""
|
|
Generate the wrapper and the header.
|
|
The filenames are given in the sys.argv
|
|
"""
|
|
self._generate_wrapper_code(mako_template = wrapper_mako, wrap_file = wrapper_target)
|
|
self._generate_py_converter_header(mako_template = header_mako, wrap_file = header_target)
|
|
|
|
|