From 5ac7741ca9e37a0594703e2e2e834ea15fcf07b7 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sun, 2 Mar 2003 00:55:07 +0000 Subject: [PATCH 01/18] Updates for ACCU [SVN r17695] --- doc/PyConDC_2003/bpl.txt | 519 +++++++++++++++++++-------------------- 1 file changed, 254 insertions(+), 265 deletions(-) diff --git a/doc/PyConDC_2003/bpl.txt b/doc/PyConDC_2003/bpl.txt index fe0f686b..3332373f 100644 --- a/doc/PyConDC_2003/bpl.txt +++ b/doc/PyConDC_2003/bpl.txt @@ -1,52 +1,37 @@ -.. This is a comment. Note how any initial comments are moved by - transforms to after the document title, subtitle, and docinfo. - -.. Need intro and conclusion -.. Exposing classes - .. Constructors - .. Overloading - .. Properties and data members - .. Inheritance - .. Operators and Special Functions - .. Virtual Functions -.. Call Policies - -++++++++++++++++++++++++++++++++++++++++++++++ - Introducing Boost.Python (Extended Abstract) -++++++++++++++++++++++++++++++++++++++++++++++ - - -.. bibliographic fields (which also require a transform): ++++++++++++++++++++++++++++++++++++++++++++ + Building Hybrid Systems with Boost.Python ++++++++++++++++++++++++++++++++++++++++++++ :Author: David Abrahams -:Address: 45 Walnut Street - Somerville, MA 02143 :Contact: dave@boost-consulting.com :organization: `Boost Consulting`_ :date: $Date$ -:status: This is a "work in progress" -:version: 1 -:copyright: Copyright David Abrahams 2002. All rights reserved -:Dedication: +:Author: Ralf W. Grosse-Kunstleve - For my girlfriend, wife, and partner Luann - -:abstract: - - This paper describes the Boost.Python library, a system for - C++/Python interoperability. - -.. meta:: - :keywords: Boost,python,Boost.Python,C++ - :description lang=en: C++/Python interoperability with Boost.Python +:status: Draft +:copyright: Copyright David Abrahams and Ralf W. Grosse-Kunstleve 2003. All rights reserved .. contents:: Table of Contents -.. section-numbering:: .. _`Boost Consulting`: http://www.boost-consulting.com +========== + Abstract +========== + +Boost.Python is an open source C++ library which provides a concise +IDL-like interface for binding C++ classes and functions to +Python. Leveraging the full power of C++ compile-time introspection +and of recently developed metaprogramming techniques, this is achieved +entirely in pure C++, without introducing a new syntax. +Boost.Python's rich set of features and high-level interface make it +possible to engineer hybrid software systems from the ground up, +giving programmers easy and coherent access to both the efficient +compile-time polymorphism of C++ and the extremely convenient run-time +polymorphism of Python. + ============== Introduction ============== @@ -77,40 +62,90 @@ Furthermore, the surface differences mask some strong similarities: * High-level concepts such as collections and iterators. -* Strong support for writers of re-usable libraries. +* High-level encapsulation facilities (C++: namespaces, Python: modules) + to support the design of re-usable libraries. + +* Exception-handling for effective management of error conditions. * C++ idioms in common use, such as handle/body classes and reference-counted smart pointers mirror Python reference semantics. -* Exception-handling for effective management of error conditions. - Given Python's rich 'C' interoperability API, it should in principle be possible to expose C++ type and function interfaces to Python with an analogous interface to their C++ counterparts. However, the facilities provided by Python alone for integration with C++ are -relatively meager. Some of this, such as the need to manage -reference-counting manually and lack of C++ exception-handling -support, comes from the limitations of the 'C' language in which the -API is implemented. Most of the remaining issues can be handled if -the code understands the C++ type system. For example: +relatively meager. Compared to C++ and Python, 'C' has only very +rudimentary abstraction facilities, and support for exception-handling +is completely missing. 'C' extension module writers are required to +manually manage Python reference counts, which is both annoyingly +tedious and extremely error-prone. Traditional extension modules also +tend to contain a great deal of boilerplate code repetition which +makes them difficult to maintain, especially when wrapping an evolving +API. -* Every argument of every wrapped function requires some kind of - extraction code to convert it from Python to C++. Likewise, the - function return value has to be converted from C++ to Python. - Appropriate Python exceptions must be raised if the conversion - fails. Argument and return types are part of the function's type, - and much of this tedium can be relieved if the wrapping system can - extract that information through introspection. +These limitations have lead to the development of a variety of wrapping +systems. SWIG_ is probably the most popular package for the +integration of C/C++ and Python. A more recent development is SIP_, +which was specifically designed for interfacing Python with the Qt_ +graphical user interface library. Both SWIG and SIP introduce their +own specialized languages for customizing inter-language bindings. +This has certain advantages, but having to deal with three different +languages (Python, C/C++ and the interface language) also introduces +practical and mental difficulties. The CXX_ package demonstrates an +interesting alternative. It shows that at least some parts of +Python's 'C' API can be wrapped and presented through a much more +user-friendly C++ interface. However, unlike SWIG and SIP, CXX does +not include support for wrapping C++ classes as new Python types. -* Passing a wrapped C++ derived class instance to a C++ function - accepting a pointer or reference to a base class requires knowledge - of the inheritance relationship and how to translate the address of - a base class into that of a derived class. +The features and goals of Boost.Python_ overlap significantly with +many of these other systems. That said, Boost.Python attempts to +maximize convenience and flexibility without introducing a separate +wrapping language. Instead, it presents the user with a high-level +C++ interface for wrapping C++ classes and functions, managing much of +the complexity behind-the-scense with static metaprogramming. +Boost.Python also goes beyond the scope of earlier systems by +providing: -The Boost.Python Library (BPL) leverages the power of C++ -meta-programming techniques to introspect about the C++ type system, -and presents a simple, IDL-like C++ interface for exposing C++ code in -extension modules. +* Support for C++ virtual functions that can be overridden in Python. + +* Comprehensive lifetime management facilities for low-level C++ + pointers and references. + +* Support for organizing extensions as Python packages, + with a central registry for inter-language type conversions. + +* A safe and convenient mechanism for tying into Python's powerful + serialization engine (pickle). + +* Coherence with the rules for handling C++ lvalues and rvalues that + can only come from a deep understanding of both the Python and C++ + type systems. + +The key insight that sparked the development of Boost.Python is that +much of the boilerplate code in traditional extension modules could be +eliminated using C++ compile-time introspection. Each argument of a +wrapped C++ function must be extracted from a Python object using a +procedure that depends on the argument type. Similarly the function's +return type determines how the return value will be converted from C++ +to Python. Of course argument and return types are part of each +function's type, and this is exactly the source from which +Boost.Python deduces most of the information required. + +This approach leads to *user guided wrapping*: as much information is +extracted directly from the source code to be wrapped as is possible +within the framework of pure C++, and some additional information is +supplied explicitly by the user. Mostly the guidance is mechanical +and little real intervention is required. Because there the interface +specification is written in the same full-featured language as the +code being exposed, the user has unprecedented power available when +she does need to take control. + +.. _Python: http://www.python.org/ +.. _SWIG: http://www.swig.org/ +.. _SIP: http://www.riverbankcomputing.co.uk/sip/index.php +.. _Qt: http://www.trolltech.com/ +.. _CXX: http://cxx.sourceforge.net/ +.. _Boost.Python: http://www.boost.org/libs/python/doc =========================== Boost.Python Design Goals @@ -211,9 +246,8 @@ and here it is in action:: Boost.Python world! -Aside from the fact that the 'C' API version is much more verbose than -the BPL one, it's worth noting that it doesn't handle a few things -correctly: +Aside from the fact that the 'C' API version is much more verbose, +it's worth noting a few things that it doesn't handle correctly: * The original function accepts an unsigned integer, and the Python 'C' API only gives us a way of extracting signed integers. The @@ -240,8 +274,8 @@ correctly: (arbitrary-precision integers) which happen to fit in an ``unsigned int`` but not in a ``signed long``, nor will it ever handle a wrapped C++ class with a user-defined implicit ``operator unsigned - int()`` conversion. The BPL's dynamic type conversion registry - allows users to add arbitrary conversion methods. + int()`` conversion. Boost.Python's dynamic type conversion + registry allows users to add arbitrary conversion methods. ================== Library Overview @@ -282,15 +316,15 @@ most of the C++ code they're used to. All the same, this is just standard C++. Because of their flexible syntax and operator overloading, C++ and Python are great for defining domain-specific (sub)languages -(DSLs), and that's what we've done in BPL. To break it down:: +(DSLs), and that's what we've done in Boost.Python. To break it down:: class_("World") constructs an unnamed object of type ``class_`` and passes ``"World"`` to its constructor. This creates a new-style Python class called ``World`` in the extension module, and associates it with the -C++ type ``World`` in the BPL type conversion registry. We might have -also written:: +C++ type ``World`` in the Boost.Python type conversion registry. We +might have also written:: class_ w("World"); @@ -314,8 +348,8 @@ So the example is equivalent to:: w.def("set", &World::set); It's occasionally useful to be able to break down the components of a -Boost.Python class wrapper in this way, but the rest of this paper -will tend to stick to the terse syntax. +Boost.Python class wrapper in this way, but the rest of this article +will stick to the terse syntax. For completeness, here's the wrapped class in use: @@ -330,9 +364,9 @@ Constructors Since our ``World`` class is just a plain ``struct``, it has an implicit no-argument (nullary) constructor. Boost.Python exposes the -nullary constructor by default, which is why we were able to write: +nullary constructor by default, which is why we were able to write: :: ->>> planet = hello.World() + >>> planet = hello.World() However, well-designed classes in any language may require constructor arguments in order to establish their invariants. Unlike Python, @@ -383,18 +417,18 @@ This does *not* result in adding attributes to the ``World`` instance ``__dict__``, which can result in substantial memory savings when wrapping large data structures. In fact, no instance ``__dict__`` will be created at all unless attributes are explicitly added from -Python. BPL owes this capability to the new Python 2.2 type system, -in particular the descriptor interface and ``property`` type. +Python. Boost.Python owes this capability to the new Python 2.2 type +system, in particular the descriptor interface and ``property`` type. In C++, publicly-accessible data members are considered a sign of poor design because they break encapsulation, and style guides usually dictate the use of "getter" and "setter" functions instead. In Python, however, ``__getattr__``, ``__setattr__``, and since 2.2, ``property`` mean that attribute access is just one more -well-encapsulated syntactic tool at the programmer's disposal. BPL -bridges this idiomatic gap by making Python ``property`` creation -directly available to users. So if ``msg`` were private, we could -still expose it as attribute in Python as follows:: +well-encapsulated syntactic tool at the programmer's disposal. +Boost.Python bridges this idiomatic gap by making Python ``property`` +creation directly available to users. If ``msg`` were private, we +could still expose it as attribute in Python as follows:: class_("World", init()) .add_property("msg", &World::greet, &World::set) @@ -412,59 +446,40 @@ The example above mirrors the familiar usage of properties in Python ... self.__msg = msg ... msg = property(greet, set) -Operators and Special Functions -=============================== +Operator Overloading +==================== -The ability to write arithmetic operators for user-defined types that -C++ and Python both allow the definition of has been a major factor in -the popularity of both languages for scientific computing. The -success of packages like NumPy attests to the power of exposing -operators in extension modules. In this example we'll wrap a class -representing a position in a large file:: +The ability to write arithmetic operators for user-defined types has +been a major factor in the success of both languages for numerical +computation, and the success of packages like NumPy_ attests to the +power of exposing operators in extension modules. Boost.Python +provides a concise mechanism for wrapping operator overloads. The +example below shows a fragment from a wrapper for the Boost rational +number library:: - class FilePos { /*...*/ }; - - // Linear offset - FilePos operator+(FilePos, int); - FilePos operator+(int, FilePos); - FilePos operator-(FilePos, int); - - // Distance between two FilePos objects - int operator-(FilePos, FilePos); - - // Offset with assignment - FilePos& operator+=(FilePos&, int); - FilePos& operator-=(FilePos&, int); - - // Comparison - bool operator<(FilePos, FilePos); - -The wrapping code looks like this:: - - class_("FilePos") - .def(self + int()) // __add__ - .def(int() + self) // __radd__ - .def(self - int()) // __sub__ - - .def(self - self) // __sub__ - - .def(self += int()) // __iadd__ - .def(self -= int()) // __isub__ - - .def(self < self); // __lt__ - ; + class_ >("rational_int") + .def(init()) // constructor, e.g. rational_int(3,4) + .def("numerator", &rational::numerator) + .def("denominator", &rational::denominator) + .def(-self) // __neg__ (unary minus) + .def(self + self) // __add__ (homogeneous) + .def(self * self) // __mul__ + .def(self + int()) // __add__ (heterogenous) + .def(int() + self) // __radd__ + ... The magic is performed using a simplified application of "expression -templates" [VELD1995]_, a technique originally developed by for +templates" [VELD1995]_, a technique originally developed for optimization of high-performance matrix algebra expressions. The essence is that instead of performing the computation immediately, operators are overloaded to construct a type *representing* the computation. In matrix algebra, dramatic optimizations are often available when the structure of an entire expression can be taken into -account, rather than processing each operation "greedily". +account, rather than evaluating each operation "greedily". Boost.Python uses the same technique to build an appropriate Python -callable object based on an expression involving ``self``, which is -then added to the class. +method object based on expressions involving ``self``. + +.. _NumPy: http://www.pfdubois.com/numpy/ Inheritance =========== @@ -479,11 +494,11 @@ parameter list as follows:: This has two effects: 1. When the ``class_<...>`` is created, Python type objects - corresponding to ``Base1`` and ``Base2`` are looked up in the BPL - registry, and are used as bases for the new Python ``Derived`` type - object [#mi]_, so methods exposed for the Python ``Base1`` and - ``Base2`` types are automatically members of the ``Derived`` type. - Because the registry is global, this works correctly even if + corresponding to ``Base1`` and ``Base2`` are looked up in + Boost.Python's registry, and are used as bases for the new Python + ``Derived`` type object, so methods exposed for the Python ``Base1`` + and ``Base2`` types are automatically members of the ``Derived`` + type. Because the registry is global, this works correctly even if ``Derived`` is exposed in a different module from either of its bases. @@ -515,20 +530,20 @@ Because C++ object construction is a one-step operation, C++ instance data cannot be constructed until the arguments are available, in the ``__init__`` function: ->>> class D(SomeBPLClass): +>>> class D(SomeBoostPythonClass): ... def __init__(self): ... pass ... ->>> D().some_bpl_method() +>>> D().some_boost_python_method() Traceback (most recent call last): File "", line 1, in ? TypeError: bad argument type for built-in operation This happened because Boost.Python couldn't find instance data of type -``SomeBPLClass`` within the ``D`` instance; ``D``'s ``__init__`` +``SomeBoostPythonClass`` within the ``D`` instance; ``D``'s ``__init__`` function masked construction of the base class. It could be corrected by either removing ``D``'s ``__init__`` function or having it call -``SomeBPLClass.__init__(...)`` explicitly. +``SomeBoostPythonClass.__init__(...)`` explicitly. Virtual Functions ================= @@ -605,13 +620,30 @@ Things to notice about the dispatcher class: exposed is not pure virtual; there's no other way ``Base::f`` can be called on an object of type ``BaseWrap``, since it overrides ``f``. +Deeper Reflection on the Horizon? +================================= + Admittedly, this formula is tedious to repeat, especially on a project -with many polymorphic classes; that it is neccessary reflects -limitations in C++'s compile-time reflection capabilities. Several -efforts are underway to write front-ends for Boost.Python which can -generate these dispatchers (and other wrapping code) automatically. -If these are successful it will mark a move away from wrapping -everything directly in pure C++ for many of our users. +with many polymorphic classes. That it is neccessary reflects some +limitations in C++'s compile-time introspection capabilities: there's +no way to enumerate the members of a class and find out which are +virtual functions. At least one very promising project has been +started to write a front-end which can generate these dispatchers (and +other wrapping code) automatically from C++ headers. + +Pyste builds on GCC_XML_, which generates an XML version of GCC's +internal program representation. Since GCC is a highly-conformant C++ +compiler, this ensures correct handling of the most-sophisticated +template code and full access to the underlying type system. In +keeping with the Boost.Python philosophy, a Pyste interface +description is neither intrusive on the code being wrapped, nor +expressed in some unfamiliar language: instead it is a 100% pure +Python script. If Pyste is successful it will mark a move away from +wrapping everything directly in C++ for many of our users. We expect +that soon, not only our users but the Boost.Python developers +themselves will be "thinking hybrid" about their own code. + +.. _`GCC_XML`: http://www.gccxml.org/HTML/Index.html --------------- Serialization @@ -622,7 +654,7 @@ form that can be stored on disk or sent over a network connection. The serialized object (most often a plain string) can be retrieved and converted back to the original object. A good serialization system will automatically convert entire object hierarchies. Python's standard -``pickle`` module is such a system. It leverages the language's strong +``pickle`` module is just such a system. It leverages the language's strong runtime introspection facilities for serializing practically arbitrary user-defined objects. With a few simple and unintrusive provisions this powerful machinery can be extended to also work for wrapped C++ objects. @@ -673,16 +705,14 @@ Of course the ``cPickle`` module can also be used for faster processing. Boost.Python's ``pickle_suite`` fully supports the ``pickle`` protocol -defined in the standard Python documentation. There is a one-to-one -correspondence between the standard pickling methods (``__getinitargs__``, -``__getstate__``, ``__setstate__``) and the functions defined by the -user in the class derived from ``pickle_suite`` (``getinitargs``, -``getstate``, ``setstate``). The ``class_::def_pickle()`` member function -is used to establish the Python bindings for all user-defined functions -simultaneously. Correct signatures for these functions are enforced at -compile time. Non-sensical combinations of the three pickle functions -are also rejected at compile time. These measures are designed to -help the user in avoiding obvious errors. +defined in the standard Python documentation. Like a __getinitargs__ +function in Python, the pickle_suite's getinitargs() is responsible for +creating the argument tuple that will be use to reconstruct the pickled +object. The other elements of the Python pickling protocol, +__getstate__ and __setstate__ can be optionally provided via C++ +getstate and setstate functions. C++'s static type system allows the +library to ensure at compile-time that nonsensical combinations of +functions (e.g. getstate without setstate) are not used. Enabling serialization of more complex C++ objects requires a little more work than is shown in the example above. Fortunately the @@ -693,58 +723,38 @@ code manageable. Object interface ------------------ -Experienced extension module authors will be familiar with the 'C' view -of Python objects, the ubiquitous ``PyObject*``. Most if not all Python -'C' API functions involve ``PyObject*`` as arguments or return type. A -major complication is the raw reference counting interface presented to -the 'C' programmer. E.g. some API functions return *new references* and -others return *borrowed references*. It is up to the extension module -writer to properly increment and decrement reference counts. This -quickly becomes cumbersome and error prone, especially if there are -multiple execution paths. +Experienced 'C' language extension module authors will be familiar +with the ubiquitous ``PyObject*``, manual reference-counting, and the +need to remember which API calls return "new" (owned) references or +"borrowed" (raw) references. These constraints are not just +cumbersome but also a major source of errors, especially in the +presence of exceptions. -Boost.Python provides a type ``object`` which is essentially a high -level wrapper around ``PyObject*``. ``object`` automates reference -counting as much as possible. It also provides the facilities for -converting arbitrary C++ types to Python objects and vice versa. -This significantly reduces the learning effort for prospective -extension module writers. +Boost.Python provides a class ``object`` which automates reference +counting and provides conversion to Python from C++ objects of +arbitrary type. This significantly reduces the learning effort for +prospective extension module writers. Creating an ``object`` from any other type is extremely simple:: - object o(3); + object s("hello, world"); // s manages a Python string ``object`` has templated interactions with all other types, with automatic to-python conversions. It happens so naturally that it's -easily overlooked. +easily overlooked:: + + object ten_Os = 10 * s[4]; // -> "oooooooooo" + +In the example above, ``4`` and ``10`` are converted to Python objects +before the indexing and multiplication operations are invoked. The ``extract`` class template can be used to convert Python objects to C++ types:: double x = extract(o); -All registered user-defined conversions are automatically accessible -through the ``object`` interface. With reference to the ``World`` class -defined in previous examples:: - - object as_python_object(World("howdy")); - World back_as_c_plus_plus_object = extract(as_python_object); - -If a C++ type cannot be converted to a Python object an appropriate -exception is thrown at runtime. Similarly, an appropriate exception is -thrown if a C++ type cannot be extracted from a Python object. -``extract`` provides facilities for avoiding exceptions if this is -desired. - -The ``object::attr()`` member function is available for accessing -and manipulating attributes of Python objects. For example:: - - object planet(World()); - planet.attr("set")("howdy"); - -``planet.attr("set")`` returns a callable ``object``. ``"howdy"`` is -converted to a Python string object which is then passed as an argument -to the ``set`` method. +If a conversion in either direction cannot be performed, an +appropriate exception is thrown at runtime. The ``object`` type is accompanied by a set of derived types that mirror the Python built-in types such as ``list``, ``dict``, @@ -756,104 +766,83 @@ manipulation of these high-level types from C++:: d["lucky_number"] = 13; list l = d.keys(); -This almost looks and works like regular Python code, but it is pure C++. +This almost looks and works like regular Python code, but it is pure +C++. Of course we can wrap C++ functions which accept or return +``object`` instances. + +.. ===================== + Development history + ===================== + + XXX Outline of development history to illustrate that the + library is mature. XXX + + This can be postponed for the PyConDC paper ================= Thinking hybrid ================= -For many applications runtime performance considerations are very -important. This is particularly true for most scientific applications. -Often the performance considerations dictate the use of a compiled -language for the core algorithms. Traditionally the decision to use a -particular programming language is an exclusive one. Because of the -practical and mental difficulties of combining different languages many -systems are written in just one language. This is quite unfortunate -because the price payed for runtime performance is typically a -significant overhead due to static typing. For example, our experience -shows that developing maintainable C++ code is typically much more -time-consuming and requires much more hard-earned working experience -than developing useful Python code. A related observation is that many -compiled packages are augmented by some type of rudimentary scripting -layer. These ad hoc solutions clearly show that many times a compiled -language alone does not get the job done. On the other hand it is also -clear that a pure Python implementation is too slow for numerically -intensive production code. +Because of the practical and mental difficulties of combining +programming languages, it is common to settle a single language at the +outset of any development effort. For many applications, performance +considerations dictate the use of a compiled language for the core +algorithms. Unfortunately, due to the complexity of the static type +system, the price we pay for runtime performance is often a +significant increase in development time. Experience shows that +writing maintainable C++ code usually takes longer and requires *far* +more hard-earned working experience than developing comparable Python +code. Even when developers are comfortable working exclusively in +compiled languages, they often augment their systems by some type of +ad hoc scripting layer for the benefit of their users without ever +availing themselves of the same advantages. -Boost.Python enables us to *think hybrid* when developing new -applications. Python can be used for rapidly prototyping a -new application. Python's ease of use and the large pool of standard -libraries give us a head start on the way to a first working system. If -necessary, the working procedure can be used to discover the -rate-limiting algorithms. To maximize performance these can be -reimplemented in C++, together with the Boost.Python bindings needed to -tie them back into the existing higher-level procedure. +Boost.Python enables us to *think hybrid*. Python can be used for +rapidly prototyping a new application; its ease of use and the large +pool of standard libraries give us a head start on the way to a +working system. If necessary, the working code can be used to +discover rate-limiting hotspots. To maximize performance these can +be reimplemented in C++, together with the Boost.Python bindings +needed to tie them back into the existing higher-level procedure. Of course, this *top-down* approach is less attractive if it is clear from the start that many algorithms will eventually have to be -implemented in a compiled language. Fortunately Boost.Python also -enables us to pursue a *bottom-up* approach. We have used this approach -very successfully in the development of a toolbox for scientific -applications (scitbx) that we will describe elsewhere. The toolbox -started out mainly as a library of C++ classes with Boost.Python -bindings, and for a while the growth was mainly concentrated on the C++ -parts. However, as the toolbox is becoming more complete, more and more -newly added functionality can be implemented in Python. We expect this -trend to continue, as illustrated qualitatively in this figure: +implemented in C++. Fortunately Boost.Python also enables us to +pursue a *bottom-up* approach. We have used this approach very +successfully in the development of a toolbox for scientific +applications. The toolbox started out mainly as a library of C++ +classes with Boost.Python bindings, and for a while the growth was +mainly concentrated on the C++ parts. However, as the toolbox is +becoming more complete, more and more newly added functionality can be +implemented in Python. .. image:: python_cpp_mix.png -This figure shows the ratio of newly added C++ and Python code over -time as new algorithms are implemented. We expect this ratio to level -out near 70% Python. The increasing ability to solve new problems -mostly with the easy-to-use Python language rather than a necessarily -more arcane statically typed language is the return on the investment -of learning how to use Boost.Python. The ability to solve some problems -entirely using only Python will enable a larger group of people to -participate in the rapid development of new applications. +This figure shows the estimated ratio of newly added C++ and Python +code over time as new algorithms are implemented. We expect this +ratio to level out near 70% Python. Being able to solve new problems +mostly in Python rather than a more difficult statically typed +language is the return on our investment in Boost.Python. The ability +to access all of our code from Python allows a broader group of +developers to use it in the rapid development of new applications. ============= Conclusions ============= -The examples in this paper illustrate that Boost.Python enables -seamless interoperability between C++ and Python. Importantly, this is -achieved without introducing a third syntax: the Python/C++ interface -definitions are written in pure C++. This avoids any problems with -parsing the C++ code to be interfaced to Python, yet the interface -definitions are concise and maintainable. Freed from most of the -development-time penalties of crossing a language boundary, software -designers can take full advantage of two rich and complimentary -language environments. In practice it turns out that some things are -very difficult to do with pure Python/C (e.g. an efficient array -library with an intuitive interface in the compiled language) and -others are very difficult to do with pure C++ (e.g. serialization). -If one has the luxury of being able to design a software system as a -hybrid system from the ground up there are many new ways of avoiding -road blocks in one language or the other. +Boost.Python achieves seamless interoperability between two rich and +complimentary language environments. Because it leverages template +metaprogramming to introspect about types and functions, the user +never has to learn a third syntax: the interface definitions are +written in concise and maintainable C++. Also, the wrapping system +doesn't have to parse C++ headers or represent the type system: the +compiler does that work for us. -.. I'm not ready to give up on all of this quite yet - -.. Perhaps one day we'll have a language with the simplicity and - expressive power of Python and the compile-time muscle of C++. Being - able to take advantage of all of these facilities without paying the - mental and development-time penalties of crossing a language barrier - would bring enormous benefits. Until then, interoperability tools - like Boost.Python can help lower the barrier and make the benefits of - both languages more accessible to both communities. - -=========== - Footnotes -=========== - -.. [#mi] For hard-core new-style class/extension module writers it is - worth noting that the normal requirement that all extension classes - with data form a layout-compatible single-inheritance chain is - lifted for Boost.Python extension classes. Clearly, either - ``Base1`` or ``Base2`` has to occupy a different offset in the - ``Derived`` class instance. This is possible because the wrapped - part of BPL extension class instances is never assumed to have a - fixed offset within the wrapper. +Computationally intensive tasks play to the strengths of C++ and are +often impossible to implement efficiently in pure Python, while jobs +like serialization that are trivial in Python can be very difficult in +pure C++. Given the luxury of building a hybrid software system from +the ground up, we can approach design with new confidence and power. =========== Citations From 15a148ab108b8c2f74b61164153f410608d90a7a Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 2 Mar 2003 02:50:49 +0000 Subject: [PATCH 02/18] minor polishing, corrections [SVN r17696] --- doc/PyConDC_2003/bpl.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/PyConDC_2003/bpl.txt b/doc/PyConDC_2003/bpl.txt index 3332373f..9241acb9 100644 --- a/doc/PyConDC_2003/bpl.txt +++ b/doc/PyConDC_2003/bpl.txt @@ -27,7 +27,7 @@ Python. Leveraging the full power of C++ compile-time introspection and of recently developed metaprogramming techniques, this is achieved entirely in pure C++, without introducing a new syntax. Boost.Python's rich set of features and high-level interface make it -possible to engineer hybrid software systems from the ground up, +possible to engineer packages from the ground up as hybrid systems, giving programmers easy and coherent access to both the efficient compile-time polymorphism of C++ and the extremely convenient run-time polymorphism of Python. @@ -102,7 +102,7 @@ many of these other systems. That said, Boost.Python attempts to maximize convenience and flexibility without introducing a separate wrapping language. Instead, it presents the user with a high-level C++ interface for wrapping C++ classes and functions, managing much of -the complexity behind-the-scense with static metaprogramming. +the complexity behind-the-scenes with static metaprogramming. Boost.Python also goes beyond the scope of earlier systems by providing: @@ -135,7 +135,7 @@ This approach leads to *user guided wrapping*: as much information is extracted directly from the source code to be wrapped as is possible within the framework of pure C++, and some additional information is supplied explicitly by the user. Mostly the guidance is mechanical -and little real intervention is required. Because there the interface +and little real intervention is required. Because the interface specification is written in the same full-featured language as the code being exposed, the user has unprecedented power available when she does need to take control. From 4b97e191b8edb3e3724d5aca161b4a6081575e37 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sun, 2 Mar 2003 15:25:35 +0000 Subject: [PATCH 03/18] Fix formatting errors [SVN r17697] --- doc/PyConDC_2003/bpl.txt | 88 ++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/doc/PyConDC_2003/bpl.txt b/doc/PyConDC_2003/bpl.txt index 9241acb9..8113f50e 100644 --- a/doc/PyConDC_2003/bpl.txt +++ b/doc/PyConDC_2003/bpl.txt @@ -351,13 +351,13 @@ It's occasionally useful to be able to break down the components of a Boost.Python class wrapper in this way, but the rest of this article will stick to the terse syntax. -For completeness, here's the wrapped class in use: +For completeness, here's the wrapped class in use: :: ->>> import hello ->>> planet = hello.World() ->>> planet.set('howdy') ->>> planet.greet() -'howdy' + >>> import hello + >>> planet = hello.World() + >>> planet.set('howdy') + >>> planet.greet() + 'howdy' Constructors ============ @@ -407,11 +407,11 @@ exposed as either ``readonly`` or ``readwrite`` attributes:: .def_readonly("msg", &World::msg) ... -and can be used directly in Python: +and can be used directly in Python: :: ->>> planet = hello.World('howdy') ->>> planet.msg -'howdy' + >>> planet = hello.World('howdy') + >>> planet.msg + 'howdy' This does *not* result in adding attributes to the ``World`` instance ``__dict__``, which can result in substantial memory savings when @@ -435,16 +435,16 @@ could still expose it as attribute in Python as follows:: ... The example above mirrors the familiar usage of properties in Python -2.2+: +2.2+: :: ->>> class World(object): -... __init__(self, msg): -... self.__msg = msg -... def greet(self): -... return self.__msg -... def set(self, msg): -... self.__msg = msg -... msg = property(greet, set) + >>> class World(object): + ... __init__(self, msg): + ... self.__msg = msg + ... def greet(self): + ... return self.__msg + ... def set(self, msg): + ... self.__msg = msg + ... msg = property(greet, set) Operator Overloading ==================== @@ -517,27 +517,27 @@ system, that works very much as for the Python built-in types. There is one significant detail in which it differs: the built-in types generally establish their invariants in their ``__new__`` function, so that derived classes do not need to call ``__init__`` on the base -class before invoking its methods : +class before invoking its methods : :: ->>> class L(list): -... def __init__(self): -... pass -... ->>> L().reverse() ->>> + >>> class L(list): + ... def __init__(self): + ... pass + ... + >>> L().reverse() + >>> Because C++ object construction is a one-step operation, C++ instance data cannot be constructed until the arguments are available, in the -``__init__`` function: +``__init__`` function: :: ->>> class D(SomeBoostPythonClass): -... def __init__(self): -... pass -... ->>> D().some_boost_python_method() -Traceback (most recent call last): - File "", line 1, in ? -TypeError: bad argument type for built-in operation + >>> class D(SomeBoostPythonClass): + ... def __init__(self): + ... pass + ... + >>> D().some_boost_python_method() + Traceback (most recent call last): + File "", line 1, in ? + TypeError: bad argument type for built-in operation This happened because Boost.Python couldn't find instance data of type ``SomeBoostPythonClass`` within the ``D`` instance; ``D``'s ``__init__`` @@ -592,16 +592,16 @@ class' virtual functions:: .def("f", &Base::f, &BaseWrap::f_default) ; -Now here's some Python code which demonstrates: +Now here's some Python code which demonstrates: :: ->>> class Derived(Base): -... def f(self, s): -... return len(s) -... ->>> calls_f(Base(), 'foo') -42 ->>> calls_f(Derived(), 'forty-two') -9 + >>> class Derived(Base): + ... def f(self, s): + ... return len(s) + ... + >>> calls_f(Base(), 'foo') + 42 + >>> calls_f(Derived(), 'forty-two') + 9 Things to notice about the dispatcher class: From 577f58149c1e7053a4e84cae89a0bf8b5c7c2ce0 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sun, 2 Mar 2003 22:11:20 +0000 Subject: [PATCH 04/18] tests for operators returning const objects [SVN r17700] --- test/operators.cpp | 15 +++++++++++---- test/operators.py | 3 +++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/test/operators.cpp b/test/operators.cpp index e4ac1e07..9e7a1e5a 100755 --- a/test/operators.cpp +++ b/test/operators.cpp @@ -21,7 +21,13 @@ using namespace boost::python; -typedef test_class<> X; +struct X : test_class<> +{ + typedef test_class<> base_t; + + X(int x) : base_t(x) {} + X const operator+(X const& r) { return X(value() + r.value()); } +}; X operator-(X const& l, X const& r) { return X(l.value() - r.value()); } X operator-(int l, X const& r) { return X(l - r.value()); } @@ -39,17 +45,17 @@ X abs(X x) { return X(x.value() < 0 ? -x.value() : x.value()); } X pow(X x, int y) { - return X(int(pow(double(x.value()), y))); + return X(int(pow(double(x.value()), double(y)))); } X pow(X x, X y) { - return X(int(pow(double(x.value()), y.value()))); + return X(int(pow(double(x.value()), double(y.value())))); } int pow(int x, X y) { - return int(pow(double(x), y.value())); + return int(pow(double(x), double(y.value()))); } std::ostream& operator<<(std::ostream& s, X const& x) @@ -61,6 +67,7 @@ BOOST_PYTHON_MODULE(operators_ext) { class_("X", init()) .def("value", &X::value) + .def(self + self) .def(self - self) .def(self - int()) .def(other() - self) diff --git a/test/operators.py b/test/operators.py index 58ffb5eb..b18d4d2d 100644 --- a/test/operators.py +++ b/test/operators.py @@ -15,6 +15,9 @@ >>> (-y).value() 39 +>>> (x + y).value() +3 + >>> abs(y).value() 39 From d028a60cc2158ac7c4614f73959ea5a31ae335dd Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Mon, 3 Mar 2003 17:21:30 +0000 Subject: [PATCH 05/18] Workaround for vc7 bug (and regression test) [SVN r17708] --- include/boost/python/detail/is_xxx.hpp | 3 ++- test/implicit.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/boost/python/detail/is_xxx.hpp b/include/boost/python/detail/is_xxx.hpp index ad888b84..0faea999 100644 --- a/include/boost/python/detail/is_xxx.hpp +++ b/include/boost/python/detail/is_xxx.hpp @@ -12,6 +12,7 @@ # if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) # include +# include # define BOOST_PYTHON_IS_XXX_DEF(name, qualified_name, nargs) \ template \ @@ -20,7 +21,7 @@ struct is_##name \ typedef char yes; \ typedef char (&no)[2]; \ \ - static X_ dummy; \ + static typename add_reference::type dummy; \ \ template < BOOST_PP_ENUM_PARAMS_Z(1, nargs, class U) > \ static yes test( \ diff --git a/test/implicit.cpp b/test/implicit.cpp index b56e6b10..c4728548 100644 --- a/test/implicit.cpp +++ b/test/implicit.cpp @@ -20,8 +20,18 @@ int x_value(X const& x) X make_x(int n) { return X(n); } + +// foo/bar -- a regression for a vc7 bug workaround +struct bar {}; +struct foo +{ + virtual void f() = 0; + operator bar() const { return bar(); } +}; + BOOST_PYTHON_MODULE(implicit_ext) { + implicitly_convertible(); implicitly_convertible(); def("x_value", x_value); From ff734e3269b9789e2e08de650c55c31afc5d83ac Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 8 Mar 2003 00:25:47 +0000 Subject: [PATCH 06/18] MIPSpro compatibility [SVN r17772] --- include/boost/python/enum.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/python/enum.hpp b/include/boost/python/enum.hpp index b1fac410..02092510 100644 --- a/include/boost/python/enum.hpp +++ b/include/boost/python/enum.hpp @@ -92,7 +92,7 @@ inline enum_& enum_::value(char const* name, T x) template inline enum_& enum_::export_values() { - this->enum_base::export_values(); + this->base::export_values(); return *this; } From ca64c961333f91327e6837f6fa06664eba0dea56 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 8 Mar 2003 03:53:19 +0000 Subject: [PATCH 07/18] Added dangling_reference FAQ Various idiomatic MPL cleanups in indirect_traits.hpp raw_function support Patches for CWPro7.2 Patches to pass tests under Python 2.3 with the new bool type. Tests for member operators returning const objects Fixes for testing Boost.Python under Cygwin [SVN r17777] --- doc/v2/def.html | 119 ++-- doc/v2/faq.html | 577 +++++++++--------- doc/v2/raw_function.html | 116 ++++ doc/v2/reference.html | 26 +- include/boost/python/class.hpp | 2 + include/boost/python/def.hpp | 7 +- .../boost/python/detail/indirect_traits.hpp | 157 +++-- include/boost/python/detail/is_shared_ptr.hpp | 18 + include/boost/python/init.hpp | 5 +- include/boost/python/object/inheritance.hpp | 11 +- .../boost/python/object/make_ptr_instance.hpp | 8 +- include/boost/python/raw_function.hpp | 47 ++ src/object/function.cpp | 96 ++- test/args.cpp | 10 + test/args.py | 8 + test/back_reference.py | 3 +- test/enum.cpp | 12 +- test/operators.cpp | 2 +- test/test_builtin_converters.py | 30 +- test/test_pointer_adoption.py | 6 +- 20 files changed, 789 insertions(+), 471 deletions(-) create mode 100755 doc/v2/raw_function.html create mode 100755 include/boost/python/detail/is_shared_ptr.hpp create mode 100755 include/boost/python/raw_function.hpp diff --git a/doc/v2/def.html b/doc/v2/def.html index fdd0516f..80978a87 100644 --- a/doc/v2/def.html +++ b/doc/v2/def.html @@ -3,7 +3,7 @@ + "HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org"> @@ -75,6 +75,12 @@ void def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&);
    +
  • If Fn is [derived from] object, it will be added to + the current scope as a single overload. To be useful, + fn should be callable.
  • +
  • If a1 is the result of an overload-dispatch-expression, @@ -104,67 +110,52 @@ void def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&);
  • -
  • - Otherwise, a single function overload built around fn (which must - not be null) is added to the current - scope: - -
      -
    • If fn is a function or member function pointer, - a1-a3 (if supplied) may be selected - in any order from the table below.
    • - -
    • Otherwise, Fn must be [derived from] object, and - a1-a2 (if supplied) may be selcted in any order - from the first two rows of the table below. To be useful, - fn should be - callable.
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Memnonic NameRequirements/Type propertiesEffects
    docstringAny ntbs.Value will be bound to the __doc__ attribute - of the resulting method overload.
    policiesA model of CallPoliciesA copy will be used as the call policies of the resulting - method overload.
    keywordsThe result of a keyword-expression - specifying no more arguments than the arity of fn.A copy will be used as the call policies of the resulting - method overload.
    -
  • +
  • Otherwise, fn must be a non-null function or member function + pointer, and a single function overload built around fn is added to + the current scope. If any of + a1-a3 are supplied, they may be selected + in any order from the table below.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Memnonic NameRequirements/Type propertiesEffects
