outputfile = "Shape.h"

%%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);
    }
  }
};

"""
Explanation: PyData London 2017
Foreign Function Interface Generator: Generating Python bindings from C++
Jonathan B Coe
jbcoe@ffig.org
https://github.com/ffig/ffig
We want to run C++ code in Python without doing any extra work.
Gathering Input
Write a C++ class out to a file in the current working directory
End of explanation
"""


%%sh
clang++ -x c++ -fsyntax-only -std=c++14 -I../ffig/include Shape.h 

"""
Explanation: Compile our header to check it's valid C++
End of explanation
"""


import sys
sys.path.insert(0,'../ffig')
sys.path.insert(0,'..')

import ffig.clang.cindex

index = ffig.clang.cindex.Index.create()
translation_unit = index.parse(outputfile, ['-x', 'c++', '-std=c++14', '-I../ffig/include'])

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])))


"""
Explanation: Read the code using libclang
End of explanation
"""


import ffig.cppmodel
import ffig.clang.cindex

model = ffig.cppmodel.Model(translation_unit=translation_unit, force_noexcept=False)

[f.name for f in model.functions][-5:]

[c.name for c in model.classes][-5:]

shape_class = [c for c in model.classes if c.name=='Shape'][0]

["{}::{}".format(shape_class.name,m.name) for m in shape_class.methods]

"""
Explanation: Turn the AST into some easy to manipulate Python classes
End of explanation
"""


%cat ../ffig/templates/json.tmpl

"""
Explanation: Code Generation
We now have some input to use in a code generator.
Look at the templates the generator uses
End of explanation
"""


%%sh
cd ../
python -m ffig -b _c.h.tmpl _c.cpp.tmpl json.tmpl python -m Shape -i demos/Shape.h -o demos

"""
Explanation: Run the code generator
End of explanation
"""


%ls

%cat Shape.json

"""
Explanation: See what it created
End of explanation
"""


%%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)

%%sh
cmake . 
cmake --build .

%%sh
nm -U libShape_c.dylib | c++filt

cat Shape/_py3.py

%%python2
import Shape
Shape.Config.set_library_path(".")
c = Shape.Circle(8)

print("A {} with radius {} has area {}".format(c.name(), 8, c.area()))

%%python3
import Shape
Shape.Config.set_library_path(".")
c = Shape.Circle(8)

print("A {} with radius {} has area {}".format(c.name(), 8, c.area()))

%%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()))

%%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()))

from Shape import *

try:
    c = Circle(-8)
except Exception as e:
    print(e)

"""
Explanation: Build some bindings with the generated code.
End of explanation
"""


