We want to run C++ code in Python without doing any extra work.
Write a C++ class out to a file in the current working directory
In [1]:
outputfile = "Shape.h"
In [2]:
%%file $outputfile
#include "ffig/attributes.h"
#include <stdexcept>
#include <string>
struct FFIG_EXPORT Shape
{
virtual ~Shape() = default;
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual const char* name() const = 0;
};
static const double pi = 3.14159;
class Circle : public Shape
{
const double radius_;
public:
double area() const override
{
return pi * radius_ * radius_;
}
double perimeter() const override
{
return 2 * pi * radius_;
}
const char* name() const override
{
return "Circle";
}
Circle(double radius) : radius_(radius)
{
if ( radius < 0 )
{
std::string s = "Circle radius \"" + std::to_string(radius_) + "\" must be non-negative.";
throw std::runtime_error(s);
}
}
};
Compile our header to check it's valid C++
In [3]:
%%sh
clang++ -x c++ -fsyntax-only -std=c++14 -I../ffig/include Shape.h
Read the code using libclang
In [4]:
import sys
sys.path.insert(0,'../ffig')
sys.path.insert(0,'..')
In [5]:
import ffig.clang.cindex
index = ffig.clang.cindex.Index.create()
translation_unit = index.parse(outputfile, ['-x', 'c++', '-std=c++14', '-I../ffig/include'])
In [6]:
import asciitree
def node_children(node):
return (c for c in node.get_children() if c.location.file.name == outputfile)
print(asciitree.draw_tree(translation_unit.cursor,
lambda n: [c for c in node_children(n)],
lambda n: "%s (%s)" % (n.spelling or n.displayname, str(n.kind).split(".")[1])))
Turn the AST into some easy to manipulate Python classes
In [7]:
import ffig.cppmodel
import ffig.clang.cindex
In [8]:
model = ffig.cppmodel.Model(translation_unit=translation_unit, force_noexcept=False)
In [9]:
[f.name for f in model.functions][-5:]
Out[9]:
In [10]:
[c.name for c in model.classes][-5:]
Out[10]:
In [11]:
shape_class = [c for c in model.classes if c.name=='Shape'][0]
In [12]:
["{}::{}".format(shape_class.name,m.name) for m in shape_class.methods]
Out[12]:
We now have some input to use in a code generator.
Look at the templates the generator uses
In [13]:
%cat ../ffig/templates/json.tmpl
Run the code generator
In [14]:
%%sh
cd ../
python -m ffig -b _c.h.tmpl _c.cpp.tmpl json.tmpl python -m Shape -i demos/Shape.h -o demos
See what it created
In [15]:
%ls
In [16]:
%cat Shape.json
Build some bindings with the generated code.
In [17]:
%%file CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
set(CMAKE_CXX_STANDARD 14)
include_directories(../ffig/include)
add_library(Shape_c SHARED Shape_c.cpp)
In [18]:
%%sh
cmake .
cmake --build .
In [19]:
%%sh
nm -U libShape_c.dylib | c++filt
In [20]:
cat Shape/_py3.py
In [21]:
%%python2
import Shape
Shape.Config.set_library_path(".")
c = Shape.Circle(8)
print("A {} with radius {} has area {}".format(c.name(), 8, c.area()))
In [22]:
%%python3
import Shape
Shape.Config.set_library_path(".")
c = Shape.Circle(8)
print("A {} with radius {} has area {}".format(c.name(), 8, c.area()))
In [23]:
%%script pypy
import Shape
Shape.Config.set_library_path(".")
c = Shape.Circle(8)
print("A {} with radius {} has area {}".format(c.name(), 8, c.area()))
In [24]:
%%script /opt/intel/intelpython35/bin/python
import Shape
Shape.Config.set_library_path(".")
c = Shape.Circle(8)
print("A {} with radius {} has area {}".format(c.name(), 8, c.area()))
In [25]:
from Shape import *
In [26]:
try:
c = Circle(-8)
except Exception as e:
print(e)
FFIG is MIT-licensed and hosted on GitHub.
Contributions, issues and feedback are very welcome.