docstringAny ntbs.Value will be bound to the __doc__ attribute of + the resulting method overload.
policiesA model of CallPoliciesA copy will be used as the call policies of the resulting + method overload.
keywordsThe result of a keyword-expression + specifying no more arguments than the arity of fn.A copy will be used as the call policies of the resulting + method overload.
@@ -174,6 +165,8 @@ void def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&); #include <boost/python/module.hpp> #include <boost/python/args.hpp> +using namespace boost::python; + char const* foo(int x, int y) { return "foo"; } BOOST_PYTHON_MODULE(def_test) @@ -184,8 +177,8 @@ BOOST_PYTHON_MODULE(def_test)

- 13 November, 2002 - + 7 March, 2003 +

© Copyright + + "HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org"> - - Boost.Python - FAQ - + + Boost.Python - FAQ + - +
+
-

- C++ Boost -

+

C++ Boost

-

- Boost.Python -

-

- Frequently Asked Questions (FAQs) -

+

Boost.Python

+ +

Frequently Asked Questions (FAQs)


+
-
- Is return_internal_reference efficient? -
-
- How can I wrap functions which take C++ - containers as arguments? -
-
- fatal error C1204:Compiler limit:internal structure - overflow -
-
- How do I debug my Python extensions? -
-
- Why doesn't my *= operator work? -
-
- Does Boost.Python work with Mac OS X? -
-
- How can I find the existing PyObject that holds a C++ - object? -
-
- How can I wrap a function which needs to take - ownership of a raw pointer? -
+
I'm getting the "attempt to return dangling + reference" error. What am I doing wrong?
+ +
Is return_internal_reference + efficient?
+ +
How can I wrap functions which take C++ + containers as arguments?
+ +
fatal error C1204:Compiler limit:internal + structure overflow
+ +
How do I debug my Python extensions?
+ +
Why doesn't my *= operator + work?
+ +
Does Boost.Python work with Mac OS X?
+ +
How can I find the existing PyObject that holds a + C++ object?
+ +
How can I wrap a function which needs to take + ownership of a raw pointer?

