+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+Pyste is a
+Boost.Python code generator. The user specifies the classes and
+functions to be exported using a simple interface file, which following the
+
+Boost.Python's philosophy, is simple Python code. Pyste then uses
+GCCXML to
+parse all the headers and extract the necessary information to automatically
+generate C++ code.
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+Even thought Pyste can identify various elements in the C++ code, like virtual
+methods, attributes, and so on, one thing that it can't do is to guess the
+semantics of functions that return pointers or references. In this case, the
+user must manually specify the policy. Policies are explained in the
+
+tutorial.
+
+The policies in Pyste are named exactly as in
+Boost.Python, only the syntax is
+slightly different. For instance, this policy:
+
+What if a function or method needs a policy and the user
+doesn't set one?
+If a function/method needs a policy and one was not set, Pyste will issue a error.
+The user should then go in the interface file and set the policy for it,
+otherwise the generated cpp won't compile.
+
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+
+
diff --git a/pyste/doc/pyste.txt b/pyste/doc/pyste.txt
new file mode 100644
index 00000000..65c2cec9
--- /dev/null
+++ b/pyste/doc/pyste.txt
@@ -0,0 +1,370 @@
+[doc Pyste Documentation]
+
+[def GCCXML [@http://www.gccxml.org GCCXML]]
+[def Boost.Python [@../../index.html Boost.Python]]
+
+[page Introduction]
+
+[h2 What is Pyste?]
+
+Pyste is a Boost.Python code generator. The user specifies the classes and
+functions to be exported using a simple ['interface file], which following the
+Boost.Python's philosophy, is simple Python code. Pyste then uses GCCXML to
+parse all the headers and extract the necessary information to automatically
+generate C++ code.
+
+[h2 Example]
+
+Let's borrow the class [^World] from the [@../../doc/tutorial/doc/exposing_classes.html tutorial]:
+
+ struct World
+ {
+ void set(std::string msg) { this->msg = msg; }
+ std::string greet() { return msg; }
+ std::string msg;
+ };
+
+Here's the interface file for it, named [^world.pyste]:
+
+ Class("World", "world.h")
+
+and that's it!
+
+The next step is invoke pyste in the command-line:
+
+[pre python pyste.py --module=hello world.pyste]
+
+this will create a file "[^hello.cpp]" in the directory where the command was
+run.
+
+Pyste supports the following features:
+
+* Functions
+* Classes
+* Class Templates
+* Virtual Methods
+* Overloading
+* Attributes
+* Enums (both "free" enums and class enums)
+* Nested Classes
+
+[page Running Pyste]
+
+To run pyste, you will need:
+
+* Python 2.2, avaiable at [@http://www.python.org python's website].
+* The great [@http://effbot.org elementtree] library, from Fredrik Lundh.
+* The excellent GCCXML, from Brad King.
+
+Installation for the tools is avaiable in their respective webpages.
+
+[blurb
+[$theme/note.gif] GCCXML must be accessible in the PATH environment variable, so
+that pyste can call it. How to do this varies from platform to platform.
+]
+
+[h2 Ok, now what?]
+
+Well, now let's fire it up:
+
+[pre
+'''
+>python pyste.py
+
+Usage:
+ pyste [options] --module= interface-files
+
+where options are:
+ -I add an include path
+ -D define symbol
+ --no-using do not declare "using namespace boost";
+ use explicit declarations instead
+ --pyste-ns= set the namespace where new types will be declared;
+ default is "pyste"
+'''
+]
+
+Options explained:
+
+The [^-I] and [^-D] are preprocessor flags, which are needed by gccxml to parse the header files correctly and by pyste to find the header files declared in the
+interface files.
+
+[^--no-using] tells pyste to don't declare "[^using namespace boost;]" in the
+generated cpp, using the namespace boost::python explicitly in all declarations.
+Use only if you're having a name conflict in one of the files.
+
+Use [^--pyste-ns] to change the namespace where new types are declared (for
+instance, the virtual wrappers). Use only if one of your header files declare a
+namespace named "pyste" and this is causing conflicts.
+
+So, the usage is simple enough:
+
+[pre >python pyste.py --module=mymodule file.pyste file2.pyste ...]
+
+will generate a file [^mymodule.cpp] in the same dir where the command was
+executed. Now you can compile the file using the same instructions of the
+[@../../doc/tutorial/doc/building_hello_world.html tutorial].
+
+[h2 Wait... how do I set those I and D flags?]
+
+Don't worry: normally GCCXML is already configured correctly for your plataform,
+so the search path to the standard libraries and the standard defines should
+already be set. You only have to set the paths to other libraries that your code
+needs, like Boost, for example.
+
+Plus, Pyste automatically uses the contents of the environment variable
+[^INCLUDE] if it exists. Windows users should run the [^Vcvars32.bat] file,
+normally located at:
+
+ C:\Program Files\Microsoft Visual Studio\VC98\bin\Vcvars32.bat
+
+with that, you should have little trouble setting up the flags.
+
+[page The Interface Files]
+
+The interface files are the heart of Pyste. The user creates one or more
+interface files declaring the classes and functions he wants to export, and then
+invokes pyste passing the interface files to it. Pyste then generates a single
+cpp file with Boost.Python code, with all the classes and functions exported.
+
+Besides declaring the classes and functions, the user has a number of other
+options, like renaming classes and methods, excluding methods and attributes,
+and so on.
+
+[h2 Basics]
+
+Suppose we have a class and some functions that we want to expose to Python
+declared in the header [^hello.h]:
+
+ struct World
+ {
+ World(std::string msg): msg(msg) {}
+ void set(std::string msg) { this->msg = msg; }
+ std::string greet() { return msg; }
+ std::string msg;
+ };
+
+ enum choice { red, blue };
+
+ namespace test {
+
+ void show(choice c) { std::cout << "value: " << (int)c << std::endl; }
+
+ }
+
+We create a file named [^hello.pyste] and create instances of the classes
+[^Function], [^Class] and [^Enum]:
+
+ Function("test::show", "hello.h")
+ Class("World", "hello.h")
+ Enum("choice", "hello.h")
+
+That will expose the class, the free function and the enum found in [^hello.h].
+
+[page:1 Renaming and Excluding]
+
+You can easily rename functions, classes, methods, attributes, etc. Just use the
+function [^rename], like this:
+
+ World = Class("World", "hello.h")
+ rename(World, "IWorld")
+ show = Function("choice", "hello.h")
+ rename(show, "Show")
+
+You can rename methods and attributes using this syntax:
+
+ rename(World.greet, "Greet")
+ rename(World.set, "Set")
+ choice = Enum("choice", "hello.h")
+ rename(choice.red, "Red")
+ rename(choice.blue, "Blue")
+
+You can exclude functions, classes, methods, attributes, etc, in the same way,
+with the function [^exclude]:
+
+ exclude(World.greet)
+ exclude(World.msg)
+
+Easy, huh? [$theme/smiley.gif]
+
+[page:1 Policies]
+
+Even thought Pyste can identify various elements in the C++ code, like virtual
+methods, attributes, and so on, one thing that it can't do is to guess the
+semantics of functions that return pointers or references. In this case, the
+user must manually specify the policy. Policies are explained in the
+[@../../doc/tutorial/doc/call_policies.html tutorial].
+
+The policies in Pyste are named exactly as in Boost.Python, only the syntax is
+slightly different. For instance, this policy:
+
+ return_internal_reference<1, with_custodian_and_ward<1, 2> >()
+
+becomes in Pyste:
+
+ return_internal_reference(1, with_custodian_and_ward(1, 2))
+
+The user can specify policies for functions and methods with the [^set_policy]
+function:
+
+ set_policy(f, return_internal_reference())
+ set_policy(C.foo, return_value_policy(manage_new_object))
+
+[blurb
+[$theme/note.gif] [*What if a function or method needs a policy and the user
+doesn't set one?][br][br]
+If a function/method needs a policy and one was not set, Pyste will issue a error.
+The user should then go in the interface file and set the policy for it,
+otherwise the generated cpp won't compile.
+]
+
+[page:1 Templates]
+
+Template Classes can easily exported too, but you can't export the "Template"
+itself... you have to export instantiations of it! So, if you want to export a
+[^std::vector], you will have to export vectors of int, doubles, etc.
+
+Suppose we have this code:
+
+ template
+ struct Point
+ {
+ T x;
+ T y;
+ };
+
+And we want to export [^Point]s of int and double:
+
+ Point = Template("Point", "point.h")
+ Point("int")
+ Point("double")
+
+Pyste will assign default names for each instantiation. In this example, those
+would be "[^Point_int]" and "[^Point_double]", but most of the time users will want to
+rename the instantiations:
+
+ Point("int", "IPoint") // renames the instantiation
+ double_inst = Point("double") // another way to do the same
+ rename(double_inst, "DPoint")
+
+Note that you can rename, exclude, set policies, etc, in the [^Template] class
+like you would do with a [^Function] or a [^Class]. This changes affect all
+[*future] instantiations:
+
+ Point = Template("Point", "point.h")
+ Point("float", "FPoint") // will have x and y as data members
+ rename(Point.x, "X")
+ rename(Point.y, "Y")
+ Point("int", "IPoint") // will have X and Y as data members
+ Point("double", "DPoint") // also will have X and Y as data member
+
+If you want to change a option of a particular instantiation, you can do so:
+
+ Point = Template("Point", "point.h")
+ Point("int", "IPoint")
+ d_inst = Point("double", "DPoint")
+ rename(d_inst.x, "X") // only DPoint is affect by this renames,
+ rename(d_inst.y, "Y") // IPoint stays intact
+
+[blurb [$theme/note.gif] [*What if my template accepts more than one type?]
+[br][br]
+When you want to instantiate a Template with more than one type, you can pass
+either a string with the types separated by whitespace, or a list of strings
+'''("int double" or ["int", "double"]''' would both work).
+]
+
+[page:1 Wrappers]
+
+Suppose you have this function:
+
+ std::vector names();
+
+But you don't want to export a vector, you want this function to return
+a python list of strings. Boost.Python has an excellent support for that:
+
+ list names_wrapper()
+ {
+ list result;
+ vector v = names();
+ // put each string in the vector in the list
+ return result;
+ }
+
+ BOOST_PYTHON_MODULE(test)
+ {
+ def("names", &names_wrapper);
+ }
+
+Nice heh?
+Pyste supports this mechanism too. You declare the [^names_wrapper] function in a
+header, like "[^test_wrappers.h]", and in the interface file:
+
+ Include("test_wrappers.h")
+ names = Function("names", "test.h")
+ set_wrapper(names, "names_wrapper")
+
+You can optionally declare the function in the interface file itself:
+
+ names_wrapper = Wrapper("names_wrapper",
+ """
+ list names_wrapper()
+ {
+ // call name() and convert the vector to a list...
+ }
+ """)
+ names = Function("names", "test.h")
+ set_wrapper(names, names_wrapper)
+
+The same mechanism can be done with methods too. Just remember that the first
+parameter of wrappers for methods is a pointer to the class, like in
+Boost.Python:
+
+ struct C
+ {
+ std::vector names();
+ }
+
+ list names_wrapper(C* c)
+ {
+ // same as before, calling c->names() and converting result to a list
+ }
+
+And then in the interface file:
+
+ C = Class("C", "test.h")
+ set_wrapper(C.names, "names_wrapper")
+
+[page:1 Exporting All Declarations from a Header]
+
+Pyste also supports a mechanism to export all declarations found in a header
+file. Suppose again our file, [^hello.h]:
+
+ struct World
+ {
+ World(std::string msg): msg(msg) {}
+ void set(std::string msg) { this->msg = msg; }
+ std::string greet() { return msg; }
+ std::string msg;
+ };
+
+ enum choice { red, blue };
+
+ void show(choice c) { std::cout << "value: " << (int)c << std::endl; }
+
+You can just use the [^AllFromHeader] construct:
+
+ hello = AllFromHeader("hello.h")
+
+this will export all the declarations found in [^hello.h], which is equivalent
+to write:
+
+ Class("World", "hello.h")
+ Enum("choice", "hello.h")
+ Function("show", "hello.h")
+
+Note that you can still use the functions [^rename], [^set_policy], [^exclude], etc. Just access
+the members of the header object like this:
+
+ rename(hello.World.greet, "Greet")
+ exclude(hello.World.set, "Set")
+
diff --git a/pyste/doc/renaming_and_excluding.html b/pyste/doc/renaming_and_excluding.html
new file mode 100644
index 00000000..29a8001b
--- /dev/null
+++ b/pyste/doc/renaming_and_excluding.html
@@ -0,0 +1,68 @@
+
+
+
+Renaming and Excluding
+
+
+
+
+
+
+
+
+
+
+ Renaming and Excluding
+
+
+
+
+
+
+
+
+
+
+
+
+You can easily rename functions, classes, methods, attributes, etc. Just use the
+function rename, like this:
+
+ World = Class("World", "hello.h")
+ rename(World, "IWorld")
+ show = Function("choice", "hello.h")
+ rename(show, "Show")
+
+
+You can rename methods and attributes using this syntax:
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+Installation for the tools is avaiable in their respective webpages.
+
+
+
+
+
+GCCXML must be accessible in the PATH environment variable, so
+that pyste can call it. How to do this varies from platform to platform.
+
+
+
+
Ok, now what?
+Well, now let's fire it up:
+
+
+>python pyste.py
+
+Usage:
+ pyste [options] --module=<name> interface-files
+
+where options are:
+ -I <path> add an include path
+ -D <symbol> define symbol
+ --no-using do not declare "using namespace boost";
+ use explicit declarations instead
+ --pyste-ns=<name> set the namespace where new types will be declared;
+ default is "pyste"
+
+
+Options explained:
+
+The -I and -D are preprocessor flags, which are needed by gccxml to parse the header files correctly and by pyste to find the header files declared in the
+interface files.
+
+--no-using tells pyste to don't declare "using namespace boost;" in the
+generated cpp, using the namespace boost::python explicitly in all declarations.
+Use only if you're having a name conflict in one of the files.
+
+Use --pyste-ns to change the namespace where new types are declared (for
+instance, the virtual wrappers). Use only if one of your header files declare a
+namespace named "pyste" and this is causing conflicts.
+will generate a file mymodule.cpp in the same dir where the command was
+executed. Now you can compile the file using the same instructions of the
+
+tutorial.
+
Wait... how do I set those I and D flags?
+Don't worry: normally
+GCCXML is already configured correctly for your plataform,
+so the search path to the standard libraries and the standard defines should
+already be set. You only have to set the paths to other libraries that your code
+needs, like Boost, for example.
+
+Plus, Pyste automatically uses the contents of the environment variable
+INCLUDE if it exists. Windows users should run the Vcvars32.bat file,
+normally located at:
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+Template Classes can easily exported too, but you can't export the "Template"
+itself... you have to export instantiations of it! So, if you want to export a
+std::vector, you will have to export vectors of int, doubles, etc.
+
+Suppose we have this code:
+
+ template <class T>
+ struct Point
+ {
+ T x;
+ T y;
+ };
+
+
+And we want to export Points of int and double:
+
+ Point = Template("Point", "point.h")
+ Point("int")
+ Point("double")
+
+
+Pyste will assign default names for each instantiation. In this example, those
+would be "Point_int" and "Point_double", but most of the time users will want to
+rename the instantiations:
+
+ Point("int", "IPoint") // renames the instantiation
+ double_inst = Point("double") // another way to do the same
+ rename(double_inst, "DPoint")
+
+
+Note that you can rename, exclude, set policies, etc, in the Template class
+like you would do with a Function or a Class. This changes affect all
+future instantiations:
+
+ Point = Template("Point", "point.h")
+ Point("float", "FPoint") // will have x and y as data members
+ rename(Point.x, "X")
+ rename(Point.y, "Y")
+ Point("int", "IPoint") // will have X and Y as data members
+ Point("double", "DPoint") // also will have X and Y as data member
+
+
+If you want to change a option of a particular instantiation, you can do so:
+
+ Point = Template("Point", "point.h")
+ Point("int", "IPoint")
+ d_inst = Point("double", "DPoint")
+ rename(d_inst.x, "X") // only DPoint is affect by this renames,
+ rename(d_inst.y, "Y") // IPoint stays intact
+
+
+
+
+What if my template accepts more than one type?
+
+When you want to instantiate a Template with more than one type, you can pass
+either a string with the types separated by whitespace, or a list of strings
+("int double" or ["int", "double"] would both work).
+
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+The interface files are the heart of Pyste. The user creates one or more
+interface files declaring the classes and functions he wants to export, and then
+invokes pyste passing the interface files to it. Pyste then generates a single
+cpp file with
+Boost.Python code, with all the classes and functions exported.
+
+Besides declaring the classes and functions, the user has a number of other
+options, like renaming classes and methods, excluding methods and attributes,
+and so on.
+
Basics
+Suppose we have a class and some functions that we want to expose to Python
+declared in the header hello.h:
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+But you don't want to export a vector<string>, you want this function to return
+a python list of strings.
+Boost.Python has an excellent support for that:
+
+ list names_wrapper()
+ {
+ list result;
+ vector<string> v = names();
+ // put each string in the vector in the list
+ return result;
+ }
+
+ BOOST_PYTHON_MODULE(test)
+ {
+ def("names", &names_wrapper);
+ }
+
+
+Nice heh?
+Pyste supports this mechanism too. You declare the names_wrapper function in a
+header, like "test_wrappers.h", and in the interface file:
+You can optionally declare the function in the interface file itself:
+
+ names_wrapper = Wrapper("names_wrapper",
+ """
+ list names_wrapper()
+ {
+ // call name() and convert the vector to a list...
+ }
+ """)
+ names = Function("names", "test.h")
+ set_wrapper(names, names_wrapper)
+
+
+The same mechanism can be done with methods too. Just remember that the first
+parameter of wrappers for methods is a pointer to the class, like in
+
+Boost.Python:
+
+ struct C
+ {
+ std::vector<std::string> names();
+ }
+
+ list names_wrapper(C* c)
+ {
+ // same as before, calling c->names() and converting result to a list
+ }
+
+
+And then in the interface file:
+
+ C = Class("C", "test.h")
+ set_wrapper(C.names, "names_wrapper")
+
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+
+
diff --git a/pyste/src/.cvsignore b/pyste/src/.cvsignore
new file mode 100644
index 00000000..0d20b648
--- /dev/null
+++ b/pyste/src/.cvsignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/pyste/src/ClassExporter.py b/pyste/src/ClassExporter.py
new file mode 100644
index 00000000..d72ff9db
--- /dev/null
+++ b/pyste/src/ClassExporter.py
@@ -0,0 +1,688 @@
+import exporters
+from Exporter import Exporter
+from declarations import *
+from enumerate import enumerate
+from settings import *
+from CodeUnit import CodeUnit
+from EnumExporter import EnumExporter
+
+
+#==============================================================================
+# ClassExporter
+#==============================================================================
+class ClassExporter(Exporter):
+ 'Generates boost.python code to export a class declaration'
+
+
+ def __init__(self, info, parser_tail=None):
+ Exporter.__init__(self, info, parser_tail)
+ # sections of code
+ self.sections = {}
+ # template: each item in the list is an item into the class_<...>
+ # section.
+ self.sections['template'] = []
+ # constructor: each item in the list is a parameter to the class_
+ # constructor, like class_(...)
+ self.sections['constructor'] = []
+ # inside: everything within the class_<> statement
+ self.sections['inside'] = []
+ # scope: items outside the class statement but within its scope.
+ # scope* s = new scope(class<>());
+ # ...
+ # delete s;
+ self.sections['scope'] = []
+ # declarations: outside the BOOST_PYTHON_MODULE macro
+ self.sections['declaration'] = []
+ self.sections['include'] = []
+ # a list of Method instances
+ self.methods = []
+ # a list of Constructor instances
+ self.constructors = []
+ # a dict of methodname => _WrapperVirtualMethod instances
+ self.virtual_wrappers = {}
+ # a list of code units, generated by nested declarations
+ self.nested_codeunits = []
+
+
+ def ScopeName(self):
+ return _ID(self.class_.FullName()) + '_scope'
+
+
+ def Name(self):
+ return self.class_.FullName()
+
+
+ def SetDeclarations(self, declarations):
+ Exporter.SetDeclarations(self, declarations)
+ decl = self.GetDeclaration(self.info.name)
+ if isinstance(decl, Typedef):
+ self.class_ = decl.type
+ if not self.info.rename:
+ self.info.rename = decl.name
+ else:
+ self.class_ = decl
+ self.public_members = \
+ [x for x in self.class_.members if x.visibility == Scope.public]
+
+
+ def Order(self):
+ '''Return the TOTAL number of bases that this class has, including the
+ bases' bases. Do this because base classes must be instantialized
+ before the derived classes in the module definition.
+ '''
+
+ def BasesCount(classname):
+ decl = self.GetDeclaration(classname)
+ bases = [x.name for x in decl.bases]
+ total = 0
+ for base in bases:
+ total += BasesCount(base)
+ return len(bases) + total
+
+ return BasesCount(self.class_.FullName())
+
+
+ def Export(self, codeunit, exported_names):
+ self.GetMethods()
+ self.ExportBasics()
+ self.ExportBases(exported_names)
+ self.ExportConstructors()
+ self.ExportVariables()
+ self.ExportMethods()
+ self.GenerateVirtualWrapper()
+ self.ExportOperators()
+ self.ExportNestedClasses(exported_names)
+ self.ExportNestedEnums()
+ self.Write(codeunit)
+
+
+ def Write(self, codeunit):
+ indent = self.INDENT
+ boost_ns = namespaces.python
+ pyste_ns = namespaces.pyste
+ code = ''
+ # begin a scope for this class if needed
+ nested_codeunits = self.nested_codeunits
+ needs_scope = self.sections['scope'] or nested_codeunits
+ if needs_scope:
+ scope_name = self.ScopeName()
+ code += indent + boost_ns + 'scope* %s = new %sscope(\n' %\
+ (scope_name, boost_ns)
+ # export the template section
+ template_params = ', '.join(self.sections['template'])
+ code += indent + boost_ns + 'class_< %s >' % template_params
+ # export the constructor section
+ constructor_params = ', '.join(self.sections['constructor'])
+ code += '(%s)\n' % constructor_params
+ # export the inside section
+ in_indent = indent*2
+ for line in self.sections['inside']:
+ code += in_indent + line + '\n'
+ # write the scope section and end it
+ if not needs_scope:
+ code += indent + ';\n'
+ else:
+ code += indent + ');\n'
+ for line in self.sections['scope']:
+ code += indent + line + '\n'
+ # write the contents of the nested classes
+ for nested_unit in nested_codeunits:
+ code += '\n' + nested_unit.Section('module')
+ # close the scope
+ code += indent + 'delete %s;\n' % scope_name
+
+ # write the code to the module section in the codeunit
+ codeunit.Write('module', code + '\n')
+
+ # write the declarations to the codeunit
+ declarations = '\n'.join(self.sections['declaration'])
+ for nested_unit in nested_codeunits:
+ declarations += nested_unit.Section('declaration')
+ if declarations:
+ codeunit.Write('declaration', declarations + '\n')
+
+ # write the includes to the codeunit
+ includes = '\n'.join(self.sections['include'])
+ for nested_unit in nested_codeunits:
+ includes += nested_unit.Section('include')
+ if includes:
+ codeunit.Write('include', includes)
+
+
+ def Add(self, section, item):
+ 'Add the item into the corresponding section'
+ self.sections[section].append(item.strip())
+
+
+ def ExportBasics(self):
+ 'Export the name of the class and its class_ statement'
+ self.Add('template', self.class_.FullName())
+ name = self.info.rename or self.class_.name
+ self.Add('constructor', '"%s"' % name)
+
+
+ def ExportBases(self, exported_names):
+ 'Expose the bases of the class into the template section'
+ bases = self.class_.bases
+ bases_list = []
+ for base in bases:
+ if base.visibility == Scope.public and base.name in exported_names:
+ bases_list.append(base.name)
+ if bases_list:
+ code = namespaces.python + 'bases< %s > ' % \
+ (', '.join(bases_list))
+ self.Add('template', code)
+
+
+ def ExportConstructors(self):
+ '''Exports all the public contructors of the class, plus indicates if the
+ class is noncopyable.
+ '''
+ py_ns = namespaces.python
+ indent = self.INDENT
+
+ def init_code(cons):
+ 'return the init<>() code for the given contructor'
+ param_list = [p.FullName() for p in cons.parameters]
+ min_params_list = param_list[:cons.minArgs]
+ max_params_list = param_list[cons.minArgs:]
+ min_params = ', '.join(min_params_list)
+ max_params = ', '.join(max_params_list)
+ init = py_ns + 'init< '
+ init += min_params
+ if max_params:
+ if min_params:
+ init += ', '
+ init += py_ns + ('optional< %s >' % max_params)
+ init += ' >()'
+ return init
+
+ constructors = [x for x in self.public_members if isinstance(x, Constructor)]
+ self.constructors = constructors[:]
+ if not constructors:
+ # declare no_init
+ self.Add('constructor', py_ns + 'no_init')
+ else:
+ # write one of the constructors to the class_ constructor
+ self.Add('constructor', init_code(constructors.pop(0)))
+ # write the rest to the inside section, using def()
+ for cons in constructors:
+ code = '.def(%s)' % init_code(cons)
+ self.Add('inside', code)
+ # check if the class is copyable
+ if not self.class_.HasCopyConstructor() or self.class_.abstract:
+ self.Add('template', namespaces.boost + 'noncopyable')
+
+
+ def ExportVariables(self):
+ 'Export the variables of the class, both static and simple variables'
+ vars = [x for x in self.public_members if isinstance(x, Variable)]
+ for var in vars:
+ if self.info[var.name].exclude:
+ continue
+ name = self.info[var.name].rename or var.name
+ fullname = var.FullName()
+ if var.static:
+ code = '%s->attr("%s") = %s;' % (self.ScopeName(), name, fullname)
+ self.Add('scope', code)
+ else:
+ if var.type.const:
+ def_ = '.def_readonly'
+ else:
+ def_ = '.def_readwrite'
+ code = '%s("%s", &%s)' % (def_, name, fullname)
+ self.Add('inside', code)
+
+
+ def GetMethods(self):
+ 'fill self.methods with a list of Method instances'
+ # get a list of all methods
+ def IsValid(m):
+ 'Returns true if the given method is exportable by this routine'
+ ignore = (Constructor, ClassOperator, Destructor)
+ return isinstance(m, Method) and not isinstance(m, ignore)
+
+ self.methods = [x for x in self.public_members if IsValid(x)]
+
+
+
+ printed_policy_warnings = {}
+
+ def CheckPolicy(self, m):
+ 'Warns the user if this method needs a policy'
+ def IsString(type):
+ return type.const and type.name == 'char' and isinstance(type, PointerType)
+ needs_policy = isinstance(m.result, (ReferenceType, PointerType))
+ if IsString(m.result):
+ needs_policy = False
+ has_policy = self.info[m.name].policy is not None
+ if needs_policy and not has_policy:
+ warning = '---> Error: Method "%s" needs a policy.' % m.FullName()
+ if warning not in self.printed_policy_warnings:
+ print warning
+ print
+ self.printed_policy_warnings[warning] = 1
+
+
+ def ExportMethods(self):
+ 'Export all the methods of the class'
+
+ def OverloadName(m):
+ 'Returns the name of the overloads struct for the given method'
+
+ return _ID(m.FullName()) + ('_overloads_%i_%i' % (m.minArgs, m.maxArgs))
+
+ declared = {}
+ def DeclareOverloads(m):
+ 'Declares the macro for the generation of the overloads'
+ if m.virtual:
+ func = self.virtual_wrappers[m.PointerDeclaration()].DefaultName()
+ else:
+ func = m.name
+ code = 'BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(%s, %s, %i, %i)\n'
+ code = code % (OverloadName(m), func, m.minArgs, m.maxArgs)
+ if code not in declared:
+ declared[code] = True
+ self.Add('declaration', code)
+
+
+ def Pointer(m):
+ 'returns the correct pointer declaration for the method m'
+ # check if this method has a wrapper set for him
+ wrapper = self.info[method.name].wrapper
+ if wrapper:
+ return '&' + wrapper.FullName()
+ # if this method is virtual, return the pointers to the class and its wrapper
+ if m.virtual:
+ return self.virtual_wrappers[m.PointerDeclaration()].Pointer()
+ # return normal pointers to the methods of the class
+ is_unique = self.class_.IsUnique(m.name)
+ if is_unique:
+ return '&' + method.FullName()
+ else:
+ return method.PointerDeclaration()
+
+
+ for method in self.methods:
+ if self.info[method.name].exclude:
+ continue # skip this method
+
+ name = self.info[method.name].rename or method.name
+ # check if this method needs to be wrapped as a virtual method
+ if method.virtual:
+ wrapper = _WrapperVirtualMethod(self.class_, method, name)
+ self.virtual_wrappers[method.PointerDeclaration()] = wrapper
+ # abstract methods don't need to be exported
+ if method.abstract:
+ continue # skip .def declaration for abstract methods
+ # warn the user if this method needs a policy and doesn't have one
+ self.CheckPolicy(method)
+
+ # check for policies
+ policy = self.info[method.name].policy or ''
+ if policy:
+ policy = ', %s%s()' % (namespaces.python, policy.Code())
+ # check for overloads
+ overload = ''
+ if method.minArgs != method.maxArgs:
+ # add the overloads for this method
+ overload_name = OverloadName(method)
+ DeclareOverloads(method)
+ if not method.virtual:
+ overload = ', %s%s()' % (namespaces.pyste, overload_name)
+ else:
+ pyste_ns = namespaces.pyste
+ pointer = self.virtual_wrappers[method.PointerDeclaration()].DefaultPointer()
+ defcode = '.def("%s", %s, %s%s())' % \
+ (name, pointer, pyste_ns, overload_name)
+ self.Add('inside', defcode)
+ # build the string to export the method
+ pointer = Pointer(method)
+ code = '.def("%s", %s' % (name, pointer)
+ code += policy
+ code += overload
+ code += ')'
+ self.Add('inside', code)
+ # static method
+ if method.static:
+ code = '.staticmethod("%s")' % name
+ self.Add('inside', code)
+ # add wrapper code if this method has one
+ wrapper = self.info[method.name].wrapper
+ if wrapper and wrapper.code:
+ self.Add('declaration', wrapper.code)
+
+
+ def GenerateVirtualWrapper(self):
+ 'Generate the wrapper to dispatch virtual methods'
+ # check if this class needs a wrapper first
+ for m in self.methods:
+ if m.virtual:
+ break
+ else:
+ return
+ # add the wrapper name to the template section
+ wrapper_name = _WrapperName(self.class_)
+ self.Add('template', namespaces.pyste + wrapper_name)
+ indent = self.INDENT
+ method_codes = [x.Code(indent) for x in self.virtual_wrappers.values()]
+ body = '\n'.join(method_codes)
+ # generate the class code
+ class_name = self.class_.FullName()
+ code = 'struct %s: %s\n' % (wrapper_name, class_name)
+ code += '{\n'
+ # generate constructors
+ for cons in self.constructors:
+ params, param_names, param_types = _ParamsInfo(cons)
+ if params:
+ params = ', ' + params
+ cons_code = indent + '%s(PyObject* self_%s):\n' % (wrapper_name, params)
+ cons_code += indent*2 + '%s(%s), self(self_) {}\n\n' % \
+ (class_name, ', '.join(param_names))
+ code += cons_code
+ code += body + '\n'
+ code += indent + 'PyObject* self;\n'
+ code += '};\n'
+ self.Add('declaration', code)
+
+
+ # operators natively supported by boost
+ BOOST_SUPPORTED_OPERATORS = '+ - * / % ^ & ! ~ | < > == != <= >= << >> && || += -='\
+ '*= /= %= ^= &= |= <<= >>='.split()
+ # create a map for faster lookup
+ BOOST_SUPPORTED_OPERATORS = dict(zip(BOOST_SUPPORTED_OPERATORS, range(len(BOOST_SUPPORTED_OPERATORS))))
+
+ # a dict of operators that are not directly supported by boost, but can be exposed
+ # simply as a function with a special signature
+ BOOST_RENAME_OPERATORS = {
+ '()' : '__call__',
+ }
+
+ # converters which has a special name in python
+ SPECIAL_CONVETERS = {
+ 'double' : '__float__',
+ 'float' : '__float__',
+ 'int' : '__int__',
+ }
+
+
+ def ExportOperators(self):
+ 'Export all member operators and free operators related to this class'
+
+ def GetFreeOperators():
+ 'Get all the free (global) operators related to this class'
+ operators = []
+ for decl in self.declarations:
+ if isinstance(decl, Operator):
+ # check if one of the params is this class
+ for param in decl.parameters:
+ if param.name == self.class_.FullName():
+ operators.append(decl)
+ break
+ return operators
+
+ def GetOperand(param):
+ 'Returns the operand of this parameter (either "self", or "other")'
+ if param.name == self.class_.FullName():
+ return namespaces.python + 'self'
+ else:
+ return namespaces.python + ('other< %s >()' % param.name)
+
+
+ def HandleSpecialOperator(operator):
+ # gatter information about the operator and its parameters
+ result_name = operator.result.name
+ param1_name = ''
+ if operator.parameters:
+ param1_name = operator.parameters[0].name
+
+ # check for str
+ ostream = 'basic_ostream'
+ is_str = result_name.find(ostream) != -1 and param1_name.find(ostream) != -1
+ if is_str:
+ namespace = namespaces.python + 'self_ns::'
+ self_ = namespaces.python + 'self'
+ return '.def(%sstr(%s))' % (namespace, self_)
+
+ # is not a special operator
+ return None
+
+
+
+ frees = GetFreeOperators()
+ members = [x for x in self.public_members if type(x) == ClassOperator]
+ all_operators = frees + members
+ operators = [x for x in all_operators if not self.info['operator'][x.name].exclude]
+
+ for operator in operators:
+ # gatter information about the operator, for use later
+ wrapper = self.info['operator'][operator.name].wrapper
+ if wrapper:
+ pointer = '&' + wrapper.FullName()
+ if wrapper.code:
+ self.Add('declaration', wrapper.code)
+ elif isinstance(operator, ClassOperator) and self.class_.IsUnique(operator.name):
+ pointer = '&' + operator.FullName()
+ else:
+ pointer = operator.PointerDeclaration()
+ rename = self.info['operator'][operator.name].rename
+
+ # check if this operator will be exported as a method
+ export_as_method = wrapper or rename or operator.name in self.BOOST_RENAME_OPERATORS
+
+ # check if this operator has a special representation in boost
+ special_code = HandleSpecialOperator(operator)
+ has_special_representation = special_code is not None
+
+ if export_as_method:
+ # export this operator as a normal method, renaming or using the given wrapper
+ if not rename:
+ if wrapper:
+ rename = wrapper.name
+ else:
+ rename = self.BOOST_RENAME_OPERATORS[operator.name]
+ policy = ''
+ policy_obj = self.info['operator'][operator.name].policy
+ if policy_obj:
+ policy = ', %s()' % policy_obj.Code()
+ self.Add('inside', '.def("%s", %s%s)' % (rename, pointer, policy))
+
+ elif has_special_representation:
+ self.Add('inside', special_code)
+
+ elif operator.name in self.BOOST_SUPPORTED_OPERATORS:
+ # export this operator using boost's facilities
+ op = operator
+ is_unary = isinstance(op, Operator) and len(op.parameters) == 1 or\
+ isinstance(op, ClassOperator) and len(op.parameters) == 0
+ if is_unary:
+ self.Add('inside', '.def( %s%sself )' % \
+ (operator.name, namespaces.python))
+ else:
+ # binary operator
+ if len(operator.parameters) == 2:
+ left_operand = GetOperand(operator.parameters[0])
+ right_operand = GetOperand(operator.parameters[1])
+ else:
+ left_operand = namespaces.python + 'self'
+ right_operand = GetOperand(operator.parameters[0])
+ self.Add('inside', '.def( %s %s %s )' % \
+ (left_operand, operator.name, right_operand))
+
+ # export the converters.
+ # export them as simple functions with a pre-determined name
+
+ converters = [x for x in self.public_members if type(x) == ConverterOperator]
+
+ def ConverterMethodName(converter):
+ result_fullname = converter.result.name
+ # extract the last name from the full name
+ result_name = _ID(result_fullname.split('::')[-1])
+ return 'to_' + result_name
+
+ for converter in converters:
+ info = self.info['operator'][converter.result.name]
+ # check if this operator should be excluded
+ if info.exclude:
+ continue
+
+ special_code = HandleSpecialOperator(converter)
+ if info.rename or not special_code:
+ # export as method
+ name = info.rename or ConverterMethodName(converter)
+ if self.class_.IsUnique(converter.name):
+ pointer = '&' + converter.FullName()
+ else:
+ pointer = converter.PointerDeclaration()
+ policy_code = ''
+ if info.policy:
+ policy_code = ', %s()' % info.policy.Code()
+ self.Add('inside', '.def("%s", %s%s)' % (name, pointer, policy_code))
+
+ elif special_code:
+ self.Add('inside', special_code)
+
+
+
+ def ExportNestedClasses(self, exported_names):
+ nested_classes = [x for x in self.public_members if isinstance(x, NestedClass)]
+ for nested_class in nested_classes:
+ nested_info = self.info[nested_class.name]
+ nested_info.include = self.info.include
+ nested_info.name = nested_class.FullName()
+ exporter = ClassExporter(nested_info)
+ exporter.SetDeclarations(self.declarations + [nested_class])
+ codeunit = CodeUnit(None)
+ exporter.Export(codeunit, exported_names)
+ self.nested_codeunits.append(codeunit)
+
+
+ def ExportNestedEnums(self):
+ nested_enums = [x for x in self.public_members if isinstance(x, ClassEnumeration)]
+ for enum in nested_enums:
+ enum_info = self.info[enum.name]
+ enum_info.include = self.info.include
+ enum_info.name = enum.FullName()
+ exporter = EnumExporter(enum_info)
+ exporter.SetDeclarations(self.declarations + [enum])
+ codeunit = CodeUnit(None)
+ exporter.Export(codeunit, None)
+ self.nested_codeunits.append(codeunit)
+
+
+
+
+def _ID(name):
+ 'Returns the name as a valid identifier'
+ for invalidchar in ('::', '<', '>', ' ', ','):
+ name = name.replace(invalidchar, '_')
+ # avoid duplications of '_' chars
+ names = [x for x in name.split('_') if x]
+ return '_'.join(names)
+
+
+#==============================================================================
+# Virtual Wrapper utils
+#==============================================================================
+
+def _WrapperName(class_):
+ return _ID(class_.FullName()) + '_Wrapper'
+
+
+def _ParamsInfo(m):
+ param_names = ['p%i' % i for i in range(len(m.parameters))]
+ param_types = [x.FullName() for x in m.parameters]
+ params = ['%s %s' % (t, n) for t, n in zip(param_types, param_names)]
+ for i, p in enumerate(m.parameters):
+ if p.default is not None:
+ #params[i] += '=%s' % p.default
+ params[i] += '=%s' % (p.name + '()')
+ params = ', '.join(params)
+ return params, param_names, param_types
+
+
+class _WrapperVirtualMethod(object):
+ 'Holds information about a virtual method that will be wrapped'
+
+ def __init__(self, class_, method, rename):
+ self.method = method
+ if rename is None:
+ rename = method.name
+ self.rename = rename
+ self.class_ = class_
+
+
+ def DefaultName(self):
+ return 'default_' + self.method.name
+
+
+ def DefaultPointer(self):
+ ns = namespaces.pyste
+ wrapper_name = _WrapperName(self.class_)
+ default_name = self.DefaultName()
+ fullname = '%s%s::%s' % (ns, wrapper_name, default_name)
+ if self.class_.IsUnique(self.method.name):
+ return '&%s' % fullname
+ else:
+ # the method is not unique, so we must specify the entire signature with it
+ param_list = [x.FullName() for x in self.method.parameters]
+ params = ', '.join(param_list)
+ result = self.method.result.FullName()
+ signature = '%s (%s%s::*)(%s)' % (result, ns, wrapper_name, params)
+ return '(%s)%s' % (signature, fullname)
+
+
+ def Pointer(self):
+ '''Returns the "pointer" declaration for this method, ie, the contents
+ of the .def after the method name (.def("name", ))'''
+ ns = namespaces.pyste
+ default_name = self.DefaultName()
+ name = self.method.name
+ class_name = self.class_.FullName()
+ wrapper = ns + _WrapperName(self.class_)
+ if self.class_.IsUnique(self.method.name):
+ return '&%s::%s, &%s::%s' % (class_name, name, wrapper, default_name)
+ else:
+ # the method is not unique, so we must specify the entire signature with it
+ param_list = [x.FullName() for x in self.method.parameters]
+ params = ', '.join(param_list)
+ result = self.method.result.FullName()
+ default_sig = '%s (%s::*)(%s)' % (result, wrapper, params)
+ normal_sig = '%s (%s::*)(%s)' % (result, class_name, params)
+ return '(%s)%s::%s, (%s)%s::%s' % \
+ (normal_sig, class_name, name, default_sig, wrapper, default_name)
+
+
+ def Code(self, indent):
+ params, param_names, param_types = _ParamsInfo(self.method)
+ result = self.method.result.FullName()
+ return_ = 'return '
+ if result == 'void':
+ return_ = ''
+ param_names = ', '.join(param_names)
+ class_name = self.class_.FullName()
+ method_name = self.method.name
+ default_name = self.DefaultName()
+ # constantness
+ const = ''
+ if self.method.const:
+ const = 'const '
+ code = ''
+ # create default_method if this method has a default implementation
+ if not self.method.abstract:
+ default_sig = '%s %s(%s) %s' % (result, default_name, params, const)
+ body = '{ %s%s::%s(%s); } ' % \
+ (return_, class_name, method_name, param_names)
+ code += indent + default_sig + body + '\n'
+ # create normal method
+ normal_sig = '%s %s(%s) %s' % (result, method_name, params, const)
+ if param_names:
+ param_names = ', ' + param_names
+ body = '{ %s%scall_method< %s >(self, "%s"%s); }' % \
+ (return_, namespaces.python, result, self.rename, param_names)
+ code += indent + normal_sig + body + '\n'
+
+ return code
+
+
+
diff --git a/pyste/src/CodeUnit.py b/pyste/src/CodeUnit.py
new file mode 100644
index 00000000..ac123f99
--- /dev/null
+++ b/pyste/src/CodeUnit.py
@@ -0,0 +1,78 @@
+from settings import *
+
+#==============================================================================
+# RemoveDuplicatedLines
+#==============================================================================
+def RemoveDuplicatedLines(text):
+ includes = text.splitlines()
+ d = dict([(include, 0) for include in includes])
+ return '\n'.join(d.keys())
+
+
+#==============================================================================
+# CodeUnit
+#==============================================================================
+class CodeUnit:
+ '''
+ Represents a cpp file, where other objects can write in one of the
+ predefined sections.
+ The avaiable sections are:
+ include - The include area of the cpp file
+ declaration - The part before the module definition
+ module - Inside the BOOST_PYTHON_MODULE macro
+ '''
+
+ USING_BOOST_NS = True
+
+ def __init__(self, modulename):
+ self.modulename = modulename
+ # define the avaiable sections
+ self.code = {}
+ self.code['include'] = ''
+ self.code['declaration'] = ''
+ self.code['module'] = ''
+
+
+ def Write(self, section, code):
+ 'write the given code in the section of the code unit'
+ if section not in self.code:
+ raise RuntimeError, 'Invalid CodeUnit section: %s' % section
+ self.code[section] += code
+
+
+ def Section(self, section):
+ return self.code[section]
+
+
+ def Save(self, filename):
+ 'Writes this code unit to the filename'
+ space = '\n\n'
+ fout = file(filename, 'w')
+ # includes
+ includes = RemoveDuplicatedLines(self.code['include'])
+ fout.write('\n' + self._leftEquals('Includes'))
+ fout.write('#include \n')
+ fout.write(includes)
+ fout.write(space)
+ # using
+ if self.USING_BOOST_NS:
+ fout.write(self._leftEquals('Using'))
+ fout.write('using namespace boost::python;\n\n')
+ # declarations
+ if self.code['declaration']:
+ pyste_namespace = namespaces.pyste[:-2]
+ fout.write(self._leftEquals('Declarations'))
+ fout.write('namespace %s {\n\n\n' % pyste_namespace)
+ fout.write(self.code['declaration'])
+ fout.write('\n\n}// namespace %s\n' % pyste_namespace)
+ fout.write(space)
+ # module
+ fout.write(self._leftEquals('Module'))
+ fout.write('BOOST_PYTHON_MODULE(%s)\n{\n' % self.modulename)
+ fout.write(self.code['module'])
+ fout.write('}\n')
+
+
+ def _leftEquals(self, s):
+ s = '// %s ' % s
+ return s + ('='*(80-len(s))) + '\n'
diff --git a/pyste/src/CppParser.py b/pyste/src/CppParser.py
new file mode 100644
index 00000000..2dd9c9ff
--- /dev/null
+++ b/pyste/src/CppParser.py
@@ -0,0 +1,94 @@
+from GCCXMLParser import ParseDeclarations
+import tempfile
+import shutil
+import os
+import os.path
+import settings
+
+class CppParserError(Exception): pass
+
+
+class CppParser:
+ 'Parses a header file and returns a list of declarations'
+
+ def __init__(self, includes=None, defines=None):
+ 'includes and defines ar the directives given to gcc'
+ if includes is None:
+ includes = []
+ if defines is None:
+ defines = []
+ self.includes = includes
+ self.defines = defines
+
+
+ def _includeparams(self, filename):
+ includes = self.includes[:]
+ filedir = os.path.dirname(filename)
+ if not filedir:
+ filedir = '.'
+ includes.insert(0, filedir)
+ includes = ['-I "%s"' % x for x in includes]
+ return ' '.join(includes)
+
+
+ def _defineparams(self):
+ defines = ['-D "%s"' % x for x in self.defines]
+ return ' '.join(defines)
+
+
+ def FindFileName(self, include):
+ if os.path.isfile(include):
+ return include
+ for path in self.includes:
+ filename = os.path.join(path, include)
+ if os.path.isfile(filename):
+ return filename
+ name = os.path.basename(include)
+ raise RuntimeError, 'Header file "%s" not found!' % name
+
+
+ def parse(self, include, symbols=None, tail=None):
+ '''Parses the given filename, and returns (declaration, header). The
+ header returned is normally the same as the given to this method,
+ except if tail is not None: in this case, the header is copied to a temp
+ filename and the tail code is appended to it before being passed on to gcc.
+ This temp filename is then returned.
+ '''
+ filename = self.FindFileName(include)
+ # copy file to temp folder, if needed
+ if tail:
+ tempfilename = tempfile.mktemp('.h')
+ infilename = tempfilename
+ shutil.copy(filename, infilename)
+ f = file(infilename, 'a')
+ f.write('\n\n'+tail)
+ f.close()
+ else:
+ infilename = filename
+ xmlfile = tempfile.mktemp('.xml')
+ try:
+ # get the params
+ includes = self._includeparams(filename)
+ defines = self._defineparams()
+ # call gccxml
+ cmd = 'gccxml %s %s %s -fxml=%s' \
+ % (includes, defines, infilename, xmlfile)
+ if symbols:
+ cmd += ' -fxml-start=' + ','.join(symbols)
+ status = os.system(cmd)
+ if status != 0 or not os.path.isfile(xmlfile):
+ raise CppParserError, 'Error executing gccxml'
+ # parse the resulting xml
+ declarations = ParseDeclarations(xmlfile)
+ # return the declarations
+ return declarations, infilename
+ finally:
+ if settings.DEBUG and os.path.isfile(xmlfile):
+ filename = os.path.basename(include)
+ shutil.copy(xmlfile, os.path.splitext(filename)[0] + '.xml')
+ # delete the temporary files
+ try:
+ os.remove(xmlfile)
+ if tail:
+ os.remove(tempfilename)
+ except OSError: pass
diff --git a/pyste/src/EnumExporter.py b/pyste/src/EnumExporter.py
new file mode 100644
index 00000000..fda4d721
--- /dev/null
+++ b/pyste/src/EnumExporter.py
@@ -0,0 +1,30 @@
+from Exporter import Exporter
+from settings import *
+
+#==============================================================================
+# EnumExporter
+#==============================================================================
+class EnumExporter(Exporter):
+ 'Exports enumerators'
+
+ def __init__(self, info):
+ Exporter.__init__(self, info)
+
+
+ def SetDeclarations(self, declarations):
+ Exporter.SetDeclarations(self, declarations)
+ self.enum = self.GetDeclaration(self.info.name)
+
+
+ def Export(self, codeunit, expoted_names):
+ indent = self.INDENT
+ in_indent = self.INDENT*2
+ rename = self.info.rename or self.enum.name
+ full_name = self.enum.FullName()
+ code = indent + namespaces.python + 'enum_< %s >("%s")\n' % (full_name, rename)
+ for name in self.enum.values:
+ rename = self.info[name].rename or name
+ value_fullname = self.enum.ValueFullName(name)
+ code += in_indent + '.value("%s", %s)\n' % (rename, value_fullname)
+ code += indent + ';\n\n'
+ codeunit.Write('module', code)
diff --git a/pyste/src/Exporter.py b/pyste/src/Exporter.py
new file mode 100644
index 00000000..02259582
--- /dev/null
+++ b/pyste/src/Exporter.py
@@ -0,0 +1,69 @@
+import os.path
+
+#==============================================================================
+# Exporter
+#==============================================================================
+class Exporter:
+ 'Base class for objects capable to generate boost.python code.'
+
+ INDENT = ' ' * 4
+
+ def __init__(self, info, parser_tail=None):
+ self.info = info
+ self.parser_tail = parser_tail
+
+
+ def Parse(self, parser):
+ self.parser = parser
+ header = self.info.include
+ tail = self.parser_tail
+ declarations, parser_header = parser.parse(header, tail=tail)
+ self.parser_header = parser_header
+ self.SetDeclarations(declarations)
+
+
+ def SetDeclarations(self, declarations):
+ self.declarations = declarations
+
+
+ def GenerateCode(self, codeunit, exported_names):
+ self.WriteInclude(codeunit)
+ self.Export(codeunit, exported_names)
+
+
+ def WriteInclude(self, codeunit):
+ codeunit.Write('include', '#include <%s>\n' % self.info.include)
+
+
+ def Export(self, codeunit, exported_names):
+ 'subclasses must override this to do the real work'
+ pass
+
+
+ def Name(self):
+ '''Returns the name of this Exporter. The name will be added to the
+ list of names exported, which may have a use for other exporters.
+ '''
+ return None
+
+
+ def GetDeclarations(self, fullname):
+ decls = [x for x in self.declarations if x.FullName() == fullname]
+ if not decls:
+ raise RuntimeError, 'no %s declaration found!' % fullname
+ return decls
+
+
+ def GetDeclaration(self, fullname):
+ decls = self.GetDeclarations(fullname)
+ assert len(decls) == 1
+ return decls[0]
+
+
+ def Order(self):
+ '''Returns a number that indicates to which order this exporter
+ belongs. The exporters will be called from the lowest order to the
+ highest order.
+ This function will only be called after Parse has been called.
+ '''
+ return None # don't care
diff --git a/pyste/src/FunctionExporter.py b/pyste/src/FunctionExporter.py
new file mode 100644
index 00000000..2638a776
--- /dev/null
+++ b/pyste/src/FunctionExporter.py
@@ -0,0 +1,81 @@
+from Exporter import Exporter
+from policies import *
+from declarations import *
+from settings import *
+
+
+class FunctionExporter(Exporter):
+ 'Generates boost.python code to export the given function.'
+
+ def __init__(self, info, tail=None):
+ Exporter.__init__(self, info, tail)
+
+
+ def Export(self, codeunit, exported_names):
+ decls = self.GetDeclarations(self.info.name)
+ for decl in decls:
+ self.CheckPolicy(decl)
+ self.ExportDeclaration(decl, len(decls) == 1, codeunit)
+ self.GenerateOverloads(decls, codeunit)
+
+
+ def Name(self):
+ return self.info.name
+
+
+ def CheckPolicy(self, func):
+ 'Warns the user if this function needs a policy'
+ needs_policy = isinstance(func.result, (ReferenceType, PointerType))
+ if needs_policy and self.info.policy is None:
+ print '---> Error: Function "%s" needs a policy.' % func.FullName()
+ print
+
+ def ExportDeclaration(self, decl, unique, codeunit):
+ name = self.info.rename or decl.name
+ defs = namespaces.python + 'def("%s", ' % name
+ wrapper = self.info.wrapper
+ if wrapper:
+ pointer = '&' + wrapper.FullName()
+ elif not unique:
+ pointer = decl.PointerDeclaration()
+ else:
+ pointer = '&' + decl.FullName()
+ defs += pointer
+ defs += self.PolicyCode()
+ overload = self.OverloadName(decl)
+ if overload:
+ defs += ', %s()' % (namespaces.pyste + overload)
+ defs += ');'
+ codeunit.Write('module', self.INDENT + defs + '\n')
+ # add the code of the wrapper
+ if wrapper and wrapper.code:
+ codeunit.Write('declaration', code + '\n')
+
+
+ def OverloadName(self, decl):
+ if decl.minArgs != decl.maxArgs:
+ return '%s_overloads_%i_%i' % \
+ (decl.name, decl.minArgs, decl.maxArgs)
+ else:
+ return ''
+
+
+ def GenerateOverloads(self, declarations, codeunit):
+ codes = {}
+ for decl in declarations:
+ overload = self.OverloadName(decl)
+ if overload and overload not in codes:
+ code = 'BOOST_PYTHON_FUNCTION_OVERLOADS(%s, %s, %i, %i)' %\
+ (overload, decl.FullName(), decl.minArgs, decl.maxArgs)
+ codeunit.Write('declaration', code + '\n')
+ codes[overload] = None
+
+
+ def PolicyCode(self):
+ policy = self.info.policy
+ if policy is not None:
+ assert isinstance(policy, Policy)
+ return ', %s()' % policy.Code()
+ else:
+ return ''
+
diff --git a/pyste/src/GCCXMLParser.py b/pyste/src/GCCXMLParser.py
new file mode 100644
index 00000000..937db92f
--- /dev/null
+++ b/pyste/src/GCCXMLParser.py
@@ -0,0 +1,395 @@
+from declarations import *
+from elementtree.ElementTree import ElementTree
+from xml.parsers.expat import ExpatError
+from copy import deepcopy
+
+
+class InvalidXMLError(Exception): pass
+
+class ParserError(Exception): pass
+
+class InvalidContextError(ParserError): pass
+
+
+class GCCXMLParser(object):
+ 'Parse a GCC_XML file and extract the top-level declarations.'
+
+ interested_tags = {'Class':0, 'Function':0, 'Variable':0, 'Enumeration':0}
+
+ def Parse(self, filename):
+ self.elements = self.GetElementsFromXML(filename)
+ # high level declarations
+ self.declarations = []
+ # parse the elements
+ for id in self.elements:
+ element, decl = self.elements[id]
+ if decl is None:
+ try:
+ self.ParseElement(id, element)
+ except InvalidContextError:
+ pass # ignore those nodes with invalid context
+ # (workaround gccxml bug)
+
+
+ def Declarations(self):
+ return self.declarations
+
+
+ def AddDecl(self, decl):
+ self.declarations.append(decl)
+
+
+ def ParseElement(self, id, element):
+ method = 'Parse' + element.tag
+ if hasattr(self, method):
+ func = getattr(self, method)
+ func(id, element)
+
+
+ def GetElementsFromXML(self,filename):
+ 'Extracts a dictionary of elements from the gcc_xml file.'
+
+ tree = ElementTree()
+ try:
+ tree.parse(filename)
+ except ExpatError:
+ raise InvalidXMLError, 'Not a XML file: %s' % filename
+
+ root = tree.getroot()
+ if root.tag != 'GCC_XML':
+ raise InvalidXMLError, 'Not a valid GCC_XML file'
+
+ # build a dictionary of id -> element, None
+ elementlist = root.getchildren()
+ elements = {}
+ for element in elementlist:
+ id = element.get('id')
+ if id:
+ elements[id] = element, None
+ return elements
+
+
+ def GetDecl(self, id):
+ if id not in self.elements:
+ if id == '_0':
+ raise InvalidContextError, 'Invalid context found in the xml file.'
+ else:
+ msg = 'ID not found in elements: %s' % id
+ raise ParserError, msg
+
+ elem, decl = self.elements[id]
+ if decl is None:
+ self.ParseElement(id, elem)
+ elem, decl = self.elements[id]
+ if decl is None:
+ raise ParserError, 'Could not parse element: %s' % elem.tag
+ return decl
+
+
+ def GetType(self, id):
+ const = False
+ volatile = False
+ if id[-1] == 'v':
+ volatile = True
+ id = id[:-1]
+ if id[-1] == 'c':
+ const = True
+ id = id[:-1]
+ decl = self.GetDecl(id)
+ if isinstance(decl, Type):
+ res = deepcopy(decl)
+ if const:
+ res.const = const
+ if volatile:
+ res.volatile = volatile
+ else:
+ res = Type(decl.FullName(), const)
+ res.volatile = volatile
+ return res
+
+
+ def GetLocation(self, location):
+ file, line = location.split(':')
+ file = self.GetDecl(file)
+ return file, int(line)
+
+
+ def Update(self, id, decl):
+ element, _ = self.elements[id]
+ self.elements[id] = element, decl
+
+
+ def ParseNamespace(self, id, element):
+ namespace = element.get('name')
+ context = element.get('context')
+ if context:
+ outerns = self.GetDecl(context)
+ if not outerns.endswith('::'):
+ outerns += '::'
+ namespace = outerns + namespace
+ if namespace.startswith('::'):
+ namespace = namespace[2:]
+ self.Update(id, namespace)
+
+
+ def ParseFile(self, id, element):
+ filename = element.get('name')
+ self.Update(id, filename)
+
+
+ def ParseVariable(self, id, element):
+ # in gcc_xml, a static Field is declared as a Variable, so we check
+ # this and call the Field parser if apply.
+ context = self.GetDecl(element.get('context'))
+ if isinstance(context, Class):
+ self.ParseField(id, element)
+ elem, decl = self.elements[id]
+ decl.static = True
+ else:
+ namespace = context
+ name = element.get('name')
+ type_ = self.GetType(element.get('type'))
+ location = self.GetLocation(element.get('location'))
+ variable = Variable(type_, name, namespace)
+ variable.location = location
+ self.AddDecl(variable)
+ self.Update(id, variable)
+
+
+ def GetArguments(self, element):
+ args = []
+ for child in element:
+ if child.tag == 'Argument':
+ type_ = self.GetType(child.get('type'))
+ type_.default = child.get('default')
+ args.append(type_)
+ return args
+
+
+ def ParseFunction(self, id, element, functionType=Function):
+ '''functionType is used because a Operator is identical to a normal
+ function, only the type of the function changes.'''
+ name = element.get('name')
+ returns = self.GetType(element.get('returns'))
+ namespace = self.GetDecl(element.get('context'))
+ location = self.GetLocation(element.get('location'))
+ params = self.GetArguments(element)
+ function = functionType(name, namespace, returns, params)
+ function.location = location
+ self.AddDecl(function)
+ self.Update(id, function)
+
+
+ def ParseOperatorFunction(self, id, element):
+ self.ParseFunction(id, element, Operator)
+
+
+ def GetBases(self, bases):
+ 'Parses the string "bases" from the xml into a list of Base instances.'
+
+ if bases is None:
+ return []
+ bases = bases.split()
+ baseobjs = []
+ for base in bases:
+ # get the visibility
+ split = base.split(':')
+ if len(split) == 2:
+ visib = split[0]
+ base = split[1]
+ else:
+ visib = Scope.public
+ decl = self.GetDecl(base)
+ baseobj = Base(decl.FullName(), visib)
+ baseobjs.append(baseobj)
+ return baseobjs
+
+
+ def GetMembers(self, members):
+ # members must be a string with the ids of the members
+ if members is None:
+ return []
+ memberobjs = []
+ for member in members.split():
+ memberobjs.append(self.GetDecl(member))
+ return memberobjs
+
+
+ def ParseClass(self, id, element):
+ name = element.get('name')
+ abstract = bool(int(element.get('abstract', '0')))
+ bases = self.GetBases(element.get('bases'))
+ location = self.GetLocation(element.get('location'))
+ context = self.GetDecl(element.get('context'))
+ if isinstance(context, Class): # a nested class
+ visib = element.get('access', Scope.public)
+ class_ = NestedClass(
+ name, context.FullName(), visib, [], abstract, bases)
+ else:
+ assert isinstance(context, str)
+ class_ = Class(name, context, [], abstract, bases)
+ self.AddDecl(class_)
+ # we have to add the declaration of the class before trying
+ # to parse its members, to avoid recursion.
+ class_.location = location
+ self.Update(id, class_)
+ # now we can get the members
+ class_.members = self.GetMembers(element.get('members'))
+
+
+ def ParseStruct(self, id, element):
+ self.ParseClass(id, element)
+
+
+ def ParseFundamentalType(self, id, element):
+ name = element.get('name')
+ type_ = FundamentalType(name)
+ self.Update(id, type_)
+
+
+ def ParseArrayType(self, id, element):
+ type_ = self.GetType(element.get('type'))
+ min = element.get('min')
+ max = element.get('max')
+ if min:
+ min = int(min)
+ if max:
+ max = int(max)
+ array = ArrayType(type_.name, min, max, type_.const)
+ self.Update(id, array)
+
+
+ def ParseReferenceType(self, id, element):
+ type_ = self.GetType(element.get('type'))
+ expand = not isinstance(type_, FunctionType)
+ ref = ReferenceType(type_.name, type_.const, None, expand)
+ self.Update(id, ref)
+
+
+ def ParsePointerType(self, id, element):
+ type_ = self.GetType(element.get('type'))
+ expand = not isinstance(type_, FunctionType)
+ ref = PointerType(type_.name, type_.const, None, expand)
+ self.Update(id, ref)
+
+
+ def ParseFunctionType(self, id, element):
+ result = self.GetType(element.get('returns'))
+ args = self.GetArguments(element)
+ func = FunctionType(result, args)
+ self.Update(id, func)
+
+
+ def ParseMethodType(self, id, element):
+ class_ = self.GetDecl(element.get('basetype')).FullName()
+ result = self.GetType(element.get('returns'))
+ args = self.GetArguments(element)
+ method = MethodType(result, args, class_)
+ self.Update(id, method)
+
+
+ def ParseField(self, id, element):
+ name = element.get('name')
+ visib = element.get('access', Scope.public)
+ classname = self.GetDecl(element.get('context')).FullName()
+ type_ = self.GetType(element.get('type'))
+ static = bool(int(element.get('extern', '0')))
+ location = self.GetLocation(element.get('location'))
+ var = ClassVariable(type_, name, classname, visib, static)
+ var.location = location
+ self.Update(id, var)
+
+
+ def ParseMethod(self, id, element, methodType=Method):
+ name = element.get('name')
+ result = self.GetType(element.get('returns'))
+ classname = self.GetDecl(element.get('context')).FullName()
+ visib = element.get('access', Scope.public)
+ static = bool(int(element.get('static', '0')))
+ virtual = bool(int(element.get('virtual', '0')))
+ abstract = bool(int(element.get('pure_virtual', '0')))
+ const = bool(int(element.get('const', '0')))
+ location = self.GetLocation(element.get('location'))
+ params = self.GetArguments(element)
+ method = methodType(
+ name, classname, result, params, visib, virtual, abstract, static, const)
+ method.location = location
+ self.Update(id, method)
+
+
+ def ParseOperatorMethod(self, id, element):
+ self.ParseMethod(id, element, ClassOperator)
+
+
+ def ParseConstructor(self, id, element):
+ name = element.get('name')
+ visib = element.get('access', Scope.public)
+ classname = self.GetDecl(element.get('context')).FullName()
+ location = self.GetLocation(element.get('location'))
+ params = self.GetArguments(element)
+ ctor = Constructor(name, classname, params, visib)
+ ctor.location = location
+ self.Update(id, ctor)
+
+
+ def ParseDestructor(self, id, element):
+ name = element.get('name')
+ visib = element.get('access', Scope.public)
+ classname = self.GetDecl(element.get('context')).FullName()
+ virtual = bool(int(element.get('virtual', '0')))
+ location = self.GetLocation(element.get('location'))
+ des = Destructor(name, classname, visib, virtual)
+ des.location = location
+ self.Update(id, des)
+
+
+ def ParseConverter(self, id, element):
+ self.ParseMethod(id, element, ConverterOperator)
+
+
+ def ParseTypedef(self, id, element):
+ name = element.get('name')
+ type = self.GetDecl(element.get('type'))
+ context = self.GetDecl(element.get('context'))
+ if isinstance(context, Class):
+ context = context.FullName()
+ typedef = Typedef(type, name, context)
+ self.Update(id, typedef)
+ self.AddDecl(typedef)
+
+
+ def ParseEnumeration(self, id, element):
+ name = element.get('name')
+ location = self.GetLocation(element.get('location'))
+ context = self.GetDecl(element.get('context'))
+ if isinstance(context, Class):
+ visib = element.get('access', Scope.public)
+ enum = ClassEnumeration(name, context.FullName(), visib)
+ else:
+ enum = Enumeration(name, context)
+ self.AddDecl(enum) # in this case, is a top level decl
+ enum.location = location
+ for child in element:
+ if child.tag == 'EnumValue':
+ name = child.get('name')
+ value = int(child.get('init'))
+ enum.values[name] = value
+ self.Update(id, enum)
+
+
+ def ParseUnimplemented(self, id, element):
+ 'No idea of what this is'
+ self.Update(id, Declaration('', ''))
+
+
+ def ParseUnion(self, id, element):
+ self.Update(id, Declaration(element.get('name'), ''))
+
+
+
+def ParseDeclarations(filename):
+ 'Returns a list of the top declarations found in the gcc_xml file.'
+
+ parser = GCCXMLParser()
+ parser.Parse(filename)
+ return parser.Declarations()
diff --git a/pyste/src/HeaderExporter.py b/pyste/src/HeaderExporter.py
new file mode 100644
index 00000000..9234e8af
--- /dev/null
+++ b/pyste/src/HeaderExporter.py
@@ -0,0 +1,67 @@
+from Exporter import Exporter
+from ClassExporter import ClassExporter
+from FunctionExporter import FunctionExporter
+from EnumExporter import EnumExporter
+from infos import *
+from declarations import *
+import os.path
+import exporters
+
+#==============================================================================
+# HeaderExporter
+#==============================================================================
+class HeaderExporter(Exporter):
+ 'Exports all declarations found in the given header'
+
+ def __init__(self, info, parser_tail=None):
+ Exporter.__init__(self, info, parser_tail)
+
+
+ def WriteInclude(self, codeunit):
+ pass
+
+
+ def SetDeclarations(self, declarations):
+ def IsInternalName(name):
+ '''Returns true if the given name looks like a internal compiler
+ structure'''
+ return name.startswith('__')
+
+ Exporter.SetDeclarations(self, declarations)
+ header = os.path.normpath(self.parser_header)
+ for decl in declarations:
+ # check if this declaration is in the header
+ location = os.path.normpath(decl.location[0])
+ if location != header or IsInternalName(decl.name):
+ continue
+ # ok, check the type of the declaration and export it accordingly
+ self.HandleDeclaration(decl)
+
+
+ def HandleDeclaration(self, decl):
+ '''Dispatch the declaration to the appropriate method, that must create
+ a suitable info object for a Exporter, create a Exporter, set its
+ declarations and append it to the list of exporters.
+ '''
+ dispatch_table = {
+ Class : ClassExporter,
+ Enumeration : EnumExporter,
+ Function : FunctionExporter,
+ }
+
+ for decl_type, exporter_type in dispatch_table.items():
+ if type(decl) == decl_type:
+ self.HandleExporter(decl, exporter_type)
+ break
+
+
+ def HandleExporter(self, decl, exporter_type):
+ info = self.info[decl.name]
+ info.name = decl.FullName()
+ info.include = self.info.include
+ exporter = exporter_type(info)
+ exporter.SetDeclarations(self.declarations)
+ exporters.exporters.append(exporter)
+
+
+
diff --git a/pyste/src/IncludeExporter.py b/pyste/src/IncludeExporter.py
new file mode 100644
index 00000000..2a7b0602
--- /dev/null
+++ b/pyste/src/IncludeExporter.py
@@ -0,0 +1,19 @@
+import os.path
+from Exporter import Exporter
+
+#==============================================================================
+# IncludeExporter
+#==============================================================================
+class IncludeExporter(Exporter):
+ '''Writes an include declaration to the module. Useful to add extra code
+ for use in the Wrappers.
+ This class just reimplements the Parse method to do nothing: the
+ WriteInclude in Exporter already does the work for us.
+ '''
+
+ def __init__(self, info, parser_tail=None):
+ Exporter.__init__(self, info, parser_tail)
+
+ def Parse(self, parser):
+ pass
+
diff --git a/pyste/src/declarations.py b/pyste/src/declarations.py
new file mode 100644
index 00000000..adba6fd3
--- /dev/null
+++ b/pyste/src/declarations.py
@@ -0,0 +1,452 @@
+'''
+Module declarations
+
+ Defines classes that represent declarations found in C++ header files.
+
+'''
+
+class Declaration(object):
+ 'Represents a basic declaration.'
+
+ def __init__(self, name, namespace):
+ # the declaration name
+ self.name = name
+ # all the namespaces, separated by '::' = 'boost::inner'
+ self.namespace = namespace
+ # tuple (filename, line)
+ self.location = '', -1
+
+
+ def FullName(self):
+ 'Returns the full qualified name: "boost::inner::Test"'
+ namespace = self.namespace or ''
+ #if not namespace:
+ # namespace = ''
+ if namespace and not namespace.endswith('::'):
+ namespace += '::'
+ return namespace + self.name
+
+
+ def __repr__(self):
+ return '' % (self.FullName(), id(self))
+
+
+ def __str__(self):
+ return 'Declaration of %s' % self.FullName()
+
+
+
+class Class(Declaration):
+ 'The declaration of a class or struct.'
+
+ def __init__(self, name, namespace, members, abstract, bases):
+ Declaration.__init__(self, name, namespace)
+ # list of members
+ self.members = members
+ # whatever the class has any abstract methods
+ self.abstract = abstract
+ # instances of Base
+ self.bases = bases
+ self._members_count = {}
+
+
+ def __iter__(self):
+ return iter(self.members)
+
+
+ def IsAbstract(self):
+ 'Returns True if any method of this class is abstract'
+ for member in self.members:
+ if isinstance(member, Method):
+ if member.abstract:
+ return True
+ return False
+
+
+ def RawName(self):
+ 'Returns the raw name of a template class. name = Foo, raw = Foo'
+ lesspos = self.name.find('<')
+ if lesspos != -1:
+ return self.name[:lesspos]
+ else:
+ return self.name
+
+
+ def Constructors(self, publics_only=True):
+ constructors = []
+ for member in self:
+ if isinstance(member, Constructor):
+ if publics_only and member.visibility != Scope.public:
+ continue
+ constructors.append(member)
+ return constructors
+
+
+ def HasCopyConstructor(self):
+ for cons in self.Constructors():
+ if cons.IsCopy():
+ return True
+ return False
+
+
+ def HasDefaultConstructor(self):
+ for cons in self.Constructors():
+ if cons.IsDefault():
+ return True
+ return False
+
+
+ def IsUnique(self, member_name):
+ if not self._members_count:
+ for m in self:
+ self._members_count[m.name] = self._members_count.get(m.name, 0) + 1
+ try:
+ return self._members_count[member_name] == 1
+ except KeyError:
+ print self._members_count
+ print 'Key', member_name
+
+
+
+class NestedClass(Class):
+ 'The declaration of a class/struct inside another class/struct.'
+
+ def __init__(self, name, class_, visib, members, abstract, bases):
+ Class.__init__(self, name, None, members, abstract, bases)
+ self.class_ = class_
+ self.visibility = visib
+
+
+ def FullName(self):
+ return '%s::%s' % (self.class_, self.name)
+
+
+
+class Base:
+ 'Represents a base class of another class.'
+
+ def __init__(self, name, visibility=None):
+ # class_ is the full name of the base class
+ self.name = name
+ # visibility of the derivation
+ if visibility is None:
+ visibility = Scope.public
+ self.visibility = visibility
+
+
+
+class Scope:
+ public = 'public'
+ private = 'private'
+ protected = 'protected'
+
+
+
+class Function(Declaration):
+ 'The declaration of a function.'
+
+ def __init__(self, name, namespace, result, params):
+ Declaration.__init__(self, name, namespace)
+ # the result type: instance of Type, or None (constructors)
+ self.result = result
+ # the parameters: instances of Type
+ self.parameters = params
+
+
+ def PointerDeclaration(self):
+ 'returns a declaration of a pointer to this function'
+ result = self.result.FullName()
+ params = ', '.join([x.FullName() for x in self.parameters])
+ return '(%s (*)(%s))%s' % (result, params, self.FullName())
+
+
+ def _MinArgs(self):
+ min = 0
+ for arg in self.parameters:
+ if arg.default is None:
+ min += 1
+ return min
+
+ minArgs = property(_MinArgs)
+
+
+ def _MaxArgs(self):
+ return len(self.parameters)
+
+ maxArgs = property(_MaxArgs)
+
+
+
+class Operator(Function):
+ 'The declaration of a custom operator.'
+ def FullName(self):
+ namespace = self.namespace or ''
+ if not namespace.endswith('::'):
+ namespace += '::'
+ return namespace + 'operator' + self.name
+
+
+
+class Method(Function):
+ 'The declaration of a method.'
+
+ def __init__(self, name, class_, result, params, visib, virtual, abstract, static, const):
+ Function.__init__(self, name, None, result, params)
+ self.visibility = visib
+ self.virtual = virtual
+ self.abstract = abstract
+ self.static = static
+ self.class_ = class_
+ self.const = const
+
+
+ def FullName(self):
+ return self.class_ + '::' + self.name
+
+
+ def PointerDeclaration(self):
+ 'returns a declaration of a pointer to this function'
+ result = self.result.FullName()
+ params = ', '.join([x.FullName() for x in self.parameters])
+ const = ''
+ if self.const:
+ const = 'const'
+ return '(%s (%s::*)(%s) %s)%s' %\
+ (result, self.class_, params, const, self.FullName())
+
+
+class Constructor(Method):
+ 'A constructor of a class.'
+
+ def __init__(self, name, class_, params, visib):
+ Method.__init__(self, name, class_, None, params, visib, False, False, False, False)
+
+
+ def IsDefault(self):
+ return len(self.parameters) == 0
+
+
+ def IsCopy(self):
+ if len(self.parameters) != 1:
+ return False
+ param = self.parameters[0]
+ class_as_param = self.parameters[0].name == self.class_
+ param_reference = isinstance(param, ReferenceType)
+ return param_reference and class_as_param and param.const
+
+
+class Destructor(Method):
+ 'The destructor of a class.'
+
+ def __init__(self, name, class_, visib, virtual):
+ Method.__init__(self, name, class_, None, [], visib, virtual, False, False, False)
+
+ def FullName(self):
+ return self.class_ + '::~' + self.name
+
+
+
+class ClassOperator(Method):
+ 'The declaration of a custom operator in a class.'
+
+ def FullName(self):
+ return self.class_ + '::operator ' + self.name
+
+
+
+class ConverterOperator(ClassOperator):
+ 'An operator in the form "operator OtherClass()".'
+
+ def FullName(self):
+ return self.class_ + '::operator ' + self.result.name
+
+
+
+class Type(Declaration):
+ 'Represents a type.'
+
+ def __init__(self, name, const=False, default=None):
+ Declaration.__init__(self, name, None)
+ # whatever the type is constant or not
+ self.const = const
+ # used when the Type is a function argument
+ self.default = default
+ self.volatile = False
+
+ def __repr__(self):
+ if self.const:
+ const = 'const '
+ else:
+ const = ''
+ return ''
+
+
+ def FullName(self):
+ if self.const:
+ const = 'const '
+ else:
+ const = ''
+ return const + self.name
+
+
+
+class ArrayType(Type):
+ 'Represents an array.'
+
+ def __init__(self, name, min, max, const=False):
+ 'min and max can be None.'
+ Type.__init__(self, name, const)
+ self.min = min
+ self.max = max
+
+
+
+class ReferenceType(Type):
+ 'A reference type.'
+
+ def __init__(self, name, const=False, default=None, expandRef=True):
+ Type.__init__(self, name, const, default)
+ self.expand = expandRef
+
+
+ def FullName(self):
+ 'expand is False for function pointers'
+ expand = ' &'
+ if not self.expand:
+ expand = ''
+ return Type.FullName(self) + expand
+
+
+
+class PointerType(Type):
+ 'A pointer type.'
+
+ def __init__(self, name, const=False, default=None, expandPointer=False):
+ Type.__init__(self, name, const, default)
+ self.expand = expandPointer
+
+
+ def FullName(self):
+ 'expand is False for function pointer'
+ expand = ' *'
+ if not self.expand:
+ expand = ''
+ return Type.FullName(self) + expand
+
+
+
+class FundamentalType(Type):
+ 'One of the fundamental types (int, void...).'
+
+ def __init__(self, name, const=False):
+ Type.__init__(self, name, const)
+
+
+
+class FunctionType(Type):
+ 'A pointer to a function.'
+
+ def __init__(self, result, params):
+ Type.__init__(self, '', False)
+ self.result = result
+ self.parameters = params
+ self.name = self.FullName()
+
+
+ def FullName(self):
+ full = '%s (*)' % self.result.FullName()
+ params = [x.FullName() for x in self.parameters]
+ full += '(%s)' % ', '.join(params)
+ return full
+
+
+
+class MethodType(FunctionType):
+ 'A pointer to a member function of a class.'
+
+ def __init__(self, result, params, class_):
+ Type.__init__(self, '', False)
+ self.result = result
+ self.parameters = params
+ self.class_ = class_
+ self.name = self.FullName()
+
+ def FullName(self):
+ full = '%s (%s::*)' % (self.result.FullName(), self.class_)
+ params = [x.FullName() for x in self.parameters]
+ full += '(%s)' % ', '.join(params)
+ return full
+
+
+
+class Variable(Declaration):
+ 'Represents a global variable.'
+
+ def __init__(self, type, name, namespace):
+ Declaration.__init__(self, name, namespace)
+ # instance of Type
+ self.type = type
+
+
+
+class ClassVariable(Variable):
+ 'Represents a class variable.'
+
+ def __init__(self, type, name, class_, visib, static):
+ Variable.__init__(self, type, name, None)
+ self.visibility = visib
+ self.static = static
+ self.class_ = class_
+
+
+ def FullName(self):
+ return self.class_ + '::' + self.name
+
+
+
+class Enumeration(Declaration):
+
+ def __init__(self, name, namespace):
+ Declaration.__init__(self, name, namespace)
+ self.values = {} # dict of str => int
+
+ def ValueFullName(self, name):
+ assert name in self.values
+ namespace = self.namespace
+ if namespace:
+ namespace += '::'
+ return namespace + name
+
+
+
+class ClassEnumeration(Enumeration):
+
+ def __init__(self, name, class_, visib):
+ Enumeration.__init__(self, name, None)
+ self.class_ = class_
+ self.visibility = visib
+
+
+ def FullName(self):
+ return '%s::%s' % (self.class_, self.name)
+
+
+ def ValueFullName(self, name):
+ assert name in self.values
+ return '%s::%s' % (self.class_, name)
+
+
+
+class Typedef(Declaration):
+
+ def __init__(self, type, name, namespace):
+ Declaration.__init__(self, name, namespace)
+ self.type = type
+ self.visibility = Scope.public
+
+
+
+
+
+
+
diff --git a/pyste/src/enumerate.py b/pyste/src/enumerate.py
new file mode 100644
index 00000000..099e42ba
--- /dev/null
+++ b/pyste/src/enumerate.py
@@ -0,0 +1,7 @@
+from __future__ import generators
+
+def enumerate(seq):
+ i = 0
+ for x in seq:
+ yield i, x
+ i += 1
diff --git a/pyste/src/exporters.py b/pyste/src/exporters.py
new file mode 100644
index 00000000..65536780
--- /dev/null
+++ b/pyste/src/exporters.py
@@ -0,0 +1,3 @@
+
+# a list of Exporter instances
+exporters = []
diff --git a/pyste/src/exporterutils.py b/pyste/src/exporterutils.py
new file mode 100644
index 00000000..5134a1e5
--- /dev/null
+++ b/pyste/src/exporterutils.py
@@ -0,0 +1,26 @@
+'''
+Various helpers for interface files.
+'''
+
+from settings import *
+
+#==============================================================================
+# FunctionWrapper
+#==============================================================================
+class FunctionWrapper(object):
+ '''Holds information about a wrapper for a function or a method. It is in 2
+ parts: the name of the Wrapper, and its code. The code is placed in the
+ declaration section of the module, while the name is used to def' the
+ function or method (with the pyste namespace prepend to it). If code is None,
+ the name is left unchanged.
+ '''
+
+ def __init__(self, name, code=None):
+ self.name = name
+ self.code = code
+
+ def FullName(self):
+ if self.code:
+ return namespaces.pyste + self.name
+ else:
+ return self.name
diff --git a/pyste/src/infos.py b/pyste/src/infos.py
new file mode 100644
index 00000000..3d23537e
--- /dev/null
+++ b/pyste/src/infos.py
@@ -0,0 +1,185 @@
+import os.path
+import copy
+import exporters
+from ClassExporter import ClassExporter
+from FunctionExporter import FunctionExporter
+from IncludeExporter import IncludeExporter
+from EnumExporter import EnumExporter
+from HeaderExporter import HeaderExporter
+from exporterutils import FunctionWrapper
+
+
+#==============================================================================
+# DeclarationInfo
+#==============================================================================
+class DeclarationInfo(object):
+
+ def __init__(self, otherInfo=None):
+ self.__infos = {}
+ self.__attributes = {}
+ if otherInfo is not None:
+ self.__infos = otherInfo.__infos.copy()
+ self.__attributes = otherInfo.__attributes.copy()
+
+
+ def __getitem__(self, name):
+ 'Used to access sub-infos'
+ default = DeclarationInfo()
+ default._Attribute('name', name)
+ return self.__infos.setdefault(name, default)
+
+
+ def __getattr__(self, name):
+ return self[name]
+
+
+ def _Attribute(self, name, value=None):
+ if value is None:
+ # get value
+ return self.__attributes.get(name)
+ else:
+ # set value
+ self.__attributes[name] = value
+
+
+#==============================================================================
+# FunctionInfo
+#==============================================================================
+class FunctionInfo(DeclarationInfo):
+
+ def __init__(self, name, include, tail=None, otherOption=None):
+ DeclarationInfo.__init__(self, otherOption)
+ self._Attribute('name', name)
+ self._Attribute('include', include)
+ # create a FunctionExporter
+ exporter = FunctionExporter(InfoWrapper(self), tail)
+ exporters.exporters.append(exporter)
+
+
+#==============================================================================
+# ClassInfo
+#==============================================================================
+class ClassInfo(DeclarationInfo):
+
+ def __init__(self, name, include, tail=None, otherOption=None):
+ DeclarationInfo.__init__(self, otherOption)
+ self._Attribute('name', name)
+ self._Attribute('include', include)
+ # create a ClassExporter
+ exporter = ClassExporter(InfoWrapper(self), tail)
+ exporters.exporters.append(exporter)
+
+
+#==============================================================================
+# IncludeInfo
+#==============================================================================
+class IncludeInfo(DeclarationInfo):
+
+ def __init__(self, include):
+ DeclarationInfo.__init__(self)
+ self._Attribute('include', include)
+ exporter = IncludeExporter(InfoWrapper(self))
+ exporters.exporters.append(exporter)
+
+
+#==============================================================================
+# templates
+#==============================================================================
+def GenerateName(name, type_list):
+ name = name.replace('::', '_')
+ names = [name] + type_list
+ return '_'.join(names)
+
+
+class ClassTemplateInfo(DeclarationInfo):
+
+ def __init__(self, name, include):
+ DeclarationInfo.__init__(self)
+ self._Attribute('name', name)
+ self._Attribute('include', include)
+
+
+ def Instantiate(self, type_list, rename=None):
+ if not rename:
+ rename = GenerateName(self._Attribute('name'), type_list)
+ # generate code to instantiate the template
+ types = ', '.join(type_list)
+ tail = 'typedef %s< %s > %s;\n' % (self._Attribute('name'), types, rename)
+ tail += 'void __instantiate_%s()\n' % rename
+ tail += '{ sizeof(%s); }\n\n' % rename
+ # create a ClassInfo
+ class_ = ClassInfo(rename, self._Attribute('include'), tail, self)
+ return class_
+
+
+ def __call__(self, types, rename=None):
+ if isinstance(types, str):
+ types = types.split()
+ return self.Instantiate(types, rename)
+
+#==============================================================================
+# EnumInfo
+#==============================================================================
+class EnumInfo(DeclarationInfo):
+
+ def __init__(self, name, include):
+ DeclarationInfo.__init__(self)
+ self._Attribute('name', name)
+ self._Attribute('include', include)
+ exporter = EnumExporter(InfoWrapper(self))
+ exporters.exporters.append(exporter)
+
+
+#==============================================================================
+# HeaderInfo
+#==============================================================================
+class HeaderInfo(DeclarationInfo):
+
+ def __init__(self, include):
+ DeclarationInfo.__init__(self)
+ self._Attribute('include', include)
+ exporter = HeaderExporter(InfoWrapper(self))
+ exporters.exporters.append(exporter)
+
+
+#==============================================================================
+# InfoWrapper
+#==============================================================================
+class InfoWrapper:
+ 'Provides a nicer interface for a info'
+
+ def __init__(self, info):
+ self.__dict__['_info'] = info # so __setattr__ is not called
+
+ def __getitem__(self, name):
+ return InfoWrapper(self._info[name])
+
+ def __getattr__(self, name):
+ return self._info._Attribute(name)
+
+ def __setattr__(self, name, value):
+ self._info._Attribute(name, value)
+
+
+#==============================================================================
+# Functions
+#==============================================================================
+def exclude(option):
+ option._Attribute('exclude', True)
+
+def set_policy(option, policy):
+ option._Attribute('policy', policy)
+
+def rename(option, name):
+ option._Attribute('rename', name)
+
+def set_wrapper(option, wrapper):
+ if isinstance(wrapper, str):
+ wrapper = FunctionWrapper(wrapper)
+ option._Attribute('wrapper', wrapper)
+
+def instantiate(template, types, rename=None):
+ if isinstance(types, str):
+ types = types.split()
+ return template.Instantiate(types, rename)
+
diff --git a/pyste/src/policies.py b/pyste/src/policies.py
new file mode 100644
index 00000000..977e7f92
--- /dev/null
+++ b/pyste/src/policies.py
@@ -0,0 +1,75 @@
+
+
+class Policy:
+ 'Represents one of the call policies of boost.python.'
+
+ def __init__(self):
+ raise RuntimeError, "Can't create an instance of the class Policy"
+
+
+ def Code(self):
+ 'Returns the string corresponding to a instancialization of the policy.'
+ pass
+
+
+ def _next(self):
+ if self.next is not None:
+ return ', %s >' % self.next.Code()
+ else:
+ return ' >'
+
+
+
+class return_internal_reference(Policy):
+ 'Ties the return value to one of the parameters.'
+
+ def __init__(self, param=1, next=None):
+ '''
+ param is the position of the parameter, or None for "self".
+ next indicates the next policy, or None.
+ '''
+ self.param = param
+ self.next=next
+
+
+ def Code(self):
+ c = 'return_internal_reference< %i' % self.param
+ c += self._next()
+ return c
+
+
+
+class with_custodian_and_ward(Policy):
+ 'Ties lifetime of two arguments of a function.'
+
+ def __init__(self, custodian, ward, next=None):
+ self.custodian = custodian
+ self.ward = ward
+ self.next = next
+
+ def Code(self):
+ c = 'with_custodian_and_ward< %i, %i' % (self.custodian, self.ward)
+ c += self._next()
+ return c
+
+
+
+class return_value_policy(Policy):
+ 'Policy to convert return values.'
+
+ def __init__(self, which, next=None):
+ self.which = which
+ self.next = next
+
+
+ def Code(self):
+ c = 'return_value_policy< %s' % self.which
+ c += self._next()
+ return c
+
+
+# values for return_value_policy
+reference_existing_object = 'reference_existing_object'
+copy_const_reference = 'copy_const_reference'
+copy_non_const_reference = 'copy_non_const_reference'
+manage_new_object = 'manage_new_object'
diff --git a/pyste/src/pyste-profile.py b/pyste/src/pyste-profile.py
new file mode 100644
index 00000000..d7afff45
--- /dev/null
+++ b/pyste/src/pyste-profile.py
@@ -0,0 +1,17 @@
+import profile
+import pstats
+import pyste
+
+import psyco
+import elementtree.XMLTreeBuilder as XMLTreeBuilder
+import GCCXMLParser
+
+
+if __name__ == '__main__':
+ #psyco.bind(XMLTreeBuilder.fixtext)
+ #psyco.bind(XMLTreeBuilder.fixname)
+ #psyco.bind(XMLTreeBuilder.TreeBuilder)
+ #psyco.bind(GCCXMLParser.GCCXMLParser)
+ profile.run('pyste.Main()', 'profile')
+ p = pstats.Stats('profile')
+ p.strip_dirs().sort_stats(-1).print_stats()
diff --git a/pyste/src/pyste.py b/pyste/src/pyste.py
new file mode 100644
index 00000000..090c7fd9
--- /dev/null
+++ b/pyste/src/pyste.py
@@ -0,0 +1,154 @@
+'''
+Usage:
+ pyste [options] --module= interface-files
+
+where options are:
+ -I add an include path
+ -D define symbol
+ --no-using do not declare "using namespace boost";
+ use explicit declarations instead
+ --pyste-ns= set the namespace where new types will be declared;
+ default is "pyste"
+'''
+
+import sys
+import os
+import getopt
+import exporters
+import CodeUnit
+import infos
+import exporterutils
+import settings
+from policies import *
+from CppParser import CppParser, CppParserError
+from Exporter import Exporter
+from FunctionExporter import FunctionExporter
+from ClassExporter import ClassExporter
+from IncludeExporter import IncludeExporter
+from HeaderExporter import HeaderExporter
+
+
+def GetDefaultIncludes():
+ if 'INCLUDE' in os.environ:
+ include = os.environ['INCLUDE']
+ return include.split(os.pathsep)
+ else:
+ return []
+
+
+def ParseArguments():
+
+ def Usage():
+ print __doc__
+ sys.exit(1)
+
+ options, files = getopt.getopt(sys.argv[1:], 'I:D:', ['module=', 'out=', 'no-using', 'pyste-ns=', 'debug'])
+ includes = GetDefaultIncludes()
+ defines = []
+ module = None
+ out = None
+ for opt, value in options:
+ if opt == '-I':
+ includes.append(value)
+ elif opt == '-D':
+ defines.append(value)
+ elif opt == '--module':
+ module = value
+ elif opt == '--out':
+ out = value
+ elif opt == '--no-using':
+ settings.namespaces.python = 'boost::python::'
+ CodeUnit.CodeUnit.USING_BOOST_NS = False
+ elif opt == '--pyste-ns':
+ settings.namespaces.pyste = value + '::'
+ elif opt == '--debug':
+ settings.DEBUG = True
+ else:
+ print 'Unknown option:', opt
+ Usage()
+
+ if not files or not module:
+ Usage()
+ if not out:
+ out = module + '.cpp'
+ return includes, defines, module, out, files
+
+
+def CreateContext():
+ 'create the context where a interface file can be executed'
+ context = {}
+ # infos
+ context['Function'] = infos.FunctionInfo
+ context['Class'] = infos.ClassInfo
+ context['Include'] = infos.IncludeInfo
+ context['Template'] = infos.ClassTemplateInfo
+ context['Enum'] = infos.EnumInfo
+ context['AllFromHeader'] = infos.HeaderInfo
+ # functions
+ context['rename'] = infos.rename
+ context['set_policy'] = infos.set_policy
+ context['exclude'] = infos.exclude
+ context['set_wrapper'] = infos.set_wrapper
+ # policies
+ context['return_internal_reference'] = return_internal_reference
+ context['with_custodian_and_ward'] = with_custodian_and_ward
+ context['return_value_policy'] = return_value_policy
+ context['reference_existing_object'] = reference_existing_object
+ context['copy_const_reference'] = copy_const_reference
+ context['copy_non_const_reference'] = copy_non_const_reference
+ context['manage_new_object'] = manage_new_object
+ # utils
+ context['Wrapper'] = exporterutils.FunctionWrapper
+ return context
+
+
+def Main():
+ includes, defines, module, out, interfaces = ParseArguments()
+ # execute the interface files
+ for interface in interfaces:
+ context = CreateContext()
+ execfile(interface, context)
+ # parse all the C++ code
+ parser = CppParser(includes, defines)
+ exports = exporters.exporters[:]
+ for export in exports:
+ try:
+ export.Parse(parser)
+ except CppParserError, e:
+ print '\n'
+ print '***', e, ': exitting'
+ return 2
+ print
+ # sort the exporters by its order
+ exports = [(x.Order(), x) for x in exporters.exporters]
+ exports.sort()
+ exports = [x for _, x in exports]
+ # now generate the wrapper code
+ codeunit = CodeUnit.CodeUnit(module)
+ exported_names = []
+ for export in exports:
+ export.GenerateCode(codeunit, exported_names)
+ exported_names.append(export.Name())
+ codeunit.Save(out)
+ print 'Module %s generated' % module
+ return 0
+
+
+def UsePsyco():
+ 'Tries to use psyco if it is installed'
+ try:
+ import psyco
+ import elementtree.XMLTreeBuilder as XMLTreeBuilder
+ import GCCXMLParser
+
+ psyco.bind(XMLTreeBuilder.fixtext)
+ psyco.bind(XMLTreeBuilder.fixname)
+ psyco.bind(XMLTreeBuilder.TreeBuilder)
+ psyco.bind(GCCXMLParser.GCCXMLParser)
+ except ImportError: pass
+
+
+if __name__ == '__main__':
+ UsePsyco()
+ status = Main()
+ sys.exit(status)
diff --git a/pyste/src/settings.py b/pyste/src/settings.py
new file mode 100644
index 00000000..e5adfc25
--- /dev/null
+++ b/pyste/src/settings.py
@@ -0,0 +1,12 @@
+
+#==============================================================================
+# Global information
+#==============================================================================
+
+DEBUG = False
+
+class namespaces:
+ boost = 'boost::'
+ pyste = ''
+ python = '' # default is to not use boost::python namespace explicitly, so
+ # use the "using namespace" statement instead
diff --git a/pyste/tests/GCCXMLParserUT.py b/pyste/tests/GCCXMLParserUT.py
new file mode 100644
index 00000000..c0a17b33
--- /dev/null
+++ b/pyste/tests/GCCXMLParserUT.py
@@ -0,0 +1,338 @@
+import sys
+sys.path.append('..')
+import unittest
+import tempfile
+import os.path
+import GCCXMLParser
+from declarations import *
+
+
+class Tester(unittest.TestCase):
+
+ def TestConstructor(self, class_, method, visib):
+ self.assert_(isinstance(method, Constructor))
+ self.assertEqual(method.FullName(), class_.FullName() + '::' + method.name)
+ self.assertEqual(method.result, None)
+ self.assertEqual(method.visibility, visib)
+ self.assert_(not method.virtual)
+ self.assert_(not method.abstract)
+ self.assert_(not method.static)
+
+ def TestDefaultConstructor(self, class_, method, visib):
+ self.TestConstructor(class_, method, visib)
+ self.assert_(method.IsDefault())
+
+ def TestCopyConstructor(self, class_, method, visib):
+ self.TestConstructor(class_, method, visib)
+ self.assertEqual(len(method.parameters), 1)
+ param = method.parameters[0]
+ self.TestType(
+ param,
+ ReferenceType,
+ class_.FullName(),
+ 'const %s &' % class_.FullName(),
+ True)
+ self.assert_(method.IsCopy())
+
+
+ def TestType(self, type_, classtype_, name, fullname, const):
+ self.assert_(isinstance(type_, classtype_))
+ self.assertEqual(type_.name, name)
+ self.assertEqual(type_.namespace, None)
+ self.assertEqual(type_.FullName(), fullname)
+ self.assertEqual(type_.const, const)
+
+
+class ClassBaseTest(Tester):
+
+ def setUp(self):
+ self.base = GetDecl('Base')
+
+ def testClass(self):
+ 'test the properties of the class Base'
+ self.assert_(isinstance(self.base, Class))
+ self.assert_(self.base.abstract)
+ self.assertEqual(self.base.RawName(), 'Base')
+
+
+ def testFoo(self):
+ 'test function foo in class Base'
+ foo = GetMember(self.base, 'foo')
+ self.assert_(isinstance(foo, Method))
+ self.assertEqual(foo.visibility, Scope.public)
+ self.assert_(foo.virtual)
+ self.assert_(foo.abstract)
+ self.failIf(foo.static)
+ self.assertEqual(foo.class_, 'test::Base')
+ self.failIf(foo.const)
+ self.assertEqual(foo.FullName(), 'test::Base::foo')
+ self.assertEqual(foo.result.name, 'void')
+ self.assertEqual(len(foo.parameters), 1)
+ param = foo.parameters[0]
+ self.TestType(param, FundamentalType, 'int', 'int', False)
+ self.assertEqual(foo.namespace, None)
+ self.assertEqual(
+ foo.PointerDeclaration(), '(void (test::Base::*)(int) )test::Base::foo')
+
+ def testX(self):
+ 'test the member x in class Base'
+ x = GetMember(self.base, 'x')
+ self.assertEqual(x.class_, 'test::Base')
+ self.assertEqual(x.FullName(), 'test::Base::x')
+ self.assertEqual(x.namespace, None)
+ self.assertEqual(x.visibility, Scope.private)
+ self.TestType(x.type, FundamentalType, 'int', 'int', False)
+ self.assertEqual(x.static, False)
+
+ def testConstructors(self):
+ 'test constructors in class Base'
+ constructors = GetMembers(self.base, 'Base')
+ for cons in constructors:
+ if len(cons.parameters) == 0:
+ self.TestDefaultConstructor(self.base, cons, Scope.public)
+ elif len(cons.parameters) == 1: # copy constructor
+ self.TestCopyConstructor(self.base, cons, Scope.public)
+ elif len(cons.parameters) == 2: # other constructor
+ intp, floatp = cons.parameters
+ self.TestType(intp, FundamentalType, 'int', 'int', False)
+ self.TestType(floatp, FundamentalType, 'float', 'float', False)
+
+ def testSimple(self):
+ 'test function simple in class Base'
+ simple = GetMember(self.base, 'simple')
+ self.assert_(isinstance(simple, Method))
+ self.assertEqual(simple.visibility, Scope.protected)
+ self.assertEqual(simple.FullName(), 'test::Base::simple')
+ self.assertEqual(len(simple.parameters), 1)
+ param = simple.parameters[0]
+ self.TestType(param, ReferenceType, 'std::string', 'const std::string &', True)
+ self.TestType(simple.result, FundamentalType, 'bool', 'bool', False)
+ self.assertEqual(
+ simple.PointerDeclaration(),
+ '(bool (test::Base::*)(const std::string &) )test::Base::simple')
+
+
+ def testZ(self):
+ z = GetMember(self.base, 'z')
+ self.assert_(isinstance(z, Variable))
+ self.assertEqual(z.visibility, Scope.public)
+ self.assertEqual(z.FullName(), 'test::Base::z')
+ self.assertEqual(z.type.name, 'int')
+ self.assertEqual(z.type.const, False)
+ self.assert_(z.static)
+
+
+class ClassTemplateTest(Tester):
+
+ def setUp(self):
+ self.template = GetDecl('Template')
+
+ def testClass(self):
+ 'test the properties of the Template class'
+ self.assert_(isinstance(self.template, Class))
+ self.assert_(not self.template.abstract)
+ self.assertEqual(self.template.FullName(), 'Template')
+ self.assertEqual(self.template.namespace, '')
+ self.assertEqual(self.template.name, 'Template')
+ self.assertEqual(self.template.RawName(), 'Template')
+
+ def testConstructors(self):
+ 'test the automatic constructors of the class Template'
+ constructors = GetMembers(self.template, 'Template')
+ for cons in constructors:
+ if len(cons.parameters) == 0:
+ self.TestDefaultConstructor(self.template, cons, Scope.public)
+ elif len(cons.parameters) == 1:
+ self.TestCopyConstructor(self.template, cons, Scope.public)
+
+
+ def testValue(self):
+ 'test the class variable value'
+ value = GetMember(self.template, 'value')
+ self.assert_(isinstance(value, ClassVariable))
+ self.assert_(value.name, 'value')
+ self.TestType(value.type, FundamentalType, 'int', 'int', False)
+ self.assert_(not value.static)
+ self.assertEqual(value.visibility, Scope.public)
+ self.assertEqual(value.class_, 'Template')
+ self.assertEqual(value.FullName(), 'Template::value')
+
+ def testBase(self):
+ 'test the superclasses of Template'
+ bases = self.template.bases
+ self.assertEqual(len(bases), 1)
+ base = bases[0]
+ self.assert_(isinstance(base, Base))
+ self.assertEqual(base.name, 'test::Base')
+ self.assertEqual(base.visibility, Scope.protected)
+
+
+
+class FreeFuncTest(Tester):
+
+ def setUp(self):
+ self.func = GetDecl('FreeFunc')
+
+ def testFunc(self):
+ 'test attributes of FreeFunc'
+ self.assert_(isinstance(self.func, Function))
+ self.assertEqual(self.func.name, 'FreeFunc')
+ self.assertEqual(self.func.FullName(), 'test::FreeFunc')
+ self.assertEqual(self.func.namespace, 'test')
+ self.assertEqual(
+ self.func.PointerDeclaration(),
+ '(const test::Base & (*)(const std::string &, int))test::FreeFunc')
+
+
+ def testResult(self):
+ 'test the return value of FreeFunc'
+ res = self.func.result
+ self.TestType(res, ReferenceType, 'test::Base', 'const test::Base &', True)
+
+ def testParameters(self):
+ 'test the parameters of FreeFunc'
+ self.assertEqual(len(self.func.parameters), 2)
+ strp, intp = self.func.parameters
+ self.TestType(strp, ReferenceType, 'std::string', 'const std::string &', True)
+ self.assertEqual(strp.default, None)
+ self.TestType(intp, FundamentalType, 'int', 'int', False)
+ self.assertEqual(intp.default, '10')
+
+
+
+class testFunctionPointers(Tester):
+
+ def testMethodPointer(self):
+ 'test declaration of a pointer-to-method'
+ meth = GetDecl('MethodTester')
+ param = meth.parameters[0]
+ fullname = 'void (test::Base::*)(int)'
+ self.TestType(param, PointerType, fullname, fullname, False)
+
+ def testFunctionPointer(self):
+ 'test declaration of a pointer-to-function'
+ func = GetDecl('FunctionTester')
+ param = func.parameters[0]
+ fullname = 'void (*)(int)'
+ self.TestType(param, PointerType, fullname, fullname, False)
+
+
+
+# =============================================================================
+# Support routines
+# =============================================================================
+
+cppcode = '''
+namespace std {
+ class string;
+}
+namespace test {
+class Base
+{
+public:
+ Base();
+ Base(const Base&);
+ Base(int, float);
+
+ virtual void foo(int = 0.0) = 0;
+ static int z;
+protected:
+ bool simple(const std::string&);
+private:
+ int x;
+};
+
+void MethodTester( void (Base::*)(int) );
+void FunctionTester( void (*)(int) );
+
+
+const Base & FreeFunc(const std::string&, int=10);
+
+}
+
+template
+struct Template: protected test::Base
+{
+ T value;
+ virtual void foo(int);
+};
+
+Template __aTemplateInt;
+'''
+
+def GetXMLFile():
+ '''Generates an gccxml file using the code from the global cppcode.
+ Returns the xml's filename.'''
+ # write the code to a header file
+ tmpfile = tempfile.mktemp() + '.h'
+ f = file(tmpfile, 'w')
+ f.write(cppcode)
+ f.close()
+ # run gccxml
+ outfile = tmpfile + '.xml'
+ if os.system('gccxml "%s" "-fxml=%s"' % (tmpfile, outfile)) != 0:
+ raise RuntimeError, 'Error executing GCCXML.'
+ # read the output file into the xmlcode
+ f = file(outfile)
+ xmlcode = f.read()
+ #print xmlcode
+ f.close()
+ # remove the header
+ os.remove(tmpfile)
+ return outfile
+
+
+
+def GetDeclarations():
+ 'Uses the GCCXMLParser module to get the declarations.'
+ xmlfile = GetXMLFile()
+ declarations = GCCXMLParser.ParseDeclarations(xmlfile)
+ os.remove(xmlfile)
+ return declarations
+
+# the declarations to be analysed
+declarations = GetDeclarations()
+
+
+def GetDecl(name):
+ 'returns one of the top declarations given its name'
+ for decl in declarations:
+ if decl.name == name:
+ return decl
+ else:
+ raise RuntimeError, 'Declaration not found: %s' % name
+
+
+def GetMember(class_, name):
+ 'gets the member of the given class by its name'
+
+ res = None
+ multipleFound = False
+ for member in class_:
+ if member.name == name:
+ if res is not None:
+ multipleFound = True
+ break
+ res = member
+ if res is None or multipleFound:
+ raise RuntimeError, \
+ 'No member or more than one member found in class %s: %s' \
+ % (class_.name, name)
+ return res
+
+
+def GetMembers(class_, name):
+ 'gets the members of the given class by its name'
+ res = []
+ for member in class_:
+ if member.name == name:
+ res.append(member)
+ if len(res) in (0, 1):
+ raise RuntimeError, \
+ 'GetMembers: 0 or 1 members found in class %s: %s' \
+ % (class_.name, name)
+ return res
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyste/tests/infosUT.py b/pyste/tests/infosUT.py
new file mode 100644
index 00000000..71d5e368
--- /dev/null
+++ b/pyste/tests/infosUT.py
@@ -0,0 +1,50 @@
+import sys
+sys.path.append('../src')
+from infos import *
+from policies import *
+from exporterutils import *
+import unittest
+
+
+class InfosTest(unittest.TestCase):
+
+ def testFunctionInfo(self):
+ info = FunctionInfo('test::foo', 'foo.h')
+ rename(info, 'hello')
+ set_policy(info, return_internal_reference())
+ set_wrapper(info, FunctionWrapper('foo_wrapper'))
+
+ info = InfoWrapper(info)
+
+ self.assertEqual(info.rename, 'hello')
+ self.assertEqual(info.policy.Code(), 'return_internal_reference< 1 >')
+ self.assertEqual(info.wrapper.name, 'foo_wrapper')
+
+
+ def testClassInfo(self):
+ info = ClassInfo('test::IFoo', 'foo.h')
+ rename(info.name, 'Name')
+ rename(info.exclude, 'Exclude')
+ rename(info, 'Foo')
+ rename(info.Bar, 'bar')
+ set_policy(info.Baz, return_internal_reference())
+ rename(info.operator['>>'], 'from_string')
+ exclude(info.Bar)
+ set_wrapper(info.Baz, FunctionWrapper('baz_wrapper'))
+
+ info = InfoWrapper(info)
+
+ self.assertEqual(info.rename, 'Foo')
+ self.assertEqual(info['Bar'].rename, 'bar')
+ self.assertEqual(info['name'].rename, 'Name')
+ self.assertEqual(info['exclude'].rename, 'Exclude')
+ self.assertEqual(info['Bar'].exclude, True)
+ self.assertEqual(info['Baz'].policy.Code(), 'return_internal_reference< 1 >')
+ self.assertEqual(info['Baz'].wrapper.name, 'baz_wrapper')
+ self.assertEqual(info['operator']['>>'].rename, 'from_string')
+
+
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyste/tests/policiesUT.py b/pyste/tests/policiesUT.py
new file mode 100644
index 00000000..bde08543
--- /dev/null
+++ b/pyste/tests/policiesUT.py
@@ -0,0 +1,59 @@
+import sys
+sys.path.append('..')
+import unittest
+from policies import *
+
+class PoliciesTest(unittest.TestCase):
+
+ def testReturnInternal(self):
+ 'tests the code from a simple internal_reference'
+
+ x = return_internal_reference(1)
+ self.assertEqual(x.Code(), 'return_internal_reference< 1 >')
+ x = return_internal_reference(3)
+ self.assertEqual(x.Code(), 'return_internal_reference< 3 >')
+
+
+ def testCustodian(self):
+ 'tests the code from a simple custodian_and_ward'
+
+ x = with_custodian_and_ward(1,2)
+ self.assertEqual(x.Code(), 'with_custodian_and_ward< 1, 2 >')
+ x = with_custodian_and_ward(3,4)
+ self.assertEqual(x.Code(), 'with_custodian_and_ward< 3, 4 >')
+
+
+ def testReturnPolicies(self):
+ 'tests all the return_value_policies'
+
+ ret = 'return_value_policy< %s >'
+ x = return_value_policy(reference_existing_object)
+ self.assertEqual(x.Code(), ret % 'reference_existing_object')
+ x = return_value_policy(copy_const_reference)
+ self.assertEqual(x.Code(), ret % 'copy_const_reference')
+ x = return_value_policy(copy_non_const_reference)
+ self.assertEqual(x.Code(), ret % 'copy_non_const_reference')
+ x = return_value_policy(manage_new_object)
+ self.assertEqual(x.Code(), ret % 'manage_new_object')
+
+
+ def testReturnWithCustodiam(self):
+ 'test the mix of return_internal with custodian'
+
+ x = return_internal_reference(1, with_custodian_and_ward(3,2))
+ self.assertEqual(
+ x.Code(),
+ 'return_internal_reference< 1, with_custodian_and_ward< 3, 2 > >')
+
+
+ def testReturnPoliciesWithInternal(self):
+ 'test the mix of return_internal with return_policy'
+
+ x = return_internal_reference(1, return_value_policy(manage_new_object))
+ self.assertEqual(
+ x.Code(),
+ 'return_internal_reference< 1, return_value_policy< manage_new_object > >')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyste/tests/runtests.py b/pyste/tests/runtests.py
new file mode 100644
index 00000000..f670e3d4
--- /dev/null
+++ b/pyste/tests/runtests.py
@@ -0,0 +1,14 @@
+import sys
+sys.path.append('../src')
+import unittest
+import os.path
+from glob import glob
+
+if __name__ == '__main__':
+ loader = unittest.defaultTestLoader
+ tests = []
+ for name in glob('*UT.py'):
+ module = __import__(os.path.splitext(name)[0])
+ tests.append(loader.loadTestsFromModule(module))
+ runner = unittest.TextTestRunner()
+ runner.run(unittest.TestSuite(tests))