Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ Not released yet.
* Enums can be defined in classes.
* Linking of external libraries.

### Breaking Changes

* Static methods of a class 'CLS' will now have the prefix 'CLS_', e.g.
'CLS_static_method()'.
* Fixed typo in interface of Config: renamed all occurences of 'decleration'
to 'declaration'. In particular, the method Config.add_decleration()
has been renamed to Config.add_declaration().
* A declaration file for each C++ header will be generated.
* Config.add_declaration() takes the arguments 'modulename', 'decl',
and 'defined_classes'.

## Version 0.1

2017/04/03
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,21 @@ There are more examples in the subdirectory `examples`.
Many things are not implemented yet, e.g.

* default values
* linking to other libraries (you can modify the `setup.py` though)
* integrating other Cython extensions (you can modify the `setup.py` though)

Feel free to work on any of these features. :)

## Why?

I think Cython is a great tool to wrap C++ code in Python. However, it requires a lot of manual work to wrap C++ code: you have to write a file that lists all the classes and functions that you want to use and you must convert custom types manually. A lot of this work can be done automatically. A pitfall is the naming of classes and functions. It is hard to have consistent names while you write a wrapper manually. With this tool you do not rely on every developer on the project to follow the same naming scheme because the names of classes and functions in the wrapper are determined automatically from their C++ decleration.
I think Cython is a great tool to wrap C++ code in Python. However, it requires
a lot of manual work to wrap C++ code: you have to write a file that lists all
the classes and functions that you want to use and you must convert custom
types manually. A lot of this work can be done automatically. A pitfall is
the naming of classes and functions. It is hard to have consistent names while
you write a wrapper manually. With this tool you do not rely on every developer
on the project to follow the same naming scheme because the names of classes
and functions in the wrapper are determined automatically from their C++
declaration.

## Install

Expand Down
16 changes: 8 additions & 8 deletions doc/source/_static/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/source/memory_management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Memory Management
Call by Value / Reference
-------------------------

Suppose we have the following decleration in a C++ header:
Suppose we have the following declaration in a C++ header:

.. code-block:: c++

Expand Down
8 changes: 5 additions & 3 deletions doc/source/template_class.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ with dynamic types. That means we cannot directly wrap template types in
Python. We have to specify how the template types should be set. In order to
do that we can create a custom configuration, e.g.

.. literalinclude:: ../../test/templateclassconfig.py
:language: python
:linenos:
.. code:: python

from pywrap.defaultconfig import Config
config = Config()
config.register_class_specialization("A", "Ad", {"T": "double"})

The corresponding C++ header looks like this:

Expand Down
17 changes: 13 additions & 4 deletions pywrap/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,13 @@ def __str__(self):


class Function(FunctionBase):
def __init__(self, filename, namespace, name, result_type, comment=""):
def __init__(self, filename, namespace, name, result_type, comment="",
clazz=None):
super(Function, self).__init__(name, comment)
self.filename = filename
self.namespace = namespace
self.result_type = result_type
self.clazz = clazz

def __str__(self):
result = super(Function, self).__str__()
Expand All @@ -133,6 +135,12 @@ def __str__(self):
"Returns (%s)" % self.result_type, 1)
return result

def exported_name(self):
if self.clazz is None:
return self.name
else:
return self.clazz + "_" + self.name


class Constructor(FunctionBase):
def __init__(self, class_name, comment=""):
Expand Down Expand Up @@ -304,12 +312,13 @@ def _remove_overloaded_functions(asts):
function_names = []
removed_functions = []
for f in functions:
if f.name in function_names:
function_name = f.exported_name()
if function_name in function_names:
warnings.warn(
"Function '%s' is already defined. Only one method "
"will be exposed." % f.name)
"will be exposed." % function_name)
removed_functions.append(f)
else:
function_names.append(f.name)
function_names.append(function_name)
for ast in asts:
ast.nodes = [n for n in ast.nodes if n not in removed_functions]
79 changes: 43 additions & 36 deletions pywrap/cython.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from .parser import Parser, Includes, TypeInfo
from .ast import postprocess_asts
from .templates import render
from .utils import make_header, file_ending, hidden_stdout, hidden_stderr
from .utils import (make_header, file_ending, hidden_stdout, hidden_stderr,
derive_module_name_from)


def load_config(custom_config):
Expand All @@ -26,7 +27,7 @@ def load_config(custom_config):
parts = custom_config.split(os.sep)
path = os.sep.join(parts[:-1])
filename = parts[-1]
module = _derive_module_name_from(filename)
module = derive_module_name_from(filename)