-

- Is return_internal_reference efficient? -

+ +

I'm getting the "attempt to return dangling + reference" error. What am I doing wrong?

+ That exception is protecting you from causing a nasty crash. It usually + happens in response to some code like this: +
+period const& get_floating_frequency() const
+{
+  return boost::python::call_method<period const&>(
+      m_self,"get_floating_frequency");
+}
+
+ And you get: +
+ReferenceError: Attempt to return dangling reference to object of type:
+class period
+
+ +

In this case, the Python method invoked by call_method + constructs a new Python object. You're trying to return a reference to a + C++ object (an instance of class period) contained within + and owned by that Python object. Because the called method handed back a + brand new object, the only reference to it is held for the duration of + get_floating_frequency() above. When the function returns, + the Python object will be destroyed, destroying the instance of + class period, and leaving the returned reference dangling. + That's already undefined behavior, and if you try to do anything with + that reference you're likely to cause a crash. Boost.Python detects this + situation at runtime and helpfully throws an exception instead of letting + you do that.
+  

+
+ +

Is return_internal_reference efficient?

+
Q: I have an object composed of 12 doubles. A const& to this object is returned by a member function of another class. From the @@ -74,126 +99,129 @@ return_internal_reference. Are there considerations that would lead me to prefer one over the other, such as size of generated code or memory overhead? -

- A: copy_const_reference will make an instance with storage for - one of your objects, size = base_size + 12 * sizeof(double). - return_internal_reference will make an instance with storage for a - pointer to one of your objects, size = base_size + sizeof(void*). - However, it will also create a weak reference object which goes in - the source object's weakreflist and a special callback object to - manage the lifetime of the internally-referenced object. My guess? - copy_const_reference is your friend here, resulting in less overall - memory use and less fragmentation, also probably fewer total cycles. -

+ +

A: copy_const_reference will make an instance with storage + for one of your objects, size = base_size + 12 * sizeof(double). + return_internal_reference will make an instance with storage for a + pointer to one of your objects, size = base_size + sizeof(void*). + However, it will also create a weak reference object which goes in the + source object's weakreflist and a special callback object to manage the + lifetime of the internally-referenced object. My guess? + copy_const_reference is your friend here, resulting in less overall + memory use and less fragmentation, also probably fewer total + cycles.


-

- How can I wrap functions which take C++ - containers as arguments? -

-

- Ralf W. Grosse-Kunstleve provides these notes: -

+ +

How can I wrap functions which take C++ + containers as arguments?

+ +

Ralf W. Grosse-Kunstleve provides these notes:

+
  1. Using the regular class_<> wrapper: -
    +
     class_<std::vector<double> >("std_vector_double")
       .def(...)
       ...
       ;
    -
    This can be moved to a template so that several types (double, int, -long, etc.) can be wrapped with the same code. This technique is used in the -file +
    + This can be moved to a template so that several types (double, int, + long, etc.) can be wrapped with the same code. This technique is used + in the file +
    scitbx/include/scitbx/array_family/boost_python/flex_wrapper.h -
    in the "scitbx" package. The file could easily be - modified for wrapping std::vector<> instantiations. -

    - This type of C++/Python binding is most suitable for containers - that may contain a large number of elements (>10000). -

    + + in the "scitbx" package. The file could easily be modified for + wrapping std::vector<> instantiations. + +

    This type of C++/Python binding is most suitable for containers + that may contain a large number of elements (>10000).

  2. +
  3. Using custom rvalue converters. Boost.Python "rvalue converters" match function signatures such as: -
    +
     void foo(std::vector<double> const& array); // pass by const-reference
     void foo(std::vector<double> array); // pass by value
    -
    Some custom rvalue converters are implemented in the file +
    + Some custom rvalue converters are implemented in the file +
    scitbx/include/scitbx/boost_python/container_conversions.h -
    This code can be used to convert from C++ container - types such as std::vector<> or std::list<> to Python - tuples and vice versa. A few simple examples can be found in the file - + + This code can be used to convert from C++ container types such as + std::vector<> or std::list<> to Python tuples and vice + versa. A few simple examples can be found in the file +
    scitbx/array_family/boost_python/regression_test_module.cpp -
    Automatic C++ container <-> Python tuple - conversions are most suitable for containers of moderate size. These - converters generate significantly less object code compared to - alternative 1 above. + + Automatic C++ container <-> Python tuple conversions are most + suitable for containers of moderate size. These converters generate + significantly less object code compared to alternative 1 above.
  4. -
A disadvantage of using alternative 2 is that operators such as + + A disadvantage of using alternative 2 is that operators such as arithmetic +,-,*,/,% are not available. It would be useful to have custom rvalue converters that convert to a "math_array" type instead of tuples. This is currently not implemented but is possible within the framework of Boost.Python V2 as it will be released in the next couple of weeks. [ed.: this was posted on 2002/03/10] -

- It would also be useful to also have "custom lvalue converters" such as - std::vector<> <-> Python list. These converters would - support the modification of the Python list from C++. For example: -

-

- C++: -

-
+
+    

It would also be useful to also have "custom lvalue converters" such + as std::vector<> <-> Python list. These converters would + support the modification of the Python list from C++. For example:

+ +

C++:

+
 void foo(std::vector<double>& array)
 {
   for(std::size_t i=0;i<array.size();i++) {
     array[i] *= 2;
   }
 }
-
Python: -
+
+ Python: +
 >>> l = [1, 2, 3]
 >>> foo(l)
 >>> print l
 [2, 4, 6]
-
Custom lvalue converters require changes to the Boost.Python core -library and are currently not available. -

- P.S.: -

-

- The "scitbx" files referenced above are available via anonymous CVS: -

-
+
+ Custom lvalue converters require changes to the Boost.Python core library + and are currently not available. + +

P.S.:

+ +

The "scitbx" files referenced above are available via anonymous + CVS:

+
 cvs -d:pserver:anonymous@cvs.cctbx.sourceforge.net:/cvsroot/cctbx login
 cvs -d:pserver:anonymous@cvs.cctbx.sourceforge.net:/cvsroot/cctbx co scitbx
 

-

- fatal error C1204:Compiler limit:internal structure - overflow -

+ +

fatal error C1204:Compiler limit:internal + structure overflow

+
Q: I get this error message when compiling a large source file. What can I do? -

- A: You have two choices: -

+ +

A: You have two choices:

+
    -
  1. - Upgrade your compiler (preferred) -
  2. +
  3. Upgrade your compiler (preferred)
  4. +
  5. Break your source file up into multiple translation units. -

    - my_module.cpp: -

    -
    +
    +          

    my_module.cpp:

    +
     ...
     void more_of_my_module();
     BOOST_PYTHON_MODULE(my_module)
    @@ -203,23 +231,25 @@ BOOST_PYTHON_MODULE(my_module)
        ...
        more_of_my_module();
     }
    -
    more_of_my_module.cpp: -
    +
    + more_of_my_module.cpp: +
     void more_of_my_module()
     {
        def("baz", baz);
        ...
     }
    -
    If you find that a class_<...> declaration can't fit -in a single source file without triggering the error, you can always pass a -reference to the class_ object to a function in another source -file, and call some of its member functions (e.g. .def(...)) in -the auxilliary source file: -

    - more_of_my_class.cpp: -

    -
    +
    + If you find that a class_<...> declaration + can't fit in a single source file without triggering the error, you + can always pass a reference to the class_ object to a + function in another source file, and call some of its member + functions (e.g. .def(...)) in the auxilliary source + file: + +

    more_of_my_class.cpp:

    +
     void more_of_my_class(class<my_class>& x)
     {
        x
    @@ -234,12 +264,11 @@ void more_of_my_class(class<my_class>& x)
           

-

- How do I debug my Python extensions? -

-

- Greg Burley gives the following answer for Unix GCC users: -

+ +

How do I debug my Python extensions?

+ +

Greg Burley gives the following answer for Unix GCC users:

+
Once you have created a boost python extension for your c++ library or class, you may need to debug the code. Afterall this is one of the @@ -249,13 +278,12 @@ void more_of_my_class(class<my_class>& x) boost::python either works or it doesn't. (ie. While errors can occur when the wrapping method is invalid, most errors are caught by the compiler ;-). -

- The basic steps required to initiate a gdb session to debug a c++ - library via python are shown here. Note, however that you should - start the gdb session in the directory that contains your BPL - my_ext.so module. -

-
+
+      

The basic steps required to initiate a gdb session to debug a c++ + library via python are shown here. Note, however that you should start + the gdb session in the directory that contains your BPL my_ext.so + module.

+
 (gdb) target exec python
 (gdb) run
  >>> from my_ext import *
@@ -269,44 +297,43 @@ Current language:  auto; currently c++
 (gdb) do debugging stuff
 
-

- Greg's approach works even better using Emacs' "gdb" - command, since it will show you each line of source as you step through - it. -

-

- On Windows, my favorite debugging solution is the debugger that - comes with Microsoft Visual C++ 7. This debugger seems to work with - code generated by all versions of Microsoft and Metrowerks toolsets; - it's rock solid and "just works" without requiring any special tricks - from the user. -

-

- Unfortunately for Cygwin and MinGW users, as of this writing gdb on - Windows has a very hard time dealing with shared libraries, which could - make Greg's approach next to useless for you. My best advice for you is - to use Metrowerks C++ for compiler conformance and Microsoft Visual - Studio as a debugger when you need one. -

-

- Debugging extensions through Boost.Build -

If you are launching your extension module tests with Greg's approach works even better using Emacs' "gdb" + command, since it will show you each line of source as you step through + it.

+ +

On Windows, my favorite debugging solution is the debugger that + comes with Microsoft Visual C++ 7. This debugger seems to work with code + generated by all versions of Microsoft and Metrowerks toolsets; it's rock + solid and "just works" without requiring any special tricks from the + user.

+ +

Unfortunately for Cygwin and MinGW users, as of this writing gdb on + Windows has a very hard time dealing with shared libraries, which could + make Greg's approach next to useless for you. My best advice for you is + to use Metrowerks C++ for compiler conformance and Microsoft Visual + Studio as a debugger when you need one.

+ +

Debugging extensions through Boost.Build

+ If you are launching your extension module tests with
Boost.Build using the boost-python-runtest rule, you can ask it to launch your debugger for you by adding "-sPYTHON_LAUNCH=debugger" to your bjam command-line: -
+
 bjam -sTOOLS=metrowerks "-sPYTHON_LAUNCH=devenv /debugexe" test
 bjam -sTOOLS=gcc -sPYTHON_LAUNCH=gdb test
-
It can also be extremely useful to add the -d+2 option -when you run your test, because Boost.Build will then show you the exact -commands it uses to invoke it. This will invariably involve setting up -PYTHONPATH and other important environment variables such as LD_LIBRARY_PATH -which may be needed by your debugger in order to get things to work right. +
+ It can also be extremely useful to add the -d+2 option when + you run your test, because Boost.Build will then show you the exact + commands it uses to invoke it. This will invariably involve setting up + PYTHONPATH and other important environment variables such as + LD_LIBRARY_PATH which may be needed by your debugger in order to get + things to work right.
-

- Why doesn't my *= operator work? -

+ +

Why doesn't my *= operator work?

+
Q: I have exported my class to python, with many overloaded operators. it works fine for me except the *= @@ -314,60 +341,57 @@ which may be needed by your debugger in order to get things to work right. type". If I use p1.__imul__(p2) instead of p1 *= p2, it successfully executes my code. What's wrong with me? -

- A: There's nothing wrong with you. This is a bug in Python - 2.2. You can see the same effect in Pure Python (you can learn a lot - about what's happening in Boost.Python by playing with new-style - classes in Pure Python). -

-
+
+      

A: There's nothing wrong with you. This is a bug in Python + 2.2. You can see the same effect in Pure Python (you can learn a lot + about what's happening in Boost.Python by playing with new-style + classes in Pure Python).

+
 >>> class X(object):
 ...     def __imul__(self, x):
 ...         print 'imul'
 ...
 >>> x = X()
 >>> x *= 1
-
To cure this problem, all you need to do is upgrade your Python to -version 2.2.1 or later. +
+ To cure this problem, all you need to do is upgrade your Python to + version 2.2.1 or later.

-

- Does Boost.Python work with Mac OS X? -

+ +

Does Boost.Python work with Mac OS X?

+
-

- The short answer: as of January 2003, unfortunately not. -

-

