├── .appveyor.yml ├── .clang-format ├── .dockerignore ├── .gitignore ├── .gitmodules ├── .travis.yml ├── .vscode ├── settings.json └── tasks.json ├── .ycm_extra_conf.py ├── BUILD ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── WORKSPACE ├── cmake ├── dotnet.cmake ├── ffig.cmake └── utils.cmake ├── demos ├── CppLondon_Aug-2017.ipynb ├── LLVM-Cauldron.ipynb └── PyDataLondon-2017.ipynb ├── docs ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug │ └── feature ├── bindings │ └── go.md └── pull_request_template.md ├── ffig.bzl ├── ffig ├── FFIG.py ├── __init__.py ├── __main__.py ├── annotations.py ├── clang │ ├── __init__.py │ ├── cindex.py │ └── enumerations.py ├── cppmodel.py ├── filters │ ├── __init__.py │ └── capi_filter.py ├── generators │ ├── __init__.py │ ├── boost_python.py │ ├── c.py │ ├── dotnet.py │ ├── generator_aliases.py │ ├── go.py │ ├── java.py │ ├── python.py │ └── swift.py ├── include │ └── ffig │ │ └── attributes.h └── templates │ ├── __init__.py.tmpl │ ├── _c.cpp.tmpl │ ├── _c.h.tmpl │ ├── _c.macros │ ├── _cpp.h.tmpl │ ├── _mocks.h.tmpl │ ├── boost_python.tmpl │ ├── config.py.tmpl │ ├── cs.macros │ ├── cs.tmpl │ ├── csproj.tmpl │ ├── d.tmpl │ ├── ffig.macros │ ├── go.c.tmpl │ ├── go.macros │ ├── go.tmpl │ ├── java.derived.tmpl │ ├── java.exception.tmpl │ ├── java.interop.tmpl │ ├── java.macros │ ├── java.tmpl │ ├── jl.macros │ ├── jl.tmpl │ ├── json.tmpl │ ├── lua.tmpl │ ├── py2.macros │ ├── py2.tmpl │ ├── py3.macros │ ├── py3.tmpl │ ├── rb.tmpl │ ├── rs.tmpl │ ├── swift.bridging-header.tmpl │ ├── swift.macros │ └── swift.tmpl ├── requirements.txt ├── scripts ├── build.py ├── codechecks.py ├── install-git-hooks.py ├── pre-push.py ├── pydiff.py └── test-docker.py └── tests ├── TestAsset.rb ├── TestShape.rb ├── __init.py__ ├── boost_python ├── test_animal_bindings.py ├── test_number_python_bindings.py ├── test_shape_python_bindings.py └── test_tree_python_bindings.py ├── cppmodel ├── test_classes.py ├── test_extend_model.py ├── test_free_functions.py ├── test_model.py ├── test_types.py └── util.py ├── dotnet ├── Number │ └── Number.net.csproj ├── Shape │ └── Shape.net.csproj ├── TestNumber │ ├── TestNumber.cs │ └── TestNumber.net.csproj ├── TestShape │ ├── TestShape.cs │ └── TestShape.net.csproj ├── TestTree │ ├── TestTree.cs │ └── TestTree.net.csproj ├── Tree │ └── Tree.net.csproj ├── ffig.net.csproj.in └── ffig.net.tests.sln ├── expected_output ├── Tree.d.expected └── Tree.rs.expected ├── ffig ├── test_build_model.py └── test_generator_list.py ├── go ├── CMakeLists.txt └── Shape_test.go ├── input ├── Animal.h ├── Asset.h ├── Number.h ├── Shape.h └── Tree.h ├── java ├── TestAsset.java ├── TestNumber.java ├── TestShape.java └── TestTree.java ├── julia └── TestShape.jl ├── src ├── CMakeLists.txt ├── TestCppCircle.cpp ├── TestCppTree.cpp ├── TestNumberCAPI.cpp ├── TestShapeMocks.cpp └── TestTreeCAPI.cpp ├── swift ├── TestShape │ └── main.swift └── TestTree │ └── main.swift ├── test_animal_bindings.py ├── test_asset_bindings.lua ├── test_asset_bindings.py ├── test_number_python_bindings.py ├── test_shape_python_bindings.py ├── test_tree_python_bindings.py └── test_type_hints_in_bindings.py /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | build_out 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Build status (on Travis-CI):** [![Build Status](https://travis-ci.org/FFIG/ffig.svg?branch=master)](https://travis-ci.org/FFIG/ffig) 2 | 3 | **Build status (on AppVeyor):** [![Build Status](https://ci.appveyor.com/api/projects/status/github/ffig/ffig?branch=master)](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 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFIG/ffig/b45060d5835292a74661678872427e3eb5a89d0c/WORKSPACE -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE/feature: -------------------------------------------------------------------------------- 1 | ## Feature Request 2 | 3 | ### Summary 4 | 5 | ### Requirements 6 | 7 | ### Non-requirements 8 | 9 | ### Additional information 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFIG/ffig/b45060d5835292a74661678872427e3eb5a89d0c/ffig/__init__.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/filters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFIG/ffig/b45060d5835292a74661678872427e3eb5a89d0c/ffig/filters/__init__.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/_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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/csproj.tmpl: -------------------------------------------------------------------------------- 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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /ffig/templates/swift.bridging-header.tmpl: -------------------------------------------------------------------------------- 1 | #import "{{module.name}}_c.h" 2 | 3 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | autopep8 2 | cython 3 | jinja2 4 | nose 5 | pycodestyle 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/__init.py__: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFIG/ffig/b45060d5835292a74661678872427e3eb5a89d0c/tests/__init.py__ -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/Tree/Tree.net.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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_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/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/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/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 | --------------------------------------------------------------------------------