├── WORKSPACE
├── ffig
├── __init__.py
├── filters
│ └── __init__.py
├── templates
│ ├── swift.bridging-header.tmpl
│ ├── csproj.tmpl
│ ├── d.tmpl
│ ├── rs.tmpl
│ ├── __init__.py.tmpl
│ ├── json.tmpl
│ ├── java.exception.tmpl
│ ├── jl.macros
│ ├── java.derived.tmpl
│ ├── ffig.macros
│ ├── _c.macros
│ ├── swift.macros
│ ├── java.interop.tmpl
│ ├── java.macros
│ ├── cs.macros
│ ├── go.macros
│ ├── jl.tmpl
│ ├── go.tmpl
│ ├── java.tmpl
│ ├── _c.h.tmpl
│ ├── py2.macros
│ ├── py3.macros
│ ├── _mocks.h.tmpl
│ ├── rb.tmpl
│ ├── swift.tmpl
│ ├── config.py.tmpl
│ ├── lua.tmpl
│ ├── boost_python.tmpl
│ ├── py2.tmpl
│ ├── _cpp.h.tmpl
│ ├── cs.tmpl
│ ├── py3.tmpl
│ └── go.c.tmpl
├── __main__.py
├── clang
│ ├── __init__.py
│ └── enumerations.py
├── generators
│ ├── boost_python.py
│ ├── generator_aliases.py
│ ├── swift.py
│ ├── c.py
│ ├── dotnet.py
│ ├── python.py
│ ├── java.py
│ ├── go.py
│ └── __init__.py
├── include
│ └── ffig
│ │ └── attributes.h
├── annotations.py
└── FFIG.py
├── tests
├── __init.py__
├── test_asset_bindings.lua
├── dotnet
│ ├── Number
│ │ └── Number.net.csproj
│ ├── Shape
│ │ └── Shape.net.csproj
│ ├── Tree
│ │ └── Tree.net.csproj
│ ├── ffig.net.csproj.in
│ ├── TestTree
│ │ ├── TestTree.net.csproj
│ │ └── TestTree.cs
│ ├── TestShape
│ │ ├── TestShape.net.csproj
│ │ └── TestShape.cs
│ ├── ffig.net.tests.sln
│ └── TestNumber
│ │ ├── TestNumber.net.csproj
│ │ └── TestNumber.cs
├── expected_output
│ ├── Tree.d.expected
│ └── Tree.rs.expected
├── julia
│ └── TestShape.jl
├── go
│ ├── CMakeLists.txt
│ └── Shape_test.go
├── test_number_python_bindings.py
├── boost_python
│ ├── test_number_python_bindings.py
│ ├── test_animal_bindings.py
│ ├── test_tree_python_bindings.py
│ └── test_shape_python_bindings.py
├── ffig
│ ├── test_generator_list.py
│ └── test_build_model.py
├── input
│ ├── Number.h
│ ├── Asset.h
│ ├── Animal.h
│ ├── Tree.h
│ └── Shape.h
├── TestAsset.rb
├── test_asset_bindings.py
├── test_animal_bindings.py
├── java
│ ├── TestAsset.java
│ ├── TestNumber.java
│ ├── TestTree.java
│ └── TestShape.java
├── TestShape.rb
├── cppmodel
│ ├── test_model.py
│ ├── test_extend_model.py
│ ├── test_types.py
│ ├── test_free_functions.py
│ ├── util.py
│ └── test_classes.py
├── src
│ ├── TestNumberCAPI.cpp
│ ├── CMakeLists.txt
│ ├── TestCppTree.cpp
│ ├── TestShapeMocks.cpp
│ ├── TestCppCircle.cpp
│ └── TestTreeCAPI.cpp
├── test_tree_python_bindings.py
├── swift
│ ├── TestShape
│ │ └── main.swift
│ └── TestTree
│ │ └── main.swift
├── test_shape_python_bindings.py
└── test_type_hints_in_bindings.py
├── .dockerignore
├── requirements.txt
├── docs
├── ISSUE_TEMPLATE
│ ├── feature
│ └── bug
├── pull_request_template.md
└── CONTRIBUTING.md
├── .ycm_extra_conf.py
├── .appveyor.yml
├── .vscode
├── settings.json
└── tasks.json
├── .gitmodules
├── scripts
├── pydiff.py
├── pre-push.py
├── test-docker.py
├── install-git-hooks.py
├── codechecks.py
└── build.py
├── .gitignore
├── cmake
├── utils.cmake
└── dotnet.cmake
├── Dockerfile
├── LICENSE
├── .clang-format
├── BUILD
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── ffig.bzl
└── README.md
/WORKSPACE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ffig/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/__init.py__:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | build_out
2 |
--------------------------------------------------------------------------------
/ffig/filters/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | autopep8
2 | cython
3 | jinja2
4 | nose
5 | pycodestyle
6 |
--------------------------------------------------------------------------------
/ffig/templates/swift.bridging-header.tmpl:
--------------------------------------------------------------------------------
1 | #import "{{module.name}}_c.h"
2 |
3 |
--------------------------------------------------------------------------------
/docs/ISSUE_TEMPLATE/feature:
--------------------------------------------------------------------------------
1 | ## Feature Request
2 |
3 | ### Summary
4 |
5 | ### Requirements
6 |
7 | ### Non-requirements
8 |
9 | ### Additional information
10 |
--------------------------------------------------------------------------------
/docs/ISSUE_TEMPLATE/bug:
--------------------------------------------------------------------------------
1 | ## Bug Report
2 |
3 | ### Expected behaviour
4 |
5 | ### Actual behaviour
6 |
7 | ### Steps to reproduce
8 |
9 | ### Additional information
10 |
11 |
--------------------------------------------------------------------------------
/ffig/templates/csproj.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/test_asset_bindings.lua:
--------------------------------------------------------------------------------
1 | require("Asset")
2 |
3 | assert(Asset_error() == "")
4 |
5 | a = CDO:new()
6 |
7 | assert(a:name() == "CDO")
8 |
9 | assert(a:value() == 99.99)
10 |
11 |
--------------------------------------------------------------------------------
/tests/dotnet/Number/Number.net.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/dotnet/Shape/Shape.net.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/dotnet/Tree/Tree.net.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ffig/templates/d.tmpl:
--------------------------------------------------------------------------------
1 | {% for class in classes -%}
2 | class {{class.name}}
3 | {
4 | {% for method in class.methods %}
5 | void {{method.name}}()
6 | {
7 | }
8 | {% endfor %}
9 | }
10 | {% endfor %}
11 |
--------------------------------------------------------------------------------
/ffig/__main__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 |
4 | from ffig.FFIG import main as ffig_main
5 |
6 | if sys.argv[0].endswith('__main__.py'):
7 | sys.argv[0] = '%s -m ffig' % sys.executable
8 |
9 | ffig_main()
10 |
--------------------------------------------------------------------------------
/tests/expected_output/Tree.d.expected:
--------------------------------------------------------------------------------
1 | class Tree
2 | {
3 | void left_subtree()
4 | {
5 | }
6 | void right_subtree()
7 | {
8 | }
9 | void data()
10 | {
11 | }
12 | void set_data()
13 | {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/julia/TestShape.jl:
--------------------------------------------------------------------------------
1 | using Base.Test
2 |
3 | include("../../build_out/generated/Shape.jl")
4 | using FFIG
5 |
6 | @test area(Square(3.0)) == 9.0
7 |
8 | @test perimeter(Square(3.0)) == 12.0
9 |
10 | @test name(Circle(3.0)) == "Circle"
11 |
--------------------------------------------------------------------------------
/ffig/templates/rs.tmpl:
--------------------------------------------------------------------------------
1 | {% for class in classes -%}
2 | pub struct {{class.name}} {
3 | }
4 |
5 | impl {{class.name}} {
6 | {% for method in class.methods %}
7 | pub fn {{method.name}}() {
8 | }
9 |
10 | {% endfor %}
11 | }
12 | {% endfor %}
13 |
--------------------------------------------------------------------------------
/tests/go/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_test(
2 | NAME test_go_shape
3 | COMMAND ${CMAKE_COMMAND} -E env "GOPATH=${CMAKE_BINARY_DIR}/generated" go test
4 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
5 | set_property(TEST test_go_shape PROPERTY LABELS GO)
6 |
--------------------------------------------------------------------------------
/tests/expected_output/Tree.rs.expected:
--------------------------------------------------------------------------------
1 | pub struct Tree {
2 | }
3 |
4 | impl Tree {
5 | pub fn left_subtree() {
6 | }
7 |
8 | pub fn right_subtree() {
9 | }
10 |
11 | pub fn data() {
12 | }
13 |
14 | pub fn set_data() {
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/docs/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Required
2 |
3 | ## What does this PR do?
4 |
5 | ## Closes [github issues]
6 |
7 | # Optional
8 |
9 | ## Where should the reviewer start?
10 |
11 | ## Ideas that were considered and discarded
12 |
13 | ## Possible future work
14 |
--------------------------------------------------------------------------------
/tests/test_number_python_bindings.py:
--------------------------------------------------------------------------------
1 | import nose
2 | import number
3 |
4 | def test_number_8_has_value_8():
5 | n = number.Number(8)
6 | assert n.value() == 8
7 |
8 | def test_number_9_comes_after_8():
9 | n8 = number.Number(8)
10 | n9 = n8.next()
11 |
12 | assert n9.value() == 9
13 |
14 |
--------------------------------------------------------------------------------
/tests/boost_python/test_number_python_bindings.py:
--------------------------------------------------------------------------------
1 | import nose
2 | import Number_py as number
3 |
4 | def test_number_8_has_value_8():
5 | n = number.Number(8)
6 | assert n.value() == 8
7 |
8 | def test_number_9_comes_after_8():
9 | n8 = number.Number(8)
10 | n9 = n8.next()
11 |
12 | assert n9.value() == 9
13 |
14 |
--------------------------------------------------------------------------------
/tests/ffig/test_generator_list.py:
--------------------------------------------------------------------------------
1 | import ffig.generators
2 | from nose.tools import assert_equals
3 |
4 | def test_generator_list():
5 | python_entry = list(filter(lambda x: x[0] == 'python',
6 | ffig.generators.generator_context.list_generators()))[0]
7 |
8 | assert_equals(python_entry[1], 'Python2 and Python3 generator using ctypes')
9 |
--------------------------------------------------------------------------------
/tests/input/Number.h:
--------------------------------------------------------------------------------
1 | #include "ffig/attributes.h"
2 |
3 | class FFIG_EXPORT Number
4 | {
5 | int value_;
6 |
7 | public:
8 | Number(int value) : value_(value)
9 | {
10 | }
11 |
12 | Number next() const {
13 | return Number(value_ + 1);
14 | }
15 |
16 | int value() const noexcept {
17 | return value_;
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/tests/TestAsset.rb:
--------------------------------------------------------------------------------
1 | require "test/unit"
2 | # FIXME: Remove this hardcoded path
3 | require_relative "../build/generated/Asset"
4 |
5 | class TestAsset < Test::Unit::TestCase
6 | def test_CDO_name
7 | assert_equal("CDO", CDO.new.name)
8 | end
9 |
10 | def test_CDO_PV
11 | assert_equal(CDO.new.PV, 0.0)
12 | end
13 | end
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ffig/templates/__init__.py.tmpl:
--------------------------------------------------------------------------------
1 | # This code was generated by FFIG .
2 | # Manual edits will be lost.
3 |
4 | import sys
5 | if sys.version_info[0] == 2:
6 | from ._py2 import *
7 | elif sys.version_info[0] == 3:
8 | from ._py3 import *
9 | else:
10 | raise Exception('Unsupported Python version {0}'.format(sys.version_info[0]))
11 |
12 |
--------------------------------------------------------------------------------
/.ycm_extra_conf.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | root_dir = os.path.dirname(__file__)
4 |
5 | flags = "-x c++ -std=c++14 -stdlib=libc++ -I{0}/build/generated -Iffig/include -isystem /usr/include/c++/v1 -isystem {0}/externals/catch2/single_include".format(
6 | root_dir).split()
7 |
8 |
9 | def FlagsForFile(filename, **kwargs):
10 | return {'flags': flags, 'do_cache': True}
11 |
--------------------------------------------------------------------------------
/ffig/templates/json.tmpl:
--------------------------------------------------------------------------------
1 | [{% for class in classes %}
2 | {
3 | "name" : "{{class.name}}"{% if class.methods %},
4 | "methods" : [{% for method in class.methods %}
5 | {
6 | "name" : "{{method.name}}",
7 | "return_type" : "{{method.return_type}}"
8 | }{% if not loop.last %},{% endif %}{% endfor %}
9 | ]{% endif %}
10 | }{% if not loop.last %},{% endif %}{% endfor %}
11 | ]
12 |
--------------------------------------------------------------------------------
/tests/test_asset_bindings.py:
--------------------------------------------------------------------------------
1 | from asset import *
2 |
3 |
4 | def test_no_argument_constructor():
5 | # Lack of exception is all we need to test.
6 | cdo = CDO()
7 |
8 |
9 | def test_id_is_renamed_to_name_and_is_a_property():
10 | cdo = CDO()
11 |
12 | assert cdo.name == "CDO"
13 |
14 |
15 | def test_PV_is_renamed_to_value():
16 | cdo = CDO()
17 |
18 | assert cdo.value() == 99.99
19 |
--------------------------------------------------------------------------------
/tests/test_animal_bindings.py:
--------------------------------------------------------------------------------
1 | import animal
2 |
3 |
4 | def test_animals_are_defined():
5 | assert hasattr(animal, "Cat")
6 | assert hasattr(animal, "Duck")
7 | assert hasattr(animal, "Squirrel")
8 |
9 |
10 | def test_animals_make_noises():
11 | assert animal.Cat().noise() == "Miaow"
12 | assert animal.Duck().noise() == "Quack"
13 | assert animal.Squirrel().noise() == ""
14 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | image:
2 | - Visual Studio 2017
3 |
4 | install:
5 | - git submodule update --init
6 | - cmd: set PATH="C:\Program Files\LLVM\bin";C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH%
7 | - cmd: C:\Python36-x64\python -m pip install jinja2 nose pycodestyle
8 |
9 | build_script:
10 | - cmd: python scripts\build.py -t --python-path "C:\\Python36-x64\\python" --disable_java --require_dotnet --clean --nobazel
11 |
--------------------------------------------------------------------------------
/tests/boost_python/test_animal_bindings.py:
--------------------------------------------------------------------------------
1 | import Animal_py as animal
2 |
3 |
4 | def test_animals_are_defined():
5 | assert hasattr(animal, "Cat")
6 | assert hasattr(animal, "Duck")
7 | assert hasattr(animal, "Squirrel")
8 |
9 |
10 | def test_animals_make_noises():
11 | assert animal.Cat().noise() == "Miaow"
12 | assert animal.Duck().noise() == "Quack"
13 | assert animal.Squirrel().noise() == ""
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "*.tmpl": "jinja"
4 | },
5 | "files.exclude": {
6 | "**/.git": true,
7 | "**/.DS_Store": true,
8 | "build/**": true,
9 | "**/*.pyc": true,
10 | "**/__pycache__": true,
11 | ".vscode/tags": true
12 | },
13 | "editor.formatOnSave": true,
14 | "python.autoComplete.extraPaths": [
15 | "build/generated"
16 | ]
17 | }
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "externals/catch"]
2 | path = externals/catch
3 | url = https://github.com/philsquared/catch.git
4 | [submodule "externals/variant"]
5 | path = externals/variant
6 | url = https://github.com/eggs-cpp/variant.git
7 | [submodule "externals/catch2"]
8 | path = externals/catch2
9 | url = https://github.com/catchorg/Catch2.git
10 | [submodule "externals/ffig-jars"]
11 | path = externals/ffig-jars
12 | url = https://github.com/FFIG/ffig-jars.git
13 |
--------------------------------------------------------------------------------
/tests/ffig/test_build_model.py:
--------------------------------------------------------------------------------
1 | import ffig.FFIG
2 | from nose.tools import assert_equals
3 |
4 |
5 | def test_build_model_from_str():
6 | source = 'class A{}; void foo();'
7 | filename = 'test.cpp'
8 | model = ffig.FFIG.build_model_from_source(filename, 'test_source',
9 | unsaved_files=[(filename, source)])
10 |
11 | assert_equals(
12 | str(model),
13 | "")
14 |
--------------------------------------------------------------------------------
/tests/dotnet/ffig.net.csproj.in:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/java/TestAsset.java:
--------------------------------------------------------------------------------
1 | import static org.junit.Assert.assertEquals;
2 | import org.junit.Test;
3 |
4 | public class TestAsset {
5 | @Test
6 | public void CDOhasPVofZero()
7 | {
8 | Asset.Asset cdo = new Asset.CDO();
9 |
10 | assertEquals(99.99, cdo.value(), 0.0);
11 | }
12 |
13 | @Test
14 | public void CDOisCalledCDO()
15 | {
16 | Asset.Asset cdo = new Asset.CDO();
17 |
18 | assertEquals(cdo.name(), "CDO");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/input/Asset.h:
--------------------------------------------------------------------------------
1 | #include "ffig/attributes.h"
2 |
3 | struct FFIG_EXPORT Asset
4 | {
5 | virtual FFIG_EXPORT_NAME(value) double PV() const = 0;
6 | virtual FFIG_PROPERTY_NAME(name) const char* id() const = 0;
7 | virtual ~Asset() = default;
8 | };
9 |
10 | struct FFIG_NAME(CDO) CollateralisedDebtObligation : Asset
11 | {
12 | CollateralisedDebtObligation() {}
13 |
14 | double PV() const override { return 99.99; }
15 | const char* id() const override { return "CDO"; }
16 | };
17 |
18 |
--------------------------------------------------------------------------------
/ffig/templates/java.exception.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'java.macros' as java_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | package {{module.name}};
6 |
7 | public class {{module.name}}Exception extends RuntimeException {
8 | public {{module.name}}Exception()
9 | {
10 | super({{module.name}}CLibrary.INSTANCE.{{module.name}}_error());
11 | {{module.name}}CLibrary.INSTANCE.{{module.name}}_clear_error();
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/tests/java/TestNumber.java:
--------------------------------------------------------------------------------
1 | import static org.junit.Assert.assertEquals;
2 | import org.junit.Test;
3 | import Number.Number;
4 |
5 | public class TestNumber {
6 | @Test
7 | public void Value()
8 | {
9 | Number number = new Number(8);
10 |
11 | assertEquals(number.value(), 8);
12 | }
13 |
14 | @Test
15 | public void NextNumberHasNextValue()
16 | {
17 | Number number = new Number(8);
18 | Number next = number.next();
19 |
20 | assertEquals(next.value(), 9);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/TestShape.rb:
--------------------------------------------------------------------------------
1 | require "test/unit"
2 | # FIXME: Remove this hardcoded path
3 | require_relative "../build_out/generated/Shape"
4 |
5 | class TestShape < Test::Unit::TestCase
6 | def test_name
7 | assert_equal("Circle", Circle.new(3).name)
8 | end
9 |
10 | def test_area
11 | assert_equal(100.0, Square.new(10).area)
12 | end
13 |
14 | def test_perimeter
15 | assert_equal(40, Square.new(10).perimeter)
16 | end
17 |
18 | def test_error
19 | assert_raise(ShapeError) { Circle.new(-1) }
20 | end
21 | end
22 |
23 |
--------------------------------------------------------------------------------
/tests/input/Animal.h:
--------------------------------------------------------------------------------
1 | #include "ffig/attributes.h"
2 |
3 | class FFIG_EXPORT Cat
4 | {
5 | public:
6 | Cat() = default;
7 |
8 | const char* noise() const
9 | {
10 | return "Miaow";
11 | }
12 | };
13 |
14 | class FFIG_EXPORT Duck
15 | {
16 | public:
17 | Duck() = default;
18 |
19 | const char* noise() const
20 | {
21 | return "Quack";
22 | }
23 | };
24 |
25 | class FFIG_EXPORT Squirrel
26 | {
27 | public:
28 | Squirrel() = default;
29 |
30 | const char* noise() const
31 | {
32 | return "";
33 | }
34 | };
35 |
36 |
--------------------------------------------------------------------------------
/scripts/pydiff.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import difflib
4 | import sys
5 | import os
6 |
7 | filename1 = os.path.abspath(sys.argv[1])
8 | filename2 = os.path.abspath(sys.argv[2])
9 |
10 | with open(filename1) as i:
11 | first = i.readlines()
12 |
13 | with open(filename2) as i:
14 | second = i.readlines()
15 |
16 | diff = [x for x in difflib.unified_diff(first, second)]
17 |
18 | if len(diff) != 0:
19 | print("{} and {} are different".format(filename1, filename2))
20 | for line in diff:
21 | print(line)
22 |
23 | if len(diff) != 0:
24 | sys.exit(-1)
25 |
--------------------------------------------------------------------------------
/tests/dotnet/TestTree/TestTree.net.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/dotnet/TestShape/TestShape.net.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/dotnet/ffig.net.tests.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Debug|x64 = Debug|x64
10 | Debug|x86 = Debug|x86
11 | Release|Any CPU = Release|Any CPU
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | EndGlobal
19 |
--------------------------------------------------------------------------------
/tests/dotnet/TestNumber/TestNumber.net.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/dotnet/TestNumber/TestNumber.cs:
--------------------------------------------------------------------------------
1 | using Number_c;
2 | using NUnit.Framework;
3 |
4 | namespace TestFFIG
5 | {
6 | [TestFixture]
7 | public class TestNumber
8 | {
9 | [Test]
10 | public void NumberValue()
11 | {
12 | var number = new Number(8);
13 |
14 | Assert.AreEqual(number.value(), 8);
15 | }
16 |
17 | [Test]
18 | public void NumberNext()
19 | {
20 | var number = new Number(8);
21 | var next_number = number.next();
22 |
23 | Assert.AreEqual(next_number.value(), 9);
24 | }
25 | }
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/scripts/pre-push.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import print_function
4 |
5 | import os
6 | import subprocess
7 | import sys
8 |
9 | this_dir = os.path.dirname(os.path.realpath(__file__))
10 | check_script = os.path.join(this_dir, 'codechecks.py')
11 |
12 | try:
13 | subprocess.check_call([check_script])
14 | sys.exit(0)
15 | except subprocess.CalledProcessError:
16 | print("""
17 | ****************************************
18 | The code checks failed. Please run
19 | {check_script} --reformat
20 | and commit the changes before pushing.
21 | ****************************************
22 | """.format(check_script=check_script))
23 | sys.exit(1)
24 |
--------------------------------------------------------------------------------
/tests/cppmodel/test_model.py:
--------------------------------------------------------------------------------
1 | from util import get_tu
2 | import ffig.cppmodel
3 | from nose.tools import assert_equals
4 | from nose.tools import assert_raises
5 |
6 |
7 | def test_repr():
8 | source = 'class A{}; void foo();'
9 | tu = get_tu(source, 'cpp')
10 |
11 | model = ffig.cppmodel.Model(tu)
12 |
13 | assert_equals(
14 | str(model),
15 | "")
16 |
17 |
18 | def test_exception_for_missing_include():
19 | source = '#include "major_tom.h"'
20 | tu = get_tu(source, 'cpp')
21 |
22 | def f():
23 | ffig.cppmodel.Model(tu)
24 | assert_raises(ValueError, f)
25 |
--------------------------------------------------------------------------------
/ffig/clang/__init__.py:
--------------------------------------------------------------------------------
1 | #===- __init__.py - Clang Python Bindings --------------------*- python -*--===#
2 | #
3 | # The LLVM Compiler Infrastructure
4 | #
5 | # This file is distributed under the University of Illinois Open Source
6 | # License. See LICENSE.TXT for details.
7 | #
8 | #===------------------------------------------------------------------------===#
9 |
10 | r"""
11 | Clang Library Bindings
12 | ======================
13 |
14 | This package provides access to the Clang compiler and libraries.
15 |
16 | The available modules are:
17 |
18 | cindex
19 |
20 | Bindings for the Clang indexing library.
21 | """
22 |
23 | __all__ = ['cindex']
24 |
25 |
--------------------------------------------------------------------------------
/ffig/generators/boost_python.py:
--------------------------------------------------------------------------------
1 | # Generator module for Boost-Python.
2 |
3 | import ffig.generators
4 | import os
5 |
6 |
7 | def generator(module_name, binding, api_classes, env, output_dir):
8 | outputs = []
9 |
10 | o = os.path.join(output_dir, module_name + '_py.cpp')
11 | ffig.generators.generate_single_output_file(
12 | module_name, 'boost_python.tmpl', api_classes, env, o)
13 | outputs.append(o)
14 |
15 | return outputs
16 |
17 |
18 | def setup_plugin(context):
19 | context.register(
20 | generator,
21 | [
22 | ('boost_python', 'Python bindings using Boost Python.')
23 | ])
24 |
25 |
26 |
--------------------------------------------------------------------------------
/scripts/test-docker.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import subprocess
3 | import sys
4 |
5 |
6 | def main(args):
7 | if '--local' in args:
8 | args = [a for a in args if a != '--local']
9 | else:
10 | subprocess.check_call("docker pull ffig/ffig-base".split())
11 | subprocess.check_call("docker build -t ffig_local .".split())
12 | subprocess.check_call(['docker',
13 | 'run',
14 | 'ffig_local',
15 | '/bin/bash',
16 | '-c',
17 | './scripts/build.py {}'.format(' '.join(args[1:]))])
18 |
19 |
20 | if __name__ == '__main__':
21 | main(sys.argv)
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | output/*
2 | *.pyc
3 | .*.swp
4 | tests/bin
5 | tests/build
6 | sandbox/CircleTest
7 | sandbox/Shape_c.h
8 | sandbox/*.o
9 | sandbox/*.dylib
10 | demos/.ipynb_checkpoints/
11 | demos/CMakeCache.txt
12 | demos/CMakeFiles/
13 | demos/CMakeLists.txt
14 | demos/LLVM-Cauldron.slides.html
15 | demos/Makefile
16 | demos/Shape
17 | demos/Shape.h
18 | demos/Shape.json
19 | demos/Shape.py
20 | demos/Shape.rb
21 | demos/Shape_c.cpp
22 | demos/Shape_c.h
23 | demos/Shape_cpp.h
24 | demos/Shape_mocks.h
25 | demos/cmake_install.cmake
26 | demos/libShape_c.dylib
27 | build_out
28 | .vscode/tags
29 | *.iml
30 | .idea/
31 | bazel-bin
32 | bazel-genfiles
33 | bazel-testlogs
34 | bazel-out
35 | bazel-genfiles
36 | bazel-ffig
37 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "taskName": "build",
8 | "type": "shell",
9 | "command": "python scripts/build.py",
10 | "group": {
11 | "kind": "build",
12 | "isDefault": true
13 | }
14 | },
15 | {
16 | "taskName": "test",
17 | "type": "shell",
18 | "command": "python scripts/build.py -t",
19 | "group": {
20 | "kind": "test",
21 | "isDefault": true
22 | }
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/ffig/templates/jl.macros:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros %}
2 |
3 | {#
4 | # method_parameter_types:
5 | # Generates a list of parameter types suitable for use in a function
6 | # declaration or definition. Commas are inserted between each element
7 | # of the list. Leading and trailing commas can be added by setting
8 | # leading_comma or trailing_comma to True as appropriate. This is useful
9 | # if there are other parameters that must be declared.
10 | #}
11 | {% macro method_parameter_types(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg.type | to_julia_param_type}}
14 | {%- endcall %}
15 | {% endmacro %}
16 |
17 |
--------------------------------------------------------------------------------
/scripts/install-git-hooks.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import print_function
4 |
5 | import os
6 |
7 | this_dir = os.path.dirname(os.path.realpath(__file__))
8 | git_dir = os.path.realpath(os.path.join(this_dir, '..', '.git'))
9 |
10 | hook_scripts = ['pre-push.py']
11 |
12 | for hook_script in hook_scripts:
13 | target = os.path.join(this_dir, hook_script)
14 | link_name = os.path.join(git_dir, 'hooks', hook_script.replace('.py', ''))
15 | if os.path.exists(link_name):
16 | print(
17 | 'Skipping {0} because {1} already exists'.format(
18 | target, link_name))
19 | else:
20 | print('Installing symbolic link {0} --> {1}'.format(link_name, target))
21 | os.symlink(target, link_name)
22 |
--------------------------------------------------------------------------------
/cmake/utils.cmake:
--------------------------------------------------------------------------------
1 | # Utility functions for CMake used by FFIG.
2 |
3 | # Set the shared library path for test execution.
4 | function(set_test_shared_library_path)
5 | set(options)
6 | set(multiValueArgs)
7 | set(oneValueArgs TEST_NAME DLL_PATH)
8 | cmake_parse_arguments(set_test_shared_library_path "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
9 |
10 | set(TEST_NAME ${set_test_shared_library_path_TEST_NAME})
11 | set(DLL_PATH ${set_test_shared_library_path_DLL_PATH})
12 |
13 | if(WIN32)
14 | set_property(TEST ${TEST_NAME}
15 | PROPERTY ENVIRONMENT "PATH=${DLL_PATH}\;%PATH%")
16 | else()
17 | set_property(TEST ${TEST_NAME}
18 | PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=${DLL_PATH}:$LD_LIBRARY_PATH")
19 | endif()
20 | endfunction()
21 |
22 |
--------------------------------------------------------------------------------
/tests/src/TestNumberCAPI.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this
2 | // in one cpp file
3 | #include "Number_c.h"
4 | #include
5 | #include
6 |
7 | TEST_CASE("Test hoist-to heap of objects returned by value", "[Number_CAPI]")
8 | {
9 | Number_Number p;
10 | auto rc = Number_Number_create(8, &p);
11 | REQUIRE(rc == Number_RC_SUCCESS);
12 |
13 | int v = 0;
14 | Number_Number_value(p, &v);
15 | CHECK(v == 8);
16 |
17 | Number_Number p_next;
18 | rc = Number_Number_next(p, &p_next);
19 | REQUIRE(rc == Number_RC_SUCCESS);
20 |
21 | v = 0;
22 | Number_Number_value(p_next, &v);
23 | CHECK(v == 9);
24 |
25 | Number_Number_dispose(p);
26 | Number_Number_dispose(p_next);
27 | }
28 |
29 |
30 |
--------------------------------------------------------------------------------
/ffig/include/ffig/attributes.h:
--------------------------------------------------------------------------------
1 | // Definitions of attributes used to expose classes and functions through FFIG.
2 |
3 | #ifndef FFIG_ATTRIBUTES_H
4 | #define FFIG_ATTRIBUTES_H
5 |
6 | #ifdef __clang__
7 | #define FFIG_EXPORT __attribute__((annotate("FFIG:EXPORT")))
8 | #define FFIG_EXPORT_NAME(x) __attribute__((annotate("FFIG:EXPORT"), annotate("FFIG:NAME:"#x)))
9 | #define FFIG_NAME(x) __attribute__((annotate("FFIG:NAME:"#x)))
10 | #define FFIG_PROPERTY __attribute__((annotate("FFIG:PROPERTY")))
11 | #define FFIG_PROPERTY_NAME(x) __attribute__((annotate("FFIG:PROPERTY"), annotate("FFIG:NAME:"#x)))
12 | #else
13 | #define FFIG_EXPORT
14 | #define FFIG_EXPORT_NAME(x)
15 | #define FFIG_NAME(x)
16 | #define FFIG_PROPERTY
17 | #define FFIG_PROPERTY_NAME(x)
18 | #endif
19 |
20 | #endif // FFIG_ATTRIBUTES_H
21 |
22 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ffig/ffig-base:latest
2 | LABEL maintainer="FFIG "
3 |
4 |
5 | RUN add-apt-repository ppa:openjdk-r/ppa; \
6 | add-apt-repository ppa:webupd8team/java; \
7 | apt update; \
8 | apt install -y openjdk-8-jdk libboost-python-dev; \
9 | echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list; \
10 | curl https://bazel.build/bazel-release.pub.gpg | apt-key add -; apt update ; apt install bazel -y; \
11 | mkdir -p /opt; curl -s https://swift.org/builds/swift-4.1-release/ubuntu1610/swift-4.1-RELEASE/swift-4.1-RELEASE-ubuntu16.10.tar.gz | tar zxf - -C /opt;
12 | ENV PATH=/opt/swift-4.1-RELEASE-ubuntu16.10/usr/bin:"$PATH"
13 |
14 | COPY . /home/ffig
15 | RUN find /home/ffig \( -name "*.py" -o -name "*.sh" \) -exec dos2unix -q {} \;
16 | WORKDIR /home/ffig
17 |
--------------------------------------------------------------------------------
/ffig/generators/generator_aliases.py:
--------------------------------------------------------------------------------
1 | # Generator module that provides aliases for other generators.
2 | # This is used to alias ruby -> rb.tmpl, for example.
3 |
4 | import ffig.generators
5 |
6 | aliases = {
7 | 'julia': ('jl.tmpl', 'Julia bindings'),
8 | 'lua': ('lua.tmpl', 'Lua bindings'),
9 | 'ruby': ('rb.tmpl', 'Ruby bindings'),
10 | }
11 |
12 |
13 | def aliased_generator(module_name, binding, api_classes, env, output_dir):
14 | try:
15 | binding = aliases[binding][0]
16 | except KeyError:
17 | raise Exception('No alias for {0}'.format(binding))
18 |
19 | return ffig.generators.default_generator(
20 | module_name, binding, api_classes, env, output_dir)
21 |
22 |
23 | def setup_plugin(context):
24 | bindings = [(key, value[1]) for key, value in aliases.items()]
25 | context.register(aliased_generator, bindings)
26 |
--------------------------------------------------------------------------------
/ffig/generators/swift.py:
--------------------------------------------------------------------------------
1 | # Generator module for Swift.
2 |
3 | import ffig.generators
4 | import os
5 |
6 |
7 | def generator(module_name, binding, api_classes, env, output_dir):
8 | outputs = []
9 |
10 | o = os.path.join(output_dir, module_name + '.swift')
11 | ffig.generators.generate_single_output_file(
12 | module_name, 'swift.tmpl', api_classes, env, o)
13 | outputs.append(o)
14 |
15 | o = os.path.join(output_dir, module_name + '-Bridging-Header.h')
16 | ffig.generators.generate_single_output_file(
17 | module_name, 'swift.bridging-header.tmpl', api_classes, env, o)
18 | outputs.append(o)
19 |
20 | return outputs
21 |
22 |
23 | def setup_plugin(context):
24 | context.register(
25 | generator,
26 | [
27 | ('swift', 'Swift generator using c-api')
28 | ])
29 |
30 |
--------------------------------------------------------------------------------
/tests/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | include_directories(
2 | ${CMAKE_SOURCE_DIR}/externals/catch/include/
3 | ${CMAKE_SOURCE_DIR}/externals/variant/include
4 | ${CMAKE_BINARY_DIR}/generated
5 | )
6 |
7 | add_executable(TestCppTree TestCppTree.cpp)
8 | target_link_libraries(TestCppTree Tree_c)
9 | add_dependencies(TestCppTree Tree.ffig.cpp)
10 |
11 | add_executable(TestTreeCAPI TestTreeCAPI.cpp)
12 | target_link_libraries(TestTreeCAPI Tree_c)
13 |
14 | add_executable(TestNumberCAPI TestNumberCAPI.cpp)
15 | target_link_libraries(TestNumberCAPI Number_c)
16 |
17 | add_executable(TestCppCircle TestCppCircle.cpp)
18 | target_link_libraries(TestCppCircle Shape_c)
19 | add_dependencies(TestCppCircle Shape.ffig.cpp)
20 |
21 | add_executable(TestShapeMocks TestShapeMocks.cpp)
22 | add_dependencies(TestShapeMocks Shape_c)
23 | add_dependencies(TestShapeMocks Shape.ffig.cpp_mocks)
24 |
--------------------------------------------------------------------------------
/ffig/generators/c.py:
--------------------------------------------------------------------------------
1 | # Generator module for Boost-Python.
2 |
3 | import ffig.generators
4 | import os
5 |
6 |
7 | def generator(module_name, binding, api_classes, env, output_dir):
8 | outputs = []
9 |
10 | o = os.path.join(output_dir, module_name + '_c.h')
11 | ffig.generators.generate_single_output_file(
12 | module_name, '_c.h.tmpl', api_classes, env, o)
13 | outputs.append(o)
14 |
15 | o = os.path.join(output_dir, module_name + '_c.cpp')
16 | ffig.generators.generate_single_output_file(
17 | module_name, '_c.cpp.tmpl', api_classes, env, o)
18 | outputs.append(o)
19 |
20 | return outputs
21 |
22 |
23 | def setup_plugin(context):
24 | context.register(
25 | generator,
26 | [
27 | ('c', "C API bindings to be used from other languages' FFI facilities.")
28 | ])
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/java/TestTree.java:
--------------------------------------------------------------------------------
1 | import static org.junit.Assert.assertEquals;
2 | import static org.junit.Assert.assertNull;
3 | import static org.junit.Assert.assertNotNull;
4 | import org.junit.Test;
5 |
6 | public class TestTree {
7 | @Test
8 | public void Depth()
9 | {
10 | Tree.Tree tree = new Tree.Tree(1);
11 |
12 | assertNotNull(tree.left_subtree());
13 | assertNull(tree.left_subtree().left_subtree());
14 | }
15 |
16 | @Test
17 | public void GetSetData()
18 | {
19 | Tree.Tree tree = new Tree.Tree(2);
20 |
21 | tree.set_data(42);
22 | assertEquals(tree.data(), 42);
23 | }
24 |
25 | @Test
26 | public void LifetimeExtension()
27 | {
28 | Tree.Tree tree = new Tree.Tree(2);
29 | Tree.Tree left = tree.left_subtree();
30 |
31 | tree = null;
32 |
33 | left.set_data(42);
34 | assertEquals(left.data(), 42);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/input/Tree.h:
--------------------------------------------------------------------------------
1 | #include "ffig/attributes.h"
2 | #include
3 | #include
4 |
5 | static std::mt19937 mt;
6 | static std::uniform_int_distribution d(1,10);
7 | static auto gen = []{return d(mt);};
8 |
9 | class FFIG_EXPORT Tree
10 | {
11 | int data_;
12 | std::shared_ptr left_;
13 | std::shared_ptr right_;
14 |
15 | public:
16 |
17 | // FIXME: Should not be noexcept as it allocates
18 | Tree(int levels=0) noexcept
19 | {
20 | data_ = gen();
21 | if ( levels <= 0 ) return;
22 | left_ = std::make_shared(levels-1);
23 | right_ = std::make_shared(levels-1);
24 | }
25 |
26 | Tree* left_subtree() const noexcept
27 | {
28 | return left_.get();
29 | }
30 |
31 | Tree* right_subtree() const noexcept
32 | {
33 | return right_.get();
34 | }
35 |
36 | int data() const noexcept
37 | {
38 | return data_;
39 | }
40 |
41 | void set_data(int x) noexcept
42 | {
43 | data_ = x;
44 | }
45 |
46 | };
47 |
48 |
--------------------------------------------------------------------------------
/ffig/templates/java.derived.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'java.macros' as java_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | package {{module.name}};
6 |
7 | import com.sun.jna.Library;
8 | import com.sun.jna.Native;
9 | import com.sun.jna.Pointer;
10 | import com.sun.jna.ptr.PointerByReference;
11 | import com.sun.jna.ptr.DoubleByReference;
12 | import com.sun.jna.ptr.IntByReference;
13 |
14 | public class {{class.name}} extends {{base_class.name}}
15 | {
16 | {% for method in class.constructors %}
17 |
18 | public {{class.name}}({{java_macros.method_parameters(method)}}) {
19 | PointerByReference rv = new PointerByReference();
20 | int rc = {{module.name}}CLibrary.INSTANCE.{{module.name}}_{{class.name}}_create({{java_macros.method_arguments(method, trailing_comma=True)}}rv);
21 | if(rc != 0) {
22 | throw new {{module.name}}Exception();
23 | }
24 | this.ptr = rv.getValue();
25 | }
26 | {% endfor %}
27 | }
28 |
--------------------------------------------------------------------------------
/ffig/generators/dotnet.py:
--------------------------------------------------------------------------------
1 | # Generator module for dotnet.
2 |
3 | import ffig.generators
4 | import os
5 | import shutil
6 |
7 | def generator(module_name, binding, api_classes, env, output_dir):
8 | outputs = []
9 |
10 | output_dir = os.path.join(output_dir, module_name + '.net')
11 | if not os.path.exists(output_dir):
12 | os.makedirs(output_dir)
13 |
14 | o = os.path.join(output_dir, module_name + '.cs')
15 | ffig.generators.generate_single_output_file(
16 | module_name, 'cs.tmpl', api_classes, env, o)
17 | outputs.append(o)
18 |
19 | o = os.path.join(output_dir, module_name + '.net.csproj')
20 | ffig.generators.generate_single_output_file(
21 | module_name, 'csproj.tmpl', api_classes, env, o)
22 | outputs.append(o)
23 |
24 | return outputs
25 |
26 |
27 | def setup_plugin(context):
28 | context.register(
29 | generator,
30 | [
31 | ('dotnet', 'dotnet generator using PInvoke')
32 | ])
33 |
34 |
--------------------------------------------------------------------------------
/tests/dotnet/TestTree/TestTree.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Tree_c;
3 |
4 | namespace TestFFIG
5 | {
6 | [TestFixture]
7 | public class TestTree
8 | {
9 | [Test]
10 | public void DepthIsAsConstructed()
11 | {
12 | var tree = new Tree(1);
13 |
14 | Assert.IsNotNull(tree.left_subtree());
15 | Assert.IsNull(tree.left_subtree().left_subtree());
16 | }
17 |
18 | [Test]
19 | public void DataIsAsSet()
20 | {
21 | var tree = new Tree(1);
22 |
23 | tree.set_data(42);
24 |
25 | Assert.AreEqual(tree.data(), 42);
26 | }
27 |
28 | [Test]
29 | public void LifetimeExtension()
30 | {
31 | var tree = new Tree(1);
32 | var left = tree.left_subtree();
33 |
34 | tree = null;
35 | left.set_data(42);
36 |
37 | Assert.AreEqual(left.data(), 42);
38 | }
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/ffig/templates/ffig.macros:
--------------------------------------------------------------------------------
1 | {#
2 | # comma_separated_list:
3 | # Format a list of items separated by commas. Each item is formatted
4 | # according to the callback body. Leading and trailing commas can be
5 | # added by setting leading_comma or trailing_comma to True.
6 | # Optional extra parameters can be passed at the end. These are
7 | # forwarded to the callback.
8 | #
9 | # Usage example: The example below generates Python code to join a list of
10 | # items that are converted to strings. The text to output for
11 | # each item is the body of the {% call %} block.
12 | # ''.join(
13 | # {% call(item) comma_separated_list(items) %}
14 | # str({{item}})
15 | # {% endcall %}
16 | # )
17 | #}
18 | {% macro comma_separated_list(items, leading_comma=False, trailing_comma=False) %}
19 | {% for item in items %}
20 | {% if leading_comma and loop.first %}, {% endif %}
21 | {{- caller(item, *varargs) -}}
22 | {% if trailing_comma or not loop.last %}, {% endif %}
23 | {% endfor %}
24 | {% endmacro %}
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jonathan B. Coe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/tests/src/TestCppTree.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
2 | #include
3 | #include
4 | #include "Tree_cpp.h"
5 |
6 | using CPP_API::Tree;
7 |
8 | TEST_CASE("Test subtree access", "[cpp_api::tree]")
9 | {
10 | auto root = Tree(3);
11 | auto left = root.left_subtree();
12 |
13 | REQUIRE(left.data());
14 | }
15 |
16 | TEST_CASE("Test lifetime extension", "[cpp_api::tree]")
17 | {
18 | auto root = Tree(3);
19 | auto left = root.left_subtree();
20 |
21 | // this will invalidate left unless the object created above has had its lifetime extended.
22 | root = Tree(1);
23 |
24 | REQUIRE(left.data());
25 | }
26 |
27 | TEST_CASE("Test move", "[cpp_api::tree]")
28 | {
29 | auto root = Tree(3);
30 | auto data = root.left_subtree().data();
31 |
32 | auto uprooted = std::move(root);
33 |
34 | REQUIRE(uprooted.left_subtree().data() == data);
35 | }
36 |
37 | TEST_CASE("Test noexcept", "[cpp_api::tree, noexcept]")
38 | {
39 | REQUIRE(noexcept(Tree(3)));
40 | auto root = Tree(3);
41 | REQUIRE(noexcept(root.left_subtree()));
42 | REQUIRE(noexcept(root.right_subtree()));
43 | }
44 |
--------------------------------------------------------------------------------
/tests/test_tree_python_bindings.py:
--------------------------------------------------------------------------------
1 | from tree import *
2 |
3 |
4 | def test_root_node_is_non_null():
5 | t = Tree(2)
6 | assert(t)
7 | assert(t.data())
8 |
9 |
10 | def test_left_node_is_non_null():
11 | t = Tree(2)
12 | lt = t.left_subtree()
13 | assert(lt)
14 | assert(lt.data())
15 |
16 |
17 | def test_right_node_is_non_null():
18 | t = Tree(2)
19 | rt = t.right_subtree()
20 | assert(rt)
21 | assert(rt.data())
22 |
23 |
24 | def test_right_3_node_is_null():
25 | t = Tree(2)
26 | r3t = t.right_subtree().right_subtree().right_subtree()
27 | assert(r3t is None)
28 |
29 |
30 | def test_use_of_null_node_is_caught():
31 | t = Tree(2)
32 | r3t = t.right_subtree().right_subtree().right_subtree()
33 | error_thrown = False
34 | try:
35 | r3t.data()
36 | except Exception as e:
37 | error_thrown = True
38 | assert(error_thrown)
39 |
40 |
41 | def test_subtree_handle_keeps_tree_alive():
42 | t = Tree(1)
43 | st = t.left_subtree()
44 | x = st.data()
45 | del t
46 | assert(st.data() == x)
47 |
48 |
49 | def test_tree_set_data():
50 | t = Tree(0)
51 | t.set_data(77)
52 | assert t.data() == 77
53 |
--------------------------------------------------------------------------------
/tests/boost_python/test_tree_python_bindings.py:
--------------------------------------------------------------------------------
1 | from Tree_py import *
2 |
3 |
4 | def test_root_node_is_non_null():
5 | t = Tree(2)
6 | assert(t)
7 | assert(t.data())
8 |
9 |
10 | def test_left_node_is_non_null():
11 | t = Tree(2)
12 | lt = t.left_subtree()
13 | assert(lt)
14 | assert(lt.data())
15 |
16 |
17 | def test_right_node_is_non_null():
18 | t = Tree(2)
19 | rt = t.right_subtree()
20 | assert(rt)
21 | assert(rt.data())
22 |
23 |
24 | def test_right_3_node_is_null():
25 | t = Tree(2)
26 | r3t = t.right_subtree().right_subtree().right_subtree()
27 | assert(r3t is None)
28 |
29 |
30 | def test_use_of_null_node_is_caught():
31 | t = Tree(2)
32 | r3t = t.right_subtree().right_subtree().right_subtree()
33 | error_thrown = False
34 | try:
35 | r3t.data()
36 | except Exception as e:
37 | error_thrown = True
38 | assert(error_thrown)
39 |
40 |
41 | def test_subtree_handle_keeps_tree_alive():
42 | t = Tree(1)
43 | st = t.left_subtree()
44 | x = st.data()
45 | del t
46 | assert(st.data() == x)
47 |
48 |
49 | def test_tree_set_data():
50 | t = Tree(0)
51 | t.set_data(77)
52 | assert t.data() == 77
53 |
--------------------------------------------------------------------------------
/ffig/clang/enumerations.py:
--------------------------------------------------------------------------------
1 | #===- enumerations.py - Python Enumerations ------------------*- python -*--===#
2 | #
3 | # The LLVM Compiler Infrastructure
4 | #
5 | # This file is distributed under the University of Illinois Open Source
6 | # License. See LICENSE.TXT for details.
7 | #
8 | #===------------------------------------------------------------------------===#
9 |
10 | """
11 | Clang Enumerations
12 | ==================
13 |
14 | This module provides static definitions of enumerations that exist in libclang.
15 |
16 | Enumerations are typically defined as a list of tuples. The exported values are
17 | typically munged into other types or classes at module load time.
18 |
19 | All enumerations are centrally defined in this file so they are all grouped
20 | together and easier to audit. And, maybe even one day this file will be
21 | automatically generated by scanning the libclang headers!
22 | """
23 |
24 | # Maps to CXTokenKind. Note that libclang maintains a separate set of token
25 | # enumerations from the C++ API.
26 | TokenKinds = [
27 | ('PUNCTUATION', 0),
28 | ('KEYWORD', 1),
29 | ('IDENTIFIER', 2),
30 | ('LITERAL', 3),
31 | ('COMMENT', 4),
32 | ]
33 |
34 | __all__ = ['TokenKinds']
35 |
--------------------------------------------------------------------------------
/ffig/generators/python.py:
--------------------------------------------------------------------------------
1 | # Generator module for Python 2 and 3.
2 |
3 | import ffig.generators
4 | import os
5 |
6 |
7 | def generator(module_name, binding, api_classes, env, output_dir):
8 | # Module name should be lowercase to conform with PEP-8:
9 | module_dir = os.path.join(output_dir, module_name.lower())
10 | if not os.path.exists(module_dir):
11 | os.makedirs(module_dir)
12 |
13 | o = os.path.join(module_dir, '__init__.py')
14 | ffig.generators.generate_single_output_file(
15 | module_name, '__init__.py.tmpl', api_classes, env, o)
16 |
17 | o = os.path.join(module_dir, '_py2.py')
18 | ffig.generators.generate_single_output_file(
19 | module_name, 'py2.tmpl', api_classes, env, o)
20 |
21 | o = os.path.join(module_dir, '_py3.py')
22 | ffig.generators.generate_single_output_file(
23 | module_name, 'py3.tmpl', api_classes, env, o)
24 |
25 | return [
26 | os.path.join(module_dir, x) for x in [
27 | '__init__.py',
28 | '_py2.py',
29 | '_py3.py']]
30 |
31 |
32 | def setup_plugin(context):
33 | context.register(
34 | generator,
35 | [
36 | ('python', 'Python2 and Python3 generator using ctypes')
37 | ])
38 |
--------------------------------------------------------------------------------
/tests/swift/TestShape/main.swift:
--------------------------------------------------------------------------------
1 | // FIXME(jbcoe): Use a proper Swift test framework.
2 | import Foundation
3 | import Shape
4 |
5 | var c = try! Circle(2)
6 |
7 | //
8 | // Test that area and perimeter are related as expected
9 | //
10 | if c.area() != c.perimeter() {
11 | print("A circle with radius 2.0 should have area == perimeter (ignoring units)")
12 | exit(-1)
13 | }
14 |
15 | //
16 | // Test that name is as expected
17 | //
18 | if c.name() != "Circle" {
19 | print("Circle name is not \"Circle\"")
20 | print(c.name())
21 | exit(-1)
22 | }
23 |
24 | //
25 | // Test that equality is implemented
26 | //
27 | if c.is_equal(c) != 1 {
28 | print("Any shape should be equal to itself")
29 | exit(-1)
30 | }
31 |
32 | //
33 | // Test that inequality is implemented
34 | //
35 | var p = try! Pentagon(1)
36 | if p.is_equal(c) != 0 {
37 | print("Shapes of different kind should not be considered equal to one another")
38 | exit(-1)
39 | }
40 |
41 | //
42 | // Test exception
43 | //
44 |
45 | let cc = try? Circle(-5)
46 | if cc != nil {
47 | print("Constructing a circle with a negative radius should be an exception")
48 | exit(-1)
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/tests/swift/TestTree/main.swift:
--------------------------------------------------------------------------------
1 | // FIXME(jbcoe): Use a proper Swift test framework.
2 | import Foundation
3 | import Tree
4 |
5 | var t = Tree(1)
6 |
7 | //
8 | // Test that depth is as constructed
9 | //
10 | if t.left_subtree() == nil {
11 | print("Tree is missing a left subtree")
12 | exit(-1)
13 | }
14 |
15 | if t.left_subtree()!.left_subtree() != nil {
16 | print("Tree has an unexpected a left-left sub-subtree")
17 | exit(-1)
18 | }
19 |
20 | if t.right_subtree() == nil {
21 | print("Tree is missing a right subtree")
22 | exit(-1)
23 | }
24 |
25 | if t.right_subtree()!.right_subtree() != nil {
26 | print("Tree has an unexpected a right-right sub-subtree")
27 | exit(-1)
28 | }
29 |
30 | //
31 | // Test that data is as set
32 | //
33 | t.set_data(82)
34 |
35 | if t.data() != 82 {
36 | print("Tree data is not as set")
37 | exit(-1)
38 | }
39 |
40 | //
41 | // Test lifetime extension
42 | //
43 | let left = t.left_subtree()!
44 | t = Tree(1)
45 |
46 | // If root lifetime was not extended then this would access invalid memory
47 | left.set_data(42);
48 | if left.data() != 42 {
49 | print("Left subtree data is not as set")
50 | exit(-1)
51 | }
52 |
--------------------------------------------------------------------------------
/ffig/templates/_c.macros:
--------------------------------------------------------------------------------
1 | {%- import 'ffig.macros' as ffig_macros -%}
2 |
3 | {#
4 | # method_parameters:
5 | # Format a parameter list with types converted to C types. This is suitable
6 | # for function parameter lists in declarations or definitions.
7 | # Commas are inserted between each parameter, and can be added to the start
8 | # or end of the list by setting leading_comma or trailing_comma to True.
9 | #}
10 | {%- macro method_parameters(module, method, leading_comma=False, trailing_comma=False) -%}
11 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
12 | {{arg.type | to_c(module.name)}} {{arg.name}}
13 | {%- endcall -%}
14 | {%- endmacro -%}
15 |
16 | {#
17 | # method_arguments:
18 | # Format an argument list with appropriate casts to restore the C++ type.
19 | # This is suitable for argument lists when calling a function.
20 | # Commas are inserted between each argument, and can be added to the start
21 | # or end of the list by setting leading_comma or trailing_comma to True.
22 | #}
23 | {%- macro method_arguments(method, leading_comma=False, trailing_comma=False) -%}
24 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
25 | {{arg | restore_cpp_type}}
26 | {%- endcall -%}
27 | {%- endmacro -%}
28 |
29 |
--------------------------------------------------------------------------------
/cmake/dotnet.cmake:
--------------------------------------------------------------------------------
1 | function(add_dotnet_project)
2 | set(oneValueArgs NAME DIRECTORY)
3 | set(multiValueArgs SOURCES)
4 | cmake_parse_arguments(add_dotnet_project "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
5 |
6 | set(NAME ${add_dotnet_project_NAME})
7 | set(DIRECTORY ${add_dotnet_project_DIRECTORY})
8 | set(SOURCES ${add_dotnet_project_SOURCES})
9 |
10 | if(NOT CMAKE_DOTNET_OUTPUT_DIRECTORY)
11 | set(CMAKE_DOTNET_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dotnet.output)
12 | endif()
13 | file(MAKE_DIRECTORY ${CMAKE_DOTNET_OUTPUT_DIRECTORY}/${NAME})
14 |
15 | set(OUTPUT ${CMAKE_DOTNET_OUTPUT_DIRECTORY}/${NAME}/${NAME}.dll)
16 | set(CSPROJ ${NAME}.csproj)
17 |
18 | set(DEPENDENCIES
19 | ${SOURCES}
20 | ${DIRECTORY}/${CSPROJ}
21 | )
22 |
23 | add_custom_command(OUTPUT ${OUTPUT}
24 | COMMAND ${CMAKE_COMMAND} -E copy
25 | ${DIRECTORY}/${CSPROJ}
26 | ${CMAKE_DOTNET_OUTPUT_DIRECTORY}/${NAME}
27 | COMMAND ${CMAKE_COMMAND} -E copy
28 | ${SOURCES}
29 | ${CMAKE_DOTNET_OUTPUT_DIRECTORY}/${NAME}
30 | COMMAND dotnet build ${CSPROJ} -o .
31 | DEPENDS ${DEPENDENCIES}
32 | WORKING_DIRECTORY ${CMAKE_DOTNET_OUTPUT_DIRECTORY}/${NAME}
33 | COMMENT "Building DOTNET assembly ${NAME}.dll"
34 | )
35 |
36 | add_custom_target(${NAME} ALL
37 | DEPENDS ${OUTPUT})
38 |
39 | endfunction()
40 |
--------------------------------------------------------------------------------
/tests/src/TestShapeMocks.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this
2 | // in one cpp file
3 | #include "Shape_mocks.h"
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | TEST_CASE("MockAbstractShape", "[mocks::MockAbstractShape]")
10 | {
11 | static_assert(std::is_base_of::value,"");
12 |
13 | GIVEN("A mock shape with expected values set")
14 | {
15 | mocks::MockAbstractShape shape;
16 | shape.area_ = 10;
17 | shape.perimeter_ = 25;
18 | shape.name_ = "Mock";
19 | shape.is_equal_ = false;
20 |
21 | THEN("All method invocations return expected values")
22 | {
23 | REQUIRE(shape.area() == 10);
24 | REQUIRE(shape.is_equal(nullptr) == false);
25 | REQUIRE(strcmp(shape.name(), "Mock")==0);
26 | REQUIRE(shape.perimeter() == 25);
27 | }
28 | }
29 |
30 | GIVEN("A mock shape with function objects returning values")
31 | {
32 | mocks::MockAbstractShape shape;
33 | int area_f_count = 0;
34 | shape.area_ = [&area_f_count]{ ++area_f_count; return 0.0;};
35 |
36 | THEN("All method invocations return expected values through function invocations")
37 | {
38 | REQUIRE(shape.area() == 0.0);
39 | REQUIRE(area_f_count == 1);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | AccessModifierOffset: -2
2 | AlignEscapedNewlinesLeft: false
3 | AlignTrailingComments: true
4 | AllowAllParametersOfDeclarationOnNextLine: true
5 | AllowShortFunctionsOnASingleLine: false
6 | AllowShortIfStatementsOnASingleLine: true
7 | AllowShortLoopsOnASingleLine: true
8 | AlwaysBreakBeforeMultilineStrings: false
9 | AlwaysBreakTemplateDeclarations: true
10 | BinPackParameters: true
11 | BreakBeforeBinaryOperators: false
12 | BreakBeforeBraces: Allman
13 | BreakConstructorInitializersBeforeComma: false
14 | ColumnLimit: 80
15 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
16 | ConstructorInitializerIndentWidth: 4
17 | Cpp11BracedListStyle: true
18 | DerivePointerBinding: false
19 | ExperimentalAutoDetectBinPacking: false
20 | IndentCaseLabels: false
21 | IndentFunctionDeclarationAfterType: false
22 | IndentWidth: 2
23 | MaxEmptyLinesToKeep: 2
24 | NamespaceIndentation: All
25 | ObjCSpaceBeforeProtocolList: true
26 | PenaltyBreakComment: 60
27 | PenaltyBreakFirstLessLess: 120
28 | PenaltyBreakString: 1000
29 | PenaltyExcessCharacter: 1000000
30 | PenaltyReturnTypeOnItsOwnLine: 60
31 | PointerBindsToType: true
32 | SpaceAfterControlStatementKeyword: true
33 | SpaceInEmptyParentheses: false
34 | SpacesBeforeTrailingComments: 1
35 | SpacesInCStyleCastParentheses: false
36 | SpacesInParentheses: false
37 | Standard: Cpp11
38 | TabWidth: 2
39 | UseTab: Never
40 |
--------------------------------------------------------------------------------
/ffig/templates/swift.macros:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros %}
2 |
3 | {#
4 | # method_parameters:
5 | # Generates a list of parameter types and names suitable for use in a
6 | # function declaration or definition. Commas are inserted between each
7 | # element of the list. Leading and trailing commas can be added by setting
8 | # leading_comma or trailing_comma to True as appropriate. This is useful
9 | # if there are other parameters that must be declared.
10 | #}
11 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg | to_swift_param}}
14 | {%- endcall %}
15 | {% endmacro %}
16 |
17 | {#
18 | # method_arguments:
19 | # Generates a list of argument names suitable for use in a
20 | # function call. Commas are inserted between each element of the list.
21 | # Leading and trailing commas can be added by setting leading_comma or
22 | # trailing_comma to True as appropriate. This is useful if there are other
23 | # parameters that must be declared.
24 | #}
25 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg | to_swift_arg}}
28 | {%- endcall %}
29 | {% endmacro %}
30 |
31 |
--------------------------------------------------------------------------------
/ffig/annotations.py:
--------------------------------------------------------------------------------
1 | from ffig.clang.cindex import CursorKind, TypeKind
2 |
3 |
4 | def _set_impl_name(o):
5 | o.impl_name = o.name
6 | names = [a for a in o.annotations if a.startswith("FFIG:NAME:")]
7 | if names:
8 | o.name = names[-1].replace("FFIG:NAME:", "")
9 |
10 |
11 | def apply_class_annotations(model_class):
12 | _set_impl_name(model_class)
13 |
14 | for m in model_class.methods:
15 | _set_impl_name(m)
16 |
17 | if "FFIG:PROPERTY" in m.annotations:
18 | m.is_property = True
19 | if m.is_pure_virtual:
20 | model_class.is_abstract = True
21 | if m.is_virtual:
22 | model_class.is_virtual = True
23 |
24 | if m.return_type.kind == TypeKind.VOID:
25 | m.returns_void = True
26 | elif m.return_type.kind in [TypeKind.INT, TypeKind.BOOL, TypeKind.DOUBLE]:
27 | pass
28 | elif m.return_type.kind == TypeKind.POINTER and m.return_type.pointee.kind == TypeKind.CHAR_S:
29 | m.returns_chars = True
30 | elif m.return_type.kind == TypeKind.POINTER:
31 | m.returns_sub_object = True
32 | m.returns_nullable = True
33 | elif m.return_type.kind == TypeKind.LVALUEREFERENCE:
34 | m.returns_sub_object = True
35 | m.returns_nullable = False
36 | elif m.return_type.kind == TypeKind.RECORD:
37 | m.returns_object_by_value = True
38 | return model_class
39 |
--------------------------------------------------------------------------------
/tests/dotnet/TestShape/TestShape.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Shape_c;
3 |
4 | namespace TestFFIG
5 | {
6 | [TestFixture]
7 | public class TestShape
8 | {
9 | [Test]
10 | public void CircleName()
11 | {
12 | double radius = 2.0;
13 | var circle = new Circle(radius);
14 |
15 | Assert.AreEqual(circle.name, "Circle");
16 | }
17 |
18 | [Test]
19 | public void CircleArea()
20 | {
21 | double radius = 2.0;
22 | var circle = new Circle(radius);
23 |
24 | Assert.AreEqual(circle.area, 12.56637061436, 10);
25 | }
26 |
27 | [Test]
28 | public void CirclePerimeter()
29 | {
30 | double radius = 2.0;
31 | var circle = new Circle(radius);
32 |
33 | Assert.AreEqual(circle.perimeter, 12.5663706144, 10);
34 | }
35 |
36 | [Test]
37 | public void CircleEquality()
38 | {
39 | var circle1 = new Circle(3);
40 | var circle2 = new Circle(3);
41 |
42 | Assert.AreEqual(circle1.is_equal(circle2), 1);
43 | }
44 |
45 | [Test]
46 | public void CircleInequality()
47 | {
48 | var circle1 = new Circle(3);
49 | var circle2 = new Circle(4);
50 |
51 | Assert.AreEqual(circle1.is_equal(circle2), 0);
52 | }
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/tests/test_shape_python_bindings.py:
--------------------------------------------------------------------------------
1 | import math
2 | import nose
3 | import shape
4 |
5 |
6 | def test_shape_Circle_is_called_Circle():
7 | c = shape.Circle(3)
8 | assert c.name == "Circle"
9 |
10 |
11 | def test_shape_Circle_has_expected_area():
12 | r = 2.0
13 | c = shape.Circle(r)
14 | a = math.pi * r * r
15 | nose.tools.assert_almost_equal(c.area, a)
16 |
17 |
18 | def test_shape_Circle_has_expected_perimeter():
19 | r = 2.0
20 | c = shape.Circle(r)
21 | p = 2.0 * math.pi * r
22 | nose.tools.assert_almost_equal(c.perimeter, p)
23 |
24 |
25 | def test_shape_Circle_is_equal_to_itself():
26 | c = shape.Circle(2)
27 | assert c.is_equal(c)
28 |
29 |
30 | def test_shape_Circle_is_equal_to_another_circle_with_the_same_radius():
31 | c1 = shape.Circle(2)
32 | c2 = shape.Circle(2)
33 | assert c1.is_equal(c2)
34 |
35 |
36 | def test_shape_Circle_is_not_equal_to_circle_with_different_radius():
37 | c1 = shape.Circle(2)
38 | c2 = shape.Circle(3)
39 | assert not c1.is_equal(c2)
40 |
41 |
42 | def test_shape_Circle_is_not_equal_to_square():
43 | c = shape.Circle(2)
44 | s = shape.Square(2)
45 | assert not c.is_equal(s)
46 |
47 |
48 | @nose.tools.raises(Exception)
49 | def test_exception_on_negative_radius():
50 | shape.Circle(-1)
51 |
52 |
53 | def test_exception_text_is_a_string():
54 | try:
55 | shape.Circle(-1)
56 | except shape.Shape_error as e:
57 | assert isinstance(str(e), str)
58 |
--------------------------------------------------------------------------------
/tests/boost_python/test_shape_python_bindings.py:
--------------------------------------------------------------------------------
1 | import math
2 | import nose
3 | import Shape_py as shape
4 |
5 |
6 | def test_shape_Circle_is_called_Circle():
7 | c = shape.Circle(3)
8 | assert c.name == "Circle"
9 |
10 |
11 | def test_shape_Circle_has_expected_area():
12 | r = 2.0
13 | c = shape.Circle(r)
14 | a = math.pi * r * r
15 | nose.tools.assert_almost_equal(c.area, a)
16 |
17 |
18 | def test_shape_Circle_has_expected_perimeter():
19 | r = 2.0
20 | c = shape.Circle(r)
21 | p = 2.0 * math.pi * r
22 | nose.tools.assert_almost_equal(c.perimeter, p)
23 |
24 |
25 | def test_shape_Circle_is_equal_to_itself():
26 | c = shape.Circle(2)
27 | assert c.is_equal(c)
28 |
29 |
30 | def test_shape_Circle_is_equal_to_another_circle_with_the_same_radius():
31 | c1 = shape.Circle(2)
32 | c2 = shape.Circle(2)
33 | assert c1.is_equal(c2)
34 |
35 |
36 | def test_shape_Circle_is_not_equal_to_circle_with_different_radius():
37 | c1 = shape.Circle(2)
38 | c2 = shape.Circle(3)
39 | assert not c1.is_equal(c2)
40 |
41 |
42 | def test_shape_Circle_is_not_equal_to_square():
43 | c = shape.Circle(2)
44 | s = shape.Square(2)
45 | assert not c.is_equal(s)
46 |
47 |
48 | @nose.tools.raises(Exception)
49 | def test_exception_on_negative_radius():
50 | shape.Circle(-1)
51 |
52 | def test_exception_text_is_a_string():
53 | try:
54 | shape.Circle(-1)
55 | except Exception as e:
56 | assert str(e) == 'Circle radius "-1.000000" must be non-negative.'
57 |
--------------------------------------------------------------------------------
/tests/java/TestShape.java:
--------------------------------------------------------------------------------
1 | import static org.junit.Assert.assertEquals;
2 | import org.junit.Test;
3 |
4 | public class TestShape {
5 | @Test
6 | public void CircleHasNameCircle()
7 | {
8 | Shape.Circle circle = new Shape.Circle(2);
9 |
10 | assertEquals(circle.name(), "Circle");
11 | }
12 |
13 | @Test
14 | public void SquareArea()
15 | {
16 | Shape.Square square = new Shape.Square(3);
17 |
18 | assertEquals(square.area(), 9.0, 0.0);
19 | }
20 |
21 | @Test
22 | public void SquarePerimeter()
23 | {
24 | Shape.Square square = new Shape.Square(3);
25 |
26 | assertEquals(square.perimeter(), 12.0, 0.0);
27 | }
28 |
29 | @Test
30 | public void SquareIsEqualToIdenticalSquare()
31 | {
32 | Shape.Square square = new Shape.Square(3);
33 | Shape.Square identicalSquare = new Shape.Square(3);
34 |
35 | assertEquals(square.is_equal(identicalSquare), 1);
36 | }
37 |
38 | @Test
39 | public void SquareIsNotEqualToCircle()
40 | {
41 | Shape.Square square = new Shape.Square(3);
42 | Shape.Circle circle = new Shape.Circle(2);
43 |
44 | assertEquals(square.is_equal(circle), 0);
45 | }
46 |
47 | @Test
48 | public void SquareIsEqualToDifferentSquare()
49 | {
50 | Shape.Square square = new Shape.Square(3);
51 | Shape.Square differentSquare = new Shape.Square(4);
52 |
53 | assertEquals(square.is_equal(differentSquare), 0);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/BUILD:
--------------------------------------------------------------------------------
1 | # Uses of Bazel build rules for FFIG.
2 |
3 | load(
4 | "//:ffig.bzl",
5 | "ffig_c_library",
6 | "ffig_csharp_src",
7 | "ffig_py_src",
8 | "ffig_ruby_src",
9 | "ffig_swift_src",
10 | )
11 |
12 | filegroup(
13 | name = "ffig_headers",
14 | srcs = glob(["ffig/include/**/*.h"]),
15 | )
16 |
17 | cc_library(
18 | name = "libffig",
19 | hdrs = glob(["ffig/include/ffig/*.h"]),
20 | )
21 |
22 | py_binary(
23 | name = "ffig_py",
24 | srcs = glob(["ffig/**/*.py"]),
25 | data = glob([
26 | "ffig/**/*.tmpl",
27 | "ffig/**/*.macros",
28 | "ffig/**/*.h",
29 | ]),
30 | main = "__main__.py",
31 | )
32 |
33 | FFIG_C_OPTS = [
34 | "-Itests/input",
35 | "-Iffig/include",
36 | "-std=c++14",
37 | "-DShape_c_EXPORTS",
38 | ]
39 |
40 | ffig_c_library(
41 | name = "Shape",
42 | srcs = ["tests/input/Shape.h"],
43 | copts = FFIG_C_OPTS,
44 | module = "Shape",
45 | )
46 |
47 | ffig_csharp_src(
48 | name = "Shape.net.src",
49 | srcs = ["tests/input/Shape.h"],
50 | copts = FFIG_C_OPTS,
51 | module = "Shape",
52 | )
53 |
54 | ffig_py_src(
55 | name = "Shape.py.src",
56 | srcs = ["tests/input/Shape.h"],
57 | copts = FFIG_C_OPTS,
58 | module = "shape",
59 | )
60 |
61 | ffig_swift_src(
62 | name = "Shape.swift.src",
63 | srcs = ["tests/input/Shape.h"],
64 | copts = FFIG_C_OPTS,
65 | module = "Shape",
66 | )
67 |
68 | ffig_ruby_src(
69 | name = "Shape.rb.src",
70 | srcs = ["tests/input/Shape.h"],
71 | copts = FFIG_C_OPTS,
72 | module = "Shape",
73 | )
74 |
--------------------------------------------------------------------------------
/tests/test_type_hints_in_bindings.py:
--------------------------------------------------------------------------------
1 | from asset import *
2 | from shape import *
3 | from tree import *
4 | from nose.tools import make_decorator
5 |
6 | if sys.version_info[0] == 3:
7 | from typing import *
8 |
9 |
10 | def python3only(func):
11 | name = func.__name__
12 |
13 | def newfunc(*arg, **kw):
14 | if sys.version_info[0] == 3:
15 | func(*arg, **kw)
16 | newfunc = make_decorator(func)(newfunc)
17 | return newfunc
18 |
19 |
20 | @python3only
21 | def test_type_hints_for_class_initialisor():
22 | p = Tree(levels=3)
23 | hs = get_type_hints(p.__init__)
24 |
25 | assert len(hs) == 2
26 | assert hs["return"] == Tree
27 | assert hs["levels"] == Union[int, type(None)]
28 |
29 |
30 | @python3only
31 | def test_type_hints_for_initialisor_of_impl_class():
32 | p = Pentagon(1.0)
33 | hs = get_type_hints(p.__init__)
34 |
35 | assert len(hs) == 2
36 | assert hs["return"] == Pentagon
37 | assert hs["side"] == Union[float, type(None)]
38 |
39 |
40 | @python3only
41 | def test_type_hints_for_class_method_with_zero_arguments():
42 | t = Tree(1)
43 | hs = get_type_hints(t.data)
44 |
45 | assert len(hs) == 1
46 | assert hs["return"] == int
47 |
48 |
49 | @python3only
50 | def test_type_hints_for_class_method_with_one_argument():
51 | c = Circle(4.0)
52 | hs = get_type_hints(c.is_equal)
53 |
54 | assert len(hs) == 2
55 | assert hs["return"] == int
56 | assert hs["s"] == AbstractShape
57 |
58 |
59 | @python3only
60 | def test_type_hints_for_class_method_returning_fwd_declared_type():
61 | t = Tree(levels=5)
62 | hs = get_type_hints(t.left_subtree)
63 |
64 | assert len(hs) == 1
65 | assert hs["return"] == Tree
66 |
--------------------------------------------------------------------------------
/tests/cppmodel/test_extend_model.py:
--------------------------------------------------------------------------------
1 | from util import get_named_tu
2 | import ffig.cppmodel
3 | import nose
4 | from nose.tools import assert_equals
5 |
6 |
7 | def test_new_class_is_added():
8 | tu_a = get_named_tu('class A{};', 'a.cpp')
9 | tu_b = get_named_tu('class B{};', 'b.cpp')
10 |
11 | model = ffig.cppmodel.Model(tu_a)
12 | model.extend(tu_b)
13 | classes = model.classes
14 |
15 | assert len(classes) == 2
16 |
17 |
18 | def test_duplicate_class_is_ignored():
19 | tu_a = get_named_tu('class A{};', 'a.cpp')
20 |
21 | model = ffig.cppmodel.Model(tu_a)
22 | model.extend(tu_a)
23 | classes = model.classes
24 |
25 | assert len(classes) == 1
26 |
27 |
28 | @nose.tools.raises(Exception)
29 | def test_multiply_defined_class_is_an_error():
30 | tu_a = get_named_tu('class A{};', 'a.cpp')
31 | tu_a_too = get_named_tu('class A{};', 'a_too.cpp')
32 |
33 | model = ffig.cppmodel.Model(tu_a)
34 | model.extend(tu_a_too)
35 |
36 |
37 | def test_new_function_is_added():
38 | tu_f = get_named_tu('void f(int);', 'f.cpp')
39 | tu_g = get_named_tu('void g(int);', 'g.cpp')
40 |
41 | model = ffig.cppmodel.Model(tu_f)
42 | model.extend(tu_g)
43 |
44 | assert len(model.functions) == 2
45 |
46 |
47 | def test_function_with_different_args_is_added():
48 | tu_f = get_named_tu('void f(int);', 'f.cpp')
49 | tu_g = get_named_tu('void f(double);', 'g.cpp')
50 |
51 | model = ffig.cppmodel.Model(tu_f)
52 | model.extend(tu_g)
53 |
54 | assert len(model.functions) == 2
55 |
56 |
57 | def test_duplicate_function_is_ignored():
58 | tu_f = get_named_tu('void f(int);', 'f.cpp')
59 | tu_f_too = get_named_tu('void f(int);', 'f_too.cpp')
60 |
61 | model = ffig.cppmodel.Model(tu_f)
62 | model.extend(tu_f_too)
63 |
64 | assert len(model.functions) == 1
65 |
--------------------------------------------------------------------------------
/ffig/generators/java.py:
--------------------------------------------------------------------------------
1 | # Generator module for Java.
2 |
3 | import ffig.generators
4 | import os
5 |
6 |
7 | def _render_template_to_file(filename, template_name, env, data):
8 | template = env.get_template(template_name)
9 | with open(filename, 'w') as output_file:
10 | output_file.write(template.render(data))
11 |
12 |
13 | def generator(module_name, binding, api_classes, env, output_dir):
14 | outputs = []
15 |
16 | output_dir = os.path.join(output_dir, 'java', 'src', module_name)
17 | if not os.path.exists(output_dir):
18 | os.makedirs(output_dir)
19 |
20 | o = os.path.join(output_dir, module_name + 'CLibrary.java')
21 | d = {'module': {'name': module_name}, 'classes': api_classes}
22 | _render_template_to_file(o, 'java.interop.tmpl', env, d)
23 | outputs.append(o)
24 |
25 | o = os.path.join(output_dir, module_name + 'Exception.java')
26 | d = {'module': {'name': module_name}, 'classes': api_classes}
27 | _render_template_to_file(o, 'java.exception.tmpl', env, d)
28 | outputs.append(o)
29 |
30 | for cls in api_classes:
31 | o = os.path.join(output_dir, cls.name + '.java')
32 | d = {'module': {'name': module_name}, 'class': cls}
33 | _render_template_to_file(o, 'java.tmpl', env, d)
34 | outputs.append(o)
35 |
36 | for impl in cls.impls:
37 | o = os.path.join(output_dir, impl.name + '.java')
38 | d = {'module': {'name': module_name},
39 | 'base_class': cls, 'class': impl}
40 | _render_template_to_file(o, 'java.derived.tmpl', env, d)
41 | outputs.append(o)
42 |
43 | return outputs
44 |
45 |
46 | def setup_plugin(context):
47 | context.register(
48 | generator,
49 | [
50 | ('java', 'Java generator using JNA and a c-api')
51 | ])
52 |
--------------------------------------------------------------------------------
/tests/src/TestCppCircle.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
2 | #include
3 | #include
4 | #include "Shape_cpp.h"
5 |
6 | using CPP_API::Circle;
7 | using CPP_API::Square;
8 | using namespace std::string_literals;
9 |
10 | static const double pi = 3.14159265359;
11 |
12 | TEST_CASE( "Unit radius circle has area pi * r * r", "[cpp_api::circle]" ) {
13 | const double r = 1.0;
14 | const Circle c(r);
15 |
16 | REQUIRE(c.area() == pi * r * r);
17 | }
18 |
19 | TEST_CASE( "Unit radius circle has circumference 2 * pi * r", "[cpp_api::circle]" ) {
20 | const double r = 1.0;
21 | const Circle c(r);
22 |
23 | REQUIRE(c.perimeter() == 2 * pi * r);
24 | }
25 |
26 | TEST_CASE( "circle has name 'circle'", "[cpp_api::circle]" ) {
27 | const double r = 1.0;
28 | const Circle c(r);
29 |
30 | REQUIRE(c.name() == "Circle"s);
31 | }
32 |
33 | TEST_CASE( "circle is_equal to self", "[cpp_api::circle]" ) {
34 | const double r = 1.0;
35 | const Circle c(r);
36 |
37 | REQUIRE(c.is_equal(&c));
38 | }
39 |
40 | TEST_CASE( "circle is_equal to circle with same radius", "[cpp_api::circle]" ) {
41 | const double r = 1.0;
42 | const Circle c(r);
43 | const Circle c2(r);
44 |
45 | REQUIRE(c.is_equal(&c2));
46 | }
47 |
48 | TEST_CASE( "circle is not equal to circle with different radius", "[cpp_api::circle]" ) {
49 | const double r = 1.0;
50 | const Circle c(r);
51 | const Circle c2(r+1.0);
52 |
53 | REQUIRE(!c.is_equal(&c2));
54 | }
55 |
56 | TEST_CASE( "circle is not equal to square", "[cpp_api::circle]" ) {
57 | const double r = 1.0;
58 | const Circle c(r);
59 | const Square s(r);
60 | REQUIRE(!c.is_equal(&s));
61 | }
62 |
63 | TEST_CASE( "circle with negative radius raises exception", "[cpp_api::circle]" ) {
64 | REQUIRE_THROWS_AS(Circle(-1), Circle::exception);
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/tests/go/Shape_test.go:
--------------------------------------------------------------------------------
1 | package gotests
2 |
3 | import (
4 | "Shape"
5 | "math"
6 | "testing"
7 | )
8 |
9 | const tolerance = 1e-11
10 |
11 | func Test_Circle_Area(t *testing.T) {
12 | r := 5.0
13 | s, err := Shape.Circle_create(r)
14 | if (err) {
15 | t.Error(`Failed to create a Circle`)
16 | }
17 |
18 | area, err := s.Area()
19 | if (err) {
20 | t.Error(`Failed to call Circle.Area()`)
21 | }
22 | if math.Abs(area - (math.Pi * r * r)) > tolerance {
23 | t.Error(`Circle.Area() does not match expectation`)
24 | }
25 | }
26 |
27 | func Test_Circle_Perimeter(t *testing.T) {
28 | r := 5.0
29 | s, err := Shape.Circle_create(r)
30 | if (err) {
31 | t.Error(`Failed to create a Circle`)
32 | }
33 |
34 | perim, err := s.Perimeter()
35 | if (err) {
36 | t.Error(`Failed to call Circle.Perimeter()`)
37 | }
38 | if math.Abs(perim - (2.0 * math.Pi * r)) > tolerance {
39 | t.Error(`Circle.Perimeter() does not match expectation`)
40 | }
41 | }
42 |
43 | func Test_Circle_Is_equal(t *testing.T) {
44 | r := 5.0
45 | s, err := Shape.Circle_create(r)
46 | if (err) {
47 | t.Error(`Failed to create a Circle`)
48 | }
49 |
50 | s2, err := Shape.Circle_create(r)
51 | if (err) {
52 | t.Error(`Failed to create a Circle`)
53 | }
54 |
55 | result, err := s.Is_equal(s2)
56 | if (err) {
57 | t.Error(`Failed to call Circle.Is_equal()`)
58 | }
59 | if result == 0 {
60 | t.Error(`Circle.Is_equal() does not match expectation (expected non-zero)`)
61 | }
62 |
63 | s3, err := Shape.Circle_create(2.0 * r)
64 | if err {
65 | t.Error(`Failed to create a Circle`)
66 | }
67 |
68 | result, err = s.Is_equal(s3)
69 | if result != 0 {
70 | t.Error(`Circle.Is_equal() does not match expectation (expected zero)`)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ffig/templates/java.interop.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'java.macros' as java_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | package {{module.name}};
6 |
7 | import com.sun.jna.Library;
8 | import com.sun.jna.Native;
9 | import com.sun.jna.Pointer;
10 | import com.sun.jna.ptr.PointerByReference;
11 | import com.sun.jna.ptr.DoubleByReference;
12 | import com.sun.jna.ptr.IntByReference;
13 |
14 | interface {{module.name}}CLibrary extends Library
15 | {
16 | // FIXME: Handle different OSs in code rather than in the template.
17 | // The JAR file should not be OS-specific.
18 | static {{module.name}}CLibrary INSTANCE = ({{module.name}}CLibrary)Native.loadLibrary(
19 | "{{module.name}}_c", {{module.name}}CLibrary.class);
20 |
21 | abstract void {{module.name}}_clear_error();
22 |
23 | abstract String {{module.name}}_error();
24 |
25 | {% for class in classes %}
26 | abstract int {{module.name}}_{{class.name}}_dispose(Pointer o);
27 | {% if not class.is_abstract %}
28 | {% for method in class.constructors %}
29 |
30 | abstract int {{module.name}}_{{class.name}}_create({{java_macros.method_c_parameters(method, trailing_comma=True)}}PointerByReference o);
31 | {% endfor %}
32 | {% endif %}
33 | {% for method in class.methods %}
34 |
35 | {% if not method.returns_void %}
36 | abstract int {{module.name}}_{{class.name}}_{{method.name}}(Pointer o, {{java_macros.method_c_parameters(method, trailing_comma=True)}}{{method.return_type|to_java_output_param}} rv);
37 | {% else %}
38 | abstract int {{module.name}}_{{class.name}}_{{method.name}}(Pointer o, {{java_macros.method_c_parameters(method)}});
39 | {% endif %}
40 | {% endfor %}
41 | {% for impl in class.impls %}
42 | {% for method in impl.constructors %}
43 |
44 | abstract int {{module.name}}_{{impl.name}}_create({{java_macros.method_c_parameters(method, trailing_comma=True)}}PointerByReference o);
45 | {% endfor %}
46 | {% endfor %}
47 | {% endfor %}
48 | }
49 |
--------------------------------------------------------------------------------
/ffig/templates/java.macros:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros %}
2 |
3 | {#
4 | # method_parameters:
5 | # Generates a list of parameter types and names suitable for use in a
6 | # function declaration or definition. Commas are inserted between each
7 | # element of the list. Leading and trailing commas can be added by setting
8 | # leading_comma or trailing_comma to True as appropriate. This is useful
9 | # if there are other parameters that must be declared.
10 | #}
11 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg | to_java_param}}
14 | {%- endcall %}
15 | {% endmacro %}
16 |
17 | {#
18 | # method_c_parameters:
19 | # Generates a list of parameter types and names suitable for use in a C
20 | # function declaration or definition. Commas are inserted between each
21 | # element of the list. Leading and trailing commas can be added by setting
22 | # leading_comma or trailing_comma to True as appropriate. This is useful
23 | # if there are other parameters that must be declared.
24 | #}
25 | {% macro method_c_parameters(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg | to_java_c_param}}
28 | {%- endcall %}
29 | {% endmacro %}
30 |
31 | {#
32 | # method_arguments:
33 | # Generates a list of argument names suitable for use in a
34 | # function call. Commas are inserted between each element of the list.
35 | # Leading and trailing commas can be added by setting leading_comma or
36 | # trailing_comma to True as appropriate. This is useful if there are other
37 | # parameters that must be declared.
38 | #}
39 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
40 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
41 | {{arg | java_to_c_arg}}
42 | {%- endcall %}
43 | {% endmacro %}
--------------------------------------------------------------------------------
/ffig/templates/cs.macros:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros %}
2 |
3 | {#
4 | # method_parameters:
5 | # Generates a list of parameter types and names suitable for use in a
6 | # function declaration or definition. Commas are inserted between each
7 | # element of the list. Leading and trailing commas can be added by setting
8 | # leading_comma or trailing_comma to True as appropriate. This is useful
9 | # if there are other parameters that must be declared.
10 | #}
11 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg | to_dotnet_param}}
14 | {%- endcall %}
15 | {% endmacro %}
16 |
17 | {#
18 | # method_c_parameters:
19 | # Generates a list of parameter types and names suitable for use in a C
20 | # function declaration or definition. Commas are inserted between each
21 | # element of the list. Leading and trailing commas can be added by setting
22 | # leading_comma or trailing_comma to True as appropriate. This is useful
23 | # if there are other parameters that must be declared.
24 | #}
25 | {% macro method_c_parameters(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg | to_dotnet_c_param}}
28 | {%- endcall %}
29 | {% endmacro %}
30 |
31 | {#
32 | # method_arguments:
33 | # Generates a list of argument names suitable for use in a
34 | # function call. Commas are inserted between each element of the list.
35 | # Leading and trailing commas can be added by setting leading_comma or
36 | # trailing_comma to True as appropriate. This is useful if there are other
37 | # parameters that must be declared.
38 | #}
39 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
40 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
41 | {{arg | dotnet_to_c_arg}}
42 | {%- endcall %}
43 | {% endmacro %}
44 |
45 |
--------------------------------------------------------------------------------
/tests/src/TestTreeCAPI.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this
2 | // in one cpp file
3 | #include "Tree_c.h"
4 | #include
5 | #include
6 |
7 | TEST_CASE("Test data mutability", "[Tree_CAPI]")
8 | {
9 | Tree_Tree root = Tree_Tree_create_noexcept(3);
10 |
11 | Tree_Tree_set_data_noexcept(root, 42);
12 |
13 | REQUIRE(Tree_Tree_data_noexcept(root) == 42);
14 |
15 | Tree_Tree_dispose(root);
16 | }
17 |
18 | TEST_CASE("Test left subtree access", "[Tree_CAPI]")
19 | {
20 | Tree_Tree root = Tree_Tree_create_noexcept(3);
21 | Tree_Tree left = Tree_Tree_left_subtree_noexcept(root);
22 |
23 | Tree_Tree_set_data_noexcept(left, 42);
24 | REQUIRE(Tree_Tree_data_noexcept(left) == 42);
25 |
26 | Tree_Tree_dispose(root);
27 | Tree_Tree_dispose(left);
28 | }
29 |
30 | TEST_CASE("Test right subtree access", "[Tree_CAPI]")
31 | {
32 | Tree_Tree root = Tree_Tree_create_noexcept(3);
33 | Tree_Tree right = Tree_Tree_right_subtree_noexcept(root);
34 |
35 | Tree_Tree_set_data_noexcept(right, 42);
36 | REQUIRE(Tree_Tree_data_noexcept(right) == 42);
37 |
38 |
39 | Tree_Tree_dispose(root);
40 | Tree_Tree_dispose(right);
41 | }
42 |
43 | TEST_CASE("Test left subtree lifetime extension", "[Tree_CAPI]")
44 | {
45 | Tree_Tree root = Tree_Tree_create_noexcept(3);
46 | Tree_Tree left = Tree_Tree_left_subtree_noexcept(root);
47 |
48 | // this will invalidate `left` unless the `root` object created above has had
49 | // its lifetime extended by the creation of `left`.
50 | Tree_Tree_dispose(root);
51 |
52 | Tree_Tree_set_data_noexcept(left, 42);
53 | REQUIRE(Tree_Tree_data_noexcept(left) == 42);
54 |
55 |
56 | Tree_Tree_dispose(left);
57 | }
58 |
59 | TEST_CASE("Test right subtree lifetime extension", "[Tree_CAPI]")
60 | {
61 | Tree_Tree root = Tree_Tree_create_noexcept(3);
62 | Tree_Tree right = Tree_Tree_right_subtree_noexcept(root);
63 |
64 | // this will invalidate `right` unless the `root` object created above has had
65 | // its lifetime extended by the creation of `right`.
66 | Tree_Tree_dispose(root);
67 |
68 | Tree_Tree_set_data_noexcept(right, 42);
69 | REQUIRE(Tree_Tree_data_noexcept(right) == 42);
70 |
71 |
72 | Tree_Tree_dispose(right);
73 | }
74 |
--------------------------------------------------------------------------------
/ffig/templates/go.macros:
--------------------------------------------------------------------------------
1 | {%- import 'ffig.macros' as ffig_macros -%}
2 | {%- import '_c.macros' as c_macros -%}
3 |
4 | {#
5 | # c_method_parameters:
6 | # Forwards directly to c_macros.method_parameters.
7 | # The indirection permits future divergence from the C implementation
8 | # without having to replace every instance in the CGo template.
9 | #}
10 | {%- macro c_method_parameters(module, method, leading_comma=False, trailing_comma=False) -%}
11 | {{c_macros.method_parameters(module, method, leading_comma, trailing_comma)}}
12 | {%- endmacro -%}
13 |
14 | {#
15 | # c_method_arguments:
16 | # Simply use the argument name, no conversion needed as this is
17 | # handled by the C bindings.
18 | #}
19 | {%- macro c_method_arguments(method, leading_comma=False, trailing_comma=False) -%}
20 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
21 | {{arg.name}}
22 | {%- endcall -%}
23 | {%- endmacro -%}
24 |
25 | {#
26 | # go_method_parameters:
27 | # List of parameter names and types converted to Go types.
28 | #}
29 | {%- macro go_method_parameters(method, impl, leading_comma=False, trailing_comma=False) -%}
30 | {%- call(arg, impl) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma, impl) -%}
31 | {{arg.name}} {{arg.type | to_go(impl.name)}}
32 | {%- endcall -%}
33 | {%- endmacro -%}
34 |
35 | {#
36 | # go_arguments_to_c_arguments:
37 | # List of arguments with conversion to C types.
38 | #}
39 | {%- macro go_arguments_to_c_arguments(module, method, leading_comma=False, trailing_comma=False) -%}
40 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
41 | C.{{arg.type | to_c(module.name)}}({{arg.name}})
42 | {%- endcall -%}
43 | {%- endmacro -%}
44 |
45 | {#
46 | # go_arguments_to_c_arguments_extract_go_object:
47 | # As go_arguments_to_c_arguments but extract objects from the Go wrapper.
48 | #}
49 | {%- macro go_arguments_to_c_arguments_extract_go_object(method, leading_comma=False, trailing_comma=False) -%}
50 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
51 | {{arg | go_object}}
52 | {%- endcall -%}
53 | {%- endmacro -%}
54 |
55 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # Modify this line to test Travis Integration ..
2 | sudo: required
3 |
4 | language: python
5 |
6 | matrix:
7 | include:
8 | - os: linux
9 | sudo: required
10 | services:
11 | - docker
12 | install:
13 | - docker pull ffig/ffig-base
14 | - docker build . -t ffig-ci
15 | before_install:
16 | - pip install autopep8
17 | script:
18 | # FIXME: Get boost::python to work in the face of a user-provided python binary.
19 | - docker run ffig-ci /bin/bash -c "./scripts/build.py -t --require_boost_python --nodetect"
20 | - docker run ffig-ci /bin/bash -c "./scripts/build.py -t --python-path python2 --venv --disable_boost_python --require_java --require_dotnet --require_lua --require_swift --require_ruby --require_go"
21 | - docker run ffig-ci /bin/bash -c "./scripts/build.py -t --python-path python3 --venv --disable_boost_python --require_java --require_dotnet --require_lua --require_swift --require_ruby --require_go"
22 | - docker run ffig-ci /bin/bash -c "./scripts/build.py -T \"CPP|MOCKS\" -c ASAN --python-path python2 --venv --disable_boost_python --require_java --require_dotnet --require_lua --require_swift --require_ruby --require_go "
23 | - docker run ffig-ci /bin/bash -c "./scripts/build.py -T \"CPP|MOCKS\" -c ASAN --python-path python3 --venv --disable_boost_python --require_java --require_dotnet --require_lua --require_swift --require_ruby --require_go "
24 | - ./scripts/codechecks.py
25 |
26 | - os: osx
27 | language: generic
28 | osx_image: xcode9.3
29 | install:
30 | - brew update
31 | - brew install ninja bazel && brew upgrade python && brew install python@2
32 | - /usr/local/opt/python@2/bin/pip2 install --user --upgrade pip
33 | - /usr/local/opt/python@2/bin/pip2 install --user jinja2 nose pycodestyle
34 | - pip3 install --user --upgrade pip && pip3 install --user jinja2 nose pycodestyle
35 | - gem install ffi
36 | script:
37 | - ./scripts/build.py -t --disable_boost_python --disable_dotnet
38 | - ./scripts/build.py -t --disable_boost_python --disable_dotnet --python-path python3
39 |
40 | notifications:
41 | slack:
42 | rooms:
43 | - c-api:ZTxxqvEcr25iaaB8XBS7luaA#travis-ci
44 |
--------------------------------------------------------------------------------
/ffig/templates/jl.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'jl.macros' as julia_macros %}
2 | # This code was generated by FFIG .
3 | # Manual edits will be lost.
4 | module FFIG
5 |
6 | {% for class in classes %} export {{class.name}}
7 | {% for method in class.methods %} export {{method.name}}
8 | {% endfor %}
9 | {% for impl in class.impls %} export {{impl.name}}
10 | {% endfor %}
11 | {% endfor %}
12 |
13 | struct {{module.name}}Exception <: Exception
14 | message::String
15 | function {{module.name}}Exception()
16 | m=ccall(("{{module.name}}_error", :lib_{{module.name}}_c),
17 | Cstring, ())
18 | new(unsafe_string(m))
19 | ccall(("{{module.name}}_clear_error", :lib_{{module.name}}_c),
20 | Void, ())
21 | end
22 | end
23 |
24 | {% for class in classes %}
25 | abstract type {{class.name}} end
26 | {% for method in class.methods %}
27 |
28 | {% if method.is_noexcept %}
29 | function {{method.name}}(o::{{class.name}})
30 | rv = ccall(("{{module.name}}_{{class.name}}_{{method.name}}_noexcept", :lib{{module.name}}_c),
31 | {{method.return_type|to_julia_return_type}}, (Ptr{Void},),
32 | o.ptr)
33 | return {{method.return_type|to_julia_return_value("rv")}}
34 |
35 | end
36 | {% else %}
37 | function {{method.name}}(o::{{class.name}})
38 | rv = Array{% raw %}{{% endraw %}{{method.return_type}}{% raw %}}{% endraw %}(1)
39 | rc = ccall(("{{module.name}}_{{class.name}}_{{method.name}}", :lib{{module.name}}_c),
40 | Int32, (Ptr{Void}, Ptr{% raw %}{{% endraw %}{{method.return_type|to_julia_return_type}}{% raw %}}{% endraw %}),
41 | o.ptr, rv)
42 | if rc != 0
43 | throw {{module.name}}Exception()
44 | endif
45 | return {{method.return_type|to_julia_return_value("rv[1]")}}
46 | end
47 | {% endif %}
48 | {% endfor %}
49 | {% for impl in class.impls %}
50 |
51 | struct {{impl.name}} <: {{class.name}}
52 | {% for method in impl.constructors %}
53 | ptr::Ptr{Void}
54 | function {{impl.name}}(r::Float64)
55 | p = Array{Ptr{Void}}(1)
56 | (r, p)
57 | ccall(("{{module.name}}_{{impl.name}}_create", :lib{{module.name}}_c),
58 | Int32, ({{julia_macros.method_parameter_types(method, trailing_comma=True)}} Ptr{Void}), r, p)
59 | new(p[1])
60 | end
61 | {% endfor %}
62 | end
63 | {% endfor %}
64 | {% endfor %}
65 |
66 | end # module FFIG
67 |
--------------------------------------------------------------------------------
/ffig/generators/go.py:
--------------------------------------------------------------------------------
1 | # Generator module for Golang.
2 |
3 | # Currently, this forwards to the default generator as an example / testcase
4 | # for generator plugins. Ultimately, it will generate Go packages in the
5 | # correct structure.
6 |
7 | import os
8 | import os.path
9 |
10 | import ffig.generators
11 | import logging
12 |
13 | log = logging.getLogger(__name__)
14 |
15 |
16 | def go_generator(module_name, binding, api_classes, env, output_dir):
17 | ''' Create Go bindings in an appropriate Go package.
18 |
19 | This generator produces a directory src/modulename inside the output
20 | directory, into which it writes a single Go file containing the
21 | bindings for this module.
22 | '''
23 | module_directory = os.path.realpath(
24 | os.path.join(output_dir, 'src', module_name))
25 | if not os.path.isdir(module_directory):
26 | log.info('Creating Go package directory {}'.format(module_directory))
27 | os.makedirs(module_directory)
28 |
29 | log.info('Generating Go bindings for module {0}'.format(module_name))
30 |
31 | # Generate the C file
32 | template = env.get_template('go.c.tmpl')
33 | generated_code = ffig.generators.render_api_and_obj_classes(
34 | module_name, api_classes, template)
35 | output_file_name = os.path.join(
36 | module_directory, ffig.generators.get_template_output(
37 | module_name, ffig.generators.get_template_name(binding))) + '.h'
38 | with open(output_file_name, 'w') as f:
39 | f.write(generated_code)
40 | log.info('Wrote CGo bindings for module {0} to {1}'.format(
41 | module_name, output_file_name))
42 |
43 | # Generate the Go file
44 | template = env.get_template('go.tmpl')
45 | generated_code = ffig.generators.render_api_and_obj_classes(
46 | module_name, api_classes, template)
47 | output_file_name = os.path.join(
48 | module_directory, ffig.generators.get_template_output(
49 | module_name, ffig.generators.get_template_name(binding)))
50 | with open(output_file_name, 'w') as f:
51 | f.write(generated_code)
52 | log.info('Wrote Go bindings for module {0} to {1}'.format(
53 | module_name, output_file_name))
54 |
55 | return [output_file_name]
56 |
57 |
58 | def setup_plugin(context):
59 | context.register(go_generator, [('go', 'Go bindings using CGo')])
60 |
--------------------------------------------------------------------------------
/ffig/templates/go.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'go.macros' as go_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | package {{module.name}}
6 |
7 | // #cgo CFLAGS: -std=c11 -I${SRCDIR} -DSOURCE_PATH=${SRCDIR}
8 | // #cgo LDFLAGS: -ldl
9 | // #include "{{module.name}}.go.h"
10 | import "C"
11 |
12 | import (
13 | "os"
14 | )
15 |
16 | func init() {
17 | if status := int(C.init()); status != 0 {
18 | os.Exit(status)
19 | }
20 | }
21 | {% for class in classes %}
22 | {% for impl in class.impls %}
23 |
24 | type {{impl.name}} struct {
25 | ptr C.{{module.name}}_{{class.name}}
26 | }
27 | {% endfor %}
28 | {% endfor %}
29 | {% for class in classes %}
30 | {% for impl in class.impls %}
31 | {% for method in impl.constructors %}
32 |
33 | func {{ impl.name }}_create({{go_macros.go_method_parameters(method, impl)}})({{impl.name}}, bool) {
34 | var pv C.RT_{{module.name}}_{{impl.name}}_create
35 | pv = C.CGo_{{module.name}}_{{impl.name}}_create({{go_macros.go_arguments_to_c_arguments(module, method)}})
36 |
37 | var obj {{impl.name}}
38 |
39 | if pv.status == 0 {
40 | obj.ptr = pv.ptr
41 | return obj, false
42 | } else {
43 | return obj, true
44 | }
45 | }
46 | {% endfor %}
47 | {% endfor %}
48 | {% endfor %}
49 | {% for class in classes %}
50 | {% for impl in class.impls %}
51 | {% for method in class.methods %}
52 |
53 | func (obj {{impl.name}}) {{method.name|to_go_method_name}}({{go_macros.go_method_parameters(method, impl)}})
54 | {%- if not method.returns_void -%}
55 | ({{method.return_type|to_go(impl)}}, bool)
56 | {%- else -%}
57 | bool
58 | {%- endif %} {
59 | var rv C.RT_{{module.name}}_{{class.name}}_{{method.name}}
60 | rv = C.CGo_{{module.name}}_{{class.name}}_{{method.name}}(
61 | obj.ptr {{go_macros.go_arguments_to_c_arguments_extract_go_object(method, leading_comma=True)}})
62 |
63 | if rv.status == 0 {
64 | {% if not method.returns_void %}
65 | value := {{method.return_type|to_go_convert}}(rv.value);
66 | return value, false
67 | {% else %}return rv.status{% endif %}
68 | } else {
69 | {% if not method.returns_void %}
70 | var value {{method.return_type|to_go(impl)}}
71 | return value, false
72 | {% else %}return rv.status{% endif %}
73 | }
74 | }
75 | {% endfor %}
76 | {% endfor %}
77 | {% endfor %}
78 |
--------------------------------------------------------------------------------
/ffig/templates/java.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'java.macros' as java_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | package {{module.name}};
6 |
7 | import com.sun.jna.Library;
8 | import com.sun.jna.Native;
9 | import com.sun.jna.Pointer;
10 | import com.sun.jna.ptr.PointerByReference;
11 | import com.sun.jna.ptr.DoubleByReference;
12 | import com.sun.jna.ptr.IntByReference;
13 |
14 | public class {{class.name}} {
15 | protected Pointer ptr = Pointer.NULL;
16 |
17 | protected void finalize() {
18 | {{module.name}}CLibrary.INSTANCE.{{module.name}}_{{class.name}}_dispose(ptr);
19 | }
20 |
21 | protected {{class.name}}()
22 | {
23 | }
24 |
25 | protected {{class.name}}(Pointer ptr)
26 | {
27 | this.ptr = ptr;
28 | }
29 | {% if not class.is_abstract %}
30 | {% for method in class.constructors %}
31 |
32 | public {{class.name}}({{java_macros.method_parameters(method)}}) {
33 | PointerByReference rv = new PointerByReference();
34 | int rc = {{module.name}}CLibrary.INSTANCE.{{module.name}}_{{class.name}}_create({{java_macros.method_arguments(method, trailing_comma=True)}}rv);
35 | if(rc != 0) {
36 | throw new {{module.name}}Exception();
37 | }
38 | this.ptr = rv.getValue();
39 | }
40 | {% endfor %}
41 | {% endif %}
42 | {% for method in class.methods %}
43 |
44 | {% if method.returns_void %}
45 | public void {{method.name}}({{java_macros.method_parameters(method)}}) {
46 | int rc = {{module.name}}CLibrary.INSTANCE.{{module.name}}_{{class.name}}_{{method.name}}(ptr, {{java_macros.method_arguments(method)}});
47 | if(rc != 0) {
48 | throw new {{module.name}}Exception();
49 | }
50 | }
51 | {% else %}
52 | public {{method.return_type|to_java_return_type}} {{method.name}}({{java_macros.method_parameters(method)}}) {
53 | {{method.return_type|to_java_output_value("rv")}};
54 | int rc = {{module.name}}CLibrary.INSTANCE.{{module.name}}_{{class.name}}_{{method.name}}(ptr, {{java_macros.method_arguments(method, trailing_comma=True)}}rv);
55 | if(rc != 0) {
56 | throw new {{module.name}}Exception();
57 | }
58 | {% if method.returns_nullable %}
59 | if(rv.getValue() == Pointer.NULL) {
60 | return null;
61 | }
62 | {% endif %}
63 | return {{method.return_type|to_java_return_value("rv")}};
64 | }
65 | {% endif %}
66 | {% endfor %}
67 | }
68 |
--------------------------------------------------------------------------------
/ffig/templates/_c.h.tmpl:
--------------------------------------------------------------------------------
1 | {% import '_c.macros' as c_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | #pragma once
6 |
7 | #define {{module.name}}_RC_SUCCESS 0
8 | #define {{module.name}}_RC_FAIL 1
9 |
10 | #ifdef _MSC_VER
11 | #ifdef {{module.name}}_c_EXPORTS
12 | #define EXPORT extern "C" __declspec(dllexport)
13 | #else
14 | #define EXPORT extern "C" __declspec(dllimport)
15 | #endif
16 | #else
17 | #ifdef __cplusplus
18 | #define EXPORT extern "C" __attribute__((visibility("default")))
19 | #else
20 | #define EXPORT
21 | #endif
22 | #endif
23 |
24 | {% for class in classes %}
25 | struct {{module.name}}_{{class.name}}_t;
26 | typedef struct {{module.name}}_{{class.name}}_t* {{module.name}}_{{class.name}};
27 | {% endfor %}
28 | #ifdef __cplusplus
29 | extern "C"
30 | {
31 | #endif
32 | EXPORT void {{module.name}}_clear_error();
33 |
34 | EXPORT const char* {{module.name}}_error();
35 | {% for class in classes %}
36 |
37 | EXPORT void {{module.name}}_{{class.name}}_dispose({{module.name}}_{{class.name}} my{{class.name}});
38 | {% if not class.is_abstract %}
39 | {% for method in class.constructors %}
40 |
41 | EXPORT int {{module.name}}_{{ class.name }}_create(
42 | {{c_macros.method_parameters(module, method, trailing_comma=True)}}
43 | {{module.name}}_{{class.name}}* rv);
44 | {% if method.is_noexcept %}
45 |
46 | EXPORT {{module.name}}_{{class.name}} {{module.name}}_{{ class.name }}_create_noexcept(
47 | {{c_macros.method_parameters(module, method)}});
48 | {% endif %}
49 | {% endfor %}
50 | {% endif %}
51 | {% for method in class.methods %}
52 |
53 | EXPORT int {{module.name}}_{{ class.name }}_{{method.name}}(
54 | {{module.name}}_{{class.name}} my{{class.name}}
55 | {% if method.returns_void %}
56 | {{c_macros.method_parameters(module, method, leading_comma=True)}}
57 | {% else %}
58 | {{c_macros.method_parameters(module, method, leading_comma=True, trailing_comma=False)}},
59 | {{method.return_type | to_c(module.name)}}* rv
60 | {% endif %}
61 | );
62 | {% if method.is_noexcept %}
63 |
64 | EXPORT {{method.return_type | to_c(module.name)}} {{module.name}}_{{ class.name }}_{{method.name}}_noexcept(
65 | {{module.name}}_{{class.name}} my{{class.name}} {{c_macros.method_parameters(module, method, leading_comma=True)}}
66 | );
67 | {% endif %}
68 | {% endfor %}
69 | {% for impl in class.impls %}
70 | {% for method in impl.constructors %}
71 |
72 | EXPORT int {{module.name}}_{{ impl.name }}_create(
73 | {{c_macros.method_parameters(module, method, trailing_comma=True)}}
74 | {{module.name}}_{{class.name}}* rv);
75 | {% endfor %}
76 | {% endfor %}
77 | {% endfor %}
78 | #ifdef __cplusplus
79 | }
80 | #endif
81 |
--------------------------------------------------------------------------------
/ffig/templates/py2.macros:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros %}
2 |
3 | {#
4 | # constructor_parameters:
5 | # Generates a list of parameter names suitable for use in a constructor
6 | # definition. Commas are inserted between each element of the list.
7 | # Leading and trailing commas can be added by setting leading_comma or
8 | # trailing_comma to True as appropriate. This is useful if there are other
9 | # parameters that must be declared.
10 | #}
11 | {% macro constructor_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg.name}}=None
14 | {%- endcall %}
15 | {% endmacro %}
16 |
17 | {#
18 | # method_parameters:
19 | # Generates a list of parameter names suitable for use in a method
20 | # definition. Commas are inserted between each element of the list.
21 | # Leading and trailing commas can be added by setting leading_comma or
22 | # trailing_comma to True as appropriate. This is useful if there are other
23 | # parameters that must be declared.
24 | #}
25 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg.name}}
28 | {%- endcall %}
29 | {% endmacro %}
30 |
31 | {#
32 | # method_arguments:
33 | # Generates a list of argument names suitable for use in a
34 | # function call. Commas are inserted between each element of the list.
35 | # Leading and trailing commas can be added by setting leading_comma or
36 | # trailing_comma to True as appropriate. This is useful if there are other
37 | # parameters that must be declared.
38 | #}
39 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
40 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
41 | {{arg.name}}
42 | {%- endcall %}
43 | {% endmacro %}
44 |
45 | {#
46 | # method_argument_types:
47 | # Generates a list of argument names suitable for use in a method
48 | # registration function call. Commas are inserted between each element of
49 | # the list. Leading and trailing commas can be added by setting
50 | # leading_comma or trailing_comma to True as appropriate. This is useful if
51 | # there are other parameters that must be declared.
52 | #}
53 | {% macro method_argument_types(method, leading_comma=False, trailing_comma=False) %}
54 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
55 | {{arg.type | to_py2_ctype}}
56 | {%- endcall %}
57 | {% endmacro %}
58 |
59 |
--------------------------------------------------------------------------------
/ffig/templates/py3.macros:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros %}
2 |
3 | {#
4 | # constructor_parameters:
5 | # Generates a list of parameter names and type hints suitable for use in a
6 | # constructor definition. Commas are inserted between each element of the list.
7 | # Leading and trailing commas can be added by setting leading_comma or
8 | # trailing_comma to True as appropriate. This is useful if there are other
9 | # parameters that must be declared.
10 | #}
11 | {% macro constructor_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg.name}}: '{{arg.type|to_hint_type}}' = None
14 | {%- endcall %}
15 | {% endmacro %}
16 |
17 | {#
18 | # method_parameters:
19 | # Generates a list of parameter names and type hints suitable for use in a
20 | # method definition. Commas are inserted between each element of the list.
21 | # Leading and trailing commas can be added by setting leading_comma or
22 | # trailing_comma to True as appropriate. This is useful if there are other
23 | # parameters that must be declared.
24 | #}
25 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg.name}}: '{{arg.type|to_hint_type}}'
28 | {%- endcall %}
29 | {% endmacro %}
30 |
31 | {#
32 | # method_arguments:
33 | # Generates a list of argument names suitable for use in a
34 | # function call. Commas are inserted between each element of the list.
35 | # Leading and trailing commas can be added by setting leading_comma or
36 | # trailing_comma to True as appropriate. This is useful if there are other
37 | # parameters that must be declared.
38 | #}
39 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
40 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
41 | {{arg.name}}
42 | {%- endcall %}
43 | {% endmacro %}
44 |
45 | {#
46 | # method_argument_types:
47 | # Generates a list of argument names suitable for use in a method
48 | # registration function call. Commas are inserted between each element of
49 | # the list. Leading and trailing commas can be added by setting
50 | # leading_comma or trailing_comma to True as appropriate. This is useful if
51 | # there are other parameters that must be declared.
52 | #}
53 | {% macro method_argument_types(method, leading_comma=False, trailing_comma=False) %}
54 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
55 | {{arg.type | to_py3_ctype}}
56 | {%- endcall %}
57 | {% endmacro %}
58 |
59 |
--------------------------------------------------------------------------------
/tests/cppmodel/test_types.py:
--------------------------------------------------------------------------------
1 | from util import get_tu
2 | import ffig.cppmodel
3 | from ffig.clang.cindex import TypeKind
4 | from nose.tools import assert_equals
5 |
6 |
7 | def test_pointer_type():
8 | source = "double* pd();"
9 |
10 | tu = get_tu(source, 'cpp')
11 | model = ffig.cppmodel.Model(tu)
12 | f = model.functions[0]
13 |
14 | assert f.return_type.kind == TypeKind.POINTER
15 | assert not f.return_type.is_const
16 | assert f.return_type.pointee.kind == TypeKind.DOUBLE
17 | assert not f.return_type.pointee.is_const
18 |
19 |
20 | def test_const_pointer_to_double_type():
21 | source = "double* const cpd();"
22 |
23 | tu = get_tu(source, 'cpp')
24 | model = ffig.cppmodel.Model(tu)
25 | f = model.functions[0]
26 |
27 | assert f.return_type.kind == TypeKind.POINTER
28 | assert f.return_type.is_const
29 | assert f.return_type.pointee.kind == TypeKind.DOUBLE
30 | assert not f.return_type.pointee.is_const
31 |
32 |
33 | def test_const_pointer_to_const_double_type():
34 | source = "const double* const cpcd();"
35 |
36 | tu = get_tu(source, 'cpp')
37 | model = ffig.cppmodel.Model(tu)
38 | functions = model.functions
39 | f = model.functions[0]
40 |
41 | assert f.return_type.kind == TypeKind.POINTER
42 | assert f.return_type.is_const
43 | assert f.return_type.pointee.kind == TypeKind.DOUBLE
44 | assert f.return_type.pointee.is_const
45 |
46 |
47 | def test_pointer_to_pointer_type():
48 | source = "double** ppd();"
49 |
50 | tu = get_tu(source, 'cpp')
51 | model = ffig.cppmodel.Model(tu)
52 | f = model.functions[0]
53 |
54 | assert f.return_type.kind == TypeKind.POINTER
55 | assert f.return_type.is_pointer
56 | assert f.return_type.pointee.kind == TypeKind.POINTER
57 | assert f.return_type.pointee.is_pointer
58 | assert f.return_type.pointee.pointee.kind == TypeKind.DOUBLE
59 |
60 |
61 | def test_pointer_to_record_type():
62 | source = "class A{}; A* pA();"
63 |
64 | tu = get_tu(source, 'cpp')
65 | model = ffig.cppmodel.Model(tu)
66 | f = model.functions[0]
67 |
68 | assert f.return_type.kind == TypeKind.POINTER
69 | assert f.return_type.is_pointer
70 | assert f.return_type.pointee.kind == TypeKind.RECORD
71 |
72 |
73 | def test_reference_to_record_type():
74 | source = "class A{}; A& pA();"
75 |
76 | tu = get_tu(source, 'cpp')
77 | model = ffig.cppmodel.Model(tu)
78 | f = model.functions[0]
79 |
80 | assert f.return_type.kind == TypeKind.LVALUEREFERENCE
81 | assert not f.return_type.is_pointer
82 | assert f.return_type.is_reference
83 | assert f.return_type.pointee.kind == TypeKind.RECORD
84 |
85 |
86 | def test_string_representation():
87 | source = "class A{};"
88 |
89 | tu = get_tu(source, 'cpp')
90 | model = ffig.cppmodel.Model(tu)
91 | c = model.classes[0]
92 |
93 | assert_equals(str(c), "")
94 |
--------------------------------------------------------------------------------
/ffig/templates/_mocks.h.tmpl:
--------------------------------------------------------------------------------
1 | {%- import 'ffig.macros' as ffig_macros -%}
2 |
3 | {#
4 | # method_type_list:
5 | # List of types of method parameters, without parameter names.
6 | #}
7 | {%- macro method_type_list(method, leading_comma=False, trailing_comma=False) -%}
8 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
9 | {{arg.type}}
10 | {%- endcall -%}
11 | {%- endmacro -%}
12 |
13 | {#
14 | # method_parameters:
15 | # Method parameters (type and name) separated by commas.
16 | #}
17 | {%- macro method_parameters(method, leading_comma=False, trailing_comma=False) -%}
18 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
19 | {{arg.type}} {{arg.name}}
20 | {%- endcall -%}
21 | {%- endmacro -%}
22 |
23 | {#
24 | # method_arguments:
25 | # Method arguments (name only) separated by commas.
26 | #}
27 | {%- macro method_arguments(method, leading_comma=False, trailing_comma=False) -%}
28 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
29 | {{arg.name}}
30 | {%- endcall -%}
31 | {%- endmacro -%}
32 | // This code was generated by FFIG .
33 | // Manual edits will be lost.
34 |
35 | #include "{{module.name}}.h"
36 | #include
37 | #include
38 | #include
39 | namespace mocks {
40 |
41 | {% for class in classes %}
42 | struct Mock{{class.name}} : {{class.name}}
43 | {
44 | enum state
45 | {
46 | value,
47 | function
48 | };
49 |
50 | struct MockMethodResultNotSpecified : std::logic_error
51 | {
52 | using std::logic_error::logic_error ;
53 | };
54 |
55 | struct MockMethodResult
56 | {
57 | template
58 | auto operator()(const char* method_name, const Value& v, Args&& ...args) const
59 | {
60 | switch(v.which())
61 | {
62 | case(value):
63 | return eggs::variants::get(v);
64 | case(function):
65 | return eggs::variants::get(v)(std::forward(args)...);
66 | default:
67 | throw MockMethodResultNotSpecified(method_name);
68 | }
69 | }
70 | };
71 |
72 | {% for method in class.methods %}
73 | eggs::variant<{{method.return_type}}, std::function<{{method.return_type}}({{method_type_list(method)}})>> {{method.name}}_;
74 | {%endfor%}
75 |
76 | {% for method in class.methods %}
77 | {% if method.is_noexcept %}
78 | {{method.return_type}} {{method.name}}({{method_parameters(method)}}) const noexcept override
79 | {% else %}
80 | {{method.return_type}} {{method.name}}({{method_parameters(method)}}) const override
81 | {% endif %}
82 | {
83 | return MockMethodResult()("{{method.name}}", {{method.name}}_
84 | {{method_arguments(method, leading_comma=True)}});
85 | }
86 | {% endfor %}
87 | };
88 | {% endfor %}
89 | } // end namespace mocks
90 |
--------------------------------------------------------------------------------
/tests/cppmodel/test_free_functions.py:
--------------------------------------------------------------------------------
1 | from util import get_tu
2 | import ffig.cppmodel
3 | from ffig.clang.cindex import TypeKind
4 | from nose.tools import assert_equals
5 |
6 |
7 | def test_function_name():
8 | source = """
9 | void foo();
10 | void bar();
11 | """
12 | tu = get_tu(source, 'cpp')
13 |
14 | model = ffig.cppmodel.Model(tu)
15 | functions = model.functions
16 |
17 | assert len(functions) == 2
18 | assert functions[0].name == 'foo'
19 | assert functions[1].name == 'bar'
20 |
21 |
22 | def test_function_return_type():
23 | source = """
24 | int foo();
25 | double* bar();
26 | """
27 |
28 | tu = get_tu(source, 'cpp')
29 |
30 | model = ffig.cppmodel.Model(tu)
31 | functions = model.functions
32 |
33 | assert functions[0].return_type.kind == TypeKind.INT
34 | assert functions[1].return_type.kind == TypeKind.POINTER
35 | assert functions[1].return_type.is_pointer
36 | assert functions[1].return_type.pointee.kind == TypeKind.DOUBLE
37 | assert not functions[1].return_type.pointee.is_const
38 |
39 |
40 | def test_function_arguments():
41 | source = """
42 | int foo();
43 | double bar(int x, char y);
44 | """
45 |
46 | tu = get_tu(source, 'cpp')
47 |
48 | model = ffig.cppmodel.Model(tu)
49 | functions = model.functions
50 |
51 | assert len(functions[0].arguments) == 0
52 | assert len(functions[1].arguments) == 2
53 | assert functions[1].arguments[0].type.kind == TypeKind.INT
54 | assert functions[1].arguments[0].name == 'x'
55 | assert functions[1].arguments[1].type.kind == TypeKind.CHAR_S
56 | assert functions[1].arguments[1].name == 'y'
57 |
58 |
59 | def test_function_equality():
60 | source = """
61 | int foo();
62 | int foo(int);
63 | int foo(double);
64 | int foo(int,int);
65 | namespace x {
66 | int foo();
67 | }
68 | """
69 |
70 | tu = get_tu(source, 'cpp')
71 |
72 | model = ffig.cppmodel.Model(tu)
73 |
74 | for i, f in enumerate(model.functions):
75 | for j, g in enumerate(model.functions):
76 | if i == j:
77 | assert f == g
78 | else:
79 | assert not f == g
80 |
81 |
82 | def test_string_representation():
83 | source = """
84 | double foo(int, char);
85 | """
86 |
87 | tu = get_tu(source, 'cpp')
88 |
89 | model = ffig.cppmodel.Model(tu)
90 | functions = model.functions
91 |
92 | assert_equals(str(functions[0]),
93 | '')
94 |
95 |
96 | def test_noexcept():
97 | source = """
98 | double foo(int, char) noexcept;
99 | """
100 |
101 | tu = get_tu(source, 'cpp')
102 |
103 | model = ffig.cppmodel.Model(tu)
104 | functions = model.functions
105 |
106 | assert_equals(str(functions[0]),
107 | '')
108 | assert functions[0].is_noexcept
109 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing to FFIG
2 | ====================
3 | We are happy to accept contributions that fix a bug or implement a feature corresponding to an open ticket in our issue tracker. These should be thoroughly tested. We'll also accept contributions that add a new language binding template or generator.
4 |
5 | If you plan to implement a larger feature, it's a good idea to get in touch to discuss it, and see how it may fit into the wider plans. For the most part, development on FFIG is done in the open, with tickets and milestones for major and minor features.
6 |
7 | The information below describes the development workflow for FFIG. Contributors are advised to read it and follow it as closely as possible, since this will make it easier for us to review, test and approve your contribution.
8 |
9 | Development Workflow
10 | --------------------
11 |
12 | 1. Open a new issue, if one does not already exist for the feature or bug you're working on. If you don't have the necessary permissions to create an issue, please get in touch with one of the FFIG maintainers and we will create a ticket for you.
13 | 2. Clone the Git repository. Contributors with commit access to `FFIG/ffig` can clone the repository directly. Other contributors should first fork the repository on GitHub, then clone their fork.
14 | 3. Create a branch with a name that contains the ticket number, e.g. `n100-fix-foo` to fix a bug logged as ticket #100. The branch name should include some hint as to the content of the branch, and should not rely solely on the ticket number..
15 | 4. Develop your feature or bugfix. Write and execute tests as appropriate (see the section below on testing).
16 | 5. Push the feature branch to GitHub; either directly to a branch in `FFIG/ffig`, or to your fork.
17 | 6. Create a pull request from your branch to the `master` branch of `FFIG/ffig`. Assign one or more reviewers from the FFIG maintainers.
18 | 7. Your branch will now be built and tested automatically by Travis CI (Linux) and AppVeyor (Windows). Failures will be reported in the pull request. The tests must pass before your branch can be merged.
19 | 8. Once the tests pass, and your change has been reviewed by a FFIG maintainer, you will be able to merge the branch into `master` of the `FFIG/ffig` repository.
20 | 9. At this point, you can close the corresponding issue, assuming no further work needs to be done for that feature or bugfix.
21 |
22 | Testing
23 | -------
24 | The existing functionality of FFIG can be tested locally by running `./scripts/build.py -t`. New tests can be added to the `tests` directory, and should be enabled by adding a corresponding section to the `CMakeLists.txt` file.
25 |
26 | The pull request and branch builds on Travis CI and AppVeyor run these tests inside a Docker container. They also check that Python files correspond to the PEP8 guidelines, using the `pep8` checker. You can use `autopep8` to ensure everything meets the requirements ahead of pushing your branch. This has been set up so that you can run `scripts/codechecks.py --reformat` to automatically apply any required changes.
27 |
--------------------------------------------------------------------------------
/ffig/templates/rb.tmpl:
--------------------------------------------------------------------------------
1 | # This code was generated by FFIG .
2 | # Manual edits will be lost.
3 |
4 | require 'ffi'
5 |
6 | module {{module.name}}_c
7 | extend FFI::Library
8 | ffi_lib ['{{module.name}}_c',
9 | File.dirname(__FILE__) + '/lib{{module.name}}_c.so',
10 | File.dirname(__FILE__) + '/lib{{module.name}}_c.dylib' ]
11 | {% for class in classes %}
12 | attach_function :{{module.name}}_{{class.name}}_dispose, [:pointer], :void
13 | attach_function :{{module.name}}_error, [], :string
14 | attach_function :{{module.name}}_clear_error, [], :void
15 | {% for method in class.methods %}
16 | {% if method.is_noexcept %}
17 | attach_function :{{module.name}}_{{class.name}}_{{method.name}}_noexcept, [:pointer{% for arg in method.arguments %}, :{{arg.type|to_ruby_type}}{% endfor %}], :{{method.return_type|to_ruby_type}}
18 | {% else %}
19 | attach_function :{{module.name}}_{{class.name}}_{{method.name}}, [:pointer{% for arg in method.arguments %}, :{{arg.type|to_ruby_type}}{% endfor %}, :pointer], :int
20 | {% endif %}
21 | {% endfor %}
22 | {% for impl in class.impls %}{% for method in impl.constructors %}
23 | attach_function :{{module.name}}_{{impl.name}}_create, [{% for arg in method.arguments %}:{{arg.type|to_ruby_type}},{% endfor %} :pointer], :int
24 | {% endfor %}{% endfor %}
25 | {% endfor %}
26 |
27 | end
28 |
29 | class {{module.name}}Error < Exception
30 | def initialize()
31 | msg = {{module.name}}_c.{{module.name}}_error
32 | {{module.name}}_c.{{module.name}}_clear_error()
33 | super(msg)
34 | end
35 | end
36 |
37 | {% for class in classes %}
38 | class {{class.name}}
39 | def initialize(objptr)
40 | @ptr = objptr.get_pointer(0)
41 | ObjectSpace.define_finalizer( self, self.class.finalize(@ptr) )
42 | end
43 |
44 | def self.finalize(ptr)
45 | proc { {{module.name}}_c.{{module.name}}_{{class.name}}_dispose(ptr) }
46 | end
47 | {% for method in class.methods %}
48 | {% if method.is_noexcept %}
49 | def {{method.name}}()
50 | return {{module.name}}_c.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(@ptr)
51 | end
52 | {% else %}
53 | def {{method.name}}()
54 | dptr = {{method.return_type|to_ruby_output_type}}
55 | rc = {{module.name}}_c.{{module.name}}_{{class.name}}_{{method.name}}(@ptr, dptr)
56 | if rc != 0
57 | raise {{module.name}}Error
58 | end
59 | dptr.{{method.return_type|restore_ruby_type}}
60 | end
61 | {% endif %}
62 | {% endfor %}end
63 |
64 | {% for impl in class.impls %}{% for method in impl.constructors %}
65 | class {{impl.name}} < {{class.name}}
66 | def initialize({% for arg in method.arguments %}{{arg.name}}{% if not loop.last %},{% endif %}{% endfor %})
67 | objptr = FFI::MemoryPointer.new :pointer
68 | rc = {{module.name}}_c.{{module.name}}_{{impl.name}}_create({% for arg in method.arguments %}{{arg.name}}, {% endfor %}objptr)
69 | if rc != 0
70 | raise {{module.name}}Error
71 | end
72 | super(objptr)
73 | end
74 | end
75 | {% endfor %}
76 | {%- endfor %}
77 | {%- endfor %}
78 |
79 |
--------------------------------------------------------------------------------
/ffig/templates/swift.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'swift.macros' as swift_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | public struct {{module.name}}Error : Error {
6 | init()
7 | {
8 | message = String(cString:{{module.name}}_error())
9 | {{module.name}}_clear_error()
10 | }
11 |
12 | let message: String
13 | }
14 |
15 | {% for class in classes -%}
16 | public class {{class.name}} {
17 |
18 | var obj_ : OpaquePointer
19 |
20 | public init(fromCPtr obj:OpaquePointer) {
21 | obj_ = obj
22 | }
23 |
24 | {% if not class.is_abstract %}
25 | {% for method in class.constructors %}
26 | public convenience init({{swift_macros.method_parameters(method)}}) {
27 | var rv : OpaquePointer?
28 | {{module.name}}_{{method.name}}_create({{swift_macros.method_arguments(method,trailing_comma=True)}}&rv)
29 | //if rc != 0 {
30 | // throw {{module.name}}Error()
31 | //}
32 | self.init(fromCPtr:rv!)
33 | }
34 | {% endfor %}
35 | {% endif %}
36 |
37 | deinit
38 | {
39 | {{module.name}}_{{class.name}}_dispose(obj_)
40 | }
41 |
42 | {% for method in class.methods %}
43 | public func {{method.name}}({{swift_macros.method_parameters(method)}}) {% if not method.is_noexcept %}throws {% endif %}{% if not method.returns_void %}-> {{method.return_type|to_swift_return_type}}{% if method.returns_nullable %}?{% endif %}{% endif %} {
44 | {% if method.is_noexcept %}
45 | {% if method.returns_void %}
46 | {{module.name}}_{{class.name}}_{{method.name}}_noexcept(obj_{{swift_macros.method_arguments(method, leading_comma=True)}})
47 | {% else %}
48 | let rv = {{module.name}}_{{class.name}}_{{method.name}}_noexcept(obj_{{swift_macros.method_arguments(method, leading_comma=True)}})
49 | {% if method.returns_nullable %}
50 | if(rv == nil) {
51 | return nil;
52 | }
53 | {% endif %}
54 | return {{method.return_type|to_swift_return_value("rv")}};
55 | {% endif %}
56 | {% else %}
57 | {% if method.returns_void %}
58 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(obj_{{swift_macros.method_arguments(method, leading_comma=True)}})
59 | if rc != 0 {
60 | throw {{module.name}}Error()
61 | }
62 | {% else %}
63 | var rv
64 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(obj_{{swift_macros.method_arguments(method, leading_comma=True), rv}})
65 | if rc != 0 {
66 | throw {{module.name}}Error()
67 | }
68 | return {{method.return_type|to_swift_return_value("rv")}};
69 | {% endif %}
70 | {% endif %}
71 | }
72 |
73 | {% endfor %}
74 | }
75 |
76 | {% for impl in class.impls %}
77 | public class {{impl.name}} : {{class.name}} {
78 | {% for method in impl.constructors %}
79 | public init({{swift_macros.method_parameters(method)}}) throws {
80 | var rv : OpaquePointer?
81 | let rc = {{module.name}}_{{method.name}}_create({{swift_macros.method_arguments(method,trailing_comma=True)}}&rv)
82 | if rc != 0 {
83 | throw {{module.name}}Error()
84 | }
85 | super.init(fromCPtr:rv!)
86 | }
87 | {% endfor %}
88 | }
89 | {% endfor %}
90 | {% endfor %}
91 |
--------------------------------------------------------------------------------
/ffig/templates/config.py.tmpl:
--------------------------------------------------------------------------------
1 | # library loading and method registrations
2 | # based on clang python bindings approach
3 |
4 |
5 | def register_method(lib, item):
6 | func = getattr(lib, item[0])
7 |
8 | if len(item) >= 2:
9 | func.argtypes = item[1]
10 |
11 | if len(item) >= 3:
12 | func.restype = item[2]
13 |
14 | if len(item) == 4:
15 | func.errcheck = item[3]
16 |
17 |
18 | class CachedProperty(object):
19 |
20 | def __init__(self, wrapped):
21 | self.wrapped = wrapped
22 | try:
23 | self.__doc__ = wrapped.__doc__
24 | except Exception:
25 | pass
26 |
27 | def __get__(self, instance, instance_type=None):
28 | if instance is None:
29 | return self
30 |
31 | value = self.wrapped(instance)
32 | setattr(instance, self.wrapped.__name__, value)
33 |
34 | return value
35 |
36 |
37 | class Config:
38 | _library_path = None
39 | _loaded = False
40 |
41 | @property
42 | def library_path(self):
43 | return type(self)._library_path
44 |
45 | @library_path.setter
46 | def library_path(self, path):
47 | if type(self)._loaded:
48 | raise Exception("library path is already set.")
49 | type(self).library_path = path
50 |
51 | @CachedProperty
52 | def lib(self):
53 | lib = self._get_library()
54 | for m in methodList:
55 | register_method(lib, m)
56 | Config._loaded = True
57 | return lib
58 |
59 | def _get_filename(self):
60 | import platform
61 | name = platform.system()
62 |
63 | if name == 'Darwin':
64 | file = 'lib{{module.name}}_c.dylib'
65 | elif name == 'Windows':
66 | file = '{{module.name}}_c.dll'
67 | else:
68 | file = 'lib{{module.name}}_c.so'
69 | return file
70 |
71 | def _get_filepath(self):
72 | filename = self._get_filename()
73 | if not Config._library_path:
74 | return filename
75 | return os.path.join(Config._library_path, filename)
76 |
77 | def _get_library(self):
78 | try:
79 | # Use user-specified library path.
80 | if Config._library_path:
81 | library = cdll.LoadLibrary(self._get_filepath())
82 | else:
83 | # Use local file
84 | try:
85 | lib_file_dir = os.path.abspath(
86 | os.path.dirname(os.path.dirname(__file__)))
87 | library = cdll.LoadLibrary(
88 | os.path.join(lib_file_dir, self._get_filename()))
89 | except Exception:
90 | # Use system library path (last).
91 | library = cdll.LoadLibrary(self._get_filename())
92 | except OSError as e:
93 | msg = str(e) + ". To provide a path to {} set the property Config.library_path".format(self._get_filename())
94 | raise Exception(msg)
95 |
96 | return library
97 |
98 |
99 | conf = Config()
100 |
101 |
--------------------------------------------------------------------------------
/tests/input/Shape.h:
--------------------------------------------------------------------------------
1 | #include "ffig/attributes.h"
2 | #include
3 | #include
4 | #include
5 |
6 | struct FFIG_EXPORT AbstractShape
7 | {
8 | virtual ~AbstractShape()
9 | {
10 | }
11 | virtual FFIG_PROPERTY double area() const noexcept = 0;
12 | virtual FFIG_PROPERTY double perimeter() const noexcept = 0;
13 | virtual FFIG_PROPERTY const char* name() const noexcept = 0;
14 | virtual int is_equal(const AbstractShape* s) const noexcept = 0;
15 | };
16 |
17 | static const double pi = 3.14159265359;
18 |
19 | class Circle : public AbstractShape
20 | {
21 | const double radius_;
22 |
23 | public:
24 | double area() const noexcept override
25 | {
26 | return pi * radius_ * radius_;
27 | }
28 |
29 | double perimeter() const noexcept override
30 | {
31 | return 2 * pi * radius_;
32 | }
33 |
34 | const char* name() const noexcept override
35 | {
36 | return "Circle";
37 | }
38 |
39 | int is_equal(const AbstractShape* s) const noexcept override
40 | {
41 | if ( auto c = dynamic_cast(s) )
42 | return c->radius_ == radius_;
43 | return false;
44 | }
45 |
46 | Circle(double radius) : radius_(radius)
47 | {
48 | if ( radius < 0 )
49 | {
50 | std::string s = "Circle radius \"" + std::to_string(radius_) + "\" must be non-negative.";
51 | throw std::runtime_error(s);
52 | }
53 | }
54 | };
55 |
56 | class Square : public AbstractShape
57 | {
58 | const double side_;
59 |
60 | public:
61 | double area() const noexcept override
62 | {
63 | return side_ * side_;
64 | }
65 |
66 | double perimeter() const noexcept override
67 | {
68 | return 4.0 * side_;
69 | }
70 |
71 | const char* name() const noexcept override
72 | {
73 | return "Square";
74 | }
75 |
76 | int is_equal(const AbstractShape* s) const noexcept override
77 | {
78 | if ( auto sq = dynamic_cast(s) )
79 | return sq->side_ == side_;
80 | return false;
81 | }
82 |
83 | Square(double side) : side_(side)
84 | {
85 | if ( side_ <= 0.0 )
86 | {
87 | std::string s = "Square side \"" + std::to_string(side_) + "\" must be non-negative.";
88 | throw std::runtime_error(s);
89 | }
90 | }
91 | };
92 |
93 | class Pentagon : public AbstractShape
94 | {
95 | const double side_;
96 |
97 | public:
98 | double area() const noexcept override
99 | {
100 | return 0.25 * sqrt(5. * (5. + 2. * sqrt(5.))) * side_ * side_;
101 | }
102 |
103 | double perimeter() const noexcept override
104 | {
105 | return 5.0 * side_;
106 | }
107 |
108 | const char* name() const noexcept override
109 | {
110 | return "Pentagon";
111 | }
112 |
113 | int is_equal(const AbstractShape* s) const noexcept override
114 | {
115 | if ( auto p = dynamic_cast(s) )
116 | return p->side_ == side_;
117 | return false;
118 | }
119 |
120 | Pentagon(double side) : side_(side)
121 | {
122 | if ( side_ <= 0.0 )
123 | {
124 | std::string s = "Pentagon side \"" + std::to_string(side_) + "\" must be non-negative.";
125 | throw std::runtime_error(s);
126 | }
127 | }
128 | };
129 |
--------------------------------------------------------------------------------
/scripts/codechecks.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import collections
5 | import logging
6 | import platform
7 | import subprocess
8 | import sys
9 | import os
10 |
11 | logging.basicConfig(level=logging.ERROR, format='%(message)s')
12 | log = logging.getLogger('codechecks')
13 |
14 | ProcessResult = collections.namedtuple('ProcessResult',
15 | ['stdout', 'stderr', 'returncode'])
16 |
17 |
18 | def _decode_terminal_output(s):
19 | '''Translate teminal output into a Python string'''
20 | if platform.python_version_tuple()[0] == '3':
21 | return s.decode("utf8")
22 | return s
23 |
24 |
25 | def _capture_output(command):
26 | '''Run command and capture the output and return code.'''
27 | process = subprocess.Popen(
28 | command,
29 | stdout=subprocess.PIPE,
30 | stderr=subprocess.PIPE)
31 | stdout, stderr = process.communicate()
32 | if len(stdout):
33 | log.info(stdout)
34 | if len(stderr):
35 | log.error(stderr)
36 | return True if process.returncode == 0 else False
37 |
38 |
39 | def is_python_file(filename):
40 | return filename.endswith('.py')
41 |
42 |
43 | def python_checks(files, reformat=False):
44 | ''' Run pep8 checks.
45 | If reformat=True, run autopep8 first.
46 | '''
47 | ignored = [
48 | 'E265', # Block comment should start with '#'
49 | 'E266', # Too many leading '#' for block comment
50 | 'E402', # Module level import not at top of file
51 | 'E501', # Line too long
52 | ]
53 |
54 | if reformat:
55 | command = ['python', '-m', 'autopep8',
56 | '--aggressive', '--aggressive', '--in-place']
57 | command.extend(files)
58 | if not _capture_output(command):
59 | return False
60 |
61 | command = ['python', '-m', 'autopep8',
62 | '--ignore={0}'.format(','.join(ignored))]
63 | return all([_capture_output(command + [f]) for f in files])
64 |
65 |
66 | def main():
67 | parser = argparse.ArgumentParser(
68 | description='Run codechecks and optionally reformat the code.')
69 | parser.add_argument(
70 | '--reformat',
71 | dest='reformat',
72 | action='store_true',
73 | default=False,
74 | help='Reformat the code.')
75 | args = parser.parse_args()
76 |
77 | # Get a list of all the files in this repository:
78 | files = _decode_terminal_output(
79 | subprocess.check_output(['git', 'ls-files'])).split('\n')
80 |
81 | # Ignore files taken and modified from llvm/clang as reformatting makes
82 | # upstreaming changes hard.
83 | ignored_directories = [os.path.join('ffig', 'clang')]
84 | files = [f for f in files if os.path.dirname(f) not in ignored_directories]
85 |
86 | # Collect the result of each stage.
87 | results = []
88 |
89 | # Run language-specific checks on subsets of the file list:
90 | results.append(
91 | python_checks(
92 | filter(
93 | is_python_file,
94 | files),
95 | reformat=args.reformat))
96 |
97 | if False in results:
98 | log.error('Checks failed')
99 | sys.exit(1)
100 | else:
101 | log.info('Checks passed')
102 | sys.exit(0)
103 |
104 |
105 | if __name__ == '__main__':
106 | main()
107 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 |
--------------------------------------------------------------------------------
/ffig.bzl:
--------------------------------------------------------------------------------
1 | LIBFFIG = "//:libffig"
2 | FFIG_PY = "//:ffig_py"
3 |
4 | def _ffig_gen_src(name, srcs = None, deps = None, module = None, templates = None, copts = None, genfiles = None):
5 | srcs = srcs or []
6 | deps = deps or []
7 | copts = copts or []
8 |
9 | if len(srcs) != 1:
10 | fail("ffig code generation only supports a single source file.")
11 |
12 | if not module:
13 | fail("module must be supplied")
14 | if not templates:
15 | fail("templates must be supplied")
16 | if not genfiles:
17 | fail("genfiles must be supplied")
18 |
19 | ffig_py_path = "$(location {})".format(FFIG_PY)
20 | source_files = " ".join(["$(locations {})".format(src) for src in srcs])
21 | cflags = " ".join(["--cflag={}".format(copt) for copt in copts])
22 |
23 | # Generate source with FFIG.
24 | # $(@D) is the output directory for the target package.
25 | native.genrule(
26 | name = name,
27 | cmd = "{} -i {} -m {} -o $(@D) -b {} {}".format(
28 | ffig_py_path,
29 | source_files,
30 | module,
31 | " ".join(templates),
32 | cflags,
33 | ),
34 | srcs = srcs + ["//:ffig_headers"],
35 | outs = genfiles,
36 | tools = [FFIG_PY, LIBFFIG],
37 | )
38 |
39 | def ffig_c_library(name, module, srcs = None, deps = None, copts = None):
40 | srcs = srcs or []
41 | deps = deps or []
42 | copts = copts or []
43 |
44 | c_srcs = [module + "_c.h", module + "_c.cpp"]
45 |
46 | # Generate source with FFIG.
47 | _ffig_gen_src(
48 | name = "_" + name + "_c_srcs",
49 | srcs = srcs,
50 | module = module,
51 | templates = ["c"],
52 | copts = copts,
53 | genfiles = c_srcs,
54 | )
55 |
56 | # Build a C DSO for FFIG's generated C-API.
57 | native.cc_binary(
58 | name = name + "_c.so",
59 | linkshared = 1,
60 | srcs = c_srcs + srcs,
61 | deps = deps + ["//:libffig"],
62 | copts = copts,
63 | )
64 |
65 | # Package the DSO and generated header into a cc_library.
66 | native.cc_library(
67 | name = name,
68 | hdrs = [name + "_c.h"],
69 | srcs = [":" + name + "_c.so"],
70 | )
71 |
72 | def ffig_csharp_src(name, module, srcs = None, deps = None, copts = None):
73 | _ffig_gen_src(
74 | name = name,
75 | srcs = srcs,
76 | module = module,
77 | templates = ["cs.tmpl"],
78 | copts = copts,
79 | genfiles = [module + ".cs"],
80 | )
81 |
82 | def ffig_py_src(name, module, srcs = None, deps = None, copts = None):
83 | if not module.islower():
84 | fail("Module name for FFIG Python source must be lower case")
85 | _ffig_gen_src(
86 | name = name,
87 | srcs = srcs,
88 | module = module,
89 | templates = ["python"],
90 | copts = copts,
91 | genfiles = [module + "/_py2.py", module + "/_py3.py", module + "/__init__.py"],
92 | )
93 |
94 | def ffig_swift_src(name, module, srcs = None, deps = None, copts = None):
95 | _ffig_gen_src(
96 | name = name,
97 | srcs = srcs,
98 | module = module,
99 | templates = ["swift"],
100 | copts = copts,
101 | genfiles = [module + ".swift", module + "-Bridging-Header.h"],
102 | )
103 |
104 | def ffig_ruby_src(name, module, srcs = None, deps = None, copts = None):
105 | _ffig_gen_src(
106 | name = name,
107 | srcs = srcs,
108 | module = module,
109 | templates = ["ruby"],
110 | copts = copts,
111 | genfiles = [module + ".rb"],
112 | )
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Build status (on Travis-CI):** [](https://travis-ci.org/FFIG/ffig)
2 |
3 | **Build status (on AppVeyor):** [](https://ci.appveyor.com/project/jbcoe/ffig?branch=master)
4 |
5 | # FFIG - a Foreign Function Interface Generator.
6 |
7 | This project uses libclang to read existing C++ class definitions and create
8 | equivalent classes in other languages (primarily Python for now) and binds them
9 | to the C++ implementation.
10 |
11 | ## A Comparison of FFIG with SWIG
12 |
13 | While similar to SWIG, , FFIG does not need an interface
14 | generation language to be used nor do the bindings it generates depend on any
15 | binary details of an interpreter. FFIG Python bindings will run on PyPy,
16 | Python2 and Python3 without requiring changes.
17 |
18 | FFIG is in early development. We welcome feedback from users but would
19 | encourage anyone looking to generate language bindings to look at SWIG.
20 |
21 |
22 | # Setup (Linux)
23 |
24 | You will need Python (2 or 3), ninja-build, cmake, and gcc (or clang).
25 |
26 | You will need libclang >=5.0.
27 |
28 | libclang can be installed from here:
29 |
30 | If you use a package manager to install libclang you may need to set up a symlink so that the name `libclang.so` exists.
31 |
32 | Set `LD_LIBRARY_PATH` so that libclang can be found.
33 |
34 |
35 | # Setup (macOS)
36 |
37 | [Install Ninja build](http://macappstore.org/ninja/).
38 |
39 | Install Xcode 9.3 or later. FFIG will use the version of libclang distributed with Xcode.
40 |
41 |
42 | # Setup (Windows)
43 |
44 | Work in progress, minor issues expected.
45 |
46 |
47 | # Setup (Generic)
48 | The following steps will need to be performed for all build platforms.
49 |
50 | ## Submodules
51 | Tests use the 'catch' test framework:
52 |
53 | To get the submodule run:
54 |
55 | ```
56 | git submodule update --init
57 | ```
58 |
59 | ## Package Dependencies
60 | Python package dependencies can be installed with
61 |
62 | ```
63 | pip[3] install -r requirements.txt
64 | ```
65 |
66 | Ruby dependencies can be installed with
67 |
68 | ```
69 | gem install ffi
70 | ```
71 |
72 | # Building
73 | The build uses cmake driven by a simple Python script. To build and run tests, run the following from the console:
74 |
75 | ```
76 | ./scripts/build.py -t [--python-path=$(which python3)]
77 | ```
78 |
79 | # Issues
80 |
81 | Please raise github issues if code cannot be generated where expected or if generated code does not behave as expected.
82 |
83 |
84 | # Contributing
85 |
86 | Contributions are very welcome, please look at unassigned github issues or raise issues for suggested improvements.
87 |
88 | # Git Hooks
89 |
90 | We have a Git pre-push hook, `scripts/pre-push.py`, which runs the code
91 | formatting checks (`scripts/codechecks.py`) and prevents a push happening if
92 | the code checks failed. This avoids a CI test cycle for simple formatting
93 | errors.
94 |
95 | To install this git hook, you can run `scripts/install-git-hooks.py`, which
96 | will link the script into your `.git/hooks` directory.
97 |
98 | # Attribution
99 |
100 | We've made considerable use of the following in putting this together:
101 |
102 | *
103 | *
104 | *
105 |
106 | Design of the python bindings is taken from clang's cindex.
107 |
108 | *
109 |
110 | Mistakes are our own.
111 |
112 |
--------------------------------------------------------------------------------
/ffig/templates/lua.tmpl:
--------------------------------------------------------------------------------
1 | {%- import 'ffig.macros' as ffig_macros -%}
2 | {%- import '_c.macros' as c_macros -%}
3 | {#
4 | # method_arguments:
5 | # Generates a list of argument names suitable for use in a
6 | # function call. Commas are inserted between each element of the list.
7 | # Leading and trailing commas can be added by setting leading_comma or
8 | # trailing_comma to True as appropriate. This is useful if there are other
9 | # parameters that must be declared.
10 | #}
11 | {%- macro method_arguments(method, leading_comma=False, trailing_comma=False) -%}
12 | {%- call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg.name}}
14 | {%- endcall -%}
15 | {%- endmacro -%}
16 |
17 | -- This code was generated by FFIG .
18 | -- Manual edits will be lost.
19 | local ffi = require("ffi")
20 | ffi.cdef[[
21 | {% for class in classes %}
22 | typedef const void* {{module.name}}_{{class.name}};
23 | {%- endfor %}
24 |
25 | void {{module.name}}_clear_error();
26 |
27 | const char* {{module.name}}_error();
28 |
29 | {% for class in classes %}
30 | void {{module.name}}_{{class.name}}_dispose({{module.name}}_{{class.name}} my{{class.name}});
31 |
32 | {% if not class.is_abstract -%}
33 | {% for method in class.constructors %}
34 | int {{module.name}}_{{ class.name }}_create(
35 | {{c_macros.method_parameters(module, method, trailing_comma=True)}}
36 | {{module.name}}_{{class.name}}* rv);
37 | {% endfor %}
38 | {%- endif -%}
39 |
40 | {% for method in class.methods %}
41 | int {{module.name}}_{{ class.name }}_{{method.name}}(
42 | {{module.name}}_{{class.name}} my{{class.name}}
43 | {%- if method.returns_void -%}
44 | {{c_macros.method_parameters(module, method, leading_comma=True)}}
45 | {%- else -%}
46 | {{c_macros.method_parameters(module, method, leading_comma=True, trailing_comma=False)}},
47 | {{method.return_type | to_c(module.name)}}* rv
48 | {%- endif -%}
49 | );
50 | {% endfor %}
51 |
52 | {%- for impl in class.impls %}
53 | {% for method in impl.constructors %}
54 | int {{module.name}}_{{ impl.name }}_create(
55 | {{c_macros.method_parameters(module, method, trailing_comma=True)}}
56 | {{module.name}}_{{class.name}}* rv);
57 | {%- endfor %}
58 | {%- endfor %}
59 | ]]
60 |
61 | local lib{{module.name}} = ffi.load("{{module.name}}_c")
62 |
63 | {{class.name}} = {}
64 | {{class.name}}.__index = {{class.name}}
65 |
66 | function {{module.name}}_clear_error()
67 | lib{{module.name}}.{{module.name}}_clear_error()
68 | end
69 |
70 | function {{module.name}}_error()
71 | return ffi.string(lib{{module.name}}.{{module.name}}_error())
72 | end
73 |
74 | {% if not class.is_abstract -%}
75 | {% for method in class.constructors %}
76 | function {{class.name}}:new(o)
77 | local o = o or {}
78 | setmetatable(o, self)
79 | self.__index = self
80 | v = ffi.new("const void*[1]")
81 | rc = lib{{module.name}}.{{module.name}}_{{class.name}}_{{method.name}}({{method_arguments(method, trailing_comma=True)}}v)
82 | o.ptr = v[0]
83 | return o
84 | end
85 | {%- endfor -%}
86 | {%- endif %}
87 |
88 | {% for impl in class.impls %}
89 | {{impl.name}} = {}
90 | {% for method in impl.constructors %}
91 | function {{impl.name}}:new(o)
92 | local o = o or {}
93 | setmetatable(o, self)
94 | self.__index = {{class.name}}
95 | v = ffi.new("const void*[1]")
96 | rc = lib{{module.name}}.{{module.name}}_{{impl.name}}_create({{method_arguments(method, trailing_comma=True)}}v)
97 | o.ptr = v[0]
98 | return o
99 | end
100 | {%- endfor -%}
101 | {% endfor %}
102 |
103 | {% for method in class.methods %}
104 | function {{class.name}}:{{method.name}}()
105 | v = ffi.new("{{method.return_type.name}}[1]")
106 | rc = lib{{module.name}}.{{module.name}}_{{class.name}}_{{method.name}}(self.ptr, {{method_arguments(method, trailing_comma=True)}}v)
107 | return {{method.return_type|to_lua("v[0]")}}
108 | end
109 | {% endfor %}
110 | {%- endfor %}
111 |
112 |
--------------------------------------------------------------------------------
/tests/cppmodel/util.py:
--------------------------------------------------------------------------------
1 | # This file provides common utility functions for the test suite.
2 |
3 | from ffig.clang.cindex import Cursor, TranslationUnit, Config
4 |
5 | import os.path
6 | import sys
7 |
8 | Config.set_compatibility_check(False)
9 |
10 |
11 | def find_clang_library_path():
12 | paths = [
13 | '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib',
14 | '/Library/Developer/CommandLineTools/usr/lib',
15 | ]
16 | for path in paths:
17 | if os.path.isfile(os.path.join(path, 'libclang.dylib')):
18 | return path
19 | raise Exception('Unable to find libclang.dylib')
20 |
21 |
22 | if sys.platform == 'darwin':
23 | # OS X doesn't use DYLD_LIBRARY_PATH if System Integrity Protection is
24 | # enabled. Set the library path for libclang manually.
25 | Config.set_library_path(find_clang_library_path())
26 |
27 |
28 | def get_tu(source, lang='c', all_warnings=False, flags=[]):
29 | """Obtain a translation unit from source and language.
30 |
31 | By default, the translation unit is created from source file "t."
32 | where is the default file extension for the specified language. By
33 | default it is C, so "t.c" is the default file name.
34 |
35 | Supported languages are {c, cpp, objc}.
36 |
37 | all_warnings is a convenience argument to enable all compiler warnings.
38 | """
39 | args = list(flags)
40 | name = 't.c'
41 | if lang == 'cpp':
42 | name = 't.cpp'
43 | args.extend('-std=c++11 -stdlib=libc++'.split())
44 | elif lang == 'objc':
45 | name = 't.m'
46 | elif lang != 'c':
47 | raise Exception('Unknown language: %s' % lang)
48 |
49 | if all_warnings:
50 | args += ['-Wall', '-Wextra']
51 |
52 | return TranslationUnit.from_source(
53 | name, args, unsaved_files=[(name, source)])
54 |
55 |
56 | def get_named_tu(source, name, all_warnings=False, flags=[]):
57 | """Obtain a translation unit from source and filename.
58 |
59 | Language is deduced from the filename.
60 |
61 | The filename does not need to correspond to a real file but will be the
62 | name of an unsaved translation unit.
63 | """
64 |
65 | args = list(flags)
66 | if name.endswith('cpp') or name.endswith('.cxx'):
67 | args.extend('-x c++ -std=c++11 -stdlib=libc++'.split())
68 | if all_warnings:
69 | args += ['-Wall', '-Wextra']
70 |
71 | return TranslationUnit.from_source(
72 | name, args, unsaved_files=[(name, source)])
73 |
74 |
75 | def get_cursor(source, spelling):
76 | """Obtain a cursor from a source object.
77 |
78 | This provides a convenient search mechanism to find a cursor with specific
79 | spelling within a source. The first argument can be either a
80 | TranslationUnit or Cursor instance.
81 |
82 | If the cursor is not found, None is returned.
83 | """
84 | # Convenience for calling on a TU.
85 | root_cursor = source if isinstance(source, Cursor) else source.cursor
86 |
87 | for cursor in root_cursor.walk_preorder():
88 | if cursor.spelling == spelling:
89 | return cursor
90 |
91 | return None
92 |
93 |
94 | def get_cursors(source, spelling):
95 | """Obtain all cursors from a source object with a specific spelling.
96 |
97 | This provides a convenient search mechanism to find all cursors with
98 | specific spelling within a source. The first argument can be either a
99 | TranslationUnit or Cursor instance.
100 |
101 | If no cursors are found, an empty list is returned.
102 | """
103 | # Convenience for calling on a TU.
104 | root_cursor = source if isinstance(source, Cursor) else source.cursor
105 |
106 | cursors = []
107 | for cursor in root_cursor.walk_preorder():
108 | if cursor.spelling == spelling:
109 | cursors.append(cursor)
110 |
111 | return cursors
112 |
113 |
114 | __all__ = [
115 | 'get_cursor',
116 | 'get_cursors',
117 | 'get_tu',
118 | ]
119 |
--------------------------------------------------------------------------------
/ffig/templates/boost_python.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros -%}
2 |
3 | {#
4 | # method_parameters:
5 | # Generates a list of parameter types and names suitable for use in a
6 | # function declaration or definition. Commas are inserted between each
7 | # element of the list. Leading and trailing commas can be added by setting
8 | # leading_comma or trailing_comma to True as appropriate. This is useful
9 | # if there are other parameters that must be declared.
10 | #}
11 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg.type}} {{arg.name}}
14 | {%- endcall %}
15 | {% endmacro -%}
16 |
17 | {#
18 | # method_arguments:
19 | # Generates a list of argument names suitable for use in a
20 | # function call. Commas are inserted between each element of the list.
21 | # Leading and trailing commas can be added by setting leading_comma or
22 | # trailing_comma to True as appropriate. This is useful if there are other
23 | # parameters that must be declared.
24 | #}
25 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg.type}}
28 | {%- endcall %}
29 | {% endmacro -%}
30 |
31 | {% macro method_values(method, leading_comma=False, trailing_comma=False) %}
32 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
33 | {{arg.name}}
34 | {%- endcall %}
35 | {% endmacro -%}
36 |
37 | // This code was generated by FFIG .
38 | // Manual edits will be lost.
39 | #include
40 | #include "{{module.name}}.h"
41 | #include
42 | #include
43 |
44 | BOOST_PYTHON_MODULE({{module.name}}_py)
45 | {
46 | using namespace boost::python;
47 | {% for class in classes %}
48 | {% if class.is_virtual %}
49 | struct {{class.name}}Wrap : {{class.name}}, wrapper<{{class.name}}>
50 | {
51 | {% for method in class.methods %}
52 | {% if method.is_property %}
53 | {{method.return_type}} {{method.name}}(){% if method.is_const %} const{% endif %}{% if method.is_noexcept %} noexcept{% endif %} {
54 | {% if not method.is_pure_virtual %}if (override f = this->get_override("f"))
55 | return f();{% endif %}
56 | return this->get_override("{{method.name}}")();
57 | }
58 |
59 | {{method.return_type}} default_{{method.name}}(){% if method.is_const %} const{% endif %}{% if method.is_noexcept %} noexcept{% endif %} {
60 | return {{class.name}}::{{method.name}}();
61 | }
62 |
63 | {% else %}
64 | {{method.return_type}} {{method.name}}({{method_parameters(method)}}){% if method.is_const %} const{% endif %}{% if method.is_noexcept %} noexcept{% endif %} {
65 | {% if not method.is_pure_virtual %}if (override f = this->get_override("{{method.name}}"))
66 | return f({{method_values(method)}});{% endif %}
67 | return this->get_override("{{method.name}}")({{method_values(method)}});
68 | }
69 |
70 | {{method.return_type}} default_{{method.name}}({{method_parameters(method)}}){% if method.is_const %} const{% endif %}{% if method.is_noexcept %} noexcept{% endif %} {
71 | return {{class.name}}::{{method.name}}({{method_values(method)}});
72 | }
73 |
74 | {% endif %}
75 | {% endfor %}
76 | };
77 | {% endif %}
78 |
79 | {% if class.is_abstract %}class_<{{class.name}}, boost::noncopyable>("{{class.name}}", no_init)
80 | {% else %}class_<{{class.name}}>("{{class.name}}"{% for constructor in class.constructors %}, init<{{method_arguments(constructor)}}>(){% endfor %}){% endif %}
81 | {% for method in class.methods %}
82 | {% if method.is_property %}
83 | .add_property("{{method.name}}", &{{class.name}}::{{method.name}}
84 | {% if method.is_virtual and not method.is_pure_virtual %}, &{{class.name}}Wrap::default_{{method.name}}{% endif %}
85 | {% if method.returns_sub_object %}return_internal_reference<1, with_custodian_and_ward_postcall<0, 1> >(){% endif %}
86 | )
87 | {% else %}
88 | .def("{{method.name}}", &{{class.name}}::{{method.name}}
89 | {% if method.is_virtual and not method.is_pure_virtual %}, &{{class.name}}Wrap::default_{{method.name}}{% endif %}
90 | {% if method.returns_sub_object %}, return_internal_reference<1, with_custodian_and_ward_postcall<0, 1> >(){% endif %}
91 | )
92 | {% endif %}
93 | {% endfor %}
94 | ;
95 |
96 | {% for impl in class.impls %}
97 | class_<{{impl.name}}, bases<{{class.name}}> >("{{impl.name}}"{% for constructor in impl.constructors %}, init<{{method_arguments(constructor)}}>(){% endfor %})
98 | ;
99 | {% endfor %}
100 |
101 | {% endfor %}
102 | }
103 |
--------------------------------------------------------------------------------
/tests/cppmodel/test_classes.py:
--------------------------------------------------------------------------------
1 | from util import get_tu
2 | import ffig.cppmodel
3 | from ffig.clang.cindex import TypeKind
4 | from nose.tools import assert_equals
5 |
6 |
7 | def test_class_name():
8 | source = 'class A{};'
9 | tu = get_tu(source, 'cpp')
10 |
11 | model = ffig.cppmodel.Model(tu)
12 | classes = model.classes
13 |
14 | assert len(classes) == 1
15 | assert classes[0].name == 'A'
16 |
17 |
18 | def test_class_methods():
19 | source = """
20 | class A{};
21 | class B{
22 | void foo();
23 | int bar();
24 | };"""
25 | tu = get_tu(source, 'cpp')
26 |
27 | model = ffig.cppmodel.Model(tu)
28 | classes = model.classes
29 |
30 | assert len(classes[0].methods) == 0
31 | assert len(classes[1].methods) == 2
32 |
33 |
34 | def test_class_method_return_types():
35 | source = """
36 | class B{
37 | void foo();
38 | int bar();
39 | };"""
40 | tu = get_tu(source, 'cpp')
41 |
42 | model = ffig.cppmodel.Model(tu)
43 | classes = model.classes
44 |
45 | assert classes[0].methods[0].return_type.kind == TypeKind.VOID
46 | assert classes[0].methods[1].return_type.kind == TypeKind.INT
47 |
48 |
49 | def test_class_method_argument_types():
50 | source = """
51 | class A {
52 | int foo(int i, const char* p);
53 | };"""
54 | tu = get_tu(source, 'cpp')
55 |
56 | model = ffig.cppmodel.Model(tu)
57 | classes = model.classes
58 | args = classes[0].methods[0].arguments
59 |
60 | assert args[0].type.kind == TypeKind.INT
61 | assert args[0].name == "i"
62 | assert args[1].type.kind == TypeKind.POINTER
63 | assert args[1].name == "p"
64 |
65 |
66 | def test_class_method_const_qualifiers():
67 | source = """
68 | class A {
69 | int foo() const;
70 | int bar();
71 | };"""
72 | tu = get_tu(source, 'cpp')
73 |
74 | model = ffig.cppmodel.Model(tu)
75 | classes = model.classes
76 | methods = classes[0].methods
77 |
78 | assert methods[0].is_const
79 | assert not methods[1].is_const
80 |
81 |
82 | def test_class_methods_are_virtual():
83 | source = """
84 | class A {
85 | virtual int foo();
86 | int bar();
87 | virtual int foobar() = 0;
88 | };"""
89 | tu = get_tu(source, 'cpp')
90 |
91 | model = ffig.cppmodel.Model(tu)
92 | classes = model.classes
93 | methods = classes[0].methods
94 |
95 | assert methods[0].is_virtual
96 | assert not methods[0].is_pure_virtual
97 | assert not methods[1].is_virtual
98 | assert methods[2].is_pure_virtual
99 |
100 |
101 | def test_namespaces():
102 | source = """
103 | class A{};
104 | namespace outer {
105 | class B{};
106 | namespace inner {
107 | class C{};
108 | } // end inner
109 | class D{};
110 | } // end outer
111 | class E{};"""
112 | tu = get_tu(source, 'cpp')
113 |
114 | model = ffig.cppmodel.Model(tu)
115 | classes = model.classes
116 |
117 | assert classes[0].namespace == ""
118 | assert classes[1].namespace == "outer"
119 | assert classes[2].namespace == "outer::inner"
120 | assert classes[3].namespace == "outer"
121 | assert classes[4].namespace == ""
122 |
123 |
124 | def test_access_specifiers():
125 | source = """
126 | class A { int foo(); };
127 | struct B { int foo(); };
128 | class C { public: int foo(); };
129 | """
130 | tu = get_tu(source, 'cpp')
131 |
132 | model = ffig.cppmodel.Model(tu)
133 | classes = model.classes
134 |
135 | assert not classes[0].methods[0].is_public
136 | assert classes[1].methods[0].is_public
137 | assert classes[2].methods[0].is_public
138 |
139 |
140 | def test_class_member_data():
141 | source = """
142 | class A {};
143 | class B {
144 | int x_;
145 | A a_;
146 | };
147 | """
148 |
149 | tu = get_tu(source, 'cpp')
150 |
151 | model = ffig.cppmodel.Model(tu)
152 | c = model.classes[1]
153 |
154 | assert c.members[0].type.kind == TypeKind.INT
155 | assert c.members[0].type.name == "int"
156 | assert c.members[0].name == "x_"
157 |
158 | assert c.members[1].type.kind == TypeKind.RECORD
159 | assert c.members[1].type.name == "A"
160 | assert c.members[1].name == "a_"
161 |
162 |
163 | def test_string_representation():
164 | source = """
165 | class A {
166 | virtual int foo();
167 | int bar();
168 | virtual int foobar() = 0;
169 | virtual int cfoobar(int x) const = 0;
170 | };"""
171 | tu = get_tu(source, 'cpp')
172 |
173 | model = ffig.cppmodel.Model(tu)
174 | classes = model.classes
175 | methods = classes[0].methods
176 |
177 | assert_equals(str(methods[0]),
178 | '')
179 | assert_equals(str(methods[1]),
180 | '')
181 | assert_equals(str(methods[2]),
182 | '')
183 | assert_equals(str(methods[3]),
184 | '')
185 |
186 |
187 | def test_noexcept():
188 | source = """
189 | class A {
190 | virtual int foo() noexcept;
191 | };"""
192 | tu = get_tu(source, 'cpp')
193 |
194 | model = ffig.cppmodel.Model(tu)
195 | classes = model.classes
196 | methods = classes[0].methods
197 |
198 | assert_equals(str(methods[0]),
199 | '')
200 | assert methods[0].is_noexcept
201 |
--------------------------------------------------------------------------------
/ffig/templates/py2.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'py2.macros' as py2_macros %}
2 | # This code was generated by FFIG .
3 | # Manual edits will be lost.
4 |
5 | import os
6 | from ctypes import *
7 | c_object_p = POINTER(c_void_p)
8 |
9 |
10 | class {{module.name}}_error(Exception):
11 |
12 | def __init__(self):
13 | self.value = conf.lib.{{module.name}}_error()
14 | conf.lib.{{module.name}}_clear_error()
15 |
16 | def __str__(self):
17 | return self.value
18 | {% for class in classes %}
19 |
20 |
21 | class {{class.name}}:
22 | {% if not class.is_abstract %}
23 |
24 | @classmethod
25 | def from_capi(cls, ptr):
26 | assert(isinstance(ptr, c_object_p))
27 | if not bool(ptr):
28 | return None
29 | return cls(_p=ptr)
30 | {% for method in class.constructors %}
31 |
32 | def __init__(self, {{py2_macros.constructor_parameters(method, trailing_comma=True)}}_p=None):
33 | if _p:
34 | self.ptr = _p
35 | else:
36 | self.ptr = c_object_p()
37 | rc = conf.lib.{{module.name}}_{{ class.name }}_create({{py2_macros.method_arguments(method, trailing_comma=True)}}byref(self.ptr))
38 | if rc != 0:
39 | raise {{module.name}}_error()
40 | {% endfor %}
41 | {% endif %}
42 | {% for method in class.methods %}
43 |
44 | {% if method.is_property %}
45 | @property
46 | {% endif %}
47 | def {{method.name}}(self{{py2_macros.method_parameters(method, leading_comma=True)}}):
48 | {% if method.is_noexcept %}
49 | {% if method.returns_void %}
50 | conf.lib.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(self{{py2_macros.method_arguments(method, leading_comma=True)}})
51 | {% elif method.returns_sub_object or method.returns_object_by_value %}
52 | rv = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(self{{py2_macros.method_arguments(method, leading_comma=True)}})
53 | return {{method.return_type|to_py2_ctype}}.from_capi(rv)
54 | {% else %}
55 | return conf.lib.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(self{{py2_macros.method_arguments(method, leading_comma=True)}})
56 | {% endif %}
57 | {%- else -%}
58 | {% if method.returns_void %}
59 | rc = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}(self{{py2_macros.method_arguments(method, leading_comma=True)}})
60 | if rc != 0:
61 | raise {{module.name}}_error()
62 | {% elif method.returns_sub_object or method.returns_object_by_value %}
63 | rv = c_object_p()
64 | rc = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}(self{{py2_macros.method_arguments(method, leading_comma=True)}}, byref(rv))
65 | if rc == 0:
66 | return {{method.return_type|to_py2_ctype}}.from_capi(rv)
67 | raise {{module.name}}_error()
68 | {% else %}
69 | rv = {{method.return_type|to_output_py2_ctype}}()
70 | rc = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}(self{{py2_macros.method_arguments(method, leading_comma=True)}}, byref(rv))
71 | if rc == 0:
72 | return rv.value
73 | raise {{module.name}}_error()
74 | {% endif -%}
75 | {%- endif -%}
76 | {% endfor %}
77 |
78 | @classmethod
79 | def from_param(k, x):
80 | assert isinstance(x, k)
81 | return x.ptr
82 |
83 | def __del__(self):
84 | conf.lib.{{module.name}}_{{class.name}}_dispose(self)
85 | {% for impl in class.impls %}
86 |
87 |
88 | class {{impl.name}}({{class.name}}):
89 |
90 | @classmethod
91 | def from_capi(cls, ptr):
92 | assert(isinstance(ptr, c_object_p))
93 | if not bool(ptr):
94 | return None
95 | return cls(_p=ptr)
96 | {% for method in impl.constructors %}
97 |
98 | def __init__(self, {{py2_macros.constructor_parameters(method, trailing_comma=True)}}_p=None):
99 | if _p:
100 | self.ptr = _p
101 | else:
102 | self.ptr = c_object_p()
103 | rc = conf.lib.{{module.name}}_{{ impl.name }}_create({{py2_macros.method_arguments(method, trailing_comma=True)}}byref(self.ptr))
104 | if rc != 0:
105 | raise {{module.name}}_error()
106 | {% endfor %}
107 | {% endfor %}
108 | {% endfor %}
109 |
110 |
111 | methodList = [
112 | ("{{module.name}}_error",
113 | [],
114 | c_char_p),
115 | ("{{module.name}}_clear_error",
116 | [],
117 | None),
118 | {% for class in classes %}
119 | ("{{module.name}}_{{class.name}}_dispose",
120 | [{{class.name}}],
121 | None),
122 | {% if not class.is_abstract %}
123 | {% for method in class.constructors %}
124 | ("{{module.name}}_{{class.name}}_create",
125 | [{{py2_macros.method_argument_types(method, trailing_comma=True)}}POINTER(c_object_p)],
126 | c_int),
127 | {% endfor %}
128 | {% endif %}
129 | {% for impl in class.impls %}
130 | {% for method in impl.constructors %}
131 | ("{{module.name}}_{{ impl.name }}_create",
132 | [{{py2_macros.method_argument_types(method, trailing_comma=True)}}POINTER(c_object_p)],
133 | c_int),
134 | {% endfor %}
135 | {% endfor %}
136 | {% for method in class.methods %}
137 | {% if method.is_noexcept %}
138 | ("{{module.name}}_{{ class.name }}_{{method.name}}_noexcept",
139 | [{{class.name}}{{py2_macros.method_argument_types(method, leading_comma=True)}}{% if not method.returns_void %}{% endif %}],
140 | {{method.return_type|to_output_py2_ctype}}),
141 | {% else %}
142 | ("{{module.name}}_{{ class.name }}_{{method.name}}",
143 | [{{class.name}}{{py2_macros.method_argument_types(method, leading_comma=True)}}{% if not method.returns_void %}, POINTER({{method.return_type|to_output_py2_ctype}}){% endif %}],
144 | c_int),
145 | {% endif %}
146 | {% endfor %}
147 | {% endfor %}
148 | ]
149 |
150 | {% include 'config.py.tmpl' %}
151 |
152 |
--------------------------------------------------------------------------------
/ffig/templates/_cpp.h.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'ffig.macros' as ffig_macros -%}
2 |
3 | {#
4 | # method_parameters:
5 | # Generates a list of parameter types and names suitable for use in a
6 | # function declaration or definition. Commas are inserted between each
7 | # element of the list. Leading and trailing commas can be added by setting
8 | # leading_comma or trailing_comma to True as appropriate. This is useful
9 | # if there are other parameters that must be declared.
10 | #}
11 | {% macro method_parameters(method, leading_comma=False, trailing_comma=False) %}
12 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
13 | {{arg.type}} {{arg.name}}
14 | {%- endcall %}
15 | {% endmacro -%}
16 |
17 | {#
18 | # method_arguments:
19 | # Generates a list of argument names suitable for use in a
20 | # function call. Commas are inserted between each element of the list.
21 | # Leading and trailing commas can be added by setting leading_comma or
22 | # trailing_comma to True as appropriate. This is useful if there are other
23 | # parameters that must be declared.
24 | #}
25 | {% macro method_arguments(method, leading_comma=False, trailing_comma=False) %}
26 | {% call(arg) ffig_macros.comma_separated_list(method.arguments, leading_comma, trailing_comma) -%}
27 | {{arg | c_object}}
28 | {%- endcall %}
29 | {% endmacro -%}
30 |
31 | // This code was generated by FFIG .
32 | // Manual edits will be lost.
33 |
34 | #include
35 | #include
36 | #include "{{module.name}}_c.h"
37 | namespace CPP_API {
38 | {% for class in classes %}
39 |
40 | class {{class.name}}
41 | {
42 | protected:
43 | {{module.name}}_{{class.name}} object_ = nullptr;
44 | {{class.name}}() = default;
45 |
46 | public:
47 | {% if not class.is_abstract %}
48 | {% for method in class.constructors %}
49 |
50 | {{class.name}}({{method_parameters(method)}}) {% if method.is_noexcept %}noexcept{% endif %}
51 | {
52 | {% if method.is_noexcept %}
53 | object_ = {{module.name}}_{{class.name}}_create_noexcept(
54 | {{method_arguments(method)}}
55 | );
56 | {% else %}
57 | int rc = {{module.name}}_{{class.name}}_create(
58 | {{method_arguments(method, trailing_comma=True)}}
59 | &object_);
60 | if ( rc == {{module.name}}_RC_SUCCESS ) return;
61 | throw exception();
62 | {% endif %}
63 | }
64 | {% endfor %}
65 | {% endif %}
66 |
67 | {{class.name}}(const {{class.name}}&) = delete;
68 | {{class.name}}& operator = (const {{class.name}}&) = delete;
69 | {{class.name}}({{class.name}}&& c)
70 | {
71 | object_ = c.object_;
72 | c.object_ = nullptr;
73 | }
74 |
75 | {{class.name}}& operator = ({{class.name}}&& c)
76 | {
77 | if(object_) {{module.name}}_{{class.name}}_dispose(object_);
78 | object_ = c.object_;
79 | c.object_ = nullptr;
80 | return *this;
81 | }
82 |
83 | class exception : public std::runtime_error
84 | {
85 | public:
86 | exception() : std::runtime_error({{module.name}}_error())
87 | {
88 | {{module.name}}_clear_error();
89 | }
90 | };
91 |
92 | virtual ~{{class.name}}()
93 | {
94 | {{module.name}}_{{class.name}}_dispose(object_);
95 | }
96 | {% for method in class.methods %}
97 |
98 | {{method.return_type | to_cpp_type}} {{method.name}}({{method_parameters(method)}}) const {% if method.is_noexcept %}noexcept{% endif %}
99 | {
100 | {% if method.returns_void %}
101 | {% if method.is_noexcept %}
102 | {{module.name}}_{{class.name}}_{{method.name}}_noexcept(
103 | object_
104 | {{method_arguments(method, leading_comma=True)}});
105 | {% else %}
106 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(
107 | object_
108 | {{method_arguments(method, leading_comma=True)}});
109 | {% endif %}
110 | {% else %}
111 | {{method.return_type | to_cpp_type}} rv;
112 | {% if method.returns_sub_object %}
113 | {% if method.is_noexcept %}
114 | rv.object_ = {{module.name}}_{{class.name}}_{{method.name}}_noexcept(
115 | object_
116 | {{method_arguments(method, leading_comma=True)}});
117 | {% else %}
118 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(
119 | object_
120 | {{method_arguments(method, leading_comma=True)}},
121 | &rv.object_);
122 | {% endif %}
123 | {% else %}
124 | {% if method.is_noexcept %}
125 | rv = {{module.name}}_{{class.name}}_{{method.name}}_noexcept(
126 | object_
127 | {{method_arguments(method, leading_comma=True)}});
128 | {% else %}
129 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(
130 | object_
131 | {{method_arguments(method, leading_comma=True)}},
132 | &rv);
133 | {% endif %}
134 | {% endif %}
135 | {% endif %}
136 |
137 | {% if method.is_noexcept %}
138 | return {% if not method.returns_void %} rv {% endif %};
139 | {% else %}
140 | if (rc == {{module.name}}_RC_SUCCESS)
141 | {
142 | return{% if not method.returns_void %} rv {% endif %};
143 | }
144 | throw exception();
145 | {% endif %}
146 | }
147 | {% endfor %}
148 | };
149 | {% for impl in class.impls %}
150 |
151 | class {{impl.name}} : public {{class.name}}
152 | {
153 | public:
154 | {% for method in impl.constructors %}
155 |
156 | {{impl.name}}({{method_parameters(method)}}) {% if method.is_noexcept %}noexcept{% endif %}
157 | {
158 | {% if method.is_noexcept %}
159 | object_ = {{module.name}}_{{impl.name}}_create_noexcept(
160 | {{method_arguments(method)}});
161 | {% else %}
162 | int rc = {{module.name}}_{{impl.name}}_create(
163 | {{method_arguments(method, trailing_comma=True)}}
164 | &object_);
165 | if ( rc == {{module.name}}_RC_SUCCESS )
166 | {
167 | return;
168 | }
169 | throw exception();
170 | {% endif %}
171 | }
172 | {% endfor %}
173 | };
174 | {% endfor %}
175 | {% endfor %}
176 | } // end namespace CPP_API
177 |
--------------------------------------------------------------------------------
/ffig/FFIG.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # FFIG (Foreign Function Interface Generation) parses C/C++ headers with
4 | # libclang and passes the class and function information on to templates to
5 | # generate a c-api and language bindings.
6 |
7 | import argparse
8 | import ffig.annotations
9 | import ffig.clang as clang
10 | import ffig.cppmodel
11 | import ffig.filters.capi_filter
12 | import ffig.generators
13 | import inspect
14 | import jinja2
15 | import logging
16 | import os
17 | import os.path
18 | import re
19 | import sys
20 |
21 | logging.basicConfig(level=logging.WARNING)
22 |
23 | clang.cindex.Config.set_compatibility_check(False)
24 |
25 |
26 | def find_clang_library_path():
27 | paths = [
28 | '/Applications/Xcode.app/Contents/Developer/Toolchains/'
29 | 'XcodeDefault.xctoolchain/usr/lib',
30 | '/Library/Developer/CommandLineTools/usr/lib',
31 | ]
32 | for path in paths:
33 | if os.path.isfile(os.path.join(path, 'libclang.dylib')):
34 | return path
35 | raise Exception('Unable to find libclang.dylib')
36 |
37 |
38 | if sys.platform == 'darwin':
39 | # OS X doesn't use DYLD_LIBRARY_PATH if System Integrity Protection is
40 | # enabled. Set the library path for libclang manually.
41 | clang.cindex.Config.set_library_path(find_clang_library_path())
42 |
43 | ffig_dir = os.path.abspath(os.path.dirname(__file__))
44 |
45 |
46 | def collect_api_and_obj_classes(classes, api_annotation):
47 | api_classes = {c.name: c
48 | for c in classes if api_annotation in c.annotations}
49 |
50 | for _, c in api_classes.items():
51 | ffig.annotations.apply_class_annotations(c)
52 | c. impls = []
53 |
54 | for c in classes:
55 | for base in c.base_classes:
56 | if base in api_classes:
57 | ffig.annotations.apply_class_annotations(c)
58 | api_classes[base].impls.append(c)
59 |
60 | return [c for k, c in api_classes.items()]
61 |
62 |
63 | def write_bindings_to_disk(
64 | module_name,
65 | bindings,
66 | api_classes,
67 | env,
68 | output_dir):
69 | """
70 | Write the bindings to disk, return Nothing
71 | Input:
72 | - module_name - string
73 | - list of bindings to generate
74 | - api_classes
75 | - environment to get templates from
76 | - output_dir where to write to
77 | """
78 |
79 | bindings = list(set(bindings))
80 |
81 | for binding in bindings:
82 | ffig.generators.generate(module_name, binding,
83 | api_classes, env, output_dir)
84 |
85 |
86 | def build_model_from_source(
87 | path_to_source,
88 | module_name,
89 | cflags=None,
90 | unsaved_files=None):
91 | """
92 | Input:
93 | - full path to source file
94 | - module_name taken from args
95 | - [Optional] List of 2-tuples of unsaved file content in the format:
96 | (filename, content_string)
97 |
98 | Returns:
99 | - model built from a clang.cindex TranslationUnit with a name from args
100 | """
101 | cflags = cflags or []
102 |
103 | tu = clang.cindex.TranslationUnit.from_source(
104 | path_to_source,
105 | ['-x', 'c++', '-std=c++14', '-stdlib=libc++'] + cflags,
106 | unsaved_files=unsaved_files)
107 |
108 | model = ffig.cppmodel.Model(tu)
109 | model.module_name = module_name
110 |
111 | return model
112 |
113 |
114 | def make_output_dir(cwd, o_dir_path):
115 | """
116 | Function that creates the output_dir under the given path in cwd,
117 | if no such dir exists yet. Returns nothing.
118 | Input:
119 | - current working directory
120 | - output_dir path
121 | """
122 | output_dir = os.path.abspath(os.path.join(cwd, o_dir_path))
123 | if not os.path.exists(output_dir):
124 | os.makedirs(output_dir)
125 |
126 |
127 | def set_template_env(template_dir):
128 | """
129 | Function that sets the Jinja2 template environment from a given dir.
130 | Returns nothing.
131 | Input:
132 | - template_dir path
133 | """
134 | env = jinja2.Environment(
135 | loader=jinja2.FileSystemLoader(template_dir),
136 | lstrip_blocks=True,
137 | trim_blocks=True)
138 | for f, _ in inspect.getmembers(ffig.filters.capi_filter):
139 | # for f in ['to_output_ctype', 'to_ctype']:
140 | env.filters[f] = getattr(ffig.filters.capi_filter, f)
141 | return env
142 |
143 |
144 | def run(args):
145 | cwd = os.getcwd()
146 |
147 | # FIXME: Remove the need for this constraint.
148 | if len(args.inputs) > 1:
149 | raise Exception("Multiple input files are currently not supported.")
150 |
151 | # FIXME: Loop over files and extend the model once we can handle multiple
152 | # input files.
153 | input_file = os.path.join(cwd, args.inputs[0])
154 | m = build_model_from_source(input_file, args.module_name, args.cflags)
155 | classes = m.classes
156 | api_classes = collect_api_and_obj_classes(classes, 'FFIG:EXPORT')
157 |
158 | make_output_dir(cwd, args.output_dir)
159 | env = set_template_env(args.template_dir)
160 | write_bindings_to_disk(
161 | args.module_name,
162 | list(args.bindings),
163 | api_classes,
164 | env,
165 | args.output_dir)
166 |
167 |
168 | def main():
169 | parser = argparse.ArgumentParser()
170 |
171 | parser.add_argument(
172 | '-o', '--output',
173 | help='output directory (relative to cwd)',
174 | default='.',
175 | dest='output_dir')
176 | parser.add_argument(
177 | '-i', '--input',
178 | nargs='+',
179 | help='header files for input',
180 | default=[],
181 | dest='inputs',
182 | required=True)
183 | parser.add_argument(
184 | '--libclang',
185 | help='path to libclang',
186 | default='',
187 | dest='libclang')
188 | parser.add_argument(
189 | '-b', '--bindings',
190 | help='bindings files to generate',
191 | nargs='+',
192 | dest='bindings',
193 | required=True)
194 | parser.add_argument(
195 | '-t', '--template-dir',
196 | help='directory to search for templates',
197 | default=os.path.join(ffig_dir, 'templates'),
198 | dest='template_dir')
199 | parser.add_argument(
200 | '-m', '--module-name',
201 | help='module name for generated files',
202 | dest='module_name',
203 | required=True)
204 | parser.add_argument(
205 | '--cflag',
206 | help='Compiler flags for clang',
207 | default=[],
208 | dest='cflags',
209 | action='append')
210 |
211 | args = parser.parse_args()
212 |
213 | run(args)
214 |
--------------------------------------------------------------------------------
/ffig/templates/cs.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'cs.macros' as cs_macros %}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 | using System;
5 | using System.Runtime.InteropServices;
6 |
7 | namespace {{module.name}}_c {
8 |
9 | public class Exception : System.Exception {
10 |
11 | [DllImport("{{module.name}}_c")]
12 | private static extern void {{module.name}}_clear_error();
13 |
14 | [DllImport("{{module.name}}_c")]
15 | private static extern IntPtr {{module.name}}_error();
16 |
17 | public Exception() : base(Marshal.PtrToStringAnsi({{module.name}}_error())) {
18 | {{module.name}}_clear_error();
19 | }
20 | }
21 |
22 | {% for class in classes %}
23 | public class {{class.name}} {
24 |
25 | [DllImport("{{module.name}}_c")]
26 | private static extern int {{module.name}}_{{class.name}}_dispose(IntPtr c_obj);
27 | {% for method in class.constructors %}
28 |
29 | [DllImport("{{module.name}}_c")]
30 | private static extern int {{module.name}}_{{class.name}}_create({{cs_macros.method_c_parameters(method, trailing_comma=True)}}out IntPtr ptr);
31 | {% endfor %}
32 | {% for method in class.methods %}
33 | {% if method.is_noexcept %}
34 | {% if method.returns_void %}
35 | [DllImport("{{module.name}}_c")]
36 | private static extern void {{module.name}}_{{class.name}}_{{method.name}}_noexcept(IntPtr c_obj{{cs_macros.method_c_parameters(method, leading_comma=True)}});
37 | {% else %}
38 | [DllImport("{{module.name}}_c")]
39 | private static extern {{method.return_type|to_dotnet_output_param}} {{module.name}}_{{class.name}}_{{method.name}}_noexcept(IntPtr c_obj{{cs_macros.method_c_parameters(method, leading_comma=True)}});
40 | {% endif %}
41 | {% else %}
42 | {% if method.returns_void %}
43 | [DllImport("{{module.name}}_c")]
44 | private static extern int {{module.name}}_{{class.name}}_{{method.name}}(IntPtr c_obj, {{cs_macros.method_c_parameters(method)}});
45 | {% else %}
46 | [DllImport("{{module.name}}_c")]
47 | private static extern int {{module.name}}_{{class.name}}_{{method.name}}(IntPtr c_obj, {{cs_macros.method_c_parameters(method, trailing_comma=True)}}out {{method.return_type|to_dotnet_output_param}} rv);
48 | {% endif %}
49 | {% endif %}
50 | {% endfor %}
51 |
52 | protected IntPtr c_obj_;
53 | {% if not class.is_abstract %}
54 | {% for method in class.constructors %}
55 |
56 | public {{class.name}}({{cs_macros.method_parameters(method)}}) {
57 | int rc = {{module.name}}_{{class.name}}_create({{cs_macros.method_arguments(method, trailing_comma=True)}}out c_obj_);
58 | if(rc != 0) {
59 | throw new {{module.name}}_c.Exception();
60 | }
61 | }
62 | {% endfor %}
63 | {% endif %}
64 |
65 | protected {{class.name}}() {
66 | }
67 |
68 | protected {{class.name}}(IntPtr c_obj) {
69 | c_obj_ = c_obj;
70 | }
71 |
72 | ~{{class.name}}() {
73 | {{module.name}}_{{class.name}}_dispose(c_obj_);
74 | }
75 | {% for method in class.methods %}
76 | {% if method.is_noexcept %}
77 | {% if method.returns_void %}
78 | public void {{method.name}}({{cs_macros.method_parameters(method)}}) {
79 | {{module.name}}_{{class.name}}_{{method.name}}_noexcept(c_obj_, {{cs_macros.method_arguments(method)}});
80 | }
81 | {% elif method.is_property %}
82 | public {{method.return_type|to_dotnet_return_type}} {{method.name}} {
83 | get {
84 | var rv = {{module.name}}_{{class.name}}_{{method.name}}_noexcept(c_obj_{{cs_macros.method_arguments(method, leading_comma=True)}});
85 | {% if method.returns_nullable %}
86 | if(rv == IntPtr.Zero) {
87 | return null;
88 | }
89 | {% endif %}
90 | return {{method.return_type|to_dotnet_return_value("rv")}};
91 | }
92 | }
93 | {% else %}
94 | public {{method.return_type|to_dotnet_return_type}} {{method.name}}({{cs_macros.method_parameters(method)}}) {
95 | var rv = {{module.name}}_{{class.name}}_{{method.name}}_noexcept(c_obj_{{cs_macros.method_arguments(method, leading_comma=True)}});
96 | {% if method.returns_nullable %}
97 | if(rv == IntPtr.Zero) {
98 | return null;
99 | }
100 | {% endif %}
101 | return {{method.return_type|to_dotnet_return_value("rv")}};
102 | }
103 | {% endif %}
104 | {% else %}
105 | {% if method.returns_void %}
106 | public void {{method.name}}({{cs_macros.method_parameters(method)}}) {
107 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(c_obj_, {{cs_macros.method_arguments(method)}});
108 | if(rc != 0) {
109 | throw new {{module.name}}_c.Exception();
110 | }
111 | }
112 | {% elif method.is_property %}
113 | public {{method.return_type|to_dotnet_return_type}} {{method.name}} {
114 | get {
115 | {{method.return_type|to_dotnet_output_value("rv")}};
116 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(c_obj_, {{cs_macros.method_arguments(method, trailing_comma=True)}}out rv);
117 | if(rc != 0) {
118 | throw new {{module.name}}_c.Exception();
119 | }
120 | {% if method.returns_nullable %}
121 | if(rv == IntPtr.Zero) {
122 | return null;
123 | }
124 | {% endif %}
125 | return {{method.return_type|to_dotnet_return_value("rv")}};
126 | }
127 | }
128 | {% else %}
129 | public {{method.return_type|to_dotnet_return_type}} {{method.name}}({{cs_macros.method_parameters(method)}}) {
130 | {{method.return_type|to_dotnet_output_value("rv")}};
131 | int rc = {{module.name}}_{{class.name}}_{{method.name}}(c_obj_, {{cs_macros.method_arguments(method, trailing_comma=True)}}out rv);
132 | if(rc != 0) {
133 | throw new {{module.name}}_c.Exception();
134 | }
135 | {% if method.returns_nullable %}
136 | if(rv == IntPtr.Zero) {
137 | return null;
138 | }
139 | {% endif %}
140 | return {{method.return_type|to_dotnet_return_value("rv")}};
141 | }
142 | {% endif %}
143 | {% endif %}
144 | {% endfor %}
145 | }
146 | {% for impl in class.impls %}
147 |
148 | public class {{impl.name}} : {{module.name}}_c.{{class.name}} {
149 | {% for method in impl.constructors %}
150 |
151 | [DllImport("{{module.name}}_c")]
152 | private static extern int {{module.name}}_{{impl.name}}_create({{cs_macros.method_c_parameters(method, trailing_comma=True)}}out IntPtr ptr);
153 |
154 | public {{impl.name}}({{cs_macros.method_parameters(method)}}) {
155 | int rc = {{module.name}}_{{impl.name}}_create({{cs_macros.method_arguments(method, trailing_comma=True)}}out c_obj_);
156 | if(rc != 0) {
157 | throw new {{module.name}}_c.Exception();
158 | }
159 | }
160 | {% endfor %}
161 | }
162 | {% endfor %}
163 |
164 | {% endfor %}
165 | }
166 |
167 |
--------------------------------------------------------------------------------
/ffig/templates/py3.tmpl:
--------------------------------------------------------------------------------
1 | {% import 'py3.macros' as py3_macros %}
2 | # This code was generated by FFIG .
3 | # Manual edits will be lost.
4 |
5 | import os
6 | from ctypes import *
7 | c_object_p = POINTER(c_void_p)
8 |
9 |
10 | class c_interop_string(c_char_p):
11 |
12 | def __init__(self, p=None):
13 | if p is None:
14 | p = ""
15 | if isinstance(p, str):
16 | p = p.encode("utf8")
17 | super(c_char_p, self).__init__(p)
18 |
19 | def __str__(self):
20 | return self.value
21 |
22 | @property
23 | def value(self):
24 | if super(c_char_p, self).value is None:
25 | return None
26 | return super(c_char_p, self).value.decode("utf8")
27 |
28 | @classmethod
29 | def from_param(cls, param):
30 | if isinstance(param, str):
31 | return cls(param)
32 | if isinstance(param, bytes):
33 | return cls(param)
34 | raise TypeError("Cannot convert '{}' to '{}'".format(type(param).__name__, cls.__name__))
35 |
36 | @staticmethod
37 | def to_python_string(x, *args):
38 | return x.value
39 |
40 |
41 | class {{module.name}}_error(Exception):
42 | def __init__(self):
43 | self.value = conf.lib.{{module.name}}_error()
44 | conf.lib.{{module.name}}_clear_error()
45 |
46 | def __str__(self):
47 | return self.value
48 | {% for class in classes %}
49 |
50 |
51 | class {{class.name}}:
52 | {% if not class.is_abstract %}
53 |
54 | @classmethod
55 | def from_capi(cls, ptr):
56 | assert(isinstance(ptr, c_object_p))
57 | if not bool(ptr):
58 | return None
59 | return cls(_p=ptr)
60 | {% for method in class.constructors %}
61 |
62 | def __init__(self, {{py3_macros.constructor_parameters(method, trailing_comma=True)}}_p=None) -> '{{class.name}}':
63 | if _p:
64 | self.ptr = _p
65 | else:
66 | self.ptr = c_object_p()
67 | rc = conf.lib.{{module.name}}_{{ class.name }}_create({{py3_macros.method_arguments(method, trailing_comma=True)}}byref(self.ptr))
68 | if rc != 0:
69 | raise {{module.name}}_error()
70 | {% endfor %}
71 | {% endif %}
72 | {% for method in class.methods %}
73 |
74 | {% if method.is_property %}
75 | @property
76 | {% endif %}
77 | def {{method.name}}(self{{py3_macros.method_parameters(method, leading_comma=True)}}) -> '{{method.return_type|to_hint_type}}':
78 | {% if method.is_noexcept %}
79 | {% if method.returns_void %}
80 | conf.lib.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(self{{py3_macros.method_arguments(method, leading_comma=True)}})
81 | {% elif method.returns_sub_object or method.returns_object_by_value %}
82 | rv = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(self{{py3_macros.method_arguments(method, leading_comma=True)}})
83 | return {{method.return_type|to_py3_ctype}}.from_capi(rv)
84 | {% else %}
85 | return conf.lib.{{module.name}}_{{class.name}}_{{method.name}}_noexcept(self{{py3_macros.method_arguments(method, leading_comma=True)}})
86 | {% endif %}
87 | {%- else -%}
88 | {% if method.returns_void %}
89 | rc = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}(self{{py3_macros.method_arguments(method, leading_comma=True)}})
90 | if rc != 0:
91 | raise {{module.name}}_error()
92 | {% elif method.returns_sub_object or method.returns_object_by_value %}
93 | rv = c_object_p()
94 | rc = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}(self{{py3_macros.method_arguments(method, leading_comma=True)}}, byref(rv))
95 | if rc == 0:
96 | return {{method.return_type|to_py3_ctype}}.from_capi(rv)
97 | raise {{module.name}}_error()
98 | {% else %}
99 | rv = {{method.return_type|to_output_py3_ctype}}()
100 | rc = conf.lib.{{module.name}}_{{class.name}}_{{method.name}}(self{{py3_macros.method_arguments(method, leading_comma=True)}}, byref(rv))
101 | if rc == 0:
102 | return rv.value
103 | raise {{module.name}}_error()
104 | {% endif -%}
105 | {%- endif -%}
106 | {% endfor %}
107 |
108 | @classmethod
109 | def from_param(k, x):
110 | assert isinstance(x, k)
111 | return x.ptr
112 |
113 | def __del__(self):
114 | conf.lib.{{module.name}}_{{class.name}}_dispose(self)
115 | {% for impl in class.impls %}
116 |
117 |
118 | class {{impl.name}}({{class.name}}):
119 |
120 | @classmethod
121 | def from_capi(cls, ptr):
122 | assert(isinstance(ptr, c_object_p))
123 | if not bool(ptr):
124 | return None
125 | return cls(_p=ptr)
126 | {% for method in impl.constructors %}
127 |
128 | def __init__(self, {{py3_macros.constructor_parameters(method, trailing_comma=True)}}_p=None) -> '{{impl.name}}':
129 | if _p:
130 | self.ptr = _p
131 | else:
132 | self.ptr = c_object_p()
133 | rc = conf.lib.{{module.name}}_{{ impl.name }}_create({{py3_macros.method_arguments(method, trailing_comma=True)}}byref(self.ptr))
134 | if rc != 0:
135 | raise {{module.name}}_error()
136 | {% endfor %}
137 | {% endfor %}
138 | {% endfor %}
139 |
140 |
141 | methodList = [
142 | ("{{module.name}}_error",
143 | [],
144 | c_interop_string,
145 | c_interop_string.to_python_string),
146 | ("{{module.name}}_clear_error",
147 | [],
148 | None),
149 | {% for class in classes %}
150 | ("{{module.name}}_{{class.name}}_dispose",
151 | [{{class.name}}],
152 | None),
153 | {% if not class.is_abstract %}
154 | {% for method in class.constructors %}
155 | ("{{module.name}}_{{class.name}}_create",
156 | [{{py3_macros.method_argument_types(method, trailing_comma=True)}}POINTER(c_object_p)],
157 | c_int),
158 | {% endfor %}
159 | {% endif %}
160 | {% for impl in class.impls %}
161 | {% for method in impl.constructors %}
162 | ("{{module.name}}_{{ impl.name }}_create",
163 | [{{py3_macros.method_argument_types(method, trailing_comma=True)}}POINTER(c_object_p)],
164 | c_int),
165 | {% endfor %}
166 | {% endfor %}
167 | {% for method in class.methods %}
168 | {% if method.is_noexcept %}
169 | ("{{module.name}}_{{ class.name }}_{{method.name}}_noexcept",
170 | [{{class.name}}{{py3_macros.method_argument_types(method, leading_comma=True)}}{% if not method.returns_void %}{% endif %}],
171 | {{method.return_type|to_output_py3_ctype}}{% if method.returns_chars %},
172 | c_interop_string.to_python_string{% endif -%}
173 | ),
174 | {% else %}
175 | ("{{module.name}}_{{ class.name }}_{{method.name}}",
176 | [{{class.name}}{{py3_macros.method_argument_types(method, leading_comma=True)}}{% if not method.returns_void %}, POINTER({{method.return_type|to_output_py3_ctype}}){% endif %}],
177 | c_int),
178 | {% endif %}
179 | {% endfor %}
180 | {% endfor %}
181 | ]
182 |
183 | {% include 'config.py.tmpl' %}
184 |
185 |
--------------------------------------------------------------------------------
/ffig/templates/go.c.tmpl:
--------------------------------------------------------------------------------
1 | {%- import 'go.macros' as go_macros -%}
2 | // This code was generated by FFIG .
3 | // Manual edits will be lost.
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #define STR(x) #x
11 | #define RSTR(x) STR(x)
12 |
13 | // Simple typedef for a void* which we know is a c-api object.
14 | {% for class in classes %}
15 | typedef const void* {{module.name}}_{{class.name}};
16 | {%- endfor %}
17 |
18 |
19 | // Function pointers for each of the exposed functions.
20 | // These will be set to the dlsym() results on initialisation.
21 | {% for class in classes %}
22 | {% for impl in class.impls -%}
23 | {%- for method in impl.constructors -%}
24 | typedef int (*{{module.name}}_{{impl.name}}_create_Ptr)(
25 | {{go_macros.c_method_parameters(module, method, trailing_comma=True)}}
26 | {{module.name}}_{{class.name}}* object);
27 | static {{module.name}}_{{impl.name}}_create_Ptr {{module.name}}_{{impl.name}}_create_ptr = NULL;
28 | {% endfor %}
29 | {%- endfor -%}
30 | {%- endfor %}
31 |
32 | {% for class in classes %}
33 | {% for method in class.methods %}
34 | {% if method.is_noexcept %}
35 | {% if method.returns_void %}
36 | typedef void (*{{module.name}}_{{class.name}}_{{method.name}}_Ptr)(
37 | const void*
38 | {{go_macros.c_method_parameters(module, method, leading_comma=True)}});
39 | {% else %}
40 | typedef {{method.return_type|to_c(module.name)}} (*{{module.name}}_{{class.name}}_{{method.name}}_Ptr)(
41 | const void*
42 | {{go_macros.c_method_parameters(module, method, leading_comma=True)}});
43 | {% endif %}
44 | {% else %}
45 | typedef int (*{{module.name}}_{{class.name}}_{{method.name}}_Ptr)(
46 | const void*
47 | {{go_macros.c_method_parameters(module, method, leading_comma=True)}}
48 | {%- if not method.returns_void -%}
49 | , {{method.return_type|to_c(module.name)}}* rv
50 | {%- endif -%}
51 | );
52 | {% endif %}
53 | static {{module.name}}_{{class.name}}_{{method.name}}_Ptr {{module.name}}_{{class.name}}_{{method.name}}_ptr = NULL;
54 | {% endfor %}
55 | {%- endfor %}
56 |
57 |
58 | int init() {
59 | // The progress code is incremented prior to each step below. If a step
60 | // fails, this code is returned and used as the exit code from the Go
61 | // layer. This allows us to inspect the generated C source and determine at
62 | // which point the process of loading the library and resolving the symbols
63 | // failed. If everything succeeds, this function explicitly returns 0.
64 | int progress_code = 0;
65 |
66 | // Assume the DSO is two levels up from the Go module:
67 | {% if dso_extension is equalto 'dll' -%}
68 | const char* library_file = RSTR(SOURCE_PATH) "\\..\\..\\lib{{module.name}}_c.{{dso_extension}}";
69 | {%- else -%}
70 | const char* library_file = RSTR(SOURCE_PATH) "/../../lib{{module.name}}_c.{{dso_extension}}";
71 | {%- endif %}
72 |
73 | // Form the canonical path to the DSO and check that it points to a valid file.
74 | // This should permit early termination in the event that the DSO file is missing.
75 | ++progress_code;
76 | char* path = realpath(library_file, NULL);
77 | struct stat st;
78 | if (stat(path, &st) != 0) {
79 | // The file doesn't exist, or cannot be accessed.
80 | return progress_code;
81 | }
82 |
83 | // Load the module's dynamic library
84 | ++progress_code;
85 | void* dynamic_library = dlopen(path, RTLD_LOCAL | RTLD_LAZY);
86 | if (!dynamic_library) {
87 | return progress_code;
88 | }
89 |
90 | // Resolve the function pointers
91 | void* symbol = NULL;
92 |
93 | {% for class in classes %}
94 | {% for impl in class.impls -%}
95 | {% for method in impl.constructors %}
96 | ++progress_code;
97 | symbol = dlsym(dynamic_library, "{{module.name}}_{{impl.name}}_create");
98 | if (!symbol) {
99 | return progress_code;
100 | }
101 | {{module.name}}_{{impl.name}}_create_ptr = ({{module.name}}_{{impl.name}}_create_Ptr)symbol;
102 | {% endfor -%}
103 | {% endfor -%}
104 | {% endfor %}
105 |
106 | {%- for class in classes %}
107 | {%- for method in class.methods %}
108 | ++progress_code;
109 | {% if method.is_noexcept %}
110 | symbol = dlsym(dynamic_library, "{{module.name}}_{{class.name}}_{{method.name}}_noexcept");
111 | {% else %}
112 | symbol = dlsym(dynamic_library, "{{module.name}}_{{class.name}}_{{method.name}}");
113 | {% endif %}
114 | if (!symbol) {
115 | return progress_code;
116 | }
117 | {{module.name}}_{{class.name}}_{{method.name}}_ptr = ({{module.name}}_{{class.name}}_{{method.name}}_Ptr)symbol;
118 | {% endfor %}
119 | {%- endfor %}
120 |
121 | free(path);
122 |
123 | return 0;
124 | }
125 |
126 | {% for class in classes -%}
127 | {% for impl in class.impls -%}
128 | {%- for method in impl.constructors %}
129 | typedef struct {{module.name}}_{{impl.name}}_create_return_type {
130 | {{module.name}}_{{class.name}} ptr;
131 | int status;
132 | } RT_{{module.name}}_{{impl.name}}_create;
133 |
134 | RT_{{module.name}}_{{impl.name}}_create CGo_{{module.name}}_{{impl.name}}_create({{go_macros.c_method_parameters(module, method)}}) {
135 | RT_{{module.name}}_{{impl.name}}_create rv;
136 | rv.status = {{module.name}}_{{impl.name}}_create_ptr(
137 | {{go_macros.c_method_arguments(method, trailing_comma=True)}}
138 | &rv.ptr);
139 | return rv;
140 | }
141 | {% endfor -%}
142 | {%- endfor %}
143 | {%- endfor %}
144 |
145 | {% for class in classes -%}
146 | {% for method in class.methods %}
147 | typedef struct {{module.name}}_{{class.name}}_{{method.name}}_return_type {
148 | {% if not method.returns_void %}
149 | {{method.return_type|to_c(module.name)}} value;
150 | {% endif %}
151 | int status;
152 | } RT_{{module.name}}_{{class.name}}_{{method.name}};
153 |
154 | RT_{{module.name}}_{{class.name}}_{{method.name}} CGo_{{module.name}}_{{class.name}}_{{method.name}}(
155 | {{module.name}}_{{class.name}} my{{class.name}} {{go_macros.c_method_parameters(module, method, leading_comma=True)}}) {
156 | RT_{{module.name}}_{{class.name}}_{{method.name}} rv;
157 | {% if method.is_noexcept %}
158 | rv.status = 0;
159 | {% if method.returns_void %}
160 | {{module.name}}_{{class.name}}_{{method.name}}_ptr(
161 | my{{class.name}}
162 | {{go_macros.c_method_arguments(method, leading_comma=True)}});
163 | {% else %}
164 | rv.value = {{module.name}}_{{class.name}}_{{method.name}}_ptr(
165 | my{{class.name}}
166 | {{go_macros.c_method_arguments(method, leading_comma=True)}});
167 | {% endif %}
168 | {% else %}
169 | rv.status = {{module.name}}_{{class.name}}_{{method.name}}_ptr(
170 | my{{class.name}}
171 | {{go_macros.c_method_arguments(method, leading_comma=True)}}
172 | {%- if not method.returns_void -%},
173 | &rv.value
174 | {%- endif -%});
175 | {% endif %}
176 | return rv;
177 | }
178 | {% endfor %}
179 | {%- endfor %}
180 |
--------------------------------------------------------------------------------
/scripts/build.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import sys
3 | import os
4 | import platform
5 | import shutil
6 | import subprocess
7 |
8 |
9 | def check_for_executable(exe_name, args=['--version']):
10 | try:
11 | cmd = [exe_name]
12 | cmd.extend(args)
13 | subprocess.check_output(cmd)
14 | return True
15 | except (subprocess.CalledProcessError, IOError):
16 | return False
17 |
18 |
19 | def process_optional_bindings(required, disabled):
20 | output = []
21 |
22 | for lang in required:
23 | output.append('-DFFIG_REQUIRE_{}=1'.format(lang.upper()))
24 | for lang in disabled:
25 | output.append('-DFFIG_DISABLE_{}=1'.format(lang.upper()))
26 |
27 | return output
28 |
29 |
30 | def main():
31 | optional_languages = ('dotnet', 'go', 'lua', 'java',
32 | 'swift', 'ruby', 'boost_python',
33 | 'julia')
34 |
35 | import argparse
36 | parser = argparse.ArgumentParser()
37 | parser.add_argument(
38 | '--clean',
39 | help='remove build directory before build',
40 | action='store_true',
41 | dest='clean')
42 |
43 | test_options = parser.add_mutually_exclusive_group()
44 | test_options.add_argument(
45 | '-t', help='run tests', action='store_true', dest='run_tests')
46 | test_options.add_argument(
47 | '-T', help='run labelled tests', dest='labelled_tests')
48 |
49 | parser.add_argument(
50 | '-v', help='verbose', action='store_true', dest='verbose')
51 | parser.add_argument(
52 | '-o',
53 | help='output dir (relative to source dir)',
54 | default='build_out',
55 | dest='out_dir')
56 | parser.add_argument(
57 | '-c',
58 | help='config (Debug or Release)',
59 | default='Debug',
60 | dest='config')
61 | parser.add_argument(
62 | '--python-path',
63 | help='path to python executable ie "/usr/local/bin/python3"',
64 | dest='python_path')
65 | parser.add_argument(
66 | '--venv',
67 | help='Use a python virtualenv, installing modules from requirements.txt',
68 | action="store_true",
69 | dest='venv')
70 |
71 | if platform.system() == "Windows":
72 | parser.add_argument(
73 | '--win32',
74 | help='Build 32-bit libraries',
75 | action='store_true',
76 | dest='win32')
77 |
78 | for lang in optional_languages:
79 | group = parser.add_mutually_exclusive_group()
80 | group.add_argument(
81 | '--disable_{}'.format(lang),
82 | dest='disabled_bindings',
83 | action='append_const',
84 | const=lang,
85 | help='Disable generation of bindings for {}'.format(lang))
86 | group.add_argument(
87 | '--require_{}'.format(lang),
88 | dest='required_bindings',
89 | action='append_const',
90 | const=lang,
91 | help='Require generation of bindings for {}'.format(lang))
92 |
93 | parser.add_argument(
94 | '--nodetect',
95 | action='store_true',
96 | help='Disable generation of all optional bindings not explicitly activated')
97 |
98 | parser.add_argument('--nobazel', action='store_false',
99 | dest='run_bazel', default=True)
100 |
101 | args = parser.parse_args()
102 | args.platform = platform.system()
103 |
104 | src_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
105 |
106 | if args.clean and os.path.exists(args.out_dir):
107 | shutil.rmtree(args.out_dir)
108 |
109 | cmake_invocation = ['cmake', '.', '-B{}'.format(args.out_dir)]
110 | if args.platform == 'Windows':
111 | if not args.win32:
112 | cmake_invocation.extend(['-A', 'x64'])
113 | else:
114 | # Use Ninja instead of Make, if available.
115 | if check_for_executable('ninja'):
116 | cmake_invocation.extend(['-GNinja'])
117 | cmake_invocation.extend(['-DCMAKE_BUILD_TYPE={}'.format(args.config)])
118 |
119 | if args.verbose:
120 | cmake_invocation.append('-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON')
121 |
122 | if not os.path.exists(os.path.join(src_dir, args.out_dir)):
123 | os.makedirs(os.path.join(src_dir, args.out_dir))
124 |
125 | if args.venv:
126 | python_executable = args.python_path if args.python_path else 'python'
127 | subprocess.check_call(
128 | '{} -m virtualenv pyenv'.format(python_executable).split(),
129 | cwd=os.path.join(src_dir,
130 | args.out_dir))
131 | subprocess.check_call(
132 | '{}/pyenv/bin/pip install -r requirements.txt'.format(
133 | os.path.join(
134 | src_dir,
135 | args.out_dir)).split(),
136 | cwd=src_dir)
137 | args.python_path = os.path.join(
138 | src_dir, args.out_dir, 'pyenv', 'bin', 'python')
139 |
140 | if args.python_path:
141 | cmake_invocation.append(
142 | '-DPYTHON_EXECUTABLE={}'.format(args.python_path))
143 |
144 | # Add required / disabled binding options
145 | required_bindings = args.required_bindings or []
146 | disabled_bindings = args.disabled_bindings or []
147 |
148 | if args.nodetect:
149 | disabled_bindings += [o for o in optional_languages
150 | if o not in required_bindings]
151 |
152 | cmake_invocation.extend(
153 | process_optional_bindings(required_bindings, disabled_bindings))
154 |
155 | cmake_cache_valid = True
156 |
157 | try:
158 | with open(os.path.join(src_dir, args.out_dir, "build.py.cache.txt"), "r") as cachefile:
159 | if cachefile.readline() != " ".join(cmake_invocation):
160 | print("CMake invocation has changed. Rebuilding CMakeCache.txt")
161 | cmake_cache_valid = False
162 | except IOError:
163 | cmake_cache_valid = False
164 | pass
165 |
166 | if not cmake_cache_valid:
167 | try:
168 | os.remove(os.path.join(src_dir, args.out_dir, "CMakeCache.txt"))
169 | except OSError:
170 | pass
171 | subprocess.check_call(cmake_invocation, cwd=src_dir)
172 | with open(os.path.join(src_dir, args.out_dir, "build.py.cache.txt"), "w") as cachefile:
173 | cachefile.write(" ".join(cmake_invocation))
174 |
175 | if args.run_bazel:
176 | subprocess.check_call('bazel build :all'.split(), cwd=src_dir)
177 |
178 | subprocess.check_call(
179 | 'cmake --build ./{}'.format(args.out_dir).split(), cwd=src_dir)
180 |
181 | rc = 0
182 | if args.run_tests:
183 | rc = subprocess.call(
184 | 'ctest . --output-on-failure -C {}'.format(args.config).split(),
185 | cwd=os.path.join(src_dir, args.out_dir))
186 | elif args.labelled_tests:
187 | rc = subprocess.call(
188 | 'ctest . --output-on-failure -C {} -L {}'.format(
189 | args.config, args.labelled_tests).split(),
190 | cwd=os.path.join(src_dir, args.out_dir))
191 | if rc != 0:
192 | sys.exit(1)
193 |
194 |
195 | if __name__ == '__main__':
196 | main()
197 |
--------------------------------------------------------------------------------
/ffig/generators/__init__.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import logging
3 | import os
4 | import os.path
5 | import re
6 | import sys
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | def dso_extension():
12 | extensions = {
13 | 'darwin': 'dylib',
14 | 'win32': 'dll',
15 | 'cygwin': 'dll',
16 | 'linux2': 'so',
17 | 'linux': 'so',
18 | }
19 | return extensions[sys.platform]
20 |
21 |
22 | def render_api_and_obj_classes(module_name, api_classes, template):
23 | '''Render a template.'''
24 | return str(template.render({"module": {"name": module_name},
25 | "classes": api_classes,
26 | "dso_extension": dso_extension()}))
27 |
28 |
29 | def get_template_name(template_path):
30 | '''Get the template name from the binding name.'''
31 | template_name = os.path.basename(template_path)
32 | return re.sub(".tmpl$", "", template_name)
33 |
34 |
35 | def get_template_output(class_name, template_name):
36 | '''Determine the output filename from the template name.'''
37 | split_name = template_name.split('.')
38 | suffix_name = '.'.join(split_name[:-1])
39 | extension = split_name[-1]
40 | return "{}{}.{}".format(class_name, suffix_name, extension)
41 |
42 |
43 | def generate_single_output_file(
44 | module_name,
45 | binding,
46 | api_classes,
47 | env,
48 | output_file_name):
49 | '''Generate a single named output file. Used by the default generator.'''
50 | with open(output_file_name, 'w') as output_file:
51 | template = env.get_template(binding)
52 | output_string = render_api_and_obj_classes(
53 | module_name, api_classes, template)
54 | output_file.write(output_string)
55 |
56 |
57 | def default_generator(module_name, binding, api_classes, env, output_dir):
58 | '''
59 | Default generator.
60 |
61 | Used when there are no custom generators registered for a binding. This
62 | generator is appropriate for simple bindings that produce a single output
63 | file.
64 |
65 | Input:
66 | - module_name: The name of the module to generate.
67 | - binding: The name of the binding to generate.
68 | - api_classes: The classes to generate bindings for.
69 | - env: The jinja2 environment.
70 | - output_dir: The base directory for generator output.
71 | '''
72 | template = env.get_template(binding)
73 | output_string = render_api_and_obj_classes(
74 | module_name, api_classes, template)
75 |
76 | output_file_name = os.path.join(
77 | output_dir, get_template_output(
78 | module_name, get_template_name(binding)))
79 | generate_single_output_file(
80 | module_name,
81 | binding,
82 | api_classes,
83 | env,
84 | output_file_name)
85 | return [output_file_name]
86 |
87 |
88 | class GeneratorContext(object):
89 | '''Holds a mapping of bindings to custom generators.'''
90 |
91 | GeneratorRecord = collections.namedtuple(
92 | 'GeneratorRecord', ['function', 'description'])
93 |
94 |
95 | def __init__(self):
96 | '''Initialise with no custom generators'''
97 | self._generator_map = {}
98 |
99 |
100 | def register(self, generator_function, bindings):
101 | '''
102 | Register a generation function.
103 |
104 | Input:
105 | - generator_function: f(module_name, binding, api_classes, env, output_dir).
106 | - bindings: List of tuples describing bindings that this function generates.
107 | The format is [(binding_name, description), ...]
108 | '''
109 | for binding in bindings:
110 | binding_name = binding[0]
111 | description = binding[1]
112 | record = GeneratorContext.GeneratorRecord(generator_function, description)
113 | self._generator_map[binding_name] = record
114 |
115 |
116 | def list_generators(self):
117 | '''
118 | Return a list of registered binding generators.
119 |
120 | Output:
121 | - List of (binding_name, description) tuples
122 | '''
123 | return [(binding, record.description)
124 | for binding, record in self._generator_map.items()]
125 |
126 |
127 | def generate(self, module_name, binding, api_classes, env, output_dir):
128 | '''
129 | Generate a set of bindings.
130 |
131 | Input:
132 | - module_name: The name of the module to generate.
133 | - binding: The type of binding to generate.
134 | - api_classes: Classes to generate bindings for.
135 | - env: The template environment.
136 | - output_dir: Directory to write generated bindings to.
137 | '''
138 | log.info('Finding generator for {}'.format(binding))
139 | if binding in self._generator_map:
140 | log.info(' found in map')
141 | generator_func = self._generator_map[binding].function
142 | return generator_func(module_name, binding, api_classes, env, output_dir)
143 | else:
144 | log.info(' using default')
145 | return default_generator(
146 | module_name, binding, api_classes, env, output_dir)
147 |
148 |
149 | # This is the default generator context.
150 | generator_context = GeneratorContext()
151 |
152 |
153 | def generate(module_name, binding, api_classes, env, output_dir):
154 | '''Forward the request to the default generator context.'''
155 | return generator_context.generate(
156 | module_name, binding, api_classes, env, output_dir)
157 |
158 |
159 | def _activate_plugin(module_name):
160 | '''Internal function used to activate a plugin that has been found.'''
161 | log.info('Importing {}'.format(module_name))
162 | module = __import__(
163 | 'ffig.generators.{0}'.format(module_name),
164 | fromlist=['setup_plugin'])
165 | module.setup_plugin(generator_context)
166 |
167 |
168 | def _scan_plugins():
169 | ''' Internal function used to search the generators directory for plugins.
170 |
171 | Plugins may be written as a module (a single python file in the
172 | generators directory) or as a package (a subdirectory of generators,
173 | containing an __init__.py).
174 |
175 | In either case, plugins must define a function to register one or more
176 | generator functions against a list of one or more binding names:
177 |
178 | def setup_plugin(context):
179 | context.register(generator_func, [(binding, description), ...])
180 |
181 | where generator_func is a function of the form
182 |
183 | f(module_name, binding, api_classes, env, output_dir)
184 | '''
185 | basedir = os.path.realpath(os.path.dirname(__file__))
186 | log.info('Scanning for plugins in {}'.format(basedir))
187 | excluded_files = ['__init__.py', '__pycache__']
188 | for entry in os.listdir(basedir):
189 | log.info('Checking {}'.format(entry))
190 | if entry in excluded_files:
191 | log.info('Skipping excluded file {}'.format(entry))
192 | continue
193 | filepath = os.path.join(basedir, entry)
194 | if os.path.isdir(filepath):
195 | log.info('Found plugin package {}'.format(entry))
196 | # This is a generator package. Import it.
197 | _activate_plugin(os.path.basename(entry))
198 | elif os.path.isfile(filepath) and entry.endswith('.py'):
199 | log.info('Found plugin module {}'.format(entry))
200 | _activate_plugin(os.path.basename(entry)[:-3])
201 |
202 |
203 | # Scan the generators directory for plugins and register them on
204 | # initialisation.
205 | _scan_plugins()
206 |
--------------------------------------------------------------------------------