- The longer answer: using Mac OS 10.2.3 with the December Developer's - Kit, Python 2.3a1, and bjam's darwin-tools.jam, Boost.Python compiles - fine, including the examples. However, there are problems at runtime - (see http://mail.python.org/pipermail/c++-sig/2003-January/003267.html). - Solutions are currently unknown. -

-

- It is known that under certain circumstances objects are - double-destructed. See http://mail.python.org/pipermail/c++-sig/2003-January/003278.html - for details. It is not clear however if this problem is related to - the Boost.Python runtime issues. -

+

The short answer: as of January 2003, unfortunately not.

+ +

The longer answer: using Mac OS 10.2.3 with the December Developer's + Kit, Python 2.3a1, and bjam's darwin-tools.jam, Boost.Python compiles + fine, including the examples. However, there are problems at runtime + (see http://mail.python.org/pipermail/c++-sig/2003-January/003267.html). + Solutions are currently unknown.

+ +

It is known that under certain circumstances objects are + double-destructed. See http://mail.python.org/pipermail/c++-sig/2003-January/003278.html + for details. It is not clear however if this problem is related to the + Boost.Python runtime issues.


-

- How can I find the existing PyObject that holds a C++ - object? -

+ +

How can I find the existing PyObject that holds a C++ + object?

+
"I am wrapping a function that always returns a pointer to an already-held C++ object." -
One way to do that is to hijack the mechanisms used for - wrapping a class with virtual functions. If you make a wrapper class with - an initial PyObject* constructor argument and store that PyObject* as - "self", you can get back to it by casting down to that wrapper type in a - thin wrapper function. For example: -
+    
+    One way to do that is to hijack the mechanisms used for wrapping a class
+    with virtual functions. If you make a wrapper class with an initial
+    PyObject* constructor argument and store that PyObject* as "self", you
+    can get back to it by casting down to that wrapper type in a thin wrapper
+    function. For example: 
+
 class X { X(int); virtual ~X(); ... };
 X* f();  // known to return Xs that are managed by Python objects
 
@@ -393,44 +417,43 @@ def("f", f_wrap());
 class_<X,X_wrap>("X", init<int>())
    ...
    ;
-
Of course, if X has no virtual functions you'll have to use -static_cast instead of dynamic_cast with no runtime -check that it's valid. This approach also only works if the X -object was constructed from Python, because Xs constructed from -C++ are of course never X_wrap objects. -

- Another approach to this requires some work on Boost.Python, but it's - work we've been meaning to get to anyway. Currently, when a - shared_ptr<X> is converted from Python, the - shared_ptr actually manages a reference to the containing Python - object. I plan to make it so that when a shared_ptr<X> is - converted back to Python, the library checks to see if it's one of - those "Python object managers" and if so just returns the original - Python object. To exploit this you'd have to be able to change the C++ - code you're wrapping so that it deals with shared_ptr instead of raw - pointers. -

-

- There are other approaches too. The functions that receive the Python - object that you eventually want to return could be wrapped with a thin - wrapper that records the correspondence between the object address and - its containing Python object, and you could have your f_wrap function - look in that mapping to get the Python object out. -

-

- How can I wrap a function which needs to take - ownership of a raw pointer? -

+
+ Of course, if X has no virtual functions you'll have to use + static_cast instead of dynamic_cast with no + runtime check that it's valid. This approach also only works if the + X object was constructed from Python, because + Xs constructed from C++ are of course never + X_wrap objects. + +

Another approach to this requires you to change your C++ code a bit; + if that's an option for you it might be a better way to go. work we've + been meaning to get to anyway. When a shared_ptr<X> is + converted from Python, the shared_ptr actually manages a reference to the + containing Python object. When a shared_ptr<X> is converted back to + Python, the library checks to see if it's one of those "Python object + managers" and if so just returns the original Python object. So you could + just write object(p) to get the Python object back. To + exploit this you'd have to be able to change the C++ code you're wrapping + so that it deals with shared_ptr instead of raw pointers.

+ +

There are other approaches too. The functions that receive the Python + object that you eventually want to return could be wrapped with a thin + wrapper that records the correspondence between the object address and + its containing Python object, and you could have your f_wrap function + look in that mapping to get the Python object out.

+ +

How can I wrap a function which needs to take + ownership of a raw pointer?

+
Part of an API that I'm wrapping goes something like this: -
+
 struct A {}; struct B { void add( A* ); }
 where B::add() takes ownership of the pointer passed to it.
 
-

- However: -

-
+
+      

However:

+
 a = mod.A()
 b = mod.B()
 b.add( a )
@@ -439,41 +462,43 @@ del b
 # python interpreter crashes 
 # later due to memory corruption.
 
-

- Even binding the lifetime of a to b via - with_custodian_and_ward doesn't prevent the python object a from - ultimately trying to delete the object it's pointing to. Is there a - way to accomplish a 'transfer-of-ownership' of a wrapped C++ object? -

-

- --Bruce Lowery -

-
Yes: Make sure the C++ object is held by auto_ptr: -
+
+      

Even binding the lifetime of a to b via + with_custodian_and_ward doesn't prevent the python object a from + ultimately trying to delete the object it's pointing to. Is there a way + to accomplish a 'transfer-of-ownership' of a wrapped C++ object?

+ +

--Bruce Lowery

+ + Yes: Make sure the C++ object is held by auto_ptr: +
 class_<A, std::auto_ptr<A> >("A")
     ...
     ;
-
Then make a thin wrapper function which takes an auto_ptr parameter: -
+
+ Then make a thin wrapper function which takes an auto_ptr parameter: +
 void b_insert(B& b, std::auto_ptr<A> a)
 {
     b.insert(a.get());
     a.release();
 }
-
Wrap that as B.add. Note that pointers returned via - manage_new_object - will also be held by auto_ptr, so this - transfer-of-ownership will also work correctly. +
+ Wrap that as B.add. Note that pointers returned via manage_new_object + will also be held by auto_ptr, so this transfer-of-ownership + will also work correctly.
-

- Revised - - 23 January, 2003 - -

-

- © Copyright Dave - Abrahams 2002-2003. All Rights Reserved. + +

Revised + + 23 January, 2003 +

+ +

© Copyright Dave Abrahams 2002-2003. All + Rights Reserved.

+ diff --git a/doc/v2/raw_function.html b/doc/v2/raw_function.html new file mode 100755 index 00000000..ae1ad6c0 --- /dev/null +++ b/doc/v2/raw_function.html @@ -0,0 +1,116 @@ + + + + + + + + + Boost.Python - <boost/python/raw_function.hpp> + + + + + + + + + +
+

C++ Boost

+
+

Boost.Python

+ +

Header <boost/python/raw_function.hpp>

+
+
+ +

Contents

+ +
+
Introduction
+ +
Functions
+ +
+
+
raw_function
+
+
+ +
Example
+
+
+ +

Introduction

+ +

raw_function(...) + is used to convert a function taking a tuple and a dict into a Python callable object + which accepts a variable number of arguments and arbitrary keyword + arguments. + +

Functions

+ raw_function +
+template <class F>
+object raw_function(F f, std::size_t min_args = 0);
+
+ +
+
Requires: f(tuple(), dict()) is + well-formed.
+ +
Returns: a callable object which requires at least min_args arguments. When called, the actual non-keyword arguments will be passed in a tuple as the first argument to f, and the keyword arguments will be passed in a dict as the second argument to f. + + +
+ +

Example

+C++: +
+#include <boost/python/def.hpp>
+#include <boost/python/tuple.hpp>
+#include <boost/python/dict.hpp>
+#include <boost/python/module.hpp>
+#include <boost/python/raw_function.hpp>
+
+using namespace boost::python;
+
+tuple raw(tuple args, dict kw)
+{
+    return make_tuple(args, kw);
+}
+
+BOOST_PYTHON_MODULE(raw_test)
+{
+    def("raw", raw_function(raw));
+}
+
+ +Python: +
+>>> from raw_test import *
+
+>>> raw(3, 4, foo = 'bar', baz = 42)
+((3, 4), {'foo': 'bar', 'baz': 42})
+
+

+ + 7 March, 2003 + +

+ +

© Copyright Dave Abrahams 2002. All Rights + Reserved.

+ + + diff --git a/doc/v2/reference.html b/doc/v2/reference.html index 55a3da34..ae2ec560 100644 --- a/doc/v2/reference.html +++ b/doc/v2/reference.html @@ -3,7 +3,7 @@ + "HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org"> @@ -13,7 +13,7 @@ p.c3 {font-style: italic} h2.c2 {text-align: center} h1.c1 {text-align: center} - + @@ -527,6 +527,24 @@ + + +
raw_function.hpp
+ +
+
+
Functions
+ +
+
+
raw_function
+
+
+
+
+ +

Models of CallPolicies

@@ -913,8 +931,8 @@

Revised - 13 November, 2002 - + 7 March, 2003 +

© Copyright > >::failed test0; +# if !BOOST_WORKAROUND(__MWERKS__, <= 0x2407) typedef typename assertion >::failed test1; +# endif typedef typename assertion >::failed test2; not_a_derived_class_member(Fn()); } diff --git a/include/boost/python/def.hpp b/include/boost/python/def.hpp index 8914818b..1c77736d 100644 --- a/include/boost/python/def.hpp +++ b/include/boost/python/def.hpp @@ -76,12 +76,17 @@ namespace detail detail::define_with_defaults( name, stubs, current, detail::get_signature(sig)); } + + template + object make_function1(T fn, ...) { return make_function(fn); } + + object make_function1(object const& x, object const*) { return x; } } template void def(char const* name, Fn fn) { - detail::scope_setattr_doc(name, boost::python::make_function(fn), 0); + detail::scope_setattr_doc(name, detail::make_function1(fn, &fn), 0); } template diff --git a/include/boost/python/detail/indirect_traits.hpp b/include/boost/python/detail/indirect_traits.hpp index 3f6a0098..b1883cd3 100644 --- a/include/boost/python/detail/indirect_traits.hpp +++ b/include/boost/python/detail/indirect_traits.hpp @@ -6,7 +6,6 @@ #ifndef INDIRECT_TRAITS_DWA2002131_HPP # define INDIRECT_TRAITS_DWA2002131_HPP # include -# include # include # include # include @@ -16,8 +15,18 @@ # include # include # include + +# include + +# include +# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407) +# include +# endif + # include # include +# include +# include # include # ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION @@ -28,27 +37,24 @@ namespace boost { namespace python { namespace detail { # ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION template -struct is_reference_to_const +struct is_reference_to_const : mpl::false_ { - BOOST_STATIC_CONSTANT(bool, value = false); }; template -struct is_reference_to_const +struct is_reference_to_const : mpl::true_ { - BOOST_STATIC_CONSTANT(bool, value = true); }; # if defined(BOOST_MSVC) && _MSC_FULL_VER <= 13102140 // vc7.01 alpha workaround template -struct is_reference_to_const +struct is_reference_to_const : mpl::true_ { - static const bool value = true; }; # endif template -struct is_reference_to_function : mpl::bool_ +struct is_reference_to_function : mpl::false_ { }; @@ -58,9 +64,8 @@ struct is_reference_to_function : is_function }; template -struct is_pointer_to_function : mpl::bool_ +struct is_pointer_to_function : mpl::false_ { - BOOST_STATIC_CONSTANT(bool, value = false); }; // There's no such thing as a pointer-to-cv-function, so we don't need @@ -71,7 +76,7 @@ struct is_pointer_to_function : is_function }; template -struct is_reference_to_member_function_pointer_impl : mpl::bool_ +struct is_reference_to_member_function_pointer_impl : mpl::false_ { }; @@ -91,23 +96,23 @@ struct is_reference_to_member_function_pointer template struct is_reference_to_function_pointer_aux + : mpl::and_< + is_reference + , is_pointer_to_function< + typename remove_cv< + typename remove_reference::type + >::type + > + > { // There's no such thing as a pointer-to-cv-function, so we don't need specializations for those - BOOST_STATIC_CONSTANT(bool, value = ( - is_reference::value - & is_pointer_to_function< - typename remove_cv< - typename remove_reference::type - >::type - >::value)); - typedef mpl::bool_ type; }; template struct is_reference_to_function_pointer - : mpl::if_c< - is_reference_to_function::value - , mpl::bool_ + : mpl::if_< + is_reference_to_function + , mpl::false_ , is_reference_to_function_pointer_aux >::type { @@ -115,70 +120,60 @@ struct is_reference_to_function_pointer template struct is_reference_to_non_const + : mpl::and_< + is_reference + , mpl::not_< + is_reference_to_const + > + > { - BOOST_STATIC_CONSTANT( - bool, value = ( - ::boost::type_traits::ice_and< - ::boost::is_reference::value - , ::boost::type_traits::ice_not< - ::boost::python::detail::is_reference_to_const::value>::value - >::value) - ); }; template -struct is_reference_to_volatile +struct is_reference_to_volatile : mpl::false_ { - BOOST_STATIC_CONSTANT(bool, value = false); }; template -struct is_reference_to_volatile +struct is_reference_to_volatile : mpl::true_ { - BOOST_STATIC_CONSTANT(bool, value = true); }; # if defined(BOOST_MSVC) && _MSC_FULL_VER <= 13102140 // vc7.01 alpha workaround template -struct is_reference_to_volatile +struct is_reference_to_volatile : mpl::true_ { - static const bool value = true; }; # endif template -struct is_reference_to_pointer +struct is_reference_to_pointer : mpl::false_ { - BOOST_STATIC_CONSTANT(bool, value = false); }; template -struct is_reference_to_pointer +struct is_reference_to_pointer : mpl::true_ { - BOOST_STATIC_CONSTANT(bool, value = true); }; template -struct is_reference_to_pointer +struct is_reference_to_pointer : mpl::true_ { - BOOST_STATIC_CONSTANT(bool, value = true); }; template -struct is_reference_to_pointer +struct is_reference_to_pointer : mpl::true_ { - BOOST_STATIC_CONSTANT(bool, value = true); }; template -struct is_reference_to_pointer +struct is_reference_to_pointer : mpl::true_ { - BOOST_STATIC_CONSTANT(bool, value = true); }; template -struct is_reference_to_class +struct is_reference_to_classx { BOOST_STATIC_CONSTANT( bool, value @@ -196,19 +191,47 @@ struct is_reference_to_class }; template -struct is_pointer_to_class +struct is_reference_to_class + : mpl::and_< + is_reference +# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407) + , mpl::not_< + is_enum< + typename remove_cv< + typename remove_reference::type + >::type + > + > +# endif + , is_class< + typename remove_cv< + typename remove_reference::type + >::type + > + > +{ +}; + +template +struct is_pointer_to_class + : mpl::and_< + is_pointer +# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407) + , mpl::not_< + is_enum< + typename remove_cv< + typename remove_pointer::type + >::type + > + > +# endif + , is_class< + typename remove_cv< + typename remove_pointer::type + >::type + > + > { - BOOST_STATIC_CONSTANT( - bool, value - = (boost::type_traits::ice_and< - is_pointer::value - , is_class< - typename remove_cv< - typename remove_pointer::type - >::type - >::value - >::value) - ); }; # else @@ -220,8 +243,8 @@ typedef char (&outer_no_type)[1]; template struct is_const_help { - typedef typename mpl::if_c< - is_const::value + typedef typename mpl::if_< + is_const , inner_yes_type , inner_no_type >::type type; @@ -230,8 +253,8 @@ struct is_const_help template struct is_volatile_help { - typedef typename mpl::if_c< - is_volatile::value + typedef typename mpl::if_< + is_volatile , inner_yes_type , inner_no_type >::type type; @@ -240,8 +263,8 @@ struct is_volatile_help template struct is_pointer_help { - typedef typename mpl::if_c< - is_pointer::value + typedef typename mpl::if_< + is_pointer , inner_yes_type , inner_no_type >::type type; @@ -250,8 +273,8 @@ struct is_pointer_help template struct is_class_help { - typedef typename mpl::if_c< - is_class::value + typedef typename mpl::if_< + is_class , inner_yes_type , inner_no_type >::type type; diff --git a/include/boost/python/detail/is_shared_ptr.hpp b/include/boost/python/detail/is_shared_ptr.hpp new file mode 100755 index 00000000..d3579a6a --- /dev/null +++ b/include/boost/python/detail/is_shared_ptr.hpp @@ -0,0 +1,18 @@ +// Copyright David Abrahams 2003. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. +#ifndef IS_SHARED_PTR_DWA2003224_HPP +# define IS_SHARED_PTR_DWA2003224_HPP + +# include +# include + +namespace boost { namespace python { namespace detail { + +BOOST_PYTHON_IS_XXX_DEF(shared_ptr, shared_ptr, 1) + +}}} // namespace boost::python::detail + +#endif // IS_SHARED_PTR_DWA2003224_HPP diff --git a/include/boost/python/init.hpp b/include/boost/python/init.hpp index bfc398d6..39bf4ac7 100644 --- a/include/boost/python/init.hpp +++ b/include/boost/python/init.hpp @@ -315,7 +315,10 @@ namespace detail , mpl::push_front<> >::type args; - typedef typename ClassT::holder_selector::type selector_t; + typedef typename ClassT::holder_selector holder_selector_t; +# if !BOOST_WORKAROUND(__MWERKS__, <= 0x2407) + typedef typename holder_selector_t::type selector_t; +# endif typedef typename ClassT::held_type held_type_t; cl.def( diff --git a/include/boost/python/object/inheritance.hpp b/include/boost/python/object/inheritance.hpp index de8cae43..610f458c 100644 --- a/include/boost/python/object/inheritance.hpp +++ b/include/boost/python/object/inheritance.hpp @@ -11,6 +11,7 @@ # include # include # include +# include namespace boost { namespace python { namespace objects { @@ -108,16 +109,20 @@ struct implicit_cast_generator template struct cast_generator { - // CWPro7 will return false sometimes, but that's OK since we can - // always cast up with dynamic_cast<> + // It's OK to return false, since we can always cast up with + // dynamic_cast<> if neccessary. +# if BOOST_WORKAROUND(__MWERKS__, <= 0x2407) + BOOST_STATIC_CONSTANT(bool, is_upcast = false); +# else BOOST_STATIC_CONSTANT( bool, is_upcast = ( is_base_and_derived::value )); +# endif typedef typename mpl::if_c< is_upcast -# if defined(__MWERKS__) && __MWERKS__ <= 0x2406 +# if BOOST_WORKAROUND(__MWERKS__, <= 0x2407) // grab a few more implicit_cast cases for CodeWarrior || !is_polymorphic::value || !is_polymorphic::value diff --git a/include/boost/python/object/make_ptr_instance.hpp b/include/boost/python/object/make_ptr_instance.hpp index 0b07ae26..25dbd77c 100644 --- a/include/boost/python/object/make_ptr_instance.hpp +++ b/include/boost/python/object/make_ptr_instance.hpp @@ -48,8 +48,14 @@ struct make_ptr_instance } template - static inline PyTypeObject* get_derived_class_object(mpl::false_, U*) + static inline PyTypeObject* get_derived_class_object(mpl::false_, U* x) { +# if BOOST_WORKAROUND(__MWERKS__, <= 0x2407) + if (typeid(*x) != typeid(U)) + return get_derived_class_object(mpl::true_(), x); +# else + (void)x; +# endif return 0; } }; diff --git a/include/boost/python/raw_function.hpp b/include/boost/python/raw_function.hpp new file mode 100755 index 00000000..3a28d127 --- /dev/null +++ b/include/boost/python/raw_function.hpp @@ -0,0 +1,47 @@ +// Copyright David Abrahams 2003. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. +#ifndef RAW_FUNCTION_DWA200336_HPP +# define RAW_FUNCTION_DWA200336_HPP + +# include +# include +# include + +# include + +namespace boost { namespace python { + +namespace detail +{ + template + struct raw_dispatcher + { + raw_dispatcher(F f) : f(f) {} + + PyObject* operator()(PyObject* args, PyObject* keywords) + { + return incref( + object( + f(tuple(borrowed_reference(args)), dict(borrowed_reference(keywords))) + ).ptr() + ); + } + private: + F f; + }; + + object BOOST_PYTHON_DECL make_raw_function(objects::py_function, std::size_t min_args); +} + +template +object raw_function(F f, std::size_t min_args = 0) +{ + return detail::make_raw_function(detail::raw_dispatcher(f), min_args); +} + +}} // namespace boost::python + +#endif // RAW_FUNCTION_DWA200336_HPP diff --git a/src/object/function.cpp b/src/object/function.cpp index bf6e8665..761a9866 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -39,10 +39,15 @@ function::function( unsigned keyword_offset = m_max_arity > num_keywords ? m_max_arity - num_keywords : 0; - - m_arg_names = object(handle<>(PyTuple_New(m_max_arity))); - for (unsigned j = 0; j < keyword_offset; ++j) - PyTuple_SET_ITEM(m_arg_names.ptr(), j, incref(Py_None)); + + unsigned tuple_size = num_keywords ? m_max_arity : 0; + m_arg_names = object(handle<>(PyTuple_New(tuple_size))); + + if (num_keywords != 0) + { + for (unsigned j = 0; j < keyword_offset; ++j) + PyTuple_SET_ITEM(m_arg_names.ptr(), j, incref(Py_None)); + } for (unsigned i = 0; i < num_keywords; ++i) { @@ -68,7 +73,7 @@ function::function( function::~function() { } - + PyObject* function::call(PyObject* args, PyObject* keywords) const { std::size_t nargs = PyTuple_GET_SIZE(args); @@ -76,47 +81,68 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const std::size_t total_args = nargs + nkeywords; function const* f = this; + + // Try overloads looking for a match do { // Check for a plausible number of arguments if (total_args >= f->m_min_arity && total_args <= f->m_max_arity) { + // This will be the args that actually get passed handle<> args2(allow_null(borrowed(args))); - if (nkeywords > 0) + + if (nkeywords > 0) // Keyword arguments were supplied { - if (!f->m_arg_names - || static_cast(PyTuple_Size(f->m_arg_names.ptr())) < total_args) + if (f->m_arg_names.ptr() == Py_None) // this overload doesn't accept keywords { args2 = handle<>(); // signal failure } else { - // build a new arg tuple - args2 = handle<>(PyTuple_New(total_args)); + std::size_t max_args + = static_cast(PyTuple_Size(f->m_arg_names.ptr())); - // Fill in the positional arguments - for (std::size_t i = 0; i < nargs; ++i) - PyTuple_SET_ITEM(args2.get(), i, incref(PyTuple_GET_ITEM(args, i))); - - // Grab remaining arguments by name from the keyword dictionary - for (std::size_t j = nargs; j < total_args; ++j) + // "all keywords are none" is a special case + // indicating we will accept any number of keyword + // arguments + if (max_args == 0) { - PyObject* value = PyDict_GetItem( - keywords, PyTuple_GET_ITEM(f->m_arg_names.ptr(), j)); - - if (!value) + // no argument preprocessing + } + else if (max_args < total_args) + { + args2 = handle<>(); + } + else + { + // build a new arg tuple + args2 = handle<>(PyTuple_New(total_args)); + + // Fill in the positional arguments + for (std::size_t i = 0; i < nargs; ++i) + PyTuple_SET_ITEM(args2.get(), i, incref(PyTuple_GET_ITEM(args, i))); + + // Grab remaining arguments by name from the keyword dictionary + for (std::size_t j = nargs; j < total_args; ++j) { - PyErr_Clear(); - args2 = handle<>(); - break; + PyObject* value = PyDict_GetItem( + keywords, PyTuple_GET_ITEM(f->m_arg_names.ptr(), j)); + + if (!value) + { + PyErr_Clear(); + args2 = handle<>(); + break; + } + PyTuple_SET_ITEM(args2.get(), j, incref(value)); } - PyTuple_SET_ITEM(args2.get(), j, incref(value)); } } } - // Call the function - PyObject* result = args2 ? f->m_fn(args2.get(), 0) : 0; + // Call the function. Pass keywords in case it's a + // function accepting any number of keywords + PyObject* result = args2 ? f->m_fn(args2.get(), keywords) : 0; // If the result is NULL but no error was set, m_fn failed // the argument-matching test. @@ -482,4 +508,20 @@ handle<> function_handle_impl(py_function const& f, unsigned min_arity, unsigned new function(f, min_arity, max_arity, 0, 0))); } -}}} // namespace boost::python::objects +} + +namespace detail +{ + object BOOST_PYTHON_DECL make_raw_function(objects::py_function f, std::size_t min_args) + { + static keyword k; + + return objects::function_object( + f + , min_args + , std::numeric_limits::max() + , keyword_range(&k,&k)); + } +} + +}} // namespace boost::python::objects diff --git a/test/args.cpp b/test/args.cpp index 281ca236..a65933cb 100644 --- a/test/args.cpp +++ b/test/args.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "test_class.hpp" @@ -39,12 +40,20 @@ struct X BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(X_f_overloads, X::f, 0, 3) + +tuple raw_func(tuple args, dict kw) +{ + return make_tuple(args, kw); +} + BOOST_PYTHON_MODULE(args_ext) { def("f", f, args("x", "y", "z") , "This is f's docstring" ); + def("raw", raw_function(raw_func)); + #if defined(BOOST_MSVC) && BOOST_MSVC <= 1200 // MSVC6 gives a fatal error LNK1179: invalid or corrupt file: // duplicate comdat error if we try to re-use the exact type of f @@ -57,6 +66,7 @@ BOOST_PYTHON_MODULE(args_ext) class_("Y", init(args("value"), "Y's docstring")) .def("value", &Y::value) + .def("raw", raw_function(raw_func)) ; class_("X", "This is X's docstring") diff --git a/test/args.py b/test/args.py index 665fae41..2c9218b1 100644 --- a/test/args.py +++ b/test/args.py @@ -1,6 +1,9 @@ """ >>> from args_ext import * +>>> raw(3, 4, foo = 'bar', baz = 42) +((3, 4), {'foo': 'bar', 'baz': 42}) + >>> f(x= 1, y = 3, z = 'hello') (1, 3.0, 'hello') @@ -101,6 +104,11 @@ >>> inner(n = 1, self = q).value() 1 + +>>> y = Y(value = 33) +>>> y.raw(this = 1, that = 'the other')[1] +{'this': 1, 'that': 'the other'} + """ def run(args = None): import sys diff --git a/test/back_reference.py b/test/back_reference.py index 21d5d1c0..7eac13c3 100644 --- a/test/back_reference.py +++ b/test/back_reference.py @@ -10,8 +10,7 @@ >>> z2 = copy_Z(z) >>> x_instances() 4 ->>> y_identity(y) is y -1 +>>> assert y_identity(y) is y >>> y_equality(y, y) 1 ''' diff --git a/test/enum.cpp b/test/enum.cpp index 09a916c3..1a4f178b 100644 --- a/test/enum.cpp +++ b/test/enum.cpp @@ -7,11 +7,21 @@ #include #include #include - +#if BOOST_WORKAROUND(__MWERKS__, <= 0x2407) +# include +# include +#endif using namespace boost::python; enum color { red = 1, green = 2, blue = 4 }; +#if BOOST_WORKAROUND(__MWERKS__, <= 0x2407) +namespace boost // Pro7 has a hard time detecting enums +{ + template <> struct is_enum : boost::mpl::true_ {}; +} +#endif + color identity_(color x) { return x; } struct colorized { diff --git a/test/operators.cpp b/test/operators.cpp index 9e7a1e5a..9096cb68 100755 --- a/test/operators.cpp +++ b/test/operators.cpp @@ -26,7 +26,7 @@ struct X : test_class<> typedef test_class<> base_t; X(int x) : base_t(x) {} - X const operator+(X const& r) { return X(value() + r.value()); } + X const operator+(X const& r) const { return X(value() + r.value()); } }; X operator-(X const& l, X const& r) { return X(l.value() - r.value()); } diff --git a/test/test_builtin_converters.py b/test/test_builtin_converters.py index 80e2149d..0b3bc645 100644 --- a/test/test_builtin_converters.py +++ b/test/test_builtin_converters.py @@ -61,19 +61,15 @@ r""" ... else: print 'expected an OverflowError!' ->>> abs(rewrap_value_float(4.2) - 4.2) < .000001 -1 +>>> assert abs(rewrap_value_float(4.2) - 4.2) < .000001 >>> rewrap_value_double(4.2) - 4.2 0.0 >>> rewrap_value_long_double(4.2) - 4.2 0.0 ->>> abs(rewrap_value_complex_float(4+.2j) - (4+.2j)) < .000001 -1 ->>> abs(rewrap_value_complex_double(4+.2j) - (4+.2j)) < .000001 -1 ->>> abs(rewrap_value_complex_long_double(4+.2j) - (4+.2j)) < .000001 -1 +>>> assert abs(rewrap_value_complex_float(4+.2j) - (4+.2j)) < .000001 +>>> assert abs(rewrap_value_complex_double(4+.2j) - (4+.2j)) < .000001 +>>> assert abs(rewrap_value_complex_long_double(4+.2j) - (4+.2j)) < .000001 >>> rewrap_value_cstring('hello, world') 'hello, world' @@ -136,19 +132,15 @@ r""" 42L ->>> abs(rewrap_const_reference_float(4.2) - 4.2) < .000001 -1 +>>> assert abs(rewrap_const_reference_float(4.2) - 4.2) < .000001 >>> rewrap_const_reference_double(4.2) - 4.2 0.0 >>> rewrap_const_reference_long_double(4.2) - 4.2 0.0 ->>> abs(rewrap_const_reference_complex_float(4+.2j) - (4+.2j)) < .000001 -1 ->>> abs(rewrap_const_reference_complex_double(4+.2j) - (4+.2j)) < .000001 -1 ->>> abs(rewrap_const_reference_complex_long_double(4+.2j) - (4+.2j)) < .000001 -1 +>>> assert abs(rewrap_const_reference_complex_float(4+.2j) - (4+.2j)) < .000001 +>>> assert abs(rewrap_const_reference_complex_double(4+.2j) - (4+.2j)) < .000001 +>>> assert abs(rewrap_const_reference_complex_long_double(4+.2j) - (4+.2j)) < .000001 >>> rewrap_const_reference_cstring('hello, world') 'hello, world' @@ -221,11 +213,9 @@ Check that classic classes also work ... else: print 'expected a TypeError exception' # show that arbitrary handle instantiations can be returned ->>> get_type(1) is type(1) -1 +>>> assert get_type(1) is type(1) ->>> return_null_handle() is None -1 +>>> assert return_null_handle() is None """ def run(args = None): diff --git a/test/test_pointer_adoption.py b/test/test_pointer_adoption.py index f684b062..d811bce8 100644 --- a/test/test_pointer_adoption.py +++ b/test/test_pointer_adoption.py @@ -70,11 +70,9 @@ Test call policies for constructors here >>> num_a_instances() 0 ->>> as_A(create('dynalloc')) is None -0 +>>> assert as_A(create('dynalloc')) is not None >>> base = Base() ->>> as_A(base) is None -1 +>>> assert as_A(base) is None """ def run(args = None): import sys From d34a11b584146474b976af6e2be2ee696259e3d6 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 8 Mar 2003 05:28:54 +0000 Subject: [PATCH 08/18] Fix for Python 2.3 long->int conversion behavior change [SVN r17779] --- src/converter/builtin_converters.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/converter/builtin_converters.cpp b/src/converter/builtin_converters.cpp index af6fada9..72437e8c 100644 --- a/src/converter/builtin_converters.cpp +++ b/src/converter/builtin_converters.cpp @@ -101,7 +101,10 @@ namespace { static T extract(PyObject* intermediate) { - return numeric_cast(PyInt_AS_LONG(intermediate)); + long x = PyInt_AsLong(intermediate); + if (PyErr_Occurred()) + throw_error_already_set(); + return numeric_cast(x); } }; From 257a6c45f89175974d6b29449ef5b35151f63754 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 8 Mar 2003 08:51:45 +0000 Subject: [PATCH 09/18] Remove flotsam [SVN r17782] --- .../boost/python/detail/indirect_traits.hpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/include/boost/python/detail/indirect_traits.hpp b/include/boost/python/detail/indirect_traits.hpp index b1883cd3..f4986143 100644 --- a/include/boost/python/detail/indirect_traits.hpp +++ b/include/boost/python/detail/indirect_traits.hpp @@ -172,24 +172,6 @@ struct is_reference_to_pointer : mpl::true_ { }; -template -struct is_reference_to_classx -{ - BOOST_STATIC_CONSTANT( - bool, value - = (boost::type_traits::ice_and< - is_reference::value - , is_class< - typename remove_cv< - typename remove_reference::type - >::type - >::value - >::value) - ); - typedef mpl::bool_ type; - BOOST_MPL_AUX_LAMBDA_SUPPORT(1,is_reference_to_class,(T)) -}; - template struct is_reference_to_class : mpl::and_< From 6aa71e1f72d8cfb916f77f12beb0dd621eee775e Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 8 Mar 2003 08:53:19 +0000 Subject: [PATCH 10/18] Remove flotsam [SVN r17783] --- .../boost/python/detail/indirect_traits.hpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/include/boost/python/detail/indirect_traits.hpp b/include/boost/python/detail/indirect_traits.hpp index f4986143..3e80d519 100644 --- a/include/boost/python/detail/indirect_traits.hpp +++ b/include/boost/python/detail/indirect_traits.hpp @@ -176,15 +176,6 @@ template struct is_reference_to_class : mpl::and_< is_reference -# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407) - , mpl::not_< - is_enum< - typename remove_cv< - typename remove_reference::type - >::type - > - > -# endif , is_class< typename remove_cv< typename remove_reference::type @@ -198,15 +189,6 @@ template struct is_pointer_to_class : mpl::and_< is_pointer -# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407) - , mpl::not_< - is_enum< - typename remove_cv< - typename remove_pointer::type - >::type - > - > -# endif , is_class< typename remove_cv< typename remove_pointer::type From 39195ac97abbecd34c987d3769c47d88f2d58217 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 8 Mar 2003 12:36:18 +0000 Subject: [PATCH 11/18] Fix for older EDGs [SVN r17786] --- include/boost/python/detail/indirect_traits.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/boost/python/detail/indirect_traits.hpp b/include/boost/python/detail/indirect_traits.hpp index 3e80d519..170f3662 100644 --- a/include/boost/python/detail/indirect_traits.hpp +++ b/include/boost/python/detail/indirect_traits.hpp @@ -183,6 +183,7 @@ struct is_reference_to_class > > { + BOOST_MPL_AUX_LAMBDA_SUPPORT(1,is_reference_to_class,(T)) }; template @@ -196,6 +197,7 @@ struct is_pointer_to_class > > { + BOOST_MPL_AUX_LAMBDA_SUPPORT(1,is_pointer_to_class,(T)) }; # else From 34bf1560a9d0e09253421598af2a22d7b04c5b4a Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 9 Mar 2003 17:26:06 +0000 Subject: [PATCH 12/18] non-template function make_function1 must be inline [SVN r17791] --- include/boost/python/def.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/python/def.hpp b/include/boost/python/def.hpp index 1c77736d..d0db5528 100644 --- a/include/boost/python/def.hpp +++ b/include/boost/python/def.hpp @@ -80,6 +80,7 @@ namespace detail template object make_function1(T fn, ...) { return make_function(fn); } + inline object make_function1(object const& x, object const*) { return x; } } From 7dcacbcfc4f98873f9b4f62775629ad2b18e57dc Mon Sep 17 00:00:00 2001 From: Bruno da Silva de Oliveira Date: Tue, 11 Mar 2003 03:20:24 +0000 Subject: [PATCH 13/18] - first version [SVN r17804] --- pyste/README | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 pyste/README diff --git a/pyste/README b/pyste/README new file mode 100644 index 00000000..c3535a2b --- /dev/null +++ b/pyste/README @@ -0,0 +1,30 @@ +Pyste - Python Semi-Automatic Exporter +====================================== + +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. + +The documentation can be found in the file index.html accompaning this README. + +Enjoy! +Bruno da Silva de Oliveira (nicodemus@globalite.com.br) + +Thanks +====== + +- David Abrahams, creator of Boost.Python, for tips on the syntax of the interface +file and support. +- Marcelo Camelo, for design tips and support. +- Brad King, creator of the excellent GCCXML (http://www.gccxml.org) +- Fredrik Lundh, creator of the elementtree library (http://effbot.org) + +Bugs +==== + +Pyste is a young tool, so please help it to get better! Send bug reports to +nicodemus@globalite.com.br, accompaining the stack trace in case of exceptions. +If possible, run pyste with --debug, and send the resulting xmls too (pyste +will output a xml file with the same of each header it parsed). From 20c52def19220464dbf6cc48144051c375ad2842 Mon Sep 17 00:00:00 2001 From: Bruno da Silva de Oliveira Date: Tue, 11 Mar 2003 03:29:22 +0000 Subject: [PATCH 14/18] - first version [SVN r17805] --- ...orting_all_declarations_from_a_header.html | 76 ++ pyste/doc/introduction.html | 74 ++ pyste/doc/policies.html | 79 ++ pyste/doc/pyste.txt | 370 ++++++++++ pyste/doc/renaming_and_excluding.html | 68 ++ pyste/doc/running_pyste.html | 110 +++ pyste/doc/templates.html | 103 +++ pyste/doc/the_interface_files.html | 81 +++ pyste/doc/theme/alert.gif | Bin 0 -> 577 bytes pyste/doc/theme/arrow.gif | Bin 0 -> 70 bytes pyste/doc/theme/bkd.gif | Bin 0 -> 1317 bytes pyste/doc/theme/bkd2.gif | Bin 0 -> 2543 bytes pyste/doc/theme/bulb.gif | Bin 0 -> 944 bytes pyste/doc/theme/bullet.gif | Bin 0 -> 152 bytes pyste/doc/theme/c++boost.gif | Bin 0 -> 8819 bytes pyste/doc/theme/l_arr.gif | Bin 0 -> 147 bytes pyste/doc/theme/l_arr_disabled.gif | Bin 0 -> 91 bytes pyste/doc/theme/note.gif | Bin 0 -> 151 bytes pyste/doc/theme/r_arr.gif | Bin 0 -> 147 bytes pyste/doc/theme/r_arr_disabled.gif | Bin 0 -> 91 bytes pyste/doc/theme/smiley.gif | Bin 0 -> 879 bytes pyste/doc/theme/style.css | 170 +++++ pyste/doc/theme/u_arr.gif | Bin 0 -> 170 bytes pyste/doc/wrappers.html | 108 +++ pyste/index.html | 71 ++ pyste/src/.cvsignore | 1 + pyste/src/ClassExporter.py | 688 ++++++++++++++++++ pyste/src/CodeUnit.py | 78 ++ pyste/src/CppParser.py | 94 +++ pyste/src/EnumExporter.py | 30 + pyste/src/Exporter.py | 69 ++ pyste/src/FunctionExporter.py | 81 +++ pyste/src/GCCXMLParser.py | 395 ++++++++++ pyste/src/HeaderExporter.py | 67 ++ pyste/src/IncludeExporter.py | 19 + pyste/src/declarations.py | 452 ++++++++++++ pyste/src/enumerate.py | 7 + pyste/src/exporters.py | 3 + pyste/src/exporterutils.py | 26 + pyste/src/infos.py | 185 +++++ pyste/src/policies.py | 75 ++ pyste/src/pyste-profile.py | 17 + pyste/src/pyste.py | 154 ++++ pyste/src/settings.py | 12 + pyste/tests/GCCXMLParserUT.py | 338 +++++++++ pyste/tests/infosUT.py | 50 ++ pyste/tests/policiesUT.py | 59 ++ pyste/tests/runtests.py | 14 + 48 files changed, 4224 insertions(+) create mode 100644 pyste/doc/exporting_all_declarations_from_a_header.html create mode 100644 pyste/doc/introduction.html create mode 100644 pyste/doc/policies.html create mode 100644 pyste/doc/pyste.txt create mode 100644 pyste/doc/renaming_and_excluding.html create mode 100644 pyste/doc/running_pyste.html create mode 100644 pyste/doc/templates.html create mode 100644 pyste/doc/the_interface_files.html create mode 100644 pyste/doc/theme/alert.gif create mode 100644 pyste/doc/theme/arrow.gif create mode 100644 pyste/doc/theme/bkd.gif create mode 100644 pyste/doc/theme/bkd2.gif create mode 100644 pyste/doc/theme/bulb.gif create mode 100644 pyste/doc/theme/bullet.gif create mode 100644 pyste/doc/theme/c++boost.gif create mode 100644 pyste/doc/theme/l_arr.gif create mode 100644 pyste/doc/theme/l_arr_disabled.gif create mode 100644 pyste/doc/theme/note.gif create mode 100644 pyste/doc/theme/r_arr.gif create mode 100644 pyste/doc/theme/r_arr_disabled.gif create mode 100644 pyste/doc/theme/smiley.gif create mode 100644 pyste/doc/theme/style.css create mode 100644 pyste/doc/theme/u_arr.gif create mode 100644 pyste/doc/wrappers.html create mode 100644 pyste/index.html create mode 100644 pyste/src/.cvsignore create mode 100644 pyste/src/ClassExporter.py create mode 100644 pyste/src/CodeUnit.py create mode 100644 pyste/src/CppParser.py create mode 100644 pyste/src/EnumExporter.py create mode 100644 pyste/src/Exporter.py create mode 100644 pyste/src/FunctionExporter.py create mode 100644 pyste/src/GCCXMLParser.py create mode 100644 pyste/src/HeaderExporter.py create mode 100644 pyste/src/IncludeExporter.py create mode 100644 pyste/src/declarations.py create mode 100644 pyste/src/enumerate.py create mode 100644 pyste/src/exporters.py create mode 100644 pyste/src/exporterutils.py create mode 100644 pyste/src/infos.py create mode 100644 pyste/src/policies.py create mode 100644 pyste/src/pyste-profile.py create mode 100644 pyste/src/pyste.py create mode 100644 pyste/src/settings.py create mode 100644 pyste/tests/GCCXMLParserUT.py create mode 100644 pyste/tests/infosUT.py create mode 100644 pyste/tests/policiesUT.py create mode 100644 pyste/tests/runtests.py diff --git a/pyste/doc/exporting_all_declarations_from_a_header.html b/pyste/doc/exporting_all_declarations_from_a_header.html new file mode 100644 index 00000000..4f2418f5 --- /dev/null +++ b/pyste/doc/exporting_all_declarations_from_a_header.html @@ -0,0 +1,76 @@ + + + +Exporting All Declarations from a Header + + + + + + + + + +
+ + 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/introduction.html b/pyste/doc/introduction.html new file mode 100644 index 00000000..ffb50e7e --- /dev/null +++ b/pyste/doc/introduction.html @@ -0,0 +1,74 @@ + + + +Introduction + + + + + + + + + +
+ + Introduction +
+
+ + + + + + +
+

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.

+

Example

+Let's borrow the class World from the +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:

+
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
+ + + + + +
+
+
+ + diff --git a/pyste/doc/policies.html b/pyste/doc/policies.html new file mode 100644 index 00000000..2869889f --- /dev/null +++ b/pyste/doc/policies.html @@ -0,0 +1,79 @@ + + + +Policies + + + + + + + + + + +
+ + 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 + +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))
+
+ + + + +
+ + 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. +
+ + + + + + +
+
+
+ + 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:

+
+    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?

+ + + + + + +
+
+
+ + diff --git a/pyste/doc/running_pyste.html b/pyste/doc/running_pyste.html new file mode 100644 index 00000000..a67d5812 --- /dev/null +++ b/pyste/doc/running_pyste.html @@ -0,0 +1,110 @@ + + + +Running Pyste + + + + + + + + + + +
+ + Running Pyste +
+
+ + + + + + +
+

+To run pyste, you will need:

+

+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.

+

+So, the usage is simple enough:

+
>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 + +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:

+
+    C:\Program Files\Microsoft Visual Studio\VC98\bin\Vcvars32.bat
+
+

+with that, you should have little trouble setting up the flags.

+ + + + + + +
+
+
+ + diff --git a/pyste/doc/templates.html b/pyste/doc/templates.html new file mode 100644 index 00000000..58548c72 --- /dev/null +++ b/pyste/doc/templates.html @@ -0,0 +1,103 @@ + + + +Templates + + + + + + + + + + +
+ + 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 <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). +
+ + + + + + +
+
+
+ + diff --git a/pyste/doc/the_interface_files.html b/pyste/doc/the_interface_files.html new file mode 100644 index 00000000..77246af7 --- /dev/null +++ b/pyste/doc/the_interface_files.html @@ -0,0 +1,81 @@ + + + +The Interface Files + + + + + + + + + + +
+ + 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.

+

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.

+ + + + + + +
+
+
+ + diff --git a/pyste/doc/theme/alert.gif b/pyste/doc/theme/alert.gif new file mode 100644 index 0000000000000000000000000000000000000000..270764cc58716d36f8545c07ffbccb76a3dbc7f4 GIT binary patch literal 577 zcmZ?wbhEHb6krfwc*el+^5x4HFJ3%)^yuNkhj;GW`TvCB_N{CGA2IyD%W(C|x&OBr z{@-G_a_Q{m__~8F~vDYEl>qqZpDSjq@WJa>5z1Lm6Vd z86rG+L!23`^cb|27{rAb*jO1D7#RNl{|}<5jTL{gu!=AwGw6T}2E_>j`@DwarsiZ7 zvzCqy8~4s$Mnes@-VRGi5tqr$%yz7-+I%yUbrd6_%=Konm?$SD`KoeGb{17nO!DDy z>t(PKkWWZ*<cQAmnAE=fsD%$UE$ROUW+NUZ1+QQKJ-!ms2) z!wk-f=<_sLpE#kg){9r_k3+JcmI}v|3yP=3Bn5<=JQ5$B644M)RP)^A(&gG6!^Fs7 F4FJH#(p~@n literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/arrow.gif b/pyste/doc/theme/arrow.gif new file mode 100644 index 0000000000000000000000000000000000000000..e33db0fb4dd85e0fe527343d95a3f28a0ebb74f9 GIT binary patch literal 70 zcmZ?wbhEHb6lLIGn8?8J9}E~6W->4^DE?&OC-XFJFH7^5yH-udiOcdjJ0Y=g*&CzkdDw`}cS6-hKG+ zVdct|pFVy1_U+rhfB)XRdGq$|+aEuEeEj(F|NsB1R;~K`_wVY}tN;A@vu4ej6)RT! z{{8#cuU~7|u3fio-TL+GfByWrapT4f8#Zj(v}yC^&D*wZ+q!k@mMvR$@7{gjz=7@C zx9{A!bMM~0yLRo`vuDr#{rh+9*s*WlzC(u&9X)#V)TvV^Po6w- zyLay%Jb3Wz*|X1|Kfizf{+Tmpu3fwK^y$-^H*a3Qe*M|AXXnnHJAeNCg$oysA3uKe z>eWk^E+4<0lBLU*@T^?5dd(`K^&2*BTFEI`2zd3>o;z)v)s9R@BSUeM~|O8Wn_5q^405CHt*hl`1r~B%hzw;zdQW;{pasr zF9v2V8Hs|fgUuYmej5*MSa`TSY=y@hkBv&AY7)j-cRDt)dE zU9$D{^$pSTGkte&%f01a^!nae>+L=F4>WVj`|WA_`1r(RZF9M$J3l|aF#GrnzrDM@ zzP^$C;>NkXyT8BJIMglgzi&_FC%sFnlY$n!TsEid z)yw4z+N54FEt!_}YUPS$t6r^IvuM`A)fY$|rssS*xogRqPp5XWJpOdrfW7(58I$WZ zJ;oN#*L*&AD&X$RBj zw_mR(wCjGmkup8^+s%ySYroyf+5Yz1?SkXF-|v)M&;5S4;`!R|_iFabaxho}0O~&E Au>b%7 literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/bkd2.gif b/pyste/doc/theme/bkd2.gif new file mode 100644 index 0000000000000000000000000000000000000000..b03d9ba97ca7a4c4de297b5413fd3d3df8e0fb49 GIT binary patch literal 2543 zcmZ?wbhEHbWZ+<8_|5Qs zOXn|KICuX1*>mU4oIQK`%$bv?P8~mS;_#6p2M--OaPZ*2|Ns8}`}_OP@1MVZe*f|P z>$k6;zkL4q>Ertk?_a-p{qohz=P#Z=efIS6lgAGqJ-mDG?(I9bZ{E6j{l@jH*REc< zdgbz!%NH+QJb&T**>h)4pE-T<)X5VkPaHpS{OGZxhmRaScdEzI5s0g$w7;ojZHx%;{67PM$b%{MfOhM~)mmbm-u}zkmPy{{8Fc z&mZ5vfBX9N%jeIZK7Rc8;lqdb@87+B`{woQS1(__c>es^)2C0LJbCi?@#9Azd+y!6 zd*}A;TQ_gsxPJZG)vH%7U%qtl;>8OWE}TDq?(Eq!r%#_cdGf^Z(|epKfinT?$xVT&!0cPfB*i?n>VjryLRc)rPHTRA3uKl*s)_rj~+dA=+MD~ z2mk;74@!aqhZTRaFfuS)WzYdTfrE*Gf#W{|D~F87h6M+kIfS)hPHb3sxSew)=K?q1 zo*A7Y+G%$@bUlvuDjn(&P-&9#40Lqr;xS&kbj37-WTr+*jb|nb#)Z$WoGg|QnD4;E z$}7m_(UGXw(-v`R%gZ2x1SgMZtI$;@2A4Kv-JK;Wppa?5sfp?1;y3&b7cVt+vFWaj z&d7bZX)5=2y*u^=wGO|(EX?0vU(3w>&A4KNyuGc!_lP^`h5Y<|jg_As9qe9ye4b6l zwLfQ^)Ai5qX{dACwdG~H&Ag8<4?GTEepS}0bcXM2GjToHy!Q__EjE9D&${OA%e|@W z?0kFswd2>dHcxwYZtIH)da>=LulJU`xVUlQ?(e*_H|6|)aMau8C$Hs+$^PfH-TQ1R z@9Zys?=x8~Jz~zw`|IWT`TqZVsPLeHC2azu+{_n~4vNl9xsbqCE>UruSHP*_aH~Yx zjBZ);8;P!ZJ54V0s3}d$NHR8C5Yf)7bE~1dTQ989TQ>AY!NFdOXC2DM;s%c&_d8rV z$RjEy66rkABPywrb=n1o!;{1;ZZ4VZcg~QVS2QKkQ8hZvvUQqhfWosGDbqY1b%i@T zJ$0w}Ja{%+SVuX1PT{G{<#UBjZ0KRB_A~MnG4fDY!7@oii*sS?F$GVa9y2Ls-9i@! zZ=Q*62``rlo-yL)nd4^Ey?kEYDNUY&1B}f&0S6cuRaQGa$kJKA>;Eq%7X2ehueoCC=QUEHU3eC%TOvq*Tmm2XSK+O4`Jr=7U( zZ&FydBX>tq9`hr!NoRJw*|ghfxAqb3_gpz=ZgVO7Y&gikymrlj6Z;tS4>lZN(O=kf zP)lWvfi~CbhBJrxwHZGi;a@(1$w(|Y=i{-}Yc{nVm0!M~Ly3DzjuDsY>P4TJJXfqS z;xahAhV9JC3u_pJvNRTKKG*rC=8T~^^WV>g9z3~U3|A=BI!SdbC~{)rlxgI+5)z(! z=4wFv+OOB57cVqrIwsM0KykT(f*DhWazVqb?9F?>F%}%x{mz&^qo{$?RU)ZLQ|-?G zTnEFpKe1;Xw1~&~a5gKitKjT0kMrV8Gl+9w?3o?NAUHkQZs)UE{&p^m^Cy4!DX?fV zgB9nB$$h_Gtq^Xgl62Y<$HAF8L5YQtMWo33-5%j4YenS`ayE?00!e>3jw-MJ^XW8y z!ygT0hxdONnic$c44Wqe{{4ES=fL0Z4~pdvd}W;Ce@23FLHxg8uhzf+_xr>dZ0q&_ro_$*)&KXHJ~>O-SIO9zX($3d>B4+=tp z9&GGMf_)VqnxzF2~2bDEV-EKv2bxY*UR~f7&IJR9RnsyOkok(ddc;Axj;dB?G%OglF%;MDPOi~D?QUFpVyv9A|h z-}B@si`tq7#+)DGNi!KXG;qF2@a}kXP|7V*RfR{=E8>Wf#<827wLZzxk8fxNZDLf5S`-*g1G-<_FP%4WjK(VO8e9VOK_kzu*20`H0i zw>bMIw5_uIp5=Zas$^%`)?kUpBJDfY-Q3o_ZSzvy++yzhJ8!O;Gs_&hbMvQ zpya!~X&!SPDP7<3OK2fW|KgaZUKcD+PhC_zpZV^KHUle0VT;nG$+E9cIWPsC>0@x( zAlp)Yd^eZyf%0RAKYZvbPkf-tUnv)NPh-JxnnTmUv3TtYXDw-J(~ak literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/bulb.gif b/pyste/doc/theme/bulb.gif new file mode 100644 index 0000000000000000000000000000000000000000..74f3baac42f12ac0551040a00f78cb573360a0ad GIT binary patch literal 944 zcmZ?wbhEHbU2JX=yWO8qb`WHgo38Gifu=q@6jF_W#UGhBGsb&&*6a zGjrydnP<+-{D0;Q!i=IrIPje+GvC495Q% z(*85d1W7Xd|8LCj-`M!SaoT_5nIN^s|No~k{7*CfpO*GNZ6-)-+W-GE8UD{S{y#JA z|IC>nlV<+^e}>`z8RP$8cYq8A8~y)3Nb~>yAnE^L0P?^n9t{C$ zm!W3VRz=HX0SO5P1_s5SEQ|~c3=BFT0%V2*i>ts1&-4h1-QU}6JQPJ#9Az#qP{_IL z>m8xcQ#>ccK&B>uvC-M7@I$1?63NFG4AfGLn>kr6)<~>7uC&oZb>E%VNA7Gc3=Gx) DZ>u*X literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/c++boost.gif b/pyste/doc/theme/c++boost.gif new file mode 100644 index 0000000000000000000000000000000000000000..58be431a3fafbd5adfa01e829ce8d80a03a83ed0 GIT binary patch literal 8819 zcmZ?wbhEHb6lDx!_|CwzZQFbH(K6ngWKJ ziiVnsJ(`+|nwo~1nogRU#hRMcnwmYDhC!N^Q#37?YC7%JbULQ#bxbqrnr8BQL(ODE zLoY+8V}?P;41=y2MlCgrx@H*l+%Wl?Ve)gs?C+L}mX?N=mWDx=hSipqQ!JgPSURn> z^vbpjT56g6-Lm?-WzTmf!(=DJY9}WzC#NVUr)(#u7ALR0PC?a9L35m__ikjmUwbv`^m{;;Wuj=n!J>P?zs)M|u zg1oYWg0h2x<^%<;4T`!KlsqLUd1+Ac+Mw)XLD|=WvhM|DKM$(D7F7K{sO5W5&-0+3 z??H3EM|mYj1#OK2ftIN3wNcggqSpRT4$4lBTAG}kot#{qoIE8tyC*q&PIC6vOS7xD zW>??KZaJ6Tb1u8*UUtv(>?z-~m%h(F_CNdD|Kj4M#l`1}=X@_-`@MMU_u_NktBZT8 zpp&i(JHp3~FQ z(gOl>dV03@^c?G%^1f%zxt=-KdX`@6S^B(Z>-#A^bEeFhGiA=&DRcHtS^9j+()UxA zexI`S-juEHr|f+`W$$+oI`@ChoO5&5URye6?b11Wmo7cFbm_UJOYbdRyLai)y3n&#m2hZSB_QYmePqd+hz%bMM!l`@Z(t_qETz@7;S2jPC8- z`+V=-_j~t#-+S!X-gEc%o_oLd-1lQ!uN`~-{oJ{G=gz%9ckkZ0d+*QP`+n{H_iNw3 z-@Etx-o5Y7LE!uQ=ilFd2LZ*OEQ|~c{~2^ZSpk$M7&!hh{O6SM*s$PWGl#HN%!v&P z54Q^_d(H9KxaerNgmKoL6B`#F?^kf{lJVTM)Z?TNcv$a| zX8p~bZNB0Df&T~6_*U;Ue!k)Nx4)0STN=lnoy=Uk&wHlPgHtoufA^>{GB7Z*NC+qx zEcpIN;8}uG+nxy)Z*Fk5pN`YwZ+XkW@@A=`LV`o-eI~Cj)^QWQ-oG;S@4wD($Mp*; zKP`6s{@DHBpR&r|f1Y;l*S~LH`{mvB-SzM1@2UKMVR3)O&lAhfXRyDTtWf>OAmU!t zs=u54Gam$&%`7~<@(I8EdENsIoYf18T1uH!F5G5d%-B3PRcBIqc*K(2v|IK+EpEtF zvVWTFZ*Tf*@q*YTCzbu}KZ#iM+uB`wtUT%073U>3|4u$WZ{ub)vwzYj1 zuldQd;&D~NvF-O#<}b8Nu8>@&;-9;Xcf)NZMy&}m=Ir4$;7VZmncQc2tKjgwqRO9N zm2B@69PXQ`ze#LXi_kW+n@esNs@-bIFpJ8}njN-lZftLp)yfr%lw4wFt350_kh}L| z+bR2hhkXvb%G=Y~cylrH%;s)`%pM)00cDHCB8v?zgnNb(GKA z#VV6;>)Rcd&s%@~^7x$1zd6b0?C$^iIo0ykHf4VM&pV8lZu@Lu*+1oT!|Hxpf4|Hp zebJ9zByaeB$KplThPhp@+}w7ws^{-I@aj&~7QP3E9IP%KooDK%P!P@VoTuVR?H94` zCpNd97&Ruj`*iYECj5P=JLyHiW`FayIluYr-`yzgH@o}hbD!0}CB}K?FTZT=v;Hk} z*Wb!~J9CMZ>G{tyuKeBLtiR*imCgMV{z~j#(jUWiC!yMCQ>B4B)0tJb8Fs#D?q0)q z;wM8smqL1^eT~I-iGN!rN2$G9{?}&G>8NXw2Oq>Pd~2*xz3{8?!x??n>n!ZQoBUAi z{}dgt z+CLsYPp}lKr}0kz_UE(Cjeixn=arWW*d5?Mdt3Fq+0Tf_yms$*G@rM4{ATfKgFi9M z=WOo^IQyI4zjJt>-Hn*T{Dw!KeD?kQBBq$%>gJQnz2<*jxcxrVXp+6cL-y^1<5sr{ z`AbdC%KXfjzPTZlzxu=CroTTf_WNC*B_^p;##iY5_F7rj>MP4aUq9$&K4+pK&+5n2 z!p(61TN3}pH%Ikq4zMyjF=2>~Y^(eCN8^~qA4a>{2K|ohC)^DWE#y!Acv#~4jpK$J zCh})UD9aza;A&SA$Xgq+QGWk|V|EKZcGtx$mio(a-29VaPu`tm*#{5Wr7lO9EI*qg z_x*>M{f@2fH8&SWOYE86f26Jd%#L$;3OOAdnOkRvpM0FMR_TBQhXG^Do5zBR5#60P z3bl5BIiCOT1+(6&VC{gj>ONC~C-i-q$Y1rM!|PW<>cn?T7pPrjwogoYI#uL~SMtlp zaz72i*j_HQ;AZM7j}w0CS{L;d zE~=m2vY1ct#=_Edu3C*3-sHVm(ags>fq^Mvq3pkZi{15F7V;NbD9dljIA%L#VNZ?D zV~H;}T(zDsa>iYGEV_O}yH-sgfBYUL`Bfc9ZFVf`kKOQS;hGc2^^z94q+D?pUozvk ze$T@GEC*-#>lH7}HwE_RS2WAr?r^g^wz03K#aZ@e#Bu9)AN$IGtZA#CBQ$x2iWPrW zvErQ=2W3_xMpmB(tM~#K=CTy=7u!5q-M^w)P^e6{jAyY(LaKY+pCi4wHck9VM_a$F zDG1wO*Cc%*fz5u(MV@MfWU1bpDlCu?UGugvkfU^sbp!| zxOzv6ZNkE?@*_%%jzze-_CD;3w@H>;ccIm0Rb%h%u*ci(Z)i6^RM?-tM_K;8hr-oY zM{UCTjyY-SaTHfRKD#%8%}iBUm67MdoiIZO^B$E338V5&x1MTFTCr?uCx;-H%JxUH z3mjamCnU1wGCUG~f1;7^%*8gxEy=u@8SPS66#7l>D9UDfd=vCC=+|Ac@aFuEb=*%9 zIm~Y?=AByMuBv0$e{@&!#tE^l)*+1Cl?xt;e2G};70%6*af4afD0ja3m4*E!OAgE5 z`6Sq&=A-;I!Rqqtvb-Xeq}~{nV=n90@#$AEa?1%Um{(T8W~i0E{Pl+oR>vIJbpBNZ zobUd>K=y9M@!IZReAOL?r4CPUtG%1ZS6F#C^R0us^@4AG_H!T0-2HKF{+)|`WhL+Z zZ%%Trf8MB5S+Ke~X!Z1l=NA(c6<5oCom90~-l(`LsX7Qzz_eLm>(Kl$h$PuI8) zit=SQw(GCB@s7b^!d0#lnQ}Hi99fkXH0=A|X!GNRb(OV21Bb-|^Vbg=IkYA;?BjP} zOgpjFT3{ka%=JgI3K@>(c7c@^42{wjKbowTENIR1P~?44&?L2MBdhL`IPPZ}i~{!< z+Y{eA@V@tOmcDn9&1OfkKwE~pVpBh-&z5GP=@$RAE(CH0Z%GyrOl{S9c91Li%wtKN zSU2-m{{$~rH$0ZEebc_^SL1`3yjeZ_&hE!-krc^Hi0k5 zfc3=%wjhC)tOtBYukkfIaO_D>ou0ti^_A=G0rm+O_>O;wIPih%^Ma^P2|W7^_&-kO zzgl4DdNuUz0vm=;hF=5Z3^ytHx0Z2z=dYb$c3#lD=7MLLBbWLX2BrgiiA?hPpEP5G zEWbE#EiaH(GGO=@e68ro2V>W*eBBi$uP*R1ec`>ff%j-a@Vy4UD+_pUU*LT(f$#PK zW2Ps(4=(V|?cn?WneTQ(*8>5*mkWwtEr?#9!S{;6l#j*KD!hcFh3`cI--#r?oipP9 zhVuVNjTaR0`N1T1ghgyVla}7KHUVM7tmpiy3DVmO)rBwk{X0;0c(ETx0&kfj-%108 z9|!oEGWvc981y%A2^ceQ8}KhI$nJ1xENb9*#K6Aj0-yH<#`_BFodfceGS$i1-_sS zY^S!dEi>nMaDc73fv>{hALm;Kj)@6;(>8FtNZ^{Tz`yzf=Vyn=Pan8DTX|Pc<$ZX- z?e+%#^)L7zFz9_vM z_8@^H^8w!!2aY!h98(MUY7$asU*%iL(7N#e_m>CUt3~-P9B@k#;VW!$xR<2=jmhS@ z#s|b=djli_8U=dYS(jurVFrU0lR}V3OrC3*TQKIBGXIrx@5i+%WI^ zVz&nkyr&I;v z0VMg!CJfaUiT*z>p;lt1tWCotY|UaQU=v)x6(_*H-(kYT3#=KhSe-8LIu$TqH(*gaz{@&;fvuD2 zUIU|s0AHa2>IQysDU<(UdLJJt z2^quLYa;mVo<-}ljZjS%Wn$&^PTy>D40HT z;OY6mp0=Qj=b`T230#{#aNiK%`x?n}@dMA52HyJ&yeAoWuNf@3Zm|7+K-V<~o{I&% z_Y&scci_G9p_Yw{PvVz-lefvPQu`MZ_~v);y;@KlaG9^3Nn_8B1+z3dUoh}LGf4b= zK{fG{Wg3&%xfea@&8wLe8FLiXc@)&c0=VuZ^s*GN**VDW6p(A(()&iRVTbh^rW0jP z7&u-8#JpX=Y}vrq&cJoSfLq`rU)}@O6Aer68?ZPF@H#(WJ*L1C_pgALCxGFC1Czl7 z9Tp>J@FH2$OfJ&0kVI$Zj~v;J(l!^Q`3hK^x}|>^FtIo=1_|wA?%17iQtqjN!s#eJ!6{sy8<;~s z@GTVJ+F!sU_>s@4fF~=8=T#JsvjF=A1s10U-dG04{R|9jA9&jv*lsH@NEECvy}%oI zfa!VxvmXO5ivjcX35=2#cqJDw-8En~e88*C!KZX`X7mSMKLytJ46KkTg##f!d za)BWsTq*JA1Kz0tk$W$2|6^LoPB zpPmbjZs5K0V8NXa_In+8I1CrO{E%SE%~R!v|B0im`Z;U?7n0ZldCCnqCqL!>b%A^T2L8k?_E#S8 zKWtE$V6D2aI8*17$dYV<35|TJA5QKP%;t1pteL=fw_%H}$eQOndS#cgzkR?laRJ|s zKYUgREUzYTF8si^R)Kwg!yfjD{3j>auqU!zSiRo)1J`A3mrq~$*sC}uF)#>z;FfY= zy;8s&yMa6K!>tny@!uTypCxbwC2&bCU^*YbEZM;KX*c&Ob%*|IdUrN(MK9o5tIivB zfMs`sz{&-zo&VNyi9h8&e}MmL0PnlW*QTiHrB%rD2`Sz&l=w4&yZ!;w-o!O(MJ(P2 zcq&VX9M=R3;3EB@aT!N zy>(#8{J^(Jpz-q$?zIg9KN>g!8U&tNv1~SAneXtJO_5(FflESxWxoLH>staF6S(vR zn0E; zD8*U$_%5SYW?)|9z`E&!z`7U>Ej)&uzoT&Iue(4Eo<2{8*p&xln+EBa!p*E-sEjR`CXb%?}vs9C+CS zc>)uTuzlq7ci>=8Thc|+W?27z4P~9K@Lgrrf@$`#({Ab*|TdmIDGqG%b)WqLkGG!L|KXgd_70Q?5T2mRp`*D&_ z@>F)I_nJZone%$HqnzpI921zQ4MoZiUy&->kjyTeJV| zTl#tWxx4!-KmWUW`n$jU{`$WX)^m1*d`dYr$u(`s^!H0nPWcyB_3s;xZv39q4|2Yn zzIRTtt=oO3^XI2^qAMyp9ril6WW+9t+TWzR^|)5woQR@53of5f?mJer=v>B$EM~_)l-R_Xznf6oeWzx^n@>{b0t*hncskrx`GWF}r z-|h43q`F^hI>qN=;Mgm6>y1iBT+#~J;wKZA&pmWUafK}B!N}*b1@|J^Wr_|7w$I7; zt5KV}^pB%DuT#X%Ba`w>PAPHs?y^)Ccrj^$o3iz~1@6k_#~hL;IaoQWPL1ul_;lu{ zd7ka_DnIRXpI@`?j=RHgxvCG2C$tuwa9};IQtaBT_j&o;vmXvFpI;sK>Wy4gRpURF zx#205?9-~>M5gqWe4B7|UVww7`icU(lge|7P6fL1pPHrU!e_baA_te>nnx^adgfI& zUFu2t;4IO%i<70n&+o%San%|HedVl@6;AWKk6m2OZ}%zks6=&KWt)7)yp89V<}rA? z%N=?ZsAE_s(VU@Vc)DeqPn#Fh=Y?f+?)J=|a&OOb@#2z)uJgQ7UK-5HJ1}Fhbfk-? zx?toX&Zoi!4wf!~hH8aNHihh&z`V2P-HKLA*LkNoW{EF3<-zG*_Tq$mkkrS6?RfzK zi)L)yccO7#VcNpweXhSMTc_5Ay!<*n>&MRZ^Y{M=JbvTQzmx0^+S0ev7cnkAYI%$4 ze2dSLdDs87w98g~n;5K6|7u~oT;-0<%jK%?RsMQewIFZ#Y*o`q%h}b9U#az)Y&)jG ztzi=NtjBP{?j)(+u9hXCeLp;pPS0B4c=VC){E6*ywHj~S?z!k$Bxs?Ch6K*n1cHi^2SLRI6N2b>kxI`y4o zyWWeBTnRUvB^xKXGX6{a$XVU+PByH-#VCbwxAUDuqyj`)%|(fb0UMCF;o@~$nC z4L=wc)LnVw{jOQbFz{ZQmDd6De@%cIkli+R?R2an})b{&`e_^_|6 zhFMB#rMr~Q3PC3yrgDjov)uMA?DEKHkxt&w<`xpzn_8nRxoQT>CkrF~*d>SZBo?w+ z?Fu|n7o#a3d8^%N%MyVOlO^eaEKg@}Jz1*eH&N-wg5$s5IPz3^D9ii_U^98v*mIiE zS@N5rtL=>~-sj(J>X{(W>-OiLW1oQ0av7aH9ty{%P0aRKC}b|x_}PG|CH_kT`~C+9 z`P3Ra+|!P*r!d@h-KEH8@F7`r^#gVlzk~em{xU@zUEyzjL2zHmpT(*FBce7jg=|i1 zy)<9)rq6VbLlX>2k11Z*;AXc+u&=u2vBZlV$DWog>^5mSEI0S{QvHvD{jvX;L=WY- z8ya6SNr^fl^rS)7*hR}Pf=5l+@4;2$GmgFaC5t5%XS5_532_J?xS(`-gOmKW<6X6H z4vGEKXfu4~%v&GQyzf&m*TY){h!&qb{Ur2BZ`q(`y zc8OfiiEy_r#m+-sDN^@09JiIawAI;rj^bTUw$0(J=ZP#j)z%h&day9b}a6P@?2ydR3y8pG#d0= zVV2oj5oW$XaLI&*$KpLZIqan_HqDN0>Ak7Tro88b+P@1|ZWs$6JnnKue8F*D?b=?) zjkV6wYB_JG7cyo|*fwR4d8Nn_h0HhaqUXA~?P_}5C;rksq}8)0KIJ=4+JzJPlRk34 z=(@}kzV1Xqec{?8jE8tS4{{_P&J@fPVV2ELnm^%d%*=L=C0k;IUJJfF$p7xk_T8;{ z?5&qR^3`zcxcKNoj_s?1x25)r%G59Av2~epH+{kOBh%FiAJtt<>Yj1<)UmG3mJF|W z3j&;#e=T4$eDYCEM!4@p^|7z+Wnn4pZ=B`+bDIhs zp272;Y;n*2 z{OG1NpCiA|f7$s7FOOf4TN2r3^2Gk%3L)nw3;WMV&Aw5swezu!b8fc&Qdu$ATh=W1 z9_H;>oVn&L+Z^jlyk{;Yue#b^`%opU3=oSpr%~ki<~5JQji;JxWPB4V%(6NB`TUQo#$ArQu`m89 zeQ3(G-2NtTYo5ba*_RpZDk}wZJ=J^VoEE!V?K14W$R8;8a>8Ggw;PMvJUr#@EjVVf zB9ZR_^M2{uAKa|JCcG~{Wm5rX_ww!QeeOTV*8nj z4)0si4<@UW7_dKEJVDoK50hrcAq^$2os;Tr?Rc+z@O$F?qcc0Sx_ah2>Rxu*Io-p) zEuv>r&_T99&dIwc{lCDIa9F6NfT!+&%9=$2-<^5Z-fDko&GE;oLv@Ra&c`)+H`o99 zwWdt_pq5I$u!s(4S++p>L(}xgX;slO(kd%dpQv=KP-;D)!WihRZ0-D~NT9xB{@Dz3 zlb3edTMm~OJKH@LGJJA)O}3$!%7hiW4?C(HaWXmL>~h2<Q%P5$_{Me6AeveR9N4XL17rgEatT*T~8M literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/l_arr.gif b/pyste/doc/theme/l_arr.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b3cb1cbf07e316c3655ac84c9ba72dfbe2e25a1 GIT binary patch literal 147 zcmZ?wbhEHb6k!l%*vtR|#>U2JX=yWO&OCGG%>OfK|If_)4`TfP|DWOif8+oE)BgXT z`Tzf!|NsBLfB#U2JX=yW!8D`F$dFITS|7mIeXEOYsnf8C?%>QSM|DT!p z|3Ab3|7ZS#f#OdVMg|6c1|5)2kQodtE+0;Mrh1v)&NktlbFtDU2JX=yWO&OCGG%>OfK|If_)4`TfP|DWOif8+oE)BgXT z`Tzf!|NsBLfB#&gTE>G6muSe93zS-%fd>Q)&EUbx3v}%NcRk;>%V0Cvz}Z0{|MoM2G+Y literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/r_arr_disabled.gif b/pyste/doc/theme/r_arr_disabled.gif new file mode 100644 index 0000000000000000000000000000000000000000..2100f78bf35a47e09ca76ffcc6e80c5a2b11e359 GIT binary patch literal 91 zcmZ?wbhEHb6k!l%n8?5|bLPzZ_wWDz|DS<@LGdRGs|W)VgAM}&0|Q8&fk|gd|H|rv v@(cz3o}Svfg4O3$GVLgQHgm3)*?|1&XIvGFi0itr1TiFoeP&p1C|3gi5M|Dy8)Dq>pVFYM_y+=U^T ztHBMdj+tQ(A2Y*wF$RYE_4D-@jtU##9xMTW{_9v6c`wK7#J8B1sE6<80-`n z7><}aTg5mA1O&%;g!uX}*f26MGK54pa~u~3ne&L_xcGT71qKC%`|QWX?G$<#K=P%G z=f$!jvLf=G=8H2hFfyd8*(n?_D`m7(uv4gs=yaGbo~{-bxlJUa@tDY4zP5<9g6(Q8 z@$VV#2iy??g*_vVXy`OS*Qv? literal 0 HcmV?d00001 diff --git a/pyste/doc/theme/style.css b/pyste/doc/theme/style.css new file mode 100644 index 00000000..53a6205e --- /dev/null +++ b/pyste/doc/theme/style.css @@ -0,0 +1,170 @@ +body +{ + background-image: url(bkd.gif); + background-color: #FFFFFF; + margin: 1em 2em 1em 2em; +} + +h1 { font-family: Verdana, Arial, Helvetica, sans-serif; font-weight: bold; text-align: left; } +h2 { font: 140% sans-serif; font-weight: bold; text-align: left; } +h3 { font: 120% sans-serif; font-weight: bold; text-align: left; } +h4 { font: bold 100% sans-serif; font-weight: bold; text-align: left; } +h5 { font: italic 100% sans-serif; font-weight: bold; text-align: left; } +h6 { font: small-caps 100% sans-serif; font-weight: bold; text-align: left; } + +pre +{ + border-top: gray 1pt solid; + border-right: gray 1pt solid; + border-left: gray 1pt solid; + border-bottom: gray 1pt solid; + + padding-top: 2pt; + padding-right: 2pt; + padding-left: 2pt; + padding-bottom: 2pt; + + display: block; + font-family: "courier new", courier, mono; + background-color: #eeeeee; font-size: small +} + +code +{ + font-family: "Courier New", Courier, mono; + font-size: small +} + +tt +{ + display: inline; + font-family: "Courier New", Courier, mono; + color: #000099; + font-size: small +} + +p +{ + text-align: justify; + font-family: Georgia, "Times New Roman", Times, serif +} + +ul +{ + list-style-image: url(bullet.gif); + font-family: Georgia, "Times New Roman", Times, serif +} + +ol +{ + font-family: Georgia, "Times New Roman", Times, serif +} + +a +{ + font-weight: bold; + color: #003366; + text-decoration: none; +} + +a:hover { color: #8080FF; } + +.literal { color: #666666; font-style: italic} +.keyword { color: #000099} +.identifier {} +.comment { font-style: italic; color: #990000} +.special { color: #800040} +.preprocessor { color: #FF0000} +.string { font-style: italic; color: #666666} +.copyright { color: #666666; font-size: small} +.white_bkd { background-color: #FFFFFF} +.dk_grey_bkd { background-color: #999999} +.quotes { color: #666666; font-style: italic; font-weight: bold} + +.note_box +{ + display: block; + + border-top: gray 1pt solid; + border-right: gray 1pt solid; + border-left: gray 1pt solid; + border-bottom: gray 1pt solid; + + padding-right: 12pt; + padding-left: 12pt; + padding-bottom: 12pt; + padding-top: 12pt; + + font-family: Arial, Helvetica, sans-serif; + background-color: #E2E9EF; + font-size: small; text-align: justify +} + +.table_title +{ + background-color: #648CCA; + + font-family: Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; + font-weight: bold +; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px +} + +.table_cells +{ + background-color: #E2E9EF; + + font-family: Geneva, Arial, Helvetica, san-serif; + font-size: small +; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px +} + +.toc +{ + DISPLAY: block; + background-color: #E2E9EF + font-family: Arial, Helvetica, sans-serif; + + border-top: gray 1pt solid; + border-left: gray 1pt solid; + border-bottom: gray 1pt solid; + border-right: gray 1pt solid; + + padding-top: 24pt; + padding-right: 24pt; + padding-left: 24pt; + padding-bottom: 24pt; +} + +.toc_title +{ + background-color: #648CCA; + padding-top: 4px; + padding-right: 4px; + padding-bottom: 4px; + padding-left: 4px; + font-family: Geneva, Arial, Helvetica, san-serif; + color: #FFFFFF; + font-weight: bold +} + +.toc_cells +{ + background-color: #E2E9EF; + padding-top: 4px; + padding-right: 4px; + padding-bottom: 4px; + padding-left: 4px; + font-family: Geneva, Arial, Helvetica, san-serif; + font-size: small +} + +div.logo +{ + float: right; +} + +.toc_cells_L0 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L1 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 44px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L2 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 88px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L3 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 122px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L4 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 166px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } diff --git a/pyste/doc/theme/u_arr.gif b/pyste/doc/theme/u_arr.gif new file mode 100644 index 0000000000000000000000000000000000000000..ada3d6e043d2e4314a20d6783f2800f2f21d89c9 GIT binary patch literal 170 zcmZ?wbhEHb6k!l%*vtR|#>U2JX=yWO&OCGG%>OfK|If_)4`TfP|DWOif8+oE)BgXT z`Tzf!|NsBLfB#pCE@bTiY5O-A{7vV42g%c7%l^E8u0x + + +Wrappers + + + + + + + + + + +
+ + Wrappers +
+
+ + + + + + +
+

+Suppose you have this function:

+
+    std::vector<std::string> names();
+
+

+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:

+
+    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<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")
+
+ + + + + + +
+
+
+ + diff --git a/pyste/index.html b/pyste/index.html new file mode 100644 index 00000000..ff153b50 --- /dev/null +++ b/pyste/index.html @@ -0,0 +1,71 @@ + + + +Pyste Documentation + + + + + + + + + +
+ + Pyste Documentation +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table of contents
+ Introduction +
+ Running Pyste +
+ The Interface Files +
+ Renaming and Excluding +
+ Policies +
+ Templates +
+ Wrappers +
+ Exporting All Declarations from a Header +
+
+
+ + 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)) From 415991f6fc8cc2129a9ae08f94cc0c8605847912 Mon Sep 17 00:00:00 2001 From: Bruno da Silva de Oliveira Date: Tue, 11 Mar 2003 03:34:28 +0000 Subject: [PATCH 15/18] - added a link to the Pyste documentation [SVN r17806] --- doc/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/index.html b/doc/index.html index 599a8ca6..6e585a44 100644 --- a/doc/index.html +++ b/doc/index.html @@ -84,6 +84,8 @@
Support Resources
Frequently Asked Questions (FAQs)
+ +
Pyste (Boost.Python code generator)
News/Change Log
From ca9920874fcbd4c2c2b64b86c27e49bb33b6df55 Mon Sep 17 00:00:00 2001 From: Bruno da Silva de Oliveira Date: Wed, 12 Mar 2003 01:32:00 +0000 Subject: [PATCH 16/18] - fixed default arguments in virtual methods [SVN r17823] --- pyste/src/ClassExporter.py | 372 +++++++++++++++++++--------------- pyste/src/FunctionExporter.py | 4 + 2 files changed, 209 insertions(+), 167 deletions(-) diff --git a/pyste/src/ClassExporter.py b/pyste/src/ClassExporter.py index d72ff9db..64bf3834 100644 --- a/pyste/src/ClassExporter.py +++ b/pyste/src/ClassExporter.py @@ -34,12 +34,9 @@ class ClassExporter(Exporter): # 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 = {} + self.wrapper_generator = None # a list of code units, generated by nested declarations self.nested_codeunits = [] @@ -83,13 +80,12 @@ class ClassExporter(Exporter): def Export(self, codeunit, exported_names): - self.GetMethods() self.ExportBasics() self.ExportBases(exported_names) self.ExportConstructors() self.ExportVariables() self.ExportMethods() - self.GenerateVirtualWrapper() + self.ExportVirtualMethods() self.ExportOperators() self.ExportNestedClasses(exported_names) self.ExportNestedEnums() @@ -151,7 +147,7 @@ class ClassExporter(Exporter): def Add(self, section, item): 'Add the item into the corresponding section' - self.sections[section].append(item.strip()) + self.sections[section].append(item) def ExportBasics(self): @@ -203,8 +199,14 @@ class ClassExporter(Exporter): # 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 constructor with less parameters to the constructor section + smaller = None + for cons in constructors: + if smaller is None or len(cons.parameters) < len(smaller.parameters): + smaller = cons + assert smaller is not None + self.Add('constructor', init_code(smaller)) + constructors.remove(smaller) # write the rest to the inside section, using def() for cons in constructors: code = '.def(%s)' % init_code(cons) @@ -234,18 +236,6 @@ class ClassExporter(Exporter): 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): @@ -265,25 +255,22 @@ class ClassExporter(Exporter): def ExportMethods(self): - 'Export all the methods of the class' + 'Export all the non-virtual methods of this class' def OverloadName(m): - 'Returns the name of the overloads struct for the given method' - + '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: + if not m.virtual: 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) + 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): @@ -292,9 +279,6 @@ class ClassExporter(Exporter): 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: @@ -302,19 +286,19 @@ class ClassExporter(Exporter): else: return method.PointerDeclaration() - - for method in self.methods: + def IsExportable(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) and not m.virtual + + methods = [x for x in self.public_members if IsExportable(x)] + + for method in 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) @@ -328,15 +312,9 @@ class ClassExporter(Exporter): # 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 + overload = ', %s%s()' % (namespaces.pyste, overload_name) + + # build the .def string to export the method pointer = Pointer(method) code = '.def("%s", %s' % (name, pointer) code += policy @@ -353,38 +331,21 @@ class ClassExporter(Exporter): 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: + def ExportVirtualMethods(self): + # check if this class has any virtual methods + has_virtual_methods = False + for member in self.class_.members: + if type(member) == Method and member.virtual: + has_virtual_methods = True 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) + if has_virtual_methods: + generator = _VirtualWrapperGenerator(self.class_, self.info) + self.Add('template', generator.FullName()) + for definition in generator.GenerateDefinitions(): + self.Add('inside', definition) + self.Add('declaration', generator.GenerateVirtualWrapper(self.INDENT)) + # operators natively supported by boost BOOST_SUPPORTED_OPERATORS = '+ - * / % ^ & ! ~ | < > == != <= >= << >> && || += -='\ @@ -585,104 +546,181 @@ def _ID(name): # 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] +def _ParamsInfo(m, count=None): + if count is None: + count = len(m.parameters) + param_names = ['p%i' % i for i in range(count)] + param_types = [x.FullName() for x in m.parameters[:count]] 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 + '()') + #for i, p in enumerate(m.parameters[:count]): + # 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' +class _VirtualWrapperGenerator(object): + 'Generates code to export the virtual methods of the given class' - def __init__(self, class_, method, rename): - self.method = method - if rename is None: - rename = method.name - self.rename = rename + def __init__(self, class_, info): self.class_ = class_ + self.info = info + self.wrapper_name = _ID(class_.FullName()) + '_Wrapper' - 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 + def DefaultImplementationNames(self, method): + '''Returns a list of default implementations for this method, one for each + number of default arguments. Always returns at least one name, and return from + the one with most arguments to the one with the least. + ''' + base_name = 'default_' + method.name + minArgs = method.minArgs + maxArgs = method.maxArgs + if minArgs == maxArgs: + return [base_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() - signature = '%s (%s%s::*)(%s)' % (result, ns, wrapper_name, params) - return '(%s)%s' % (signature, fullname) + return [base_name + ('_%i' % i) for i in range(minArgs, maxArgs+1)] + + + def Declaration(self, method, indent): + '''Returns a string with the declarations of the virtual wrapper and + its default implementations. This string must be put inside the Wrapper + body. + ''' + pyste = namespaces.pyste + python = namespaces.python + rename = self.info[method.name].rename or method.name + result = method.result.FullName() + return_str = 'return ' + if result == 'void': + return_str = '' + params, param_names, param_types = _ParamsInfo(method) + constantness = '' + if method.const: + constantness = ' const' + + # call_method callback + decl = indent + '%s %s(%s)%s {\n' % (result, method.name, params, constantness) + param_names_str = ', '.join(param_names) + if param_names_str: + param_names_str = ', ' + param_names_str + decl += indent*2 + '%s%scall_method<%s>(self, "%s"%s);\n' %\ + (return_str, python, result, rename, param_names_str) + decl += indent + '}\n' + + # default implementations (with overloading) + if not method.abstract: + minArgs = method.minArgs + maxArgs = method.maxArgs + impl_names = self.DefaultImplementationNames(method) + for impl_name, argNum in zip(impl_names, range(minArgs, maxArgs+1)): + params, param_names, param_types = _ParamsInfo(method, argNum) + decl += '\n' + decl += indent + '%s %s(%s)%s {\n' % (result, impl_name, params, constantness) + decl += indent*2 + '%s%s::%s(%s);\n' % \ + (return_str, self.class_.FullName(), method.name, ', '.join(param_names)) + decl += indent + '}\n' + return decl - 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 + def MethodDefinition(self, method): + '''Returns a list of lines, which should be put inside the class_ + statement to export this method.''' + # dont define abstract methods + if method.abstract: + return [] + pyste = namespaces.pyste + rename = self.info[method.name].rename or method.name + default_names = self.DefaultImplementationNames(method) 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) + wrapper_name = pyste + self.wrapper_name + result = method.result.FullName() + is_method_unique = self.class_.IsUnique(method.name) + constantness = '' + if method.const: + constantness = ' const' + + # create a list of default-impl pointers + minArgs = method.minArgs + maxArgs = method.maxArgs + if is_method_unique: + default_pointers = ['&%s::%s' % (wrapper_name, x) for x in default_names] 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) + default_pointers = [] + for impl_name, argNum in zip(default_names, range(minArgs, maxArgs+1)): + param_list = [x.FullName() for x in method.parameters[:argNum]] + params = ', '.join(param_list) + signature = '%s (%s::*)(%s)%s' % (result, wrapper_name, params, constantness) + default_pointer = '(%s)%s::%s' % (signature, wrapper_name, impl_name) + default_pointers.append(default_pointer) + + # get the pointer of the method + if is_method_unique: + pointer = '&' + method.FullName() + else: + pointer = method.PointerDeclaration() + # generate the defs + definitions = [] + # basic def + definitions.append('.def("%s", %s, %s)' % (rename, pointer, default_pointers[-1])) + for default_pointer in default_pointers[:-1]: + definitions.append('.def("%s", %s)' % (rename, default_pointer)) + return definitions - 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 - - + def FullName(self): + return namespaces.pyste + self.wrapper_name + + + def VirtualMethods(self): + return [m for m in self.class_.members if type(m) == Method and m.virtual] + + + def Constructors(self): + return [m for m in self.class_.members if isinstance(m, Constructor)] + + + def GenerateDefinitions(self): + defs = [] + for method in self.VirtualMethods(): + if not self.info[method.name].exclude: + defs.extend(self.MethodDefinition(method)) + return defs + + + def GenerateVirtualWrapper(self, indent): + 'Return the wrapper for this class' + + # generate the class code + class_name = self.class_.FullName() + code = 'struct %s: %s\n' % (self.wrapper_name, class_name) + code += '{\n' + # generate constructors (with the overloads for each one) + for cons in self.Constructors(): + minArgs = cons.minArgs + maxArgs = cons.maxArgs + # from the min number of arguments to the max number, generate + # all version of the given constructor + cons_code = '' + for argNum in range(minArgs, maxArgs+1): + params, param_names, param_types = _ParamsInfo(cons, argNum) + if params: + params = ', ' + params + cons_code += indent + '%s(PyObject* self_%s):\n' % \ + (self.wrapper_name, params) + cons_code += indent*2 + '%s(%s), self(self_) {}\n\n' % \ + (class_name, ', '.join(param_names)) + code += cons_code + # generate the body + body = [] + for method in self.VirtualMethods(): + if not self.info[method.name].exclude: + body.append(self.Declaration(method, indent)) + body = '\n'.join(body) + code += body + '\n' + # add the self member + code += indent + 'PyObject* self;\n' + code += '};\n' + return code diff --git a/pyste/src/FunctionExporter.py b/pyste/src/FunctionExporter.py index 2638a776..60735ca0 100644 --- a/pyste/src/FunctionExporter.py +++ b/pyste/src/FunctionExporter.py @@ -25,7 +25,11 @@ class FunctionExporter(Exporter): def CheckPolicy(self, func): 'Warns the user if this function needs a policy' + def IsString(type): + return type.const and type.name == 'char' and isinstance(type, PointerType) needs_policy = isinstance(func.result, (ReferenceType, PointerType)) + if IsString(func.result): + needs_policy = False if needs_policy and self.info.policy is None: print '---> Error: Function "%s" needs a policy.' % func.FullName() print From bc4feb42b580bbe27222284b1d49494f3253e41d Mon Sep 17 00:00:00 2001 From: Bruno da Silva de Oliveira Date: Wed, 12 Mar 2003 01:32:48 +0000 Subject: [PATCH 17/18] - fixed "deepcopy" of infos bug [SVN r17824] --- pyste/src/infos.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyste/src/infos.py b/pyste/src/infos.py index 3d23537e..ce8334c5 100644 --- a/pyste/src/infos.py +++ b/pyste/src/infos.py @@ -12,18 +12,20 @@ from exporterutils import FunctionWrapper #============================================================================== # DeclarationInfo #============================================================================== -class DeclarationInfo(object): +class DeclarationInfo: def __init__(self, otherInfo=None): self.__infos = {} self.__attributes = {} if otherInfo is not None: - self.__infos = otherInfo.__infos.copy() - self.__attributes = otherInfo.__attributes.copy() + self.__infos = copy.deepcopy(otherInfo.__infos) + self.__attributes = copy.deepcopy(otherInfo.__attributes) def __getitem__(self, name): 'Used to access sub-infos' + if name.startswith('__'): + raise AttributeError default = DeclarationInfo() default._Attribute('name', name) return self.__infos.setdefault(name, default) @@ -61,8 +63,8 @@ class FunctionInfo(DeclarationInfo): #============================================================================== class ClassInfo(DeclarationInfo): - def __init__(self, name, include, tail=None, otherOption=None): - DeclarationInfo.__init__(self, otherOption) + def __init__(self, name, include, tail=None, otherInfo=None): + DeclarationInfo.__init__(self, otherInfo) self._Attribute('name', name) self._Attribute('include', include) # create a ClassExporter From 7d5c453f5986d609b9d10d1f939e567543264ff0 Mon Sep 17 00:00:00 2001 From: Bruno da Silva de Oliveira Date: Wed, 12 Mar 2003 01:39:28 +0000 Subject: [PATCH 18/18] no message [SVN r17825] --- pyste/README | 5 +-- pyste/doc/pyste.txt | 4 +-- pyste/doc/running_pyste.html | 4 +-- pyste/example/README | 5 +++ pyste/example/basic.h | 21 +++++++++++++ pyste/example/basic.pyste | 2 ++ pyste/example/enums.h | 18 +++++++++++ pyste/example/enums.pyste | 8 +++++ pyste/example/header_test.h | 23 ++++++++++++++ pyste/example/header_test.pyste | 1 + pyste/example/nested.h | 21 +++++++++++++ pyste/example/nested.pyste | 1 + pyste/example/operator.h | 47 ++++++++++++++++++++++++++++ pyste/example/operator.pyste | 12 +++++++ pyste/example/templates.h | 8 +++++ pyste/example/templates.pyste | 8 +++++ pyste/example/wrappertest.h | 35 +++++++++++++++++++++ pyste/example/wrappertest.pyste | 15 +++++++++ pyste/example/wrappertest_wrappers.h | 26 +++++++++++++++ 19 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 pyste/example/README create mode 100644 pyste/example/basic.h create mode 100644 pyste/example/basic.pyste create mode 100644 pyste/example/enums.h create mode 100644 pyste/example/enums.pyste create mode 100644 pyste/example/header_test.h create mode 100644 pyste/example/header_test.pyste create mode 100644 pyste/example/nested.h create mode 100644 pyste/example/nested.pyste create mode 100644 pyste/example/operator.h create mode 100644 pyste/example/operator.pyste create mode 100644 pyste/example/templates.h create mode 100644 pyste/example/templates.pyste create mode 100644 pyste/example/wrappertest.h create mode 100644 pyste/example/wrappertest.pyste create mode 100644 pyste/example/wrappertest_wrappers.h diff --git a/pyste/README b/pyste/README index c3535a2b..b0cc7d36 100644 --- a/pyste/README +++ b/pyste/README @@ -16,8 +16,9 @@ Thanks ====== - David Abrahams, creator of Boost.Python, for tips on the syntax of the interface -file and support. -- Marcelo Camelo, for design tips and support. + file and support. +- Marcelo Camelo, for design tips, support and inspiration for this project. + Also, the name was his idea. 8) - Brad King, creator of the excellent GCCXML (http://www.gccxml.org) - Fredrik Lundh, creator of the elementtree library (http://effbot.org) diff --git a/pyste/doc/pyste.txt b/pyste/doc/pyste.txt index 65c2cec9..b6fbb281 100644 --- a/pyste/doc/pyste.txt +++ b/pyste/doc/pyste.txt @@ -113,8 +113,8 @@ 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: +[^INCLUDE] if it exists. Visual C++ users should run the [^Vcvars32.bat] file, +which for Visual C++ 6 is normally located at: C:\Program Files\Microsoft Visual Studio\VC98\bin\Vcvars32.bat diff --git a/pyste/doc/running_pyste.html b/pyste/doc/running_pyste.html index a67d5812..42834000 100644 --- a/pyste/doc/running_pyste.html +++ b/pyste/doc/running_pyste.html @@ -86,8 +86,8 @@ 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:

+INCLUDE if it exists. Visual C++ users should run the Vcvars32.bat file, +which for Visual C++ 6 is normally located at:

     C:\Program Files\Microsoft Visual Studio\VC98\bin\Vcvars32.bat
 
diff --git a/pyste/example/README b/pyste/example/README new file mode 100644 index 00000000..2f917a4e --- /dev/null +++ b/pyste/example/README @@ -0,0 +1,5 @@ +To use this examples, just execute in the command-line: + +pyste --module= .pyste + +For more information, please refer to the documentation. diff --git a/pyste/example/basic.h b/pyste/example/basic.h new file mode 100644 index 00000000..5a619e72 --- /dev/null +++ b/pyste/example/basic.h @@ -0,0 +1,21 @@ +struct C +{ + virtual int f(int x = 10) + { + return x*2; + } + + int foo(int x=1){ + return x+1; + } +}; + +int call_f(C& c) +{ + return c.f(); +} + +int call_f(C& c, int x) +{ + return c.f(x); +} diff --git a/pyste/example/basic.pyste b/pyste/example/basic.pyste new file mode 100644 index 00000000..a6b4e17b --- /dev/null +++ b/pyste/example/basic.pyste @@ -0,0 +1,2 @@ +Class('C', 'basic.h') +Function('call_f', 'basic.h') diff --git a/pyste/example/enums.h b/pyste/example/enums.h new file mode 100644 index 00000000..440cefd2 --- /dev/null +++ b/pyste/example/enums.h @@ -0,0 +1,18 @@ +namespace test { +enum color { red, blue }; + +struct X +{ + enum choices + { + good = 1, + bad = 2 + }; + + int set(choices c) + { + return (int)c; + } +}; + +} diff --git a/pyste/example/enums.pyste b/pyste/example/enums.pyste new file mode 100644 index 00000000..dd9d7fbc --- /dev/null +++ b/pyste/example/enums.pyste @@ -0,0 +1,8 @@ +color = Enum('test::color', 'enums.h') +rename(color.red, 'Red') +rename(color.blue, 'Blue') +X = Class('test::X', 'enums.h') +rename(X.choices.bad, 'Bad') +rename(X.choices.good, 'Good') +rename(X.choices, 'Choices') + diff --git a/pyste/example/header_test.h b/pyste/example/header_test.h new file mode 100644 index 00000000..d3d60fcc --- /dev/null +++ b/pyste/example/header_test.h @@ -0,0 +1,23 @@ +#include +#include +#include + +enum choice { red, blue }; + +void print_choice(choice c) +{ + std::map choice_map; + choice_map[red] = "red"; + choice_map[blue] = "blue"; + std::cout << "You chose: " << choice_map[c] << std::endl; +} + +struct C +{ + choice c; + + void print_() + { + print_choice(c); + } +}; diff --git a/pyste/example/header_test.pyste b/pyste/example/header_test.pyste new file mode 100644 index 00000000..b0e752ff --- /dev/null +++ b/pyste/example/header_test.pyste @@ -0,0 +1 @@ +AllFromHeader('header_test.h') diff --git a/pyste/example/nested.h b/pyste/example/nested.h new file mode 100644 index 00000000..35804fbb --- /dev/null +++ b/pyste/example/nested.h @@ -0,0 +1,21 @@ + +struct X +{ + struct Y + { + int valueY; + static int staticYValue; + struct Z + { + int valueZ; + }; + }; + + static int staticXValue; + int valueX; +}; + +int X::staticXValue = 10; +int X::Y::staticYValue = 20; + +typedef X Root; diff --git a/pyste/example/nested.pyste b/pyste/example/nested.pyste new file mode 100644 index 00000000..b6291385 --- /dev/null +++ b/pyste/example/nested.pyste @@ -0,0 +1 @@ +Class('Root', 'nested.h') diff --git a/pyste/example/operator.h b/pyste/example/operator.h new file mode 100644 index 00000000..5c07549b --- /dev/null +++ b/pyste/example/operator.h @@ -0,0 +1,47 @@ +#include + + +struct C +{ + static double x; + double value; + + const C operator+(const C other) const + { + C c; + c.value = value + other.value; + return c; + } + operator int() const + { + return value; + } + double operator()() + { + return x; + } + + double operator()(double other) + { + return x + other; + } + + +}; + +double C::x = 10; + +const C operator*(const C& lhs, const C& rhs) +{ + C c; + c.value = lhs.value * rhs.value; + return c; +} + +std::ostream& operator <<( std::ostream& s, const C& c) +{ + std::cout << "here"; + s << "C instance: "; + return s; +} + diff --git a/pyste/example/operator.pyste b/pyste/example/operator.pyste new file mode 100644 index 00000000..ffa5725c --- /dev/null +++ b/pyste/example/operator.pyste @@ -0,0 +1,12 @@ +Include('iostream') +test = Wrapper('sum', +''' +const C sum(const C&, const C&) +{ + std::cout << "sum!" << std::endl; + return C(); +} +''' +) +C = Class('C', 'operator.h') +set_wrapper(C.operator['+'], test) diff --git a/pyste/example/templates.h b/pyste/example/templates.h new file mode 100644 index 00000000..de2afe44 --- /dev/null +++ b/pyste/example/templates.h @@ -0,0 +1,8 @@ + +template +struct Point +{ + X x; + Y y; +}; + diff --git a/pyste/example/templates.pyste b/pyste/example/templates.pyste new file mode 100644 index 00000000..30bef79e --- /dev/null +++ b/pyste/example/templates.pyste @@ -0,0 +1,8 @@ +Point = Template('Point', 'templates.h') +rename(Point.x, 'i') +rename(Point.y, 'j') +IPoint = Point('int double') +FPoint = Point('double int') +rename(IPoint, 'IPoint') +rename(IPoint.x, '_x_') + diff --git a/pyste/example/wrappertest.h b/pyste/example/wrappertest.h new file mode 100644 index 00000000..a75cddcc --- /dev/null +++ b/pyste/example/wrappertest.h @@ -0,0 +1,35 @@ +#ifndef WRAPPER_TEST +#define WRAPPER_TEST + + +#include + +std::vector Range(int count) +{ + std::vector v; + v.reserve(count); + for (int i = 0; i < count; ++i){ + v.push_back(i); + } + return v; +} + + +struct C +{ + C() {} + + std::vector Mul(int value) + { + std::vector res; + res.reserve(value); + std::vector::const_iterator it; + std::vector v(Range(value)); + for (it = v.begin(); it != v.end(); ++it){ + res.push_back(*it * value); + } + return res; + } +}; + +#endif diff --git a/pyste/example/wrappertest.pyste b/pyste/example/wrappertest.pyste new file mode 100644 index 00000000..80c76cda --- /dev/null +++ b/pyste/example/wrappertest.pyste @@ -0,0 +1,15 @@ +Include('wrappertest_wrappers.h') + +f = Function('Range', 'wrappertest.h') +set_wrapper(f, 'RangeWrapper') + +mul = Wrapper('MulWrapper', +''' +list MulWrapper(C& c, int value){ + return VectorToList(c.Mul(value)); +} +''' +) + +C = Class('C', 'wrappertest.h') +set_wrapper(C.Mul, mul) diff --git a/pyste/example/wrappertest_wrappers.h b/pyste/example/wrappertest_wrappers.h new file mode 100644 index 00000000..a45c3671 --- /dev/null +++ b/pyste/example/wrappertest_wrappers.h @@ -0,0 +1,26 @@ +#ifndef WRAPPER_TEST_WRAPPERS +#define WRAPPER_TEST_WRAPPERS + +#include +#include +#include "wrappertest.h" + +using namespace boost::python; + +template +list VectorToList(const std::vector & v) +{ + list res; + std::vector::const_iterator it; + for(it = v.begin(); it != v.end(); ++it){ + res.append(*it); + } + Py_XINCREF(res.ptr()); + return res; +} + +list RangeWrapper(int count){ + return VectorToList(Range(count)); +} + +#endif