sys.path.insert(0, path)
imported_module = __import__(module)
Expand Down Expand Up @@ -74,7 +75,7 @@ def make_cython_wrapper(filenames, sources, modulename=None, target=".",
if isinstance(filenames, str):
filenames = [filenames]
if len(filenames) == 1 and modulename is None:
modulename = _derive_module_name_from(filenames[0])
modulename = derive_module_name_from(filenames[0])
if modulename is None:
raise ValueError("Please give a module name when there are multiple "
"C++ files that you want to wrap.")
Expand All @@ -93,14 +94,15 @@ def make_cython_wrapper(filenames, sources, modulename=None, target=".",
includes, type_info, asts = _parse_files(
filenames, config, incdirs, verbose)

postprocess_asts(asts)
# TODO check if duplicates from separate modules are removed
postprocess_asts(asts.values())

results = dict(
[_make_extension(modulename, asts, includes, type_info, config),
_make_declarations(asts, includes, config),
_make_setup(sources, modulename, target, incdirs, compiler_flags,
config)]
)
extensions = _make_extension(
modulename, asts, includes, type_info, config)
declarations = _make_declarations(asts, includes, type_info, config)
setup = _make_setup(sources, modulename, asts.keys(), target, incdirs,
compiler_flags, config)
results = dict(declarations + [extensions, setup])

if verbose >= 2:
for filename in sorted(results.keys()):
Expand All @@ -110,52 +112,57 @@ def make_cython_wrapper(filenames, sources, modulename=None, target=".",
return results


def _derive_module_name_from(filename):
filename = filename.split(os.sep)[-1]
return filename.split(".")[0]


def _parse_files(filenames, config, incdirs, verbose):
includes = Includes()
type_info = TypeInfo(config)
asts = []
asts = {}
for filename in filenames:
modulename = derive_module_name_from(filename)
parser = Parser(filename, includes, type_info, incdirs, verbose)
asts.append(parser.parse())
asts[modulename] = parser.parse()
return includes, type_info, asts


def _make_extension(modulename, asts, includes, type_info, config):
cie = CythonImplementationExporter(includes, type_info, config)
for ast in asts:
body = ""
for module, ast in asts.iteritems():
cie = CythonImplementationExporter(
module, includes, type_info, config)
ast.accept(cie)
pyx_filename = modulename + "." + config.pyx_file_ending
body = cie.export()
includes.add_custom_module(module)
body += cie.export()
extension = includes.implementations_import() + body
pyx_filename = modulename + "." + config.pyx_file_ending
return pyx_filename, extension


def _make_declarations(asts, includes, config):
cde = CythonDeclarationExporter(includes, config)
for ast in asts:
def _make_declarations(asts, includes, type_info, config):
cdes = {}
for modulename, ast in asts.iteritems():
cde = CythonDeclarationExporter(modulename, includes, type_info, config)
ast.accept(cde)
body = cde.export()
declarations = includes.declarations_import() + body
for decl in config.additional_declerations:
declarations += decl
pxd_filename = "_declarations." + config.pxd_file_ending
return pxd_filename, declarations
cdes[modulename] = cde
results = []
for modulename, cde in cdes.iteritems():
pxd_filename = "_%s.%s" % (modulename, config.pxd_file_ending)
body = cde.export()
declarations = includes.declarations_import() + body
results.append((pxd_filename, declarations))
for modulename, decl in config.additional_declarations.iteritems():
results.append(("_%s.%s" % (modulename, config.pxd_file_ending), decl))
return results


def _make_setup(sources, modulename, target, incdirs, compiler_flags, config):
def _make_setup(sources, modulename, modules, target, incdirs, compiler_flags,
config):
sourcedir = os.path.relpath(".", start=target)
source_relpaths = [os.path.relpath(filename, start=target)
for filename in sources]
return "setup.py", render("setup", filenames=source_relpaths,
module=modulename, sourcedir=sourcedir,
incdirs=incdirs, compiler_flags=compiler_flags,
library_dirs=config.library_dirs,
libraries=config.libraries)
setup_py = render(
"setup", filenames=source_relpaths, modulename=modulename,
sourcedir=sourcedir, incdirs=incdirs, compiler_flags=compiler_flags,
library_dirs=config.library_dirs, libraries=config.libraries)
return "setup.py", setup_py


def write_files(files, target="."):
Expand Down
